Files
2026-03-28 15:28:19 +08:00

187 lines
6.2 KiB
Go

package main
import (
"flag"
"log"
"net"
"strings"
"time"
kcp "github.com/xtaci/kcp-go/v5"
"omnisocketgo/cmd/internal/latencylog"
"omnisocketgo/cmd/internal/server"
"omnisocketgo/cmd/internal/transport"
)
const (
kcpServerModeHub = "hub"
kcpServerModeRelay = "relay"
)
func main() {
mode := flag.String("mode", kcpServerModeHub, "kcpserver mode: hub or relay")
listenAddr := flag.String("listen", ":9002", "listen address; KCP listener in hub mode, UDP relay listener in relay mode")
bindDevice := flag.String("bind-device", "", "optional Linux network device used when listening")
logPath := flag.String("latency-log", "", "optional JSONL file path for latency timestamp logs")
kcpTimestampDebugLogPath := flag.String("kcp-ts-debug-log", "", "optional JSONL file path for KCP packet kernel timestamp debug records")
kcpSessionStatsLogPath := flag.String("kcp-session-stats-log", "", "optional JSONL file path for KCP session stats records")
kcpSessionStatsInterval := flag.String("kcp-session-stats-interval", transport.DefaultKCPSessionStatsInterval.String(), "sampling interval for KCP session stats, for example 100ms")
relayListenAddr := flag.String("relay-listen", "", "deprecated alias for -listen in relay mode")
relayRemoteAddr := flag.String("relay-remote", "", "fixed remote UDP address used in relay mode")
relayPeerAddr := flag.String("relay-peer", "", "deprecated alias for -relay-remote")
flag.Parse()
var relayRemoteFlagSet bool
var relayPeerFlagSet bool
var relayListenFlagSet bool
flag.Visit(func(f *flag.Flag) {
switch f.Name {
case "relay-listen":
relayListenFlagSet = true
case "relay-remote":
relayRemoteFlagSet = true
case "relay-peer":
relayPeerFlagSet = true
}
})
switch {
case relayRemoteFlagSet && relayPeerFlagSet && *relayRemoteAddr != *relayPeerAddr:
log.Fatal("flags -relay-remote and -relay-peer must match when both are set")
case *relayRemoteAddr == "" && *relayPeerAddr != "":
*relayRemoteAddr = *relayPeerAddr
}
if relayPeerFlagSet {
log.Printf("warning: flag -relay-peer is deprecated; use -relay-remote instead")
}
if relayListenFlagSet {
if *relayListenAddr == "" {
log.Fatal("flag -relay-listen must not be empty when set")
}
if *mode != kcpServerModeRelay {
log.Fatal("flag -relay-listen may only be used in relay mode")
}
if *listenAddr != ":9002" && *listenAddr != *relayListenAddr {
log.Fatal("flags -listen and -relay-listen must match when both are set in relay mode")
}
*listenAddr = *relayListenAddr
log.Printf("warning: flag -relay-listen is deprecated; use -listen with -mode=relay instead")
}
statsInterval, err := transport.ParseKCPSessionStatsInterval(*kcpSessionStatsInterval)
if err != nil {
log.Fatalf("parse -kcp-session-stats-interval=%q: %v", *kcpSessionStatsInterval, err)
}
switch *mode {
case kcpServerModeHub:
if *relayRemoteAddr != "" {
log.Fatal("flag -relay-remote may only be used in relay mode")
}
runHubServer(*listenAddr, *bindDevice, *logPath, *kcpTimestampDebugLogPath, *kcpSessionStatsLogPath, statsInterval)
case kcpServerModeRelay:
if *bindDevice != "" {
log.Fatal("flag -bind-device is not supported in relay mode")
}
if *relayRemoteAddr == "" {
log.Fatal("flag -relay-remote is required in relay mode")
}
runUDPRelayServer(*listenAddr, *relayRemoteAddr)
default:
log.Fatalf("unsupported -mode=%q; want %q or %q", *mode, kcpServerModeHub, kcpServerModeRelay)
}
}
func runHubServer(listenAddr, bindDevice, logPath, packetDebugLogPath, sessionStatsLogPath string, statsInterval time.Duration) {
listenNetwork, _, err := transport.ResolveUDPListenConfig(listenAddr)
if err != nil {
log.Fatalf("resolve kcp listen address %s: %v", listenAddr, err)
}
hubOptions := make([]server.KCPOption, 0, 2)
if logPath != "" {
logger, err := latencylog.NewJSONLLogger(logPath)
if err != nil {
log.Fatalf("create latency logger %s: %v", logPath, err)
}
defer logger.Close()
hubOptions = append(hubOptions, server.WithKCPLogger(logger))
}
var packetLogger transport.KCPPacketDebugLogger
if packetDebugLogPath != "" {
logger, err := transport.NewJSONLKCPPacketDebugLogger(packetDebugLogPath)
if err != nil {
log.Fatalf("create kcp packet debug logger %s: %v", packetDebugLogPath, err)
}
defer logger.Close()
packetLogger = logger
}
if sessionStatsLogPath != "" {
logger, err := transport.NewJSONLKCPSessionStatsLogger(sessionStatsLogPath)
if err != nil {
log.Fatalf("create kcp session stats logger %s: %v", sessionStatsLogPath, err)
}
defer logger.Close()
hubOptions = append(hubOptions, server.WithKCPSessionStatsLogger(logger, statsInterval))
}
listener, packetConn, err := transport.ListenKCPSessions(listenAddr, bindDevice, packetLogger, latencylog.NodeRoleServer, "hub")
if err != nil {
log.Fatalf("listen kcp on %s: %v", listenAddr, err)
}
defer packetConn.Close()
defer listener.Close()
hub := server.NewKCPHub(hubOptions...)
log.Printf("kcp hub listening on %s %s", listenNetwork, packetConn.LocalAddr())
for {
session, err := listener.AcceptKCP()
if err != nil {
if strings.Contains(err.Error(), "closed") {
return
}
log.Printf("accept kcp session: %v", err)
continue
}
go func(sess *kcp.UDPSession) {
if serveErr := hub.ServeSession(sess); serveErr != nil {
log.Printf("kcp session closed: %v", serveErr)
}
}(session)
}
}
func runUDPRelayServer(listenAddr, remoteAddr string) {
listenNetwork, udpListenAddr, err := transport.ResolveUDPListenConfig(listenAddr)
if err != nil {
log.Fatalf("resolve udp relay listen address %s: %v", listenAddr, err)
}
conn, err := net.ListenPacket(listenNetwork, udpListenAddr.String())
if err != nil {
log.Fatalf("listen %s relay on %s: %v", listenNetwork, udpListenAddr, err)
}
defer conn.Close()
remote, err := net.ResolveUDPAddr("udp", remoteAddr)
if err != nil {
log.Fatalf("resolve relay remote %s: %v", remoteAddr, err)
}
relay, err := server.NewUDPRelay(conn, remote)
if err != nil {
_ = conn.Close()
log.Fatalf("create udp relay: %v", err)
}
log.Printf("udp relay listening on %s %s and forwarding to %s", listenNetwork, conn.LocalAddr(), remote)
if err := relay.Serve(); err != nil {
log.Fatalf("udp relay stopped: %v", err)
}
}