package ike import ( "context" "encoding/binary" "errors" "fmt" "io" "net" "strconv" "strings" "sync" "time" "vocat/internal/vowifi" ) type datagramTransport interface { LocalAddr() *net.UDPAddr RemoteAddr() *net.UDPAddr Float(context.Context) error RoundTrip(context.Context, []byte) ([]byte, error) SendESP(context.Context, []byte) error ReceiveESP(context.Context, []byte) (int, error) SendSessionPacket(context.Context, []byte, bool) error ReceiveSessionPacket(context.Context, []byte) (int, bool, error) Close() error } type transportConfig struct { Resolver *net.Resolver Dialer *net.Dialer Timeout time.Duration } func newDatagramTransport( ctx context.Context, config transportConfig, route vowifi.ProxyRoute, host string, ) (datagramTransport, error) { if config.Resolver == nil { config.Resolver = net.DefaultResolver } if config.Dialer == nil { config.Dialer = &net.Dialer{} } if config.Timeout <= 0 { config.Timeout = 12 * time.Second } addresses, err := resolveEPDG(ctx, config.Resolver, host) if err != nil { return nil, fmt.Errorf("ike: resolve ePDG: %w", err) } var remoteIPs []net.IP for _, address := range addresses { candidate := address.IP.To4() if candidate == nil { candidate = address.IP.To16() } if candidate == nil { continue } duplicate := false for _, existing := range remoteIPs { if existing.Equal(candidate) { duplicate = true break } } if !duplicate { remoteIPs = append(remoteIPs, append(net.IP(nil), candidate...)) } } if len(remoteIPs) == 0 { return nil, errors.New("ike: ePDG did not resolve to an IP address") } remotes := make([]*net.UDPAddr, 0, len(remoteIPs)) for _, remoteIP := range remoteIPs { remotes = append(remotes, &net.UDPAddr{IP: remoteIP, Port: 500}) } switch route.Mode { case "", vowifi.ProxyModeDirect: return newDirectUDP(ctx, config, remotes[0]) case vowifi.ProxyModeSOCKS5: return newSOCKS5UDP(ctx, config, route, remotes) default: return nil, fmt.Errorf("ike: unsupported proxy mode %q", route.Mode) } } func roundTripDatagram( ctx context.Context, timeout time.Duration, write func([]byte) error, read func([]byte, time.Time) (int, error), packet []byte, ) ([]byte, error) { if ctx == nil { ctx = context.Background() } deadline := time.Now().Add(timeout) if callerDeadline, ok := ctx.Deadline(); ok && callerDeadline.Before(deadline) { deadline = callerDeadline } retransmit := []time.Duration{500 * time.Millisecond, time.Second, 2 * time.Second, 4 * time.Second} buffer := make([]byte, 65535) var lastErr error for _, interval := range retransmit { if err := ctx.Err(); err != nil { return nil, err } if err := write(packet); err != nil { return nil, err } attemptDeadline := time.Now().Add(interval) if deadline.Before(attemptDeadline) { attemptDeadline = deadline } for time.Now().Before(attemptDeadline) { if err := ctx.Err(); err != nil { return nil, err } n, err := read(buffer, attemptDeadline) if err == nil { return append([]byte(nil), buffer[:n]...), nil } if timeoutError, ok := err.(net.Error); ok && timeoutError.Timeout() { lastErr = err break } return nil, err } if !time.Now().Before(deadline) { break } } if lastErr == nil { lastErr = context.DeadlineExceeded } return nil, fmt.Errorf("ike: UDP exchange timed out: %w", lastErr) } type directUDP struct { mu sync.Mutex readMu sync.Mutex writeMu sync.Mutex config transportConfig conn *net.UDPConn remote *net.UDPAddr floated bool } func newDirectUDP(ctx context.Context, config transportConfig, remote *net.UDPAddr) (*directUDP, error) { transport := &directUDP{config: config, remote: cloneUDPAddr(remote)} if err := transport.dial(ctx, false); err != nil { return nil, err } return transport, nil } func (transport *directUDP) dial(ctx context.Context, bind4500 bool) error { dialer := *transport.config.Dialer if bind4500 { localIP := net.IP(nil) if transport.conn != nil { if current, ok := transport.conn.LocalAddr().(*net.UDPAddr); ok { localIP = append(net.IP(nil), current.IP...) } } dialer.LocalAddr = &net.UDPAddr{IP: localIP, Port: 4500} } connection, err := dialer.DialContext(ctx, "udp", transport.remote.String()) if err != nil && bind4500 { dialer.LocalAddr = nil connection, err = dialer.DialContext(ctx, "udp", transport.remote.String()) } if err != nil { return fmt.Errorf("ike: dial ePDG UDP: %w", err) } udp, ok := connection.(*net.UDPConn) if !ok { _ = connection.Close() return errors.New("ike: UDP dialer returned a non-UDP connection") } old := transport.conn transport.conn = udp if old != nil { _ = old.Close() } return nil } func (transport *directUDP) LocalAddr() *net.UDPAddr { transport.mu.Lock() defer transport.mu.Unlock() if transport.conn == nil { return nil } address, _ := transport.conn.LocalAddr().(*net.UDPAddr) return cloneUDPAddr(address) } func (transport *directUDP) RemoteAddr() *net.UDPAddr { transport.mu.Lock() defer transport.mu.Unlock() return cloneUDPAddr(transport.remote) } func (transport *directUDP) Float(ctx context.Context) error { transport.mu.Lock() defer transport.mu.Unlock() if transport.floated { return nil } transport.remote.Port = 4500 if err := transport.dial(ctx, true); err != nil { return err } transport.floated = true return nil } func (transport *directUDP) RoundTrip(ctx context.Context, packet []byte) ([]byte, error) { transport.mu.Lock() defer transport.mu.Unlock() if transport.conn == nil { return nil, errors.New("ike: UDP transport is closed") } requestHeader, _, err := parseIKEPacket(packet) if err != nil { return nil, fmt.Errorf("ike: invalid outbound packet: %w", err) } wirePacket := packet if transport.floated { wirePacket = append([]byte{0, 0, 0, 0}, packet...) } write := func(value []byte) error { if err := transport.conn.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err := transport.conn.Write(value) return err } read := func(buffer []byte, attemptDeadline time.Time) (int, error) { for { if err := transport.conn.SetReadDeadline(attemptDeadline); err != nil { return 0, err } n, err := transport.conn.Read(buffer) if err != nil { return 0, err } if transport.floated { // IKE and ESP legitimately share UDP/4500. An ESP packet can // arrive immediately before the IKE response that completes // CHILD_SA setup; discard it here and keep the same absolute // attempt deadline while waiting for marked IKE. if !hasNonESPMarker(buffer[:n]) { continue } copy(buffer, buffer[4:n]) n -= 4 } if !ikeResponseMatchesRequest(buffer[:n], requestHeader) { continue } return n, nil } } return roundTripDatagram(ctx, transport.config.Timeout, write, read, wirePacket) } func (transport *directUDP) SendESP(ctx context.Context, packet []byte) error { transport.mu.Lock() defer transport.mu.Unlock() if transport.conn == nil || !transport.floated { return errors.New("ike: ESP relay requires an active UDP/4500 transport") } if len(packet) < 8 { return errors.New("ike: ESP packet is too short") } if err := transport.conn.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err := transport.conn.Write(packet) return err } func (transport *directUDP) ReceiveESP(ctx context.Context, buffer []byte) (int, error) { transport.mu.Lock() defer transport.mu.Unlock() if transport.conn == nil || !transport.floated { return 0, errors.New("ike: ESP relay requires an active UDP/4500 transport") } if err := transport.conn.SetReadDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return 0, err } n, err := transport.conn.Read(buffer) if err != nil { return 0, err } if n >= 4 && buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0 { return 0, errors.New("ike: received an IKE packet on the ESP relay") } return n, nil } func (transport *directUDP) SendSessionPacket(ctx context.Context, packet []byte, ike bool) error { transport.mu.Lock() connection := transport.conn floated := transport.floated transport.mu.Unlock() if connection == nil { return errors.New("ike: UDP transport is closed") } wire := packet if floated { if ike { wire = append([]byte{0, 0, 0, 0}, packet...) } } else if !ike { return errors.New("ike: ESP is not UDP encapsulated on an un-floated transport") } transport.writeMu.Lock() defer transport.writeMu.Unlock() if err := connection.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err := connection.Write(wire) return err } func (transport *directUDP) ReceiveSessionPacket(ctx context.Context, buffer []byte) (int, bool, error) { transport.mu.Lock() connection := transport.conn floated := transport.floated transport.mu.Unlock() if connection == nil { return 0, false, errors.New("ike: UDP transport is closed") } transport.readMu.Lock() defer transport.readMu.Unlock() if err := connection.SetReadDeadline(deadlineFor(ctx, time.Second)); err != nil { return 0, false, err } n, err := connection.Read(buffer) if err != nil { return 0, false, err } if !floated { return n, true, nil } if n >= 4 && buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0 { copy(buffer, buffer[4:n]) return n - 4, true, nil } return n, false, nil } func (transport *directUDP) Close() error { transport.mu.Lock() defer transport.mu.Unlock() if transport.conn == nil { return nil } err := transport.conn.Close() transport.conn = nil return err } type socks5UDP struct { mu sync.Mutex readMu sync.Mutex writeMu sync.Mutex config transportConfig control net.Conn udp *net.UDPConn relay *net.UDPAddr remote *net.UDPAddr remotes []*net.UDPAddr floated bool } func newSOCKS5UDP( ctx context.Context, config transportConfig, route vowifi.ProxyRoute, remotes []*net.UDPAddr, ) (*socks5UDP, error) { if len(remotes) == 0 || remotes[0] == nil { return nil, errors.New("ike: SOCKS5 transport requires an ePDG destination") } proxyAddress := strings.TrimSpace(route.Address) if _, _, err := net.SplitHostPort(proxyAddress); err != nil { return nil, fmt.Errorf("ike: invalid SOCKS5 proxy address: %w", err) } control, err := config.Dialer.DialContext(ctx, "tcp", proxyAddress) if err != nil { return nil, fmt.Errorf("ike: connect SOCKS5 proxy %s: %w", proxyAddress, err) } fail := func(cause error) (*socks5UDP, error) { _ = control.Close() return nil, cause } if err := control.SetDeadline(deadlineFor(ctx, config.Timeout)); err != nil { return fail(err) } methods := []byte{0} if route.Username != "" { methods = append(methods, 2) } greeting := append([]byte{5, byte(len(methods))}, methods...) if _, err := control.Write(greeting); err != nil { return fail(fmt.Errorf("ike: SOCKS5 greeting: %w", err)) } var selection [2]byte if _, err := io.ReadFull(control, selection[:]); err != nil { return fail(fmt.Errorf("ike: SOCKS5 method selection: %w", err)) } if selection[0] != 5 { return fail(errors.New("ike: SOCKS5 proxy returned an invalid version")) } switch selection[1] { case 0: case 2: if err := socksUserPassword(control, route.Username, route.Password); err != nil { return fail(err) } default: return fail(fmt.Errorf("ike: SOCKS5 proxy selected unsupported authentication method %d", selection[1])) } if _, err := control.Write([]byte{5, 3, 0, 1, 0, 0, 0, 0, 0, 0}); err != nil { return fail(fmt.Errorf("ike: SOCKS5 UDP ASSOCIATE request: %w", err)) } relay, err := readSOCKS5Reply(ctx, control, config.Resolver) if err != nil { return fail(err) } if relay.IP == nil || relay.IP.IsUnspecified() { if peer, ok := control.RemoteAddr().(*net.TCPAddr); ok { relay.IP = append(net.IP(nil), peer.IP...) } } udpConnection, err := net.DialUDP("udp", nil, relay) if err != nil { return fail(fmt.Errorf("ike: dial SOCKS5 UDP relay: %w", err)) } _ = control.SetDeadline(time.Time{}) return &socks5UDP{ config: config, control: control, udp: udpConnection, relay: relay, remote: cloneUDPAddr(remotes[0]), remotes: cloneUDPAddrs(remotes), }, nil } func socksUserPassword(connection net.Conn, username, password string) error { if len(username) > 255 || len(password) > 255 { return errors.New("ike: SOCKS5 username or password exceeds 255 bytes") } request := []byte{1, byte(len(username))} request = append(request, username...) request = append(request, byte(len(password))) request = append(request, password...) if _, err := connection.Write(request); err != nil { return fmt.Errorf("ike: SOCKS5 credential exchange: %w", err) } var response [2]byte if _, err := io.ReadFull(connection, response[:]); err != nil { return fmt.Errorf("ike: SOCKS5 credential response: %w", err) } if response[0] != 1 || response[1] != 0 { return errors.New("ike: SOCKS5 authentication failed") } return nil } func readSOCKS5Reply(ctx context.Context, connection net.Conn, resolver *net.Resolver) (*net.UDPAddr, error) { var header [4]byte if _, err := io.ReadFull(connection, header[:]); err != nil { return nil, fmt.Errorf("ike: SOCKS5 UDP ASSOCIATE response: %w", err) } if header[0] != 5 || header[1] != 0 || header[2] != 0 { return nil, fmt.Errorf("ike: SOCKS5 UDP ASSOCIATE rejected with code %d", header[1]) } ip, name, err := readSOCKSAddress(connection, header[3]) if err != nil { return nil, err } var encodedPort [2]byte if _, err := io.ReadFull(connection, encodedPort[:]); err != nil { return nil, fmt.Errorf("ike: SOCKS5 relay port: %w", err) } if ip == nil && name != "" { if resolver == nil { resolver = net.DefaultResolver } addresses, err := resolver.LookupIPAddr(ctx, name) if err != nil || len(addresses) == 0 { return nil, fmt.Errorf("ike: resolve SOCKS5 UDP relay domain %q: %w", name, err) } ip = addresses[0].IP } return &net.UDPAddr{IP: ip, Port: int(binary.BigEndian.Uint16(encodedPort[:]))}, nil } func readSOCKSAddress(reader io.Reader, kind byte) (net.IP, string, error) { switch kind { case 1: ip := make(net.IP, net.IPv4len) if _, err := io.ReadFull(reader, ip); err != nil { return nil, "", err } return ip, "", nil case 4: ip := make(net.IP, net.IPv6len) if _, err := io.ReadFull(reader, ip); err != nil { return nil, "", err } return ip, "", nil case 3: var length [1]byte if _, err := io.ReadFull(reader, length[:]); err != nil { return nil, "", err } name := make([]byte, int(length[0])) if _, err := io.ReadFull(reader, name); err != nil { return nil, "", err } return nil, string(name), nil default: return nil, "", fmt.Errorf("ike: unsupported SOCKS5 address type %d", kind) } } func (transport *socks5UDP) LocalAddr() *net.UDPAddr { transport.mu.Lock() defer transport.mu.Unlock() if transport.udp == nil { return nil } address, _ := transport.udp.LocalAddr().(*net.UDPAddr) return cloneUDPAddr(address) } func (transport *socks5UDP) RemoteAddr() *net.UDPAddr { transport.mu.Lock() defer transport.mu.Unlock() return cloneUDPAddr(transport.remote) } func (transport *socks5UDP) Float(_ context.Context) error { transport.mu.Lock() defer transport.mu.Unlock() transport.remote.Port = 4500 transport.floated = true return nil } func (transport *socks5UDP) RoundTrip(ctx context.Context, packet []byte) ([]byte, error) { transport.mu.Lock() defer transport.mu.Unlock() if transport.udp == nil { return nil, errors.New("ike: SOCKS5 UDP transport is closed") } requestHeader, _, err := parseIKEPacket(packet) if err != nil { return nil, fmt.Errorf("ike: invalid outbound packet: %w", err) } // Carrier ePDG hostnames commonly return several gateways. A SOCKS5 // egress can reach a different subset than the local host, so an initial // timeout on one address must not make the entire hostname unavailable. // Once a gateway answers, keep it pinned for the lifetime of the IKE SA. if !transport.floated && requestHeader.Exchange == exchangeIKEInit && requestHeader.MessageID == 0 && len(transport.remotes) > 1 { var lastErr error for _, candidate := range transport.remotes { transport.remote = cloneUDPAddr(candidate) response, attemptErr := transport.roundTripLocked(ctx, packet, requestHeader) if attemptErr == nil { return response, nil } lastErr = attemptErr if ctx.Err() != nil || !isNetworkTimeout(attemptErr) { return nil, attemptErr } } return nil, fmt.Errorf("ike: all %d resolved ePDG addresses timed out: %w", len(transport.remotes), lastErr) } return transport.roundTripLocked(ctx, packet, requestHeader) } func (transport *socks5UDP) roundTripLocked(ctx context.Context, packet []byte, requestHeader ikeHeader) ([]byte, error) { wireIKE := packet if transport.floated { wireIKE = append([]byte{0, 0, 0, 0}, packet...) } datagram, err := marshalSOCKS5Datagram(transport.remote, wireIKE) if err != nil { return nil, err } write := func(value []byte) error { if err := transport.udp.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err := transport.udp.Write(value) return err } read := func(buffer []byte, attemptDeadline time.Time) (int, error) { for { payload, err := readExpectedSOCKS5Datagram( transport.udp, transport.remote, buffer, attemptDeadline, ) if err != nil { return 0, err } if transport.floated { // The relay can deliver ESP before the marked IKE response on // the same UDP/4500 association. Do not accept it as IKE, and // do not abort the exchange; keep waiting within the original // deadline. if !hasNonESPMarker(payload) { continue } payload = payload[4:] } if !ikeResponseMatchesRequest(payload, requestHeader) { continue } copy(buffer, payload) return len(payload), nil } } return roundTripDatagram(ctx, transport.config.Timeout, write, read, datagram) } func isNetworkTimeout(err error) bool { var networkError net.Error return errors.As(err, &networkError) && networkError.Timeout() } func (transport *socks5UDP) SendESP(ctx context.Context, packet []byte) error { transport.mu.Lock() defer transport.mu.Unlock() if transport.udp == nil || !transport.floated { return errors.New("ike: SOCKS5 ESP relay requires an active UDP/4500 association") } if len(packet) < 8 { return errors.New("ike: ESP packet is too short") } datagram, err := marshalSOCKS5Datagram(transport.remote, packet) if err != nil { return err } if err := transport.udp.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err = transport.udp.Write(datagram) return err } func (transport *socks5UDP) ReceiveESP(ctx context.Context, buffer []byte) (int, error) { transport.mu.Lock() defer transport.mu.Unlock() if transport.udp == nil || !transport.floated { return 0, errors.New("ike: SOCKS5 ESP relay requires an active UDP/4500 association") } wire := make([]byte, len(buffer)+32) payload, err := readExpectedSOCKS5Datagram( transport.udp, transport.remote, wire, deadlineFor(ctx, transport.config.Timeout), ) if err != nil { return 0, err } if len(payload) >= 4 && payload[0] == 0 && payload[1] == 0 && payload[2] == 0 && payload[3] == 0 { return 0, errors.New("ike: received an IKE packet on the SOCKS5 ESP relay") } if len(payload) > len(buffer) { return 0, io.ErrShortBuffer } copy(buffer, payload) return len(payload), nil } func (transport *socks5UDP) SendSessionPacket(ctx context.Context, packet []byte, ike bool) error { transport.mu.Lock() connection := transport.udp floated := transport.floated remote := cloneUDPAddr(transport.remote) transport.mu.Unlock() if connection == nil || !floated { return errors.New("ike: SOCKS5 session transport is not on UDP/4500") } wire := packet if ike { wire = append([]byte{0, 0, 0, 0}, packet...) } datagram, err := marshalSOCKS5Datagram(remote, wire) if err != nil { return err } transport.writeMu.Lock() defer transport.writeMu.Unlock() if err := connection.SetWriteDeadline(deadlineFor(ctx, transport.config.Timeout)); err != nil { return err } _, err = connection.Write(datagram) return err } func (transport *socks5UDP) ReceiveSessionPacket(ctx context.Context, buffer []byte) (int, bool, error) { transport.mu.Lock() connection := transport.udp floated := transport.floated remote := cloneUDPAddr(transport.remote) transport.mu.Unlock() if connection == nil || !floated { return 0, false, errors.New("ike: SOCKS5 session transport is not on UDP/4500") } wire := make([]byte, len(buffer)+32) transport.readMu.Lock() defer transport.readMu.Unlock() payload, err := readExpectedSOCKS5Datagram( connection, remote, wire, deadlineFor(ctx, time.Second), ) if err != nil { return 0, false, err } isIKE := len(payload) >= 4 && payload[0] == 0 && payload[1] == 0 && payload[2] == 0 && payload[3] == 0 if isIKE { payload = payload[4:] } if len(payload) > len(buffer) { return 0, false, io.ErrShortBuffer } copy(buffer, payload) return len(payload), isIKE, nil } func (transport *socks5UDP) Close() error { transport.mu.Lock() defer transport.mu.Unlock() var errs []error if transport.udp != nil { if err := transport.udp.Close(); err != nil { errs = append(errs, err) } transport.udp = nil } if transport.control != nil { if err := transport.control.Close(); err != nil { errs = append(errs, err) } transport.control = nil } return errors.Join(errs...) } // readExpectedSOCKS5Datagram discards valid datagrams attributed to a // different destination until the caller's deadline. A retransmitted // IKE_SA_INIT response from UDP/500 can legitimately remain queued after the // transport floats to UDP/4500; it must not be accepted as the current // exchange, but it is not a reason to abort the authenticated session either. func readExpectedSOCKS5Datagram( connection *net.UDPConn, remote *net.UDPAddr, wire []byte, deadline time.Time, ) ([]byte, error) { if connection == nil || remote == nil { return nil, errors.New("ike: SOCKS5 UDP transport is closed") } for { if err := connection.SetReadDeadline(deadline); err != nil { return nil, err } n, err := connection.Read(wire) if err != nil { return nil, err } payload, source, err := parseSOCKS5Datagram(wire[:n]) if err != nil { return nil, err } if source.IP.Equal(remote.IP) && source.Port == remote.Port { return payload, nil } } } func hasNonESPMarker(packet []byte) bool { return len(packet) >= 4 && packet[0] == 0 && packet[1] == 0 && packet[2] == 0 && packet[3] == 0 } func ikeResponseMatchesRequest( packet []byte, request ikeHeader, ) bool { response, _, err := parseIKEPacket(packet) if err != nil { return false } if response.InitiatorSPI != request.InitiatorSPI || response.Exchange != request.Exchange || response.MessageID != request.MessageID || response.Flags&flagResponse == 0 || response.Flags&flagInitiator != 0 { return false } var zeroSPI [8]byte if request.ResponderSPI == zeroSPI { return response.ResponderSPI != zeroSPI } return response.ResponderSPI == request.ResponderSPI } func marshalSOCKS5Datagram(remote *net.UDPAddr, payload []byte) ([]byte, error) { if remote == nil || remote.IP == nil || remote.Port < 1 || remote.Port > 65535 { return nil, errors.New("ike: invalid SOCKS5 UDP destination") } result := []byte{0, 0, 0} if ip4 := remote.IP.To4(); ip4 != nil { result = append(result, 1) result = append(result, ip4...) } else if ip16 := remote.IP.To16(); ip16 != nil { result = append(result, 4) result = append(result, ip16...) } else { return nil, errors.New("ike: SOCKS5 UDP destination is not an IP address") } var port [2]byte binary.BigEndian.PutUint16(port[:], uint16(remote.Port)) result = append(result, port[:]...) result = append(result, payload...) return result, nil } func parseSOCKS5Datagram(encoded []byte) ([]byte, *net.UDPAddr, error) { if len(encoded) < 4 || encoded[0] != 0 || encoded[1] != 0 { return nil, nil, errors.New("ike: malformed SOCKS5 UDP datagram") } if encoded[2] != 0 { return nil, nil, errors.New("ike: fragmented SOCKS5 UDP datagrams are unsupported") } offset := 4 var ip net.IP switch encoded[3] { case 1: if offset+4 > len(encoded) { return nil, nil, errors.New("ike: truncated SOCKS5 IPv4 address") } ip = append(net.IP(nil), encoded[offset:offset+4]...) offset += 4 case 4: if offset+16 > len(encoded) { return nil, nil, errors.New("ike: truncated SOCKS5 IPv6 address") } ip = append(net.IP(nil), encoded[offset:offset+16]...) offset += 16 case 3: if offset >= len(encoded) { return nil, nil, errors.New("ike: truncated SOCKS5 domain length") } length := int(encoded[offset]) offset++ if offset+length > len(encoded) { return nil, nil, errors.New("ike: truncated SOCKS5 domain") } addresses, err := net.LookupIP(string(encoded[offset : offset+length])) if err != nil || len(addresses) == 0 { return nil, nil, errors.New("ike: cannot resolve SOCKS5 UDP response domain") } ip = addresses[0] offset += length default: return nil, nil, errors.New("ike: unsupported SOCKS5 UDP address type") } if offset+2 > len(encoded) { return nil, nil, errors.New("ike: truncated SOCKS5 UDP port") } port := int(binary.BigEndian.Uint16(encoded[offset : offset+2])) offset += 2 return append([]byte(nil), encoded[offset:]...), &net.UDPAddr{IP: ip, Port: port}, nil } func deadlineFor(ctx context.Context, maximum time.Duration) time.Time { deadline := time.Now().Add(maximum) if ctx != nil { if caller, ok := ctx.Deadline(); ok && caller.Before(deadline) { return caller } } return deadline } func cloneUDPAddr(address *net.UDPAddr) *net.UDPAddr { if address == nil { return nil } return &net.UDPAddr{IP: append(net.IP(nil), address.IP...), Port: address.Port, Zone: address.Zone} } func cloneUDPAddrs(addresses []*net.UDPAddr) []*net.UDPAddr { result := make([]*net.UDPAddr, 0, len(addresses)) for _, address := range addresses { if address != nil { result = append(result, cloneUDPAddr(address)) } } return result } func parsePort(value string) (int, error) { port, err := strconv.Atoi(value) if err != nil || port < 1 || port > 65535 { return 0, errors.New("ike: invalid UDP port") } return port, nil }