This commit is contained in:
commit
a4dbe9c399
7 changed files with 337 additions and 0 deletions
56
.forgejo/workflows/build.yml
Normal file
56
.forgejo/workflows/build.yml
Normal 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
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
.env
|
||||
Cargo.lock
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
24
Dockerfile
Normal 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
71
src/ipc.rs
Normal 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
120
src/main.rs
Normal 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
55
src/types.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue