From f6429b6eeeb91db5b308600998cfa06560bd9168 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 23 Feb 2025 00:59:51 -0500 Subject: [PATCH] Basic holepunch working --- go.mod | 1 + go.sum | 14 ++++ network/network.go | 202 +++++++++++++++++++++++++++++++++++++++++++++ nohup.out | 25 ++++++ wg/wg.go | 159 ++++++++++++----------------------- 5 files changed, 295 insertions(+), 106 deletions(-) create mode 100644 network/network.go create mode 100644 nohup.out diff --git a/go.mod b/go.mod index c6931ef..c9d2752 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/josharian/native v1.1.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect diff --git a/go.sum b/go.sum index a0deda0..5e6875a 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -18,22 +20,34 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= diff --git a/network/network.go b/network/network.go new file mode 100644 index 0000000..0703e8b --- /dev/null +++ b/network/network.go @@ -0,0 +1,202 @@ +package network + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "log" + "net" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/vishvananda/netlink" + "golang.org/x/net/bpf" + "golang.org/x/net/ipv4" +) + +const ( + udpProtocol = 17 + // EmptyUDPSize is the size of an empty UDP packet + EmptyUDPSize = 28 + timeout = time.Second * 10 +) + +// Server stores data relating to the server +type Server struct { + Hostname string + Addr *net.IPAddr + Port uint16 +} + +// PeerNet stores data about a peer's endpoint +type PeerNet struct { + Resolved bool + IP net.IP + Port uint16 + NewtID string +} + +// GetClientIP gets source ip address that will be used when sending data to dstIP +func GetClientIP(dstIP net.IP) net.IP { + routes, err := netlink.RouteGet(dstIP) + if err != nil { + log.Fatalln("Error getting route:", err) + } + return routes[0].Src +} + +// HostToAddr resolves a hostname, whether DNS or IP to a valid net.IPAddr +func HostToAddr(hostStr string) *net.IPAddr { + remoteAddrs, err := net.LookupHost(hostStr) + if err != nil { + log.Fatalln("Error parsing remote address:", err) + } + + for _, addrStr := range remoteAddrs { + if remoteAddr, err := net.ResolveIPAddr("ip4", addrStr); err == nil { + return remoteAddr + } + } + return nil +} + +// SetupRawConn creates an ipv4 and udp only RawConn and applies packet filtering +func SetupRawConn(server *Server, client *PeerNet) *ipv4.RawConn { + packetConn, err := net.ListenPacket("ip4:udp", client.IP.String()) + if err != nil { + log.Fatalln("Error creating packetConn:", err) + } + + rawConn, err := ipv4.NewRawConn(packetConn) + if err != nil { + log.Fatalln("Error creating rawConn:", err) + } + + ApplyBPF(rawConn, server, client) + + return rawConn +} + +// ApplyBPF constructs a BPF program and applies it to the RawConn +func ApplyBPF(rawConn *ipv4.RawConn, server *Server, client *PeerNet) { + const ipv4HeaderLen = 20 + const srcIPOffset = 12 + const srcPortOffset = ipv4HeaderLen + 0 + const dstPortOffset = ipv4HeaderLen + 2 + + ipArr := []byte(server.Addr.IP.To4()) + ipInt := uint32(ipArr[0])<<(3*8) + uint32(ipArr[1])<<(2*8) + uint32(ipArr[2])<<8 + uint32(ipArr[3]) + + bpfRaw, err := bpf.Assemble([]bpf.Instruction{ + bpf.LoadAbsolute{Off: srcIPOffset, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: ipInt, SkipFalse: 5, SkipTrue: 0}, + + bpf.LoadAbsolute{Off: srcPortOffset, Size: 2}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(server.Port), SkipFalse: 3, SkipTrue: 0}, + + bpf.LoadAbsolute{Off: dstPortOffset, Size: 2}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(client.Port), SkipFalse: 1, SkipTrue: 0}, + + bpf.RetConstant{Val: 1<<(8*4) - 1}, + bpf.RetConstant{Val: 0}, + }) + + if err != nil { + log.Fatalln("Error assembling BPF:", err) + } + + err = rawConn.SetBPF(bpfRaw) + if err != nil { + log.Fatalln("Error setting BPF:", err) + } +} + +// MakePacket constructs a request packet to send to the server +func MakePacket(payload []byte, server *Server, client *PeerNet) []byte { + buf := gopacket.NewSerializeBuffer() + + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + ipHeader := layers.IPv4{ + SrcIP: client.IP, + DstIP: server.Addr.IP, + Version: 4, + TTL: 64, + Protocol: layers.IPProtocolUDP, + } + + udpHeader := layers.UDP{ + SrcPort: layers.UDPPort(client.Port), + DstPort: layers.UDPPort(server.Port), + } + + payloadLayer := gopacket.Payload(payload) + + udpHeader.SetNetworkLayerForChecksum(&ipHeader) + + gopacket.SerializeLayers(buf, opts, &ipHeader, &udpHeader, &payloadLayer) + + return buf.Bytes() +} + +// SendPacket sends packet to the Server +func SendPacket(packet []byte, conn *ipv4.RawConn, server *Server, client *PeerNet) error { + fullPacket := MakePacket(packet, server, client) + _, err := conn.WriteToIP(fullPacket, server.Addr) + return err +} + +// SendDataPacket sends a JSON payload to the Server +func SendDataPacket(data interface{}, conn *ipv4.RawConn, server *Server, client *PeerNet) error { + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal payload: %v", err) + } + + return SendPacket(jsonData, conn, server, client) +} + +// RecvPacket receives a UDP packet from server +func RecvPacket(conn *ipv4.RawConn, server *Server, client *PeerNet) ([]byte, int, error) { + err := conn.SetReadDeadline(time.Now().Add(timeout)) + if err != nil { + return nil, 0, err + } + + response := make([]byte, 4096) + n, err := conn.Read(response) + if err != nil { + return nil, n, err + } + return response, n, nil +} + +// RecvDataPacket receives and unmarshals a JSON packet from server +func RecvDataPacket(conn *ipv4.RawConn, server *Server, client *PeerNet) ([]byte, error) { + response, n, err := RecvPacket(conn, server, client) + if err != nil { + return nil, err + } + + // Extract payload from UDP packet + payload := response[EmptyUDPSize:n] + return payload, nil +} + +// ParseResponse takes a response packet and parses it into an IP and port +func ParseResponse(response []byte) (net.IP, uint16) { + ip := net.IP(response[:4]) + port := binary.BigEndian.Uint16(response[4:6]) + return ip, port +} + +func parseForBPF(response []byte) (srcIP net.IP, srcPort uint16, dstPort uint16) { + srcIP = net.IP(response[12:16]) + srcPort = binary.BigEndian.Uint16(response[20:22]) + dstPort = binary.BigEndian.Uint16(response[22:24]) + return +} diff --git a/nohup.out b/nohup.out new file mode 100644 index 0000000..58bc6f3 --- /dev/null +++ b/nohup.out @@ -0,0 +1,25 @@ +INFO: 2025/02/22 23:25:47 Requesting WireGuard configuration from remote server +INFO: 2025/02/22 23:25:47 Sent registration message +INFO: 2025/02/22 23:25:47 Received message: {newt/wg/receive-config map[ipAddress:100.90.128.1/24 listenPort:51822 peers:[]]} +INFO: 2025/02/22 23:25:47 Created WireGuard interface wg1 +INFO: 2025/02/22 23:25:47 Assigning IP address 100.90.128.1/24 to interface wg1 +INFO: 2025/02/22 23:25:47 WireGuard interface wg1 created and configured +INFO: 2025/02/22 23:25:47 Received registration message +INFO: 2025/02/22 23:25:47 Received: {Type:newt/wg/connect Data:map[endpoint:pangolin.fosrl.io:51820 publicKey:tng9Z/BN32flFjqwwT1yAxN/twFkmgbZA+D9N+YqdjM= serverIP:100.89.128.1 targets:map[tcp:[] udp:[]] tunnelIP:100.89.128.4]} +INFO: 2025/02/22 23:25:47 WireGuard device created. Lets ping the server now... +INFO: 2025/02/22 23:25:47 Ping attempt 1 of 5 +INFO: 2025/02/22 23:25:47 Pinging 100.89.128.1 +INFO: 2025/02/22 23:25:47 Ping latency: 9.00105ms +INFO: 2025/02/22 23:25:47 Starting ping check +INFO: 2025/02/22 23:26:48 Peer P9pacnRfUSfvDibaQTdTk59q27eRpgtbMMmMpkNwKl0= removed successfully +INFO: 2025/02/22 23:26:48 Peer NMrcorGgTTi4tAUZ1lLru0qISNrt9D9JdsFGyDYlcSQ= added successfully +INFO: 2025/02/22 23:28:58 Peer NMrcorGgTTi4tAUZ1lLru0qISNrt9D9JdsFGyDYlcSQ= removed successfully +INFO: 2025/02/22 23:28:58 Peer n8ZKTG8vsROL/OiqHYJELU/Rg9XDifz0YjE/lQsL0m0= added successfully +INFO: 2025/02/22 23:33:59 Peer n8ZKTG8vsROL/OiqHYJELU/Rg9XDifz0YjE/lQsL0m0= removed successfully +INFO: 2025/02/22 23:33:59 Peer /i8YTgrLkZh08HKXLXqNFQJsyg1E8I2ELXqF0zuP9D8= added successfully +INFO: 2025/02/22 23:34:06 Peer /i8YTgrLkZh08HKXLXqNFQJsyg1E8I2ELXqF0zuP9D8= removed successfully +INFO: 2025/02/22 23:34:06 Peer 50+RB00sDoSG+KAKzl/baaqPkKGOe7upX7uqRCKqsRo= added successfully +INFO: 2025/02/22 23:35:07 Peer 50+RB00sDoSG+KAKzl/baaqPkKGOe7upX7uqRCKqsRo= removed successfully +INFO: 2025/02/22 23:35:07 Peer Aa2Y2NEmc+SITlT89+fsOeqDkXJVu9RBY14+77TXa3w= added successfully +INFO: 2025/02/23 00:55:55 Peer Aa2Y2NEmc+SITlT89+fsOeqDkXJVu9RBY14+77TXa3w= removed successfully +INFO: 2025/02/23 00:55:55 Peer 2AXNjMQzT7GGvdbIG6MJVFpO3FIzQ+qCqZkdSnBA3DE= added successfully diff --git a/wg/wg.go b/wg/wg.go index 6883ca9..fa6760f 100644 --- a/wg/wg.go +++ b/wg/wg.go @@ -5,10 +5,13 @@ import ( "fmt" "net" "os" + "strconv" + "strings" "sync" "time" "github.com/fosrl/newt/logger" + "github.com/fosrl/newt/network" "github.com/fosrl/newt/websocket" "github.com/vishvananda/netlink" "golang.org/x/exp/rand" @@ -214,7 +217,7 @@ func (s *WireGuardService) handleConfig(msg websocket.WSMessage) { s.config = config // stop the holepunch - close(s.stopHolepunch) + // close(s.stopHolepunch) // Ensure the WireGuard interface and peers are configured if err := s.ensureWireguardInterface(config); err != nil { @@ -373,94 +376,6 @@ func (s *WireGuardService) ensureWireguardPeers(peers []Peer) error { return nil } -// func (s *WireGuardService) ensureMSSClamping() error { -// // Calculate MSS value (MTU - 40 for IPv4 header (20) and TCP header (20)) -// mssValue := mtuInt - 40 - -// // Rules to be managed - just the chains, we'll construct the full command separately -// chains := []string{"INPUT", "OUTPUT", "FORWARD"} - -// // First, try to delete any existing rules -// for _, chain := range chains { -// deleteCmd := exec.Command("/usr/sbin/iptables", -// "-t", "mangle", -// "-D", chain, -// "-p", "tcp", -// "--tcp-flags", "SYN,RST", "SYN", -// "-j", "TCPMSS", -// "--set-mss", fmt.Sprintf("%d", mssValue)) - -// logger.Info("Attempting to delete existing MSS clamping rule for chain %s", chain) - -// // Try deletion multiple times to handle multiple existing rules -// for i := 0; i < 3; i++ { -// out, err := deleteCmd.CombinedOutput() -// if err != nil { -// // Convert exit status 1 to string for better logging -// if exitErr, ok := err.(*exec.ExitError); ok { -// logger.Debug("Deletion stopped for chain %s: %v (output: %s)", -// chain, exitErr.String(), string(out)) -// } -// break // No more rules to delete -// } -// logger.Info("Deleted MSS clamping rule for chain %s (attempt %d)", chain, i+1) -// } -// } - -// // Then add the new rules -// var errors []error -// for _, chain := range chains { -// addCmd := exec.Command("/usr/sbin/iptables", -// "-t", "mangle", -// "-A", chain, -// "-p", "tcp", -// "--tcp-flags", "SYN,RST", "SYN", -// "-j", "TCPMSS", -// "--set-mss", fmt.Sprintf("%d", mssValue)) - -// logger.Info("Adding MSS clamping rule for chain %s", chain) - -// if out, err := addCmd.CombinedOutput(); err != nil { -// errMsg := fmt.Sprintf("Failed to add MSS clamping rule for chain %s: %v (output: %s)", -// chain, err, string(out)) -// logger.Error(errMsg) -// errors = append(errors, fmt.Errorf(errMsg)) -// continue -// } - -// // Verify the rule was added -// checkCmd := exec.Command("/usr/sbin/iptables", -// "-t", "mangle", -// "-C", chain, -// "-p", "tcp", -// "--tcp-flags", "SYN,RST", "SYN", -// "-j", "TCPMSS", -// "--set-mss", fmt.Sprintf("%d", mssValue)) - -// if out, err := checkCmd.CombinedOutput(); err != nil { -// errMsg := fmt.Sprintf("Rule verification failed for chain %s: %v (output: %s)", -// chain, err, string(out)) -// logger.Error(errMsg) -// errors = append(errors, fmt.Errorf(errMsg)) -// continue -// } - -// logger.Info("Successfully added and verified MSS clamping rule for chain %s", chain) -// } - -// // If we encountered any errors, return them combined -// if len(errors) > 0 { -// var errMsgs []string -// for _, err := range errors { -// errMsgs = append(errMsgs, err.Error()) -// } -// return fmt.Errorf("MSS clamping setup encountered errors:\n%s", -// strings.Join(errMsgs, "\n")) -// } - -// return nil -// } - func (s *WireGuardService) handleAddPeer(msg websocket.WSMessage) { var peer Peer @@ -681,40 +596,72 @@ func (s *WireGuardService) reportPeerBandwidth() error { } func (s *WireGuardService) sendUDPHolePunch(serverAddr string) error { - // Bind to specific local port - localAddr := &net.UDPAddr{ - Port: int(s.port), - IP: net.IPv4zero, + // Parse server address + serverSplit := strings.Split(serverAddr, ":") + if len(serverSplit) < 2 { + return fmt.Errorf("invalid server address format, expected hostname:port") } - remoteAddr, err := net.ResolveUDPAddr("udp", serverAddr) + serverHostname := serverSplit[0] + serverPort, err := strconv.ParseUint(serverSplit[1], 10, 16) if err != nil { - return fmt.Errorf("failed to resolve UDP address: %v", err) + return fmt.Errorf("failed to parse server port: %v", err) } - conn, err := net.ListenUDP("udp", localAddr) - if err != nil { - return fmt.Errorf("failed to bind UDP socket: %v", err) + // Resolve server hostname to IP + serverIPAddr := network.HostToAddr(serverHostname) + if serverIPAddr == nil { + return fmt.Errorf("failed to resolve server hostname") } - defer conn.Close() + // Get client IP based on route to server + clientIP := network.GetClientIP(serverIPAddr.IP) + + // Create server and client configs + server := &network.Server{ + Hostname: serverHostname, + Addr: serverIPAddr, + Port: uint16(serverPort), + } + + client := &network.PeerNet{ + IP: clientIP, + Port: s.port, + NewtID: s.newtId, + } + + // Setup raw connection with BPF filtering + rawConn := network.SetupRawConn(server, client) + defer rawConn.Close() + + // Create JSON payload payload := struct { NewtID string `json:"newtId"` }{ NewtID: s.newtId, } - data, err := json.Marshal(payload) - if err != nil { - return fmt.Errorf("failed to marshal payload: %v", err) - } - - _, err = conn.WriteToUDP(data, remoteAddr) + // Send the packet using the raw connection + err = network.SendDataPacket(payload, rawConn, server, client) if err != nil { return fmt.Errorf("failed to send UDP packet: %v", err) } - logger.Info("Sent UDP hole punch to %s", serverAddr) + // logger.Info("Sent UDP hole punch to %s", serverAddr) + + // // Wait for response if needed + // response, err := network.RecvDataPacket(rawConn, server, client) + // if err != nil { + // if err, ok := err.(net.Error); ok && err.Timeout() { + // return fmt.Errorf("connection to %s timed out", serverAddr) + // } + // return fmt.Errorf("error receiving response: %v", err) + // } + + // // Process response if needed + // if len(response) > 0 { + // logger.Info("Received response from server") + // } return nil }