Initial Commit
Some checks failed
Build and Test / rust-boss (push) Has been cancelled

This commit is contained in:
BloxerHD 2026-07-16 21:25:41 +01:00
commit a4dbe9c399
Signed by: bloxerhd018
SSH key fingerprint: SHA256:ItSfcfhpXlfkMK3uQSDYW5X3XKm0hbHWQqWcCK57px8
7 changed files with 337 additions and 0 deletions

View file

@ -0,0 +1,56 @@
name: Build and Test
on:
push:
branches: ["**"]
env:
DOCKER_TLS_CERTDIR: /certs
IMAGE_TAG: ${{ github.sha }}
SHORT_SHA: ${{ github.sha }}
jobs:
rust-boss:
runs-on: debian-trixie
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
submodules: recursive
- name: Cache Container Storage
uses: actions/cache@v6
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to Registry
run: $RNEX_CONTAINER_PLATFORM login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set Short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build and Push
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/nncs
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: |
$RNEX_CONTAINER_PLATFORM login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
$RNEX_CONTAINER_PLATFORM build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
- name: Push Retagged Latest
if: github.ref == 'refs/heads/main'
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/nncs
CI_COMMIT_SHORT_SHA: latest
CI_COMMIT_PREVIOUS_SHA: ${{ env.SHORT_SHA }}
run: |
$RNEX_CONTAINER_PLATFORM login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
$RNEX_CONTAINER_PLATFORM pull "$CI_REGISTRY_IMAGE:$CI_COMMIT_PREVIOUS_SHA"
$RNEX_CONTAINER_PLATFORM tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_PREVIOUS_SHA" "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
.env
Cargo.lock

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "nncs"
version = "0.1.0"
edition = "2024"
[dependencies]
dotenvy = "0.15.7"
tokio = { version = "1.52.3", features = ["full"] }

24
Dockerfile Normal file
View file

@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM rust:alpine AS builder
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static protobuf-dev lld
WORKDIR /app
COPY Cargo.toml ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo fetch
COPY . .
ENV SQLX_OFFLINE=true
RUN OPENSSL_LIB_DIR=/usr/lib OPENSSL_INCLUDE_DIR=/usr/include/openssl OPENSSL_STATIC=1 RUSTFLAGS="-C target-feature=+aes,+sse -C relocation-model=static -C linker=ld.lld" cargo build --release --target x86_64-unknown-linux-musl
FROM alpine:latest AS final
RUN apk add --no-cache ca-certificates
RUN update-ca-certificates
WORKDIR /
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/nncs /nncs
ENTRYPOINT ["/nncs"]

71
src/ipc.rs Normal file
View file

@ -0,0 +1,71 @@
use std::net::{Ipv4Addr, SocketAddrV4};
use std::sync::Arc;
use tokio::net::UdpSocket;
use crate::types::{Message, Sockets};
pub struct IpcFrame {
pub message: Message,
pub remote_addr: SocketAddrV4,
}
impl IpcFrame {
pub fn to_bytes(&self, socket: Arc<UdpSocket>) -> [u8; 22] {
let message_bytes = self.message.to_response_bytes(socket, self.remote_addr);
let mut buf = [0u8; 22];
buf[0..2].copy_from_slice(&self.remote_addr.port().to_be_bytes());
buf[2..6].copy_from_slice(&u32::from(*self.remote_addr.ip()).to_be_bytes());
buf[6..].copy_from_slice(&message_bytes.unwrap());
buf
}
pub fn from_bytes(slice: &[u8]) -> Result<Self, String> {
if slice.len() < 6 {
return Err("Frame too small to extract client metadata".into())
}
let remote_port = u16::from_be_bytes(slice[0..2].try_into().unwrap());
let remote_ip = Ipv4Addr::from(u32::from_be_bytes(slice[2..6].try_into().unwrap()));
let remote_addr = SocketAddrV4::new(remote_ip, remote_port);
let message = Message::from_bytes(&slice[6..])?;
Ok(IpcFrame{message, remote_addr})
}
}
pub async fn run(host: String, port: u16, sockets: Arc<Sockets>) -> Result<(), Box<dyn std::error::Error>> {
let ipc_socket = UdpSocket::bind(format!("{host}:{port}")).await?;
let mut buf = [0u8; 2048];
tokio::spawn(async move {
loop {
match ipc_socket.recv_from(&mut buf).await {
Ok((len, _sender_addr)) => {
match IpcFrame::from_bytes(&buf[..len]) {
Ok(ipc_frame) => {
let socket = &sockets.alt;
let _ = socket.send_to(
&ipc_frame.message.to_response_bytes(socket.clone(), ipc_frame.remote_addr).unwrap(),
format!("{}:{}", ipc_frame.remote_addr.ip(), ipc_frame.remote_addr.port()),
).await;
},
Err(e) => {
eprintln!("Failed to decode IPC Frame: {e}")
}
}
},
Err(e) => {
eprintln!("IPC Socket Error: {e}");
break;
}
}
}
});
Ok(())
}

120
src/main.rs Normal file
View file

@ -0,0 +1,120 @@
use std::net::{IpAddr, SocketAddrV4};
use std::sync::Arc;
use std::env;
use dotenvy::dotenv;
use tokio::net::UdpSocket;
use crate::ipc::IpcFrame;
use crate::types::{Message, Sockets};
pub mod types;
mod ipc;
async fn respond(socket: Arc<UdpSocket>, message: Message, remote_addr: SocketAddrV4) {
let _ = socket.send_to(
&message.to_response_bytes(socket.clone(), remote_addr).unwrap(),
remote_addr
).await;
}
async fn respond_from_partner(socket: Arc<UdpSocket>, message: crate::types::Message, remote_addr: SocketAddrV4, sockets: Arc<Sockets>) -> Result<(), Box<dyn std::error::Error>> {
let partner_addr = env::var("NNCS_PARTNER_IP")?;
let ipc_sender_socket = sockets.alt.clone();
let frame = IpcFrame {
message,
remote_addr
};
let frame_bytes = frame.to_bytes(socket);
ipc_sender_socket.send_to(&frame_bytes, partner_addr).await?;
Ok(())
}
fn init_port(socket: Arc<UdpSocket>, sockets: Arc<Sockets>) {
let mut buf = [0; 64];
tokio::spawn(async move {
loop {
match socket.recv_from(&mut buf).await {
Ok((len, src_addr)) => {
let remote_ip = match src_addr.ip() {
IpAddr::V4(ip) => ip,
IpAddr::V6(_) => break,
};
let remote_port = src_addr.port();
let remote_addr = SocketAddrV4::new(remote_ip, remote_port);
match types::Message::from_bytes(&buf[..len]) {
Ok(packet) => {
match packet.r#type {
1 => respond(socket.clone(), packet, remote_addr).await, // Server responds from regular ip and regular port
2 => match respond_from_partner(socket.clone(), packet, remote_addr, sockets.clone()).await { // Server responds from different ip and different port
Ok(_) => (),
Err(e) => eprintln!("Error responding from partner: {e}"),
},
3 => { // Server responds from regular ip and different port
let socket = sockets.alt.clone();
respond(socket, packet, remote_addr).await;
},
4 => respond(socket.clone(), packet, remote_addr).await, // Server responds from regular ip and regular port
5 => respond(socket.clone(), packet, remote_addr).await, // Server responds from regular ip and regular port
101 => respond(socket.clone(), packet, remote_addr).await, // Server responds from regular ip and regular port
102 => { // Server responds from regular ip and different port
let socket = sockets.alt.clone();
respond(socket, packet, remote_addr).await;
},
103 => respond(socket.clone(), packet, remote_addr).await, // Server responds from regular ip and regular port
_ => {
eprintln!("[{}] Invalid message type from {src_addr}: {}", socket.local_addr().unwrap().port(), packet.r#type)
},
}
},
Err(e) => {
eprintln!("[{}] Error parsing message from {src_addr}: {e}", socket.local_addr().unwrap().port())
}
}
},
Err(e) => {
eprintln!("[{}] Socket error: {e}", socket.local_addr().unwrap().port());
break;
}
}
}
});
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok();
let sockets = Arc::new(Sockets{
primary: Arc::new(UdpSocket::bind("0.0.0.0:10025").await?),
secondary: Arc::new(UdpSocket::bind("0.0.0.0:10125").await?),
alt: Arc::new(UdpSocket::bind("0.0.0.0:0").await?),
p33334: Arc::new(UdpSocket::bind("0.0.0.0:33334").await?),
p33335: Arc::new(UdpSocket::bind("0.0.0.0:33335").await?),
});
init_port(sockets.primary.clone(), sockets.clone());
init_port(sockets.secondary.clone(), sockets.clone());
println!("NNCS Running on Ports 10025 and 10125");
let ipc_port: u16= env::var("NNCS_IPC_PORT").unwrap_or("9001".into()).parse()?;
ipc::run("0.0.0.0".into(), ipc_port, sockets).await?;
println!("IPC Running on Port {ipc_port}");
std::future::pending::<()>().await;
Ok(())
}

55
src/types.rs Normal file
View file

@ -0,0 +1,55 @@
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc;
use tokio::net::UdpSocket;
pub struct Sockets {
pub primary: Arc<UdpSocket>,
pub secondary: Arc<UdpSocket>,
pub alt: Arc<UdpSocket>,
pub p33334: Arc<UdpSocket>,
pub p33335: Arc<UdpSocket>,
}
pub struct Message {
pub r#type: u32,
pub external_port: u32,
pub external_address: Ipv4Addr,
pub local_address: u32,
}
impl Message {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
if bytes.len() < 16 {
return Err(format!("Buffer too short, expected 16 bytes but found {} bytes", bytes.len()))
};
let r#type = u32::from_be_bytes(bytes[0..4].try_into().unwrap());
let external_port = u32::from_be_bytes(bytes[4..8].try_into().unwrap());
let external_address = Ipv4Addr::from(u32::from_be_bytes(bytes[8..12].try_into().unwrap()));
let local_address = u32::from_be_bytes(bytes[12..16].try_into().unwrap());
Ok(Message{
r#type,
external_port,
external_address,
local_address,
})
}
pub fn to_response_bytes(&self, socket: Arc<UdpSocket>, remote_addr: SocketAddrV4) -> Result<[u8; 16], String> {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&self.r#type.to_be_bytes());
bytes[4..8].copy_from_slice(&remote_addr.port().to_be_bytes());
bytes[8..12].copy_from_slice(&u32::from(*remote_addr.ip()).to_be_bytes());
let local_ip = match socket.local_addr().unwrap() {
SocketAddr::V4(addr) => *addr.ip(),
SocketAddr::V6(_) => {
return Err("Ipv6 not supported".into());
},
};
bytes[12..16].copy_from_slice(&u32::from(local_ip).to_be_bytes());
Ok(bytes)
}
}