Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd172f341 | |||
| b4222df349 | |||
| 527b462291 |
@@ -0,0 +1,55 @@
|
|||||||
|
name: build
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: hugmaster
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
run: |
|
||||||
|
git clone "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" .
|
||||||
|
git checkout "$GITHUB_SHA"
|
||||||
|
|
||||||
|
- name: Build Rust
|
||||||
|
run: |
|
||||||
|
cd gatunad
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
- name: Build .NET
|
||||||
|
run: |
|
||||||
|
cd gatuna-win
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
- name: Publish Windows (framework-dependent)
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: |
|
||||||
|
cd gatuna-win
|
||||||
|
dotnet publish -c Release -r win-x64 --no-self-contained -o ../publish/win
|
||||||
|
|
||||||
|
- name: Package release assets
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: |
|
||||||
|
mkdir -p release
|
||||||
|
cp gatunad/target/release/gatunad release/gatunad-linux-amd64
|
||||||
|
cd publish/win && zip -r ../../release/gatuna-win.zip . && cd ../..
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
RESP=$(curl -sS -X POST \
|
||||||
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Automated build for $TAG\"}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases")
|
||||||
|
RID=$(echo "$RESP" | jq .id)
|
||||||
|
curl -sS -X POST \
|
||||||
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @release/gatunad-linux-amd64 \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RID}/assets?name=gatunad-linux-amd64"
|
||||||
|
curl -sS -X POST \
|
||||||
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @release/gatuna-win.zip \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RID}/assets?name=gatuna-win.zip"
|
||||||
@@ -100,7 +100,7 @@ sealed class SessionManager : IDisposable
|
|||||||
|
|
||||||
case Frame.Close close:
|
case Frame.Close close:
|
||||||
if (_sessions.TryRemove(close.SessionId, out var s))
|
if (_sessions.TryRemove(close.SessionId, out var s))
|
||||||
s.Dispose();
|
s.OnRemoteClose();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Frame.Pong pong:
|
case Frame.Pong pong:
|
||||||
@@ -394,6 +394,7 @@ sealed class Session(
|
|||||||
readonly RecvState _recv = new();
|
readonly RecvState _recv = new();
|
||||||
readonly object _sendLock = new();
|
readonly object _sendLock = new();
|
||||||
readonly object _recvLock = new();
|
readonly object _recvLock = new();
|
||||||
|
volatile bool _closeSent;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
@@ -403,6 +404,17 @@ sealed class Session(
|
|||||||
_ = RetransmitTimer();
|
_ = RetransmitTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a CLOSE frame arrives from the server. The server has
|
||||||
|
/// already torn down its side — we just need to flush queued data to
|
||||||
|
/// the local socket and close gracefully. Do NOT echo CLOSE back.
|
||||||
|
/// </summary>
|
||||||
|
public void OnRemoteClose()
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_deliverChannel.Writer.TryComplete();
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle a DATA frame from the tunnel.
|
/// Handle a DATA frame from the tunnel.
|
||||||
public void HandleData(Frame.Data data)
|
public void HandleData(Frame.Data data)
|
||||||
{
|
{
|
||||||
@@ -440,10 +452,10 @@ sealed class Session(
|
|||||||
|
|
||||||
async Task PumpSocketToTunnel()
|
async Task PumpSocketToTunnel()
|
||||||
{
|
{
|
||||||
|
var buf = new byte[Proto.MaxPayload];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stream = client.GetStream();
|
var stream = client.GetStream();
|
||||||
var buf = new byte[Proto.MaxPayload];
|
|
||||||
using var reg = _cts.Token.Register(() => client.Dispose());
|
using var reg = _cts.Token.Register(() => client.Dispose());
|
||||||
while (!_cts.IsCancellationRequested)
|
while (!_cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -476,6 +488,8 @@ sealed class Session(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
_cts.Cancel();
|
||||||
|
_deliverChannel.Writer.TryComplete();
|
||||||
SendClose();
|
SendClose();
|
||||||
onClosed();
|
onClosed();
|
||||||
}
|
}
|
||||||
@@ -485,10 +499,11 @@ sealed class Session(
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stream = client.GetStream();
|
var stream = client.GetStream();
|
||||||
await foreach (var payload in _deliverChannel.Reader.ReadAllAsync(_cts.Token))
|
await foreach (var payload in _deliverChannel.Reader.ReadAllAsync())
|
||||||
await stream.WriteAsync(payload, _cts.Token);
|
await stream.WriteAsync(payload);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
finally { client.Close(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task RetransmitTimer()
|
async Task RetransmitTimer()
|
||||||
@@ -539,7 +554,12 @@ sealed class Session(
|
|||||||
link.SendTo(serverMac, new Frame.Data(sessionId, seq, ackSeq, []));
|
link.SendTo(serverMac, new Frame.Data(sessionId, seq, ackSeq, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendClose() => link.SendTo(serverMac, new Frame.Close(sessionId, null));
|
void SendClose()
|
||||||
|
{
|
||||||
|
if (_closeSent) return;
|
||||||
|
_closeSent = true;
|
||||||
|
link.SendTo(serverMac, new Frame.Close(sessionId, null));
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ use crate::frame::{ETHERTYPE, ETH_HEADER_LEN};
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct MacAddr(pub [u8; 6]);
|
pub struct MacAddr(pub [u8; 6]);
|
||||||
|
|
||||||
|
impl std::fmt::Display for MacAddr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
|
||||||
|
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MacAddr {
|
impl MacAddr {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn broadcast() -> Self {
|
pub fn broadcast() -> Self {
|
||||||
|
|||||||
+8
-7
@@ -114,7 +114,7 @@ async fn main() -> ExitCode {
|
|||||||
let frame = match Frame::parse(payload) {
|
let frame = match Frame::parse(payload) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("decode from {src:?}: {e}");
|
error!("decode from {src}: {e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -145,16 +145,16 @@ async fn handle_frame(
|
|||||||
) {
|
) {
|
||||||
match frame {
|
match frame {
|
||||||
Frame::Discover => {
|
Frame::Discover => {
|
||||||
info!("DISCOVER from {src:?}");
|
info!("DISCOVER from {src}");
|
||||||
let manifest = Frame::Manifest {
|
let manifest = Frame::Manifest {
|
||||||
hostname: (**hostname).clone(),
|
hostname: (**hostname).clone(),
|
||||||
entries: table.entries(),
|
entries: table.entries(),
|
||||||
};
|
};
|
||||||
let _ = tx.send((src, manifest.encode())).await;
|
let _ = tx.send((src, manifest.encode())).await;
|
||||||
info!("MANIFEST sent to {src:?} ({} upstreams)", table.0.len());
|
info!("MANIFEST sent to {src} ({} upstreams)", table.0.len());
|
||||||
}
|
}
|
||||||
Frame::Open { upstream_id, proto: _ } => {
|
Frame::Open { upstream_id, proto: _ } => {
|
||||||
info!("OPEN upstream {upstream_id} from {src:?}");
|
info!("OPEN upstream {upstream_id} from {src}");
|
||||||
let upstream = table.get(upstream_id);
|
let upstream = table.get(upstream_id);
|
||||||
match upstream {
|
match upstream {
|
||||||
Some(upstream) => {
|
Some(upstream) => {
|
||||||
@@ -188,7 +188,7 @@ async fn handle_frame(
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("connect 127.0.0.1:{port} failed: {e}");
|
error!("connect 127.0.0.1:{port} failed: {e}");
|
||||||
info!("OPEN_NAK upstream {upstream_id} (connect_failed) to {src:?}");
|
info!("OPEN_NAK upstream {upstream_id} (connect_failed) to {src}");
|
||||||
let nak =
|
let nak =
|
||||||
Frame::OpenNak { upstream_id, reason: REASON_CONNECT_FAILED };
|
Frame::OpenNak { upstream_id, reason: REASON_CONNECT_FAILED };
|
||||||
let _ = tx.send((src, nak.encode())).await;
|
let _ = tx.send((src, nak.encode())).await;
|
||||||
@@ -224,8 +224,9 @@ async fn handle_frame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Frame::Close { session_id, reason: _ } => {
|
Frame::Close { session_id, reason: _ } => {
|
||||||
info!("CLOSE session {session_id} from {src:?}");
|
if store.lock().expect("store poisoned").remove(&session_id).is_some() {
|
||||||
store.lock().expect("store poisoned").remove(&session_id);
|
info!("CLOSE session {session_id} from {src}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Frame::Ping { nonce } => {
|
Frame::Ping { nonce } => {
|
||||||
let pong = Frame::Pong { nonce };
|
let pong = Frame::Pong { nonce };
|
||||||
|
|||||||
@@ -275,12 +275,12 @@ pub fn spawn_pump(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if should_close {
|
if should_close {
|
||||||
|
store.lock().expect("store poisoned").remove(&session_id);
|
||||||
let close = Frame::Close {
|
let close = Frame::Close {
|
||||||
session_id,
|
session_id,
|
||||||
reason: Some(REASON_MAX_RETRIES),
|
reason: Some(REASON_MAX_RETRIES),
|
||||||
};
|
};
|
||||||
let _ = tx.try_send((peer_mac, close.encode()));
|
let _ = tx.try_send((peer_mac, close.encode()));
|
||||||
store.lock().expect("store poisoned").remove(&session_id);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,8 +330,8 @@ pub fn spawn_pump(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
store.lock().expect("store poisoned").remove(&session_id);
|
||||||
let close = Frame::Close { session_id, reason: None };
|
let close = Frame::Close { session_id, reason: None };
|
||||||
let _ = tx.send((peer_mac, close.encode())).await;
|
let _ = tx.send((peer_mac, close.encode())).await;
|
||||||
store.lock().expect("store poisoned").remove(&session_id);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user