初版功能完成
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled

This commit is contained in:
qsc
2026-08-29 13:12:17 +08:00
commit 142e5dc7d6
217 changed files with 21313 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
// Package udprelay preserves UDP datagram boundaries across a host socket flow.
package udprelay
import (
"context"
"errors"
"net"
"sync/atomic"
"time"
)
// Relay copies connected UDP datagrams until idle timeout or cancellation.
func Relay(ctx context.Context, left, right net.Conn, idleTimeout time.Duration) error {
parent := ctx
ctx, cancel := context.WithCancel(parent)
defer cancel()
if idleTimeout <= 0 {
idleTimeout = 60 * time.Second
}
var lastActivity atomic.Int64
lastActivity.Store(time.Now().UnixNano())
results := make(chan error, 2)
copyDatagrams := func(destination, source net.Conn) {
buffer := make([]byte, 65535)
for {
deadline := time.Now().Add(min(idleTimeout/2, time.Second))
_ = source.SetReadDeadline(deadline)
count, err := source.Read(buffer)
if err != nil {
if timeout, ok := err.(net.Error); ok && timeout.Timeout() {
last := time.Unix(0, lastActivity.Load())
if time.Since(last) < idleTimeout {
continue
}
}
results <- err
return
}
if _, err := destination.Write(buffer[:count]); err != nil {
results <- err
return
}
lastActivity.Store(time.Now().UnixNano())
}
}
go copyDatagrams(left, right)
go copyDatagrams(right, left)
go func() {
<-ctx.Done()
_ = left.Close()
_ = right.Close()
}()
err := <-results
cancel()
_ = left.Close()
_ = right.Close()
<-results
if parent.Err() != nil {
return parent.Err()
}
if timeout, ok := err.(net.Error); ok && timeout.Timeout() {
return nil
}
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
@@ -0,0 +1,44 @@
package udprelay
import (
"context"
"net"
"testing"
"time"
)
func TestRelayIdleTimeoutIsNormalFlowCompletion(t *testing.T) {
leftRelay, leftPeer := net.Pipe()
rightRelay, rightPeer := net.Pipe()
defer leftPeer.Close()
defer rightPeer.Close()
done := make(chan error, 1)
go func() { done <- Relay(context.Background(), leftRelay, rightRelay, 20*time.Millisecond) }()
select {
case err := <-done:
if err != nil {
t.Fatalf("idle Relay returned %v, want nil", err)
}
case <-time.After(time.Second):
t.Fatal("idle Relay did not reclaim the flow")
}
}
func TestRelayReturnsParentCancellation(t *testing.T) {
leftRelay, leftPeer := net.Pipe()
rightRelay, rightPeer := net.Pipe()
defer leftPeer.Close()
defer rightPeer.Close()
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- Relay(ctx, leftRelay, rightRelay, time.Hour) }()
cancel()
select {
case err := <-done:
if err != context.Canceled {
t.Fatalf("Relay cancellation = %v, want context.Canceled", err)
}
case <-time.After(time.Second):
t.Fatal("canceled Relay did not stop")
}
}