diff --git a/cmd/backplane/main.go b/cmd/backplane/main.go index 2767324..55e3103 100644 --- a/cmd/backplane/main.go +++ b/cmd/backplane/main.go @@ -263,7 +263,7 @@ func main() { var ( extIPv4Prefix netip.Prefix ) - extAddrs, err := tunnet.GetGlobalUnicastAddresses(*extIface) + extAddrs, err := tunnet.GetGlobalUnicastAddresses(*extIface, false) if err != nil { log.Warnf("Failed to get local IPv6 address: %v", err) } else { diff --git a/cmd/tunnelproxy/main.go b/cmd/tunnelproxy/main.go index 35fb809..5c0320e 100644 --- a/cmd/tunnelproxy/main.go +++ b/cmd/tunnelproxy/main.go @@ -119,7 +119,7 @@ func main() { extIPv4Prefix netip.Prefix extIPv6Prefix netip.Prefix ) - extAddrs, err := tunnet.GetGlobalUnicastAddresses(*extIface) + extAddrs, err := tunnet.GetGlobalUnicastAddresses(*extIface, false) if err != nil { log.Warnf("Failed to get local IPv6 address: %v", err) } else { diff --git a/go.mod b/go.mod index 0e44cb7..f8bdcd7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/adrg/xdg v0.5.3 github.com/alphadose/haxmap v1.4.1 github.com/anatol/vmtest v0.0.0-20250318022921-2f32244e2f0f - github.com/apoxy-dev/icx v0.8.0 + github.com/apoxy-dev/icx v0.9.0 github.com/avast/retry-go/v4 v4.6.1 github.com/bramvdbogaerde/go-scp v1.5.0 github.com/buraksezer/olric v0.5.6 diff --git a/go.sum b/go.sum index c64f2ed..169118d 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/apoxy-dev/connect-ip-go v0.0.0-20250530062404-603929a73f45 h1:SwPk1n/ github.com/apoxy-dev/connect-ip-go v0.0.0-20250530062404-603929a73f45/go.mod h1:z5rtgIizc+/K27UtB0occwZgqg/mz3IqgyUJW8aubbI= github.com/apoxy-dev/icx v0.8.0 h1:Aj/LWtFokyBYYFuISFqgbiWBQJpMdIN6vCMa21MIROc= github.com/apoxy-dev/icx v0.8.0/go.mod h1:Muuk3bRXTp3YB5Xj+xHOGQ/T1xVxIKJuvmMfLBXhIN4= +github.com/apoxy-dev/icx v0.9.0 h1:kdRnjfaksrhtLhEt7caVGlpNPJiClvvQ23KJbb5MsY4= +github.com/apoxy-dev/icx v0.9.0/go.mod h1:Muuk3bRXTp3YB5Xj+xHOGQ/T1xVxIKJuvmMfLBXhIN4= github.com/apoxy-dev/quic-go v0.0.0-20250530165952-53cca597715e h1:10GIpiVyKoRgCyr0J2TvJtdn17bsFHN+ROWkeVJpcOU= github.com/apoxy-dev/quic-go v0.0.0-20250530165952-53cca597715e/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/apoxy-dev/upgrade-cli v0.0.0-20240213232412-a56c3a52fa0e h1:FBNxMQD93z2ththupB/BYKLEaMWaEr+G+sJWJqU2wC4= diff --git a/pkg/cmd/alpha/tunnel_relay.go b/pkg/cmd/alpha/tunnel_relay.go index dfd05d4..d8073e6 100644 --- a/pkg/cmd/alpha/tunnel_relay.go +++ b/pkg/cmd/alpha/tunnel_relay.go @@ -1,5 +1,3 @@ -//go:build linux - package alpha import ( @@ -9,24 +7,29 @@ import ( "log/slog" "net" "net/netip" + "runtime" "github.com/alphadose/haxmap" "github.com/spf13/cobra" "github.com/apoxy-dev/apoxy/pkg/cryptoutils" "github.com/apoxy-dev/apoxy/pkg/tunnel" + "github.com/apoxy-dev/apoxy/pkg/tunnel/bifurcate" "github.com/apoxy-dev/apoxy/pkg/tunnel/controllers" "github.com/apoxy-dev/apoxy/pkg/tunnel/hasher" tunnet "github.com/apoxy-dev/apoxy/pkg/tunnel/net" "github.com/apoxy-dev/apoxy/pkg/tunnel/router" "github.com/apoxy-dev/apoxy/pkg/tunnel/vni" + "github.com/apoxy-dev/icx" ) var ( - relayName string // the name for the relay - relayTunnel string // the name of the tunnel to serve - extIfaceName string // the external interface name - listenAddress string // the address to listen on for incoming connections + relayName string // the name for the relay + relayTunnel string // the name of the tunnel to serve + extIfaceName string // the external interface name + listenAddress string // the address to listen on for incoming connections + userMode bool // whether to use user-mode routing (no special privileges required) + relaySocksListenAddr string // when using user-mode routing, the address to listen on for SOCKS5 connections ) var tunnelRelayCmd = &cobra.Command{ @@ -36,18 +39,40 @@ var tunnelRelayCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { routerOpts := []router.Option{ router.WithExternalInterface(extIfaceName), + router.WithEgressGateway(true), + router.WithSocksListenAddr(relaySocksListenAddr), // only used in user-mode } - rtr, err := router.NewICXNetlinkRouter(routerOpts...) - if err != nil { - return fmt.Errorf("failed to create router: %w", err) - } - + // One UDP socket shared between Geneve (data) and QUIC (control). pc, err := net.ListenPacket("udp", listenAddress) if err != nil { return fmt.Errorf("failed to create UDP listener: %w", err) } + pcGeneve, pcQuic := bifurcate.Bifurcate(pc) + defer pcGeneve.Close() + defer pcQuic.Close() + + var rtr router.Router + var handler *icx.Handler + if userMode { + routerOpts = append(routerOpts, router.WithPacketConn(pcGeneve)) + + r, err := router.NewICXNetstackRouter(routerOpts...) + if err != nil { + return fmt.Errorf("failed to create router: %w", err) + } + rtr = r + handler = r.Handler + } else { + r, err := router.NewICXNetlinkRouter(routerOpts...) + if err != nil { + return fmt.Errorf("failed to create router: %w", err) + } + rtr = r + handler = r.Handler + } + idHasher := hasher.NewHasher([]byte("C0rr3ct-Horse-Battery-Staple_But_Salty_1x9Q7p3Z")) _, cert, err := cryptoutils.GenerateSelfSignedTLSCert(relayName) @@ -55,12 +80,13 @@ var tunnelRelayCmd = &cobra.Command{ return fmt.Errorf("failed to generate self-signed TLS cert: %w", err) } - relay := tunnel.NewRelay(relayName, pc, cert, rtr.Handler, idHasher, rtr) + relay := tunnel.NewRelay(relayName, pcQuic, cert, handler, idHasher, rtr) slog.Info("Configuring relay", slog.String("tunnelName", relayTunnel), slog.String("listenAddress", listenAddress), slog.String("externalInterface", extIfaceName)) relay.SetCredentials(relayTunnel, "letmein") - relay.SetRelayAddresses(relayTunnel, []string{pc.LocalAddr().String()}) + relay.SetRelayAddresses(relayTunnel, []string{pcQuic.LocalAddr().String()}) + relay.SetEgressGateway(true) systemULA := tunnet.NewULA(cmd.Context(), tunnet.SystemNetworkID) agentIPAM, err := systemULA.IPAM(cmd.Context(), 96) @@ -88,7 +114,10 @@ var tunnelRelayCmd = &cobra.Command{ slog.String("agent", agentName), slog.String("connID", conn.ID()), slog.String("prefix", pfx.String())) - conn.SetOverlayAddress(pfx.String()) + if err := conn.SetOverlayAddress(pfx.String()); err != nil { + agentIPAM.Release(pfx) + return fmt.Errorf("failed to set overlay address on connection: %w", err) + } vni, err := vpool.Allocate() if err != nil { @@ -140,7 +169,9 @@ func init() { tunnelRelayCmd.Flags().StringVarP(&relayName, "name", "n", "dev", "The name of the relay.") tunnelRelayCmd.Flags().StringVarP(&relayTunnel, "tunnel-name", "t", "dev", "The name of the tunnel to serve.") tunnelRelayCmd.Flags().StringVar(&extIfaceName, "ext-iface", "eth0", "External interface name.") - tunnelRelayCmd.Flags().StringVar(&listenAddress, "listen-addr", ":6081", "The address to listen on for incoming connections.") + tunnelRelayCmd.Flags().StringVar(&listenAddress, "listen-addr", "127.0.0.1:6081", "The address to listen on for incoming connections.") + tunnelRelayCmd.Flags().BoolVar(&userMode, "user-mode", runtime.GOOS != "linux", "Use user-mode routing (no special privileges required).") + tunnelRelayCmd.Flags().StringVar(&relaySocksListenAddr, "socks-addr", "localhost:1080", "When using user-mode routing, the address to listen on for SOCKS5 connections.") tunnelCmd.AddCommand(tunnelRelayCmd) } diff --git a/pkg/cmd/alpha/tunnel_run.go b/pkg/cmd/alpha/tunnel_run.go index a529955..4fccae0 100644 --- a/pkg/cmd/alpha/tunnel_run.go +++ b/pkg/cmd/alpha/tunnel_run.go @@ -73,7 +73,11 @@ var tunnelRunCmd = &cobra.Command{ // Lazily create router/handler on first successful Connect. getHandler := func(connectResp *api.ConnectResponse) (*icx.Handler, error) { routerOnce.Do(func() { - var routerOpts []router.Option + routerOpts := []router.Option{ + router.WithPacketConn(pcGeneve), + router.WithTunnelMTU(connectResp.MTU), + } + if socksListenAddr != "" { routerOpts = append(routerOpts, router.WithSocksListenAddr(socksListenAddr)) } @@ -89,13 +93,29 @@ var tunnelRunCmd = &cobra.Command{ routerOpts = append(routerOpts, router.WithResolveConfig(resolveConf)) } - r, routerErr = router.NewICXNetstackRouter(pcGeneve, connectResp.MTU, routerOpts...) + r, routerErr = router.NewICXNetstackRouter(routerOpts...) if routerErr != nil { return } handler = r.Handler + for _, addrStr := range connectResp.Addresses { + slog.Info("Adding address", slog.String("address", addrStr)) + + addr, err := netip.ParsePrefix(addrStr) + if err != nil { + slog.Warn("Failed to parse address", slog.String("address", addrStr), slog.Any("error", err)) + continue + } + + if err := r.AddAddr(addr, nil); err != nil { + slog.Warn("Failed to add address", slog.String("address", addrStr), slog.Any("error", err)) + } + } + for _, route := range connectResp.Routes { + slog.Info("Adding route", slog.String("destination", route.Destination)) + dst, err := netip.ParsePrefix(route.Destination) if err != nil { slog.Warn("Failed to parse route prefix", slog.String("prefix", route.Destination), slog.Any("error", err)) @@ -336,7 +356,16 @@ func manageRelayConnection( return cleanupOnErr(fmt.Errorf("parse assigned addresses: %w", err)) } - // Multi-home: same VNI across different relays. + for _, route := range connectResp.Routes { + dst, err := netip.ParsePrefix(route.Destination) + if err != nil { + slog.Warn("Failed to parse route prefix", slog.String("prefix", route.Destination), slog.Any("error", err)) + continue + } + + overlayAddrs = append(overlayAddrs, dst) + } + if err := handler.AddVirtualNetwork(connectResp.VNI, netstack.ToFullAddress(remoteAddr), overlayAddrs); err != nil { return cleanupOnErr(fmt.Errorf("add virtual network: %w", err)) } diff --git a/pkg/netstack/icx_network.go b/pkg/netstack/icx_network.go index 081b5df..f379b42 100644 --- a/pkg/netstack/icx_network.go +++ b/pkg/netstack/icx_network.go @@ -291,21 +291,6 @@ func (net *ICXNetwork) DelAddr(addr netip.Prefix) error { return nil } -// LocalAddresses returns the list of local addresses assigned to the network. -func (net *ICXNetwork) LocalAddresses() ([]netip.Prefix, error) { - nic := net.stack.NICInfo()[net.nicID] - - var addrs []netip.Prefix - for _, assignedAddr := range nic.ProtocolAddresses { - addrs = append(addrs, netip.PrefixFrom( - addrFromNetstackIP(assignedAddr.AddressWithPrefix.Address), - assignedAddr.AddressWithPrefix.PrefixLen, - )) - } - - return addrs, nil -} - // ForwardTo forwards all inbound TCP traffic to the upstream network. func (net *ICXNetwork) ForwardTo(ctx context.Context, upstream network.Network) error { // Allow outgoing packets to have a source address different from the NIC. @@ -326,3 +311,24 @@ func (net *ICXNetwork) ForwardTo(ctx context.Context, upstream network.Network) return nil } + +func prefixToSubnet(p netip.Prefix) (tcpip.Subnet, error) { + addr := tcpip.AddrFromSlice(p.Addr().AsSlice()) + + totalBits := 128 + if p.Addr().Is4() { + totalBits = 32 + } + ones := p.Bits() + if ones < 0 || ones > totalBits { + return tcpip.Subnet{}, fmt.Errorf("invalid prefix length %d", ones) + } + + maskBytes := make([]byte, totalBits/8) + for i := 0; i < ones; i++ { + maskBytes[i/8] |= 1 << (7 - uint(i%8)) + } + mask := tcpip.MaskFromBytes(maskBytes) + + return tcpip.NewSubnet(addr, mask) +} diff --git a/pkg/netstack/icx_network_test.go b/pkg/netstack/icx_network_test.go index 6071aa4..01fa2ac 100644 --- a/pkg/netstack/icx_network_test.go +++ b/pkg/netstack/icx_network_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" - "gvisor.dev/gvisor/pkg/tcpip" "github.com/apoxy-dev/icx" @@ -58,12 +57,10 @@ func TestICXNetwork_Speed(t *testing.T) { aB := netip.AddrPortFrom(netip.MustParseAddr(uaB.IP.String()), uint16(uaB.Port)) // Build ICX handlers in L3 mode and link them together - hA, err := icx.NewHandler(icx.WithLocalAddr(netstack.ToFullAddress(aA)), - icx.WithVirtMAC(tcpip.GetRandMacAddr()), icx.WithLayer3VirtFrames()) + hA, err := icx.NewHandler(icx.WithLocalAddr(netstack.ToFullAddress(aA)), icx.WithLayer3VirtFrames()) require.NoError(t, err) - hB, err := icx.NewHandler(icx.WithLocalAddr(netstack.ToFullAddress(aB)), - icx.WithVirtMAC(tcpip.GetRandMacAddr()), icx.WithLayer3VirtFrames()) + hB, err := icx.NewHandler(icx.WithLocalAddr(netstack.ToFullAddress(aB)), icx.WithLayer3VirtFrames()) require.NoError(t, err) const vni = 0x424242 diff --git a/pkg/netstack/tcp_forwarder.go b/pkg/netstack/tcp_forwarder.go index ea1aefa..08131a7 100644 --- a/pkg/netstack/tcp_forwarder.go +++ b/pkg/netstack/tcp_forwarder.go @@ -30,27 +30,6 @@ func TCPForwarder(ctx context.Context, ipstack *stack.Stack, upstream network.Ne return tcpForwarder.HandlePacket } -// Unmap4in6 converts an IPv6 address to an IPv4 address if it is an IPv4-mapped IPv6 address. -// If the address is not an IPv4-mapped IPv6 address, it is returned unchanged. -// If the IPv4 address is zero, returns 127.0.0.1. -// It is following /96 embedding scheme from RFC 6052 (https://datatracker.ietf.org/doc/html/rfc6052#section-2.2). -func Unmap4in6(addr netip.Addr) netip.Addr { - if !addr.Is6() { - return addr - } - b16 := addr.As16() - v4addr := netip.AddrFrom4([4]byte{ - b16[12], - b16[13], - b16[14], - b16[15], - }) - if !v4addr.IsValid() { - return netip.AddrFrom4([4]byte{127, 0, 0, 1}) - } - return v4addr -} - func tcpHandler(ctx context.Context, upstream network.Network) func(req *tcp.ForwarderRequest) { return func(req *tcp.ForwarderRequest) { reqDetails := req.ID() @@ -60,7 +39,7 @@ func tcpHandler(ctx context.Context, upstream network.Network) func(req *tcp.For // - IPv4-mapped IPv6 addresses (::ffff:192.168.1.1) are converted to IPv4 // - Regular IPv6 addresses left as is (::1) dstAddrPort := netip.AddrPortFrom( - Unmap4in6(addrFromNetstackIP(reqDetails.LocalAddress)), + addrFromNetstackIP(reqDetails.LocalAddress).Unmap(), reqDetails.LocalPort, ) diff --git a/pkg/netstack/udp_forwarder.go b/pkg/netstack/udp_forwarder.go index 63e3f2b..3eddf01 100644 --- a/pkg/netstack/udp_forwarder.go +++ b/pkg/netstack/udp_forwarder.go @@ -86,7 +86,7 @@ func udpHandler(ctx context.Context, upstream network.Network) func(req *udp.For // - IPv4-mapped IPv6 addresses (::ffff:192.168.1.1) are converted to IPv4 // - Regular IPv6 addresses left as is (::1) dstAddrPort := netip.AddrPortFrom( - Unmap4in6(addrFromNetstackIP(reqDetails.LocalAddress)), + addrFromNetstackIP(reqDetails.LocalAddress).Unmap(), reqDetails.LocalPort, ) diff --git a/pkg/tunnel/bifurcate/bifurcate.go b/pkg/tunnel/bifurcate/bifurcate.go index 538d200..5dd72e5 100644 --- a/pkg/tunnel/bifurcate/bifurcate.go +++ b/pkg/tunnel/bifurcate/bifurcate.go @@ -1,7 +1,6 @@ package bifurcate import ( - "log/slog" "net" "sync" @@ -36,8 +35,6 @@ func Bifurcate(pc net.PacketConn) (net.PacketConn, net.PacketConn) { otherClosed := otherConn.closed go func() { - defer func() { slog.Info("Bifurcate exiting") }() - for { // If both sides are gone, stop. if geneveCh == nil && otherCh == nil { diff --git a/pkg/tunnel/conntrackpc/conntrackpc.go b/pkg/tunnel/conntrackpc/conntrackpc.go index 1d9f731..f775951 100644 --- a/pkg/tunnel/conntrackpc/conntrackpc.go +++ b/pkg/tunnel/conntrackpc/conntrackpc.go @@ -4,6 +4,7 @@ package conntrackpc import ( "errors" + "log/slog" "net" "sync" "time" @@ -25,9 +26,6 @@ type Options struct { // Size of each per-flow inbound queue (non-blocking fanout). RxBufSize int - - // If true, vconn.WriteTo's addr parameter can change the remote and re-key the flow. - AllowAddrOverrideOnWrite bool } func (o Options) withDefaults() Options { @@ -74,6 +72,8 @@ func New(underlying net.PacketConn, opts Options) *ConntrackPacketConn { // If v.key changed (due to re-key), skip closing. if v.key == k { _ = v.closeLocked(errFlowExpired) + slog.Debug("conntrack_packet_conn: flow evicted", + slog.String("key", k), slog.String("remote", v.remote.String())) } } } @@ -137,11 +137,13 @@ func (c *ConntrackPacketConn) Open(remote *net.UDPAddr) (*VirtualPacketConn, err if v, ok := c.flows.Get(key); ok && !v.isClosed() { // Refresh TTL by re-adding. c.flows.Add(key, v) + slog.Debug("conntrack_packet_conn: flow opened (existing)", slog.String("key", key), slog.String("remote", remote.String())) return v, nil } v := newVirtual(c, key, remote, c.opts.RxBufSize) c.flows.Add(key, v) + slog.Debug("conntrack_packet_conn: flow opened", slog.String("key", key), slog.String("remote", remote.String())) return v, nil } @@ -185,11 +187,13 @@ func (c *ConntrackPacketConn) readLoop() { if !ok { if !c.opts.AutoCreate { c.mu.Unlock() + slog.Debug("conntrack_packet_conn: packet dropped due to unknown flow", slog.String("from", from.String()), slog.Int("bytes", n)) continue } udpFrom, _ := from.(*net.UDPAddr) v = newVirtual(c, key, udpFrom, c.opts.RxBufSize) c.flows.Add(key, v) // registers & sets TTL + slog.Debug("conntrack_packet_conn: flow auto-created", slog.String("key", key), slog.String("remote", udpFrom.String())) } else { // refresh TTL on activity c.flows.Add(key, v) @@ -199,8 +203,10 @@ func (c *ConntrackPacketConn) readLoop() { select { case v.inbound <- append([]byte(nil), buf[:n]...): v.touch() + slog.Debug("conntrack_packet_conn: packet delivered", slog.String("key", key), slog.Int("bytes", n)) default: // drop to avoid HOL blocking + slog.Debug("conntrack_packet_conn: packet dropped due to backpressure", slog.String("key", key), slog.Int("bytes", n)) } c.mu.Unlock() } @@ -288,26 +294,6 @@ func (v *VirtualPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { } remote := v.remote - // Optional remote override + re-keying - if v.parent.opts.AllowAddrOverrideOnWrite && addr != nil { - if ua, ok := addr.(*net.UDPAddr); ok { - newKey := ua.String() - if newKey != v.key { - // Re-key safely: update fields, add new key, then remove old key. - v.parent.mu.Lock() - oldKey := v.key - v.key = newKey - v.remote = cloneUDPAddr(ua) - v.parent.flows.Add(newKey, v) - v.parent.flows.Remove(oldKey) // onEvicted won't close us now - v.parent.mu.Unlock() - } else { - v.remote = cloneUDPAddr(ua) - } - remote = ua - } - } - // Respect write deadline by temporarily setting it on the shared socket. timer := v.nextWriteTimer() if timer != nil { diff --git a/pkg/tunnel/conntrackpc/conntrackpc_test.go b/pkg/tunnel/conntrackpc/conntrackpc_test.go index 923754f..a8d1191 100644 --- a/pkg/tunnel/conntrackpc/conntrackpc_test.go +++ b/pkg/tunnel/conntrackpc/conntrackpc_test.go @@ -160,57 +160,6 @@ func TestMaxFlowsEvictsOldestAndCloses(t *testing.T) { assert.True(t, errors.Is(err, net.ErrClosed)) } -func TestAllowAddrOverrideOnWriteRekeysFlow(t *testing.T) { - under, local := makeUDP(t) - ct := conntrackpc.New(under, conntrackpc.Options{ - AutoCreate: true, - TTL: time.Minute, - MaxFlows: 32, - RxBufSize: 8, - AllowAddrOverrideOnWrite: true, - }) - t.Cleanup(func() { _ = ct.Close() }) - - // Two peers - peer1PC, peer1 := makeUDP(t) - peer2PC, peer2 := makeUDP(t) - - // Establish flow with peer1 - v, err := ct.Open(peer1) - require.NoError(t, err) - - // Write to peer2 using override; this should re-key the virtual flow to peer2. - msg := []byte("rekey to peer2") - nw, err := v.WriteTo(msg, peer2) - require.NoError(t, err) - assert.Equal(t, len(msg), nw) - - // Peer2 should receive it. - got, from := recvFrom(t, peer2PC, 2*time.Second) - assert.Equal(t, local.String(), from.String()) - assert.Equal(t, msg, got) - - // Now send from peer2 back to ct; v should receive it (flow has re-keyed). - reply := []byte("ack from peer2") - sendTo(t, peer2PC, local, reply) - - buf := make([]byte, 1500) - require.NoError(t, v.SetReadDeadline(time.Now().Add(2*time.Second))) - n, addr, err := v.ReadFrom(buf) - require.NoError(t, err) - assert.Equal(t, peer2.String(), addr.String()) - assert.Equal(t, reply, buf[:n]) - - // And a packet from peer1 should auto-create a *new* flow (since v moved). - sendTo(t, peer1PC, local, []byte("peer1 still here")) - v1, err := ct.Open(peer1) // should be a different handle than v - require.NoError(t, err) - require.NoError(t, v1.SetReadDeadline(time.Now().Add(2*time.Second))) - n, _, err = v1.ReadFrom(buf) - require.NoError(t, err) - assert.Equal(t, []byte("peer1 still here"), buf[:n]) -} - func TestClosePropagatesToFlows(t *testing.T) { under, _ := makeUDP(t) ct := conntrackpc.New(under, conntrackpc.Options{ diff --git a/pkg/tunnel/controllers/relay.go b/pkg/tunnel/controllers/relay.go index 6a34bf4..44d0298 100644 --- a/pkg/tunnel/controllers/relay.go +++ b/pkg/tunnel/controllers/relay.go @@ -15,6 +15,8 @@ type Relay interface { SetCredentials(tunnelName, token string) // SetRelayAddresses sets the list of relay addresses that are serving a tunnel. SetRelayAddresses(tunnelName string, addresses []string) + // SetEgressGateway enables or disables internet egress for the tunnel agents. + SetEgressGateway(enabled bool) // SetOnConnect sets a callback that is invoked when a new connection is established to the relay. SetOnConnect(onConnect func(ctx context.Context, agentName string, conn Connection) error) // SetOnDisconnect sets a callback that is invoked when a connection is closed. diff --git a/pkg/tunnel/controllers/tunnel_agent_reconciler_test.go b/pkg/tunnel/controllers/tunnel_agent_reconciler_test.go index 287370d..9241f29 100644 --- a/pkg/tunnel/controllers/tunnel_agent_reconciler_test.go +++ b/pkg/tunnel/controllers/tunnel_agent_reconciler_test.go @@ -91,9 +91,11 @@ func TestTunnelAgentReconciler_RemoveConnection(t *testing.T) { // Two mock conns conn1 := &mockConn{} conn1.On("ID").Return("c1").Maybe() + conn1.On("Close").Return(nil).Once() conn2 := &mockConn{} conn2.On("ID").Return("c2").Maybe() + conn2.On("Close").Return(nil).Once() require.NoError(t, r.AddConnection(ctx, agent.Name, conn1)) require.NoError(t, r.AddConnection(ctx, agent.Name, conn2)) diff --git a/pkg/tunnel/controllers/tunnel_reconciler.go b/pkg/tunnel/controllers/tunnel_reconciler.go index f1b0431..265e991 100644 --- a/pkg/tunnel/controllers/tunnel_reconciler.go +++ b/pkg/tunnel/controllers/tunnel_reconciler.go @@ -49,6 +49,13 @@ func (r *TunnelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // Update relay addresses if they have changed. r.relay.SetRelayAddresses(tunnel.Name, tunnel.Status.Addresses) + // Update egress gateway setting + var egressGatewayEnabled bool + if tunnel.Spec.EgressGateway != nil { + egressGatewayEnabled = tunnel.Spec.EgressGateway.Enabled + } + r.relay.SetEgressGateway(egressGatewayEnabled) + // Add our relay address to the list of addresses if missing. if !slices.Contains(tunnel.Status.Addresses, r.relay.Address().String()) { err := retry.RetryOnConflict(retry.DefaultRetry, func() error { diff --git a/pkg/tunnel/controllers/tunnel_reconciler_test.go b/pkg/tunnel/controllers/tunnel_reconciler_test.go index eac95dd..7fa647f 100644 --- a/pkg/tunnel/controllers/tunnel_reconciler_test.go +++ b/pkg/tunnel/controllers/tunnel_reconciler_test.go @@ -39,7 +39,10 @@ func TestTunnelReconciler(t *testing.T) { Build() relay := &mockRelay{} + relay.On("Address").Return(netip.MustParseAddrPort("1.1.1.1:443")) relay.On("SetCredentials", "tun-1", "secret-token").Once() + relay.On("SetRelayAddresses", "tun-1", mock.Anything).Once() + relay.On("SetEgressGateway", mock.Anything).Return().Once() r := controllers.NewTunnelReconciler(c, relay, "") @@ -80,6 +83,10 @@ func (m *mockRelay) SetRelayAddresses(tunnelName string, addresses []string) { m.Called(tunnelName, addresses) } +func (m *mockRelay) SetEgressGateway(enabled bool) { + m.Called(enabled) +} + func (m *mockRelay) SetOnConnect(onConnect func(ctx context.Context, agentName string, conn controllers.Connection) error) { m.Called(onConnect) } diff --git a/pkg/tunnel/l2pc/l2pc.go b/pkg/tunnel/l2pc/l2pc.go index 226e58c..b2197f1 100644 --- a/pkg/tunnel/l2pc/l2pc.go +++ b/pkg/tunnel/l2pc/l2pc.go @@ -4,11 +4,15 @@ import ( "errors" "fmt" "net" + "net/netip" "sync" + "github.com/apoxy-dev/icx/addrselect" "github.com/apoxy-dev/icx/udp" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" + + tunnet "github.com/apoxy-dev/apoxy/pkg/tunnel/net" ) var ErrInvalidFrame = errors.New("invalid frame") @@ -16,7 +20,7 @@ var ErrInvalidFrame = errors.New("invalid frame") // L2PacketConn adapts a net.PacketConn (UDP) to read/write L2 Ethernet frames. type L2PacketConn struct { pc net.PacketConn - localAddr *tcpip.FullAddress + localAddrs addrselect.AddressList localMAC tcpip.LinkAddress peerMACCache sync.Map pktPool sync.Pool @@ -29,22 +33,48 @@ func NewL2PacketConn(pc net.PacketConn) (*L2PacketConn, error) { return nil, fmt.Errorf("PacketConn must be UDP") } + localAddrPort := netip.AddrPortFrom(netip.MustParseAddr(ua.IP.String()), + uint16(ua.Port)) + + localAddrPorts := []netip.AddrPort{localAddrPort} + if localAddrPort.Addr().IsUnspecified() { + localAddrs, err := tunnet.GetAllGlobalUnicastAddresses(true) + if err != nil { + return nil, fmt.Errorf("failed to get local addresses: %w", err) + } + + for _, addr := range localAddrs { + localAddrPorts = append(localAddrPorts, netip.AddrPortFrom(addr.Addr(), localAddrPort.Port())) + } + } + // Random locally-administered unicast MAC for "our" link address. localMAC := tcpip.GetRandMacAddr() - c := &L2PacketConn{ - pc: pc, - localAddr: &tcpip.FullAddress{ + var localAddrs addrselect.AddressList + for _, ap := range localAddrPorts { + la := &tcpip.FullAddress{ Addr: func() tcpip.Address { - if ua.IP.To4() != nil { - return tcpip.AddrFrom4Slice(ua.IP.To4()) + if ap.Addr().Is4() { + return tcpip.AddrFrom4Slice(ap.Addr().AsSlice()) } - return tcpip.AddrFrom16Slice(ua.IP.To16()) + return tcpip.AddrFrom16Slice(ap.Addr().AsSlice()) }(), Port: uint16(ua.Port), LinkAddr: localMAC, - }, - localMAC: localMAC, + } + localAddrs = append(localAddrs, la) + } + + // Ensure we have at least one address. + if len(localAddrs) == 0 { + return nil, fmt.Errorf("no valid local addresses found") + } + + c := &L2PacketConn{ + pc: pc, + localAddrs: localAddrs, + localMAC: localMAC, pktPool: sync.Pool{ New: func() any { b := make([]byte, 0, 65535) @@ -135,11 +165,7 @@ func (c *L2PacketConn) ReadFrame(dst []byte) (int, error) { // Build addresses for udp.Encode (note: for an inbound frame, // src = remote, dst = local). srcFA := toFullAddr(remote) - dstFA := &tcpip.FullAddress{ - Addr: c.localAddr.Addr, - Port: c.localAddr.Port, - LinkAddr: c.localMAC, // Ethernet Dst = us - } + dstFA := c.localAddrs.Pick(srcFA) // Random-but-stable (per remote IP) src MAC. srcFA.LinkAddr = c.peerMACForIP(remote.IP) @@ -158,11 +184,6 @@ func (c *L2PacketConn) ReadFrame(dst []byte) (int, error) { return frameLen, nil } -// LocalAddr returns the local address of this connection. -func (c *L2PacketConn) LocalAddr() *tcpip.FullAddress { - return c.localAddr -} - // LocalMAC returns the locally-administered unicast MAC address used by this connection. func (c *L2PacketConn) LocalMAC() tcpip.LinkAddress { return c.localMAC diff --git a/pkg/tunnel/l2pc/l2pc_test.go b/pkg/tunnel/l2pc/l2pc_test.go index c346eb8..eb7cbf5 100644 --- a/pkg/tunnel/l2pc/l2pc_test.go +++ b/pkg/tunnel/l2pc/l2pc_test.go @@ -70,7 +70,6 @@ func TestNewL2PacketConn_UDPOnly(t *testing.T) { c, err := l2pc.NewL2PacketConn(pc) require.NoError(t, err) require.NotNil(t, c) - require.Equal(t, uint16(pc.LocalAddr().(*net.UDPAddr).Port), c.LocalAddr().Port) require.NotEmpty(t, c.LocalMAC()) } diff --git a/pkg/tunnel/net/addr.go b/pkg/tunnel/net/addr.go index 05b03cd..11840f0 100644 --- a/pkg/tunnel/net/addr.go +++ b/pkg/tunnel/net/addr.go @@ -7,7 +7,7 @@ import ( ) // GetGlobalUnicastAddresses returns the global unicast IPv4/IPv6 address of the specified interface. -func GetGlobalUnicastAddresses(ifcName string) ([]netip.Prefix, error) { +func GetGlobalUnicastAddresses(ifcName string, includeLoopback bool) ([]netip.Prefix, error) { interfaces, err := net.Interfaces() if err != nil { return nil, fmt.Errorf("failed to get network interfaces: %w", err) @@ -20,7 +20,7 @@ func GetGlobalUnicastAddresses(ifcName string) ([]netip.Prefix, error) { } // Skip loopback and down interfaces - if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 { + if (!includeLoopback && iface.Flags&net.FlagLoopback != 0) || iface.Flags&net.FlagUp == 0 { continue } @@ -60,3 +60,27 @@ func GetGlobalUnicastAddresses(ifcName string) ([]netip.Prefix, error) { return out, nil } + +// GetAllGlobalUnicastAddresses returns the global unicast IPv4/IPv6 addresses +// across all non-loopback, up interfaces by calling GetGlobalUnicastAddresses +// for each interface. +func GetAllGlobalUnicastAddresses(includeLoopback bool) ([]netip.Prefix, error) { + interfaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("failed to get network interfaces: %w", err) + } + + var out []netip.Prefix + for _, iface := range interfaces { + addrs, err := GetGlobalUnicastAddresses(iface.Name, includeLoopback) + if err == nil { + out = append(out, addrs...) + } + } + + if len(out) == 0 { + return nil, fmt.Errorf("no global unicast addresses found on any interface") + } + + return out, nil +} diff --git a/pkg/tunnel/relay.go b/pkg/tunnel/relay.go index a5a92b0..8e24e11 100644 --- a/pkg/tunnel/relay.go +++ b/pkg/tunnel/relay.go @@ -34,18 +34,19 @@ const ( ) type Relay struct { - mu sync.Mutex - name string - pc net.PacketConn - cert tls.Certificate - handler *icx.Handler - idHasher *hasher.Hasher - router router.Router - tokens *haxmap.Map[string, string] // map[tunnelName]token - relayAddrs *haxmap.Map[string, []string] // map[tunnelName][]string - conns *haxmap.Map[string, *connection] // map[connectionID]Connection - onConnect func(ctx context.Context, agentName string, conn controllers.Connection) error - onDisconnect func(ctx context.Context, agentName, id string) error + mu sync.Mutex + name string + pc net.PacketConn + cert tls.Certificate + handler *icx.Handler + idHasher *hasher.Hasher + router router.Router + egressGateway bool + tokens *haxmap.Map[string, string] // map[tunnelName]token + relayAddrs *haxmap.Map[string, []string] // map[tunnelName][]string + conns *haxmap.Map[string, *connection] // map[connectionID]Connection + onConnect func(ctx context.Context, agentName string, conn controllers.Connection) error + onDisconnect func(ctx context.Context, agentName, id string) error } func NewRelay(name string, pc net.PacketConn, cert tls.Certificate, handler *icx.Handler, idHasher *hasher.Hasher, router router.Router) *Relay { @@ -82,6 +83,14 @@ func (r *Relay) SetRelayAddresses(tunnelName string, addresses []string) { r.relayAddrs.Set(tunnelName, addresses) } +// SetEgressGateway enables or disables internet egress for the tunnel agents. +func (r *Relay) SetEgressGateway(enabled bool) { + r.mu.Lock() + defer r.mu.Unlock() + + r.egressGateway = enabled +} + // SetOnConnect sets a callback that is invoked when a new connection is established to the relay. func (r *Relay) SetOnConnect(onConnect func(ctx context.Context, agentName string, conn controllers.Connection) error) { r.mu.Lock() @@ -255,6 +264,12 @@ func (r *Relay) handleConnect(w http.ResponseWriter, req *http.Request, ps httpr routes = append(routes, api.Route{Destination: pfx.String()}) } } + if r.egressGateway { + // Default route for all traffic. + routes = append(routes, + api.Route{Destination: "0.0.0.0/0"}, + api.Route{Destination: "::/0"}) + } tunnelName := ps.ByName("name") relayAddrs, _ := r.relayAddrs.Get(tunnelName) diff --git a/pkg/tunnel/router/icx_netlink.go b/pkg/tunnel/router/icx_netlink.go new file mode 100644 index 0000000..81719fb --- /dev/null +++ b/pkg/tunnel/router/icx_netlink.go @@ -0,0 +1,54 @@ +//go:build !linux + +package router + +import ( + "context" + "errors" + "fmt" + "net/netip" + + "github.com/apoxy-dev/icx" + "gvisor.dev/gvisor/pkg/tcpip" + + "github.com/apoxy-dev/apoxy/pkg/netstack" + "github.com/apoxy-dev/apoxy/pkg/tunnel/connection" +) + +func NewICXNetlinkRouter(_ ...Option) (*ICXNotImplementedRouter, error) { + h, err := icx.NewHandler(icx.WithLocalAddr(netstack.ToFullAddress(netip.MustParseAddrPort("127.0.0.1:6081"))), + icx.WithVirtMAC(tcpip.GetRandMacAddr())) + if err != nil { + return nil, fmt.Errorf("failed to create ICX handler: %w", err) + } + + return &ICXNotImplementedRouter{Handler: h}, nil +} + +type ICXNotImplementedRouter struct { + Handler *icx.Handler +} + +func (r *ICXNotImplementedRouter) Start(ctx context.Context) error { + return errors.New("not implemented") +} + +func (r *ICXNotImplementedRouter) AddAddr(_ netip.Prefix, _ connection.Connection) error { + return errors.New("not implemented") +} + +func (r *ICXNotImplementedRouter) DelAddr(_ netip.Prefix) error { + return errors.New("not implemented") +} + +func (r *ICXNotImplementedRouter) AddRoute(dst netip.Prefix) error { + return errors.New("not implemented") +} + +func (r *ICXNotImplementedRouter) DelRoute(dst netip.Prefix) error { + return errors.New("not implemented") +} + +func (r *ICXNotImplementedRouter) Close() error { + return nil +} diff --git a/pkg/tunnel/router/server_icx_netlink_linux.go b/pkg/tunnel/router/icx_netlink_linux.go similarity index 98% rename from pkg/tunnel/router/server_icx_netlink_linux.go rename to pkg/tunnel/router/icx_netlink_linux.go index 72e5d23..a1b080d 100644 --- a/pkg/tunnel/router/server_icx_netlink_linux.go +++ b/pkg/tunnel/router/icx_netlink_linux.go @@ -32,7 +32,6 @@ import ( const ( icxDefaultPort = 6081 - extPathMTU = 1500 ) var ( @@ -77,7 +76,7 @@ func NewICXNetlinkRouter(opts ...Option) (*ICXNetlinkRouter, error) { return nil, fmt.Errorf("failed to get number of TX queues for interface %s: %w", options.extIfaceName, err) } - tunDev, err := veth.Create(options.tunIfaceName, numQueues, icx.MTU(extPathMTU)) + tunDev, err := veth.Create(options.tunIfaceName, numQueues, options.tunMTU) if err != nil { return nil, fmt.Errorf("failed to create veth device: %w", err) } @@ -431,7 +430,7 @@ func selectSourceAddr(addrs []net.Addr) (*tcpip.FullAddress, error) { } func getExternalIPPrefixes(extIfaceName string) (extIPv4Prefix, extIPv6Prefix netip.Prefix) { - extAddrs, err := tunnet.GetGlobalUnicastAddresses(extIfaceName) + extAddrs, err := tunnet.GetGlobalUnicastAddresses(extIfaceName, false) if err != nil { slog.Warn("Failed to get local IPv4 address", slog.String("ext_iface", extIfaceName), slog.Any("error", err)) diff --git a/pkg/tunnel/router/client_icx_netstack.go b/pkg/tunnel/router/icx_netstack.go similarity index 60% rename from pkg/tunnel/router/client_icx_netstack.go rename to pkg/tunnel/router/icx_netstack.go index c1bec25..1a35ac2 100644 --- a/pkg/tunnel/router/client_icx_netstack.go +++ b/pkg/tunnel/router/icx_netstack.go @@ -13,12 +13,12 @@ import ( "github.com/apoxy-dev/icx" "github.com/dpeckett/network" "golang.org/x/sync/errgroup" - "gvisor.dev/gvisor/pkg/tcpip" "github.com/apoxy-dev/apoxy/pkg/netstack" "github.com/apoxy-dev/apoxy/pkg/socksproxy" "github.com/apoxy-dev/apoxy/pkg/tunnel/connection" "github.com/apoxy-dev/apoxy/pkg/tunnel/l2pc" + tunnet "github.com/apoxy-dev/apoxy/pkg/tunnel/net" ) var ( @@ -26,37 +26,64 @@ var ( ) type ICXNetstackRouter struct { - Handler *icx.Handler - phy *l2pc.L2PacketConn - net *netstack.ICXNetwork - proxy *socksproxy.ProxyServer - closeOnce sync.Once + Handler *icx.Handler + phy *l2pc.L2PacketConn + net *netstack.ICXNetwork + proxy *socksproxy.ProxyServer + egressGateway bool + closeOnce sync.Once } -func NewICXNetstackRouter(pc net.PacketConn, mtu int, opts ...Option) (*ICXNetstackRouter, error) { +func NewICXNetstackRouter(opts ...Option) (*ICXNetstackRouter, error) { options := defaultOptions() for _, opt := range opts { opt(options) } - phy, err := l2pc.NewL2PacketConn(pc) + if options.pc == nil { + return nil, fmt.Errorf("packet conn is required for ICX netstack router") + } + + phy, err := l2pc.NewL2PacketConn(options.pc) if err != nil { return nil, fmt.Errorf("failed to create L2 packet connection phy: %w", err) } - localUDPAddr := pc.LocalAddr().(*net.UDPAddr) + handlerOpts := []icx.HandlerOption{ + icx.WithLayer3VirtFrames(), + } + if options.sourcePortHashing { + handlerOpts = append(handlerOpts, icx.WithSourcePortHashing()) + } - localAddr := netip.AddrPortFrom(netip.MustParseAddr(localUDPAddr.IP.String()), + localUDPAddr := options.pc.LocalAddr().(*net.UDPAddr) + + localAddrPort := netip.AddrPortFrom(netip.MustParseAddr(localUDPAddr.IP.String()), uint16(localUDPAddr.Port)) - handler, err := icx.NewHandler( - icx.WithLocalAddr(netstack.ToFullAddress(localAddr)), - icx.WithVirtMAC(tcpip.GetRandMacAddr()), icx.WithLayer3VirtFrames()) + localAddrPorts := []netip.AddrPort{localAddrPort} + if localAddrPort.Addr().IsUnspecified() { + localAddrs, err := tunnet.GetAllGlobalUnicastAddresses(true) + if err != nil { + _ = phy.Close() + return nil, fmt.Errorf("failed to get local addresses: %w", err) + } + + for _, addr := range localAddrs { + localAddrPorts = append(localAddrPorts, netip.AddrPortFrom(addr.Addr(), localAddrPort.Port())) + } + } + + for _, addr := range localAddrPorts { + handlerOpts = append(handlerOpts, icx.WithLocalAddr(netstack.ToFullAddress(addr))) + } + + handler, err := icx.NewHandler(handlerOpts...) if err != nil { return nil, fmt.Errorf("failed to create ICX handler: %w", err) } - net, err := netstack.NewICXNetwork(handler, phy, mtu, options.resolveConf, options.pcapPath) + net, err := netstack.NewICXNetwork(handler, phy, options.tunMTU, options.resolveConf, options.pcapPath) if err != nil { _ = phy.Close() return nil, fmt.Errorf("failed to create ICX network: %w", err) @@ -69,10 +96,11 @@ func NewICXNetstackRouter(pc net.PacketConn, mtu int, opts ...Option) (*ICXNetst ) return &ICXNetstackRouter{ - Handler: handler, - phy: phy, - net: net, - proxy: proxy, + Handler: handler, + phy: phy, + net: net, + proxy: proxy, + egressGateway: options.egressGateway, }, nil } @@ -107,13 +135,24 @@ func (r *ICXNetstackRouter) Start(ctx context.Context) error { return fmt.Errorf("failed to parse SOCKS listen port: %w", err) } - slog.Info("Forwarding all inbound traffic to loopback interface") + if r.egressGateway { + slog.Info("Forwarding inbound traffic to internet") - if err := r.net.ForwardTo(ctx, network.Filtered(&network.FilteredNetworkConfig{ - DeniedPorts: []uint16{uint16(socksListenPort)}, - Upstream: network.Host(), - })); err != nil { - return fmt.Errorf("failed to forward to loopback: %w", err) + if err := r.net.ForwardTo(ctx, network.Filtered(&network.FilteredNetworkConfig{ + DeniedDestinations: []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8"), netip.MustParsePrefix("::1/128")}, + Upstream: network.Host(), + })); err != nil { + return fmt.Errorf("failed to forward to internet: %w", err) + } + } else { + slog.Info("Forwarding all inbound traffic to loopback interface") + + if err := r.net.ForwardTo(ctx, network.Filtered(&network.FilteredNetworkConfig{ + DeniedPorts: []uint16{uint16(socksListenPort)}, + Upstream: network.Loopback(), + })); err != nil { + return fmt.Errorf("failed to forward to loopback: %w", err) + } } g, ctx := errgroup.WithContext(ctx) @@ -149,7 +188,7 @@ func (r *ICXNetstackRouter) Start(ctx context.Context) error { } // AddAddr adds a tun with an associated address to the router. -func (r *ICXNetstackRouter) AddAddr(addr netip.Prefix, tun connection.Connection) error { +func (r *ICXNetstackRouter) AddAddr(addr netip.Prefix, _ connection.Connection) error { if err := r.net.AddAddr(addr); err != nil { return fmt.Errorf("failed to add address to ICX network: %w", err) } diff --git a/pkg/tunnel/router/options.go b/pkg/tunnel/router/options.go index ed739b9..3329c01 100644 --- a/pkg/tunnel/router/options.go +++ b/pkg/tunnel/router/options.go @@ -1,8 +1,10 @@ package router import ( + "net" "net/netip" + "github.com/apoxy-dev/icx" "github.com/dpeckett/network" ) @@ -16,16 +18,20 @@ type routerOptions struct { pcapPath string extIfaceName string tunIfaceName string + tunMTU int socksListenAddr string cksumRecalc bool preserveDefaultGwDsts []netip.Prefix sourcePortHashing bool + pc net.PacketConn + egressGateway bool } func defaultOptions() *routerOptions { return &routerOptions{ extIfaceName: "eth0", tunIfaceName: "tun0", + tunMTU: icx.MTU(1500), // Default to a 1500 byte path MTU socksListenAddr: "localhost:1080", cksumRecalc: false, } @@ -75,6 +81,13 @@ func WithTunnelInterface(name string) Option { } } +// WithTunnelMTU sets the tunnel interface MTU. +func WithTunnelMTU(mtu int) Option { + return func(o *routerOptions) { + o.tunMTU = mtu + } +} + // WithSocksListenAddr sets the SOCKS listen address for the netstack router. // Only valid for netstack routers. func WithSocksListenAddr(addr string) Option { @@ -106,3 +119,18 @@ func WithSourcePortHashing(enable bool) Option { o.sourcePortHashing = enable } } + +// WithPacketConn sets the underlying PacketConn for the ICX router. +// Only valid for ICX netstack router. +func WithPacketConn(pc net.PacketConn) Option { + return func(o *routerOptions) { + o.pc = pc + } +} + +// WithEgressGateway enables or disables internet egress for the router. +func WithEgressGateway(enable bool) Option { + return func(o *routerOptions) { + o.egressGateway = enable + } +} diff --git a/pkg/tunnel/router/server_icx_netlink.go b/pkg/tunnel/router/server_icx_netlink.go deleted file mode 100644 index 3d088b7..0000000 --- a/pkg/tunnel/router/server_icx_netlink.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build !linux - -package router - -import ( - "context" - "errors" - "net/netip" - - "github.com/apoxy-dev/apoxy/pkg/tunnel/connection" -) - -func NewICXNetlinkRouter(_ ...Option) (Router, error) { - return ¬ImplementedRouter{}, nil -} - -type notImplementedRouter struct{} - -func (r *notImplementedRouter) Start(ctx context.Context) error { - return errors.New("not implemented") -} - -func (r *notImplementedRouter) AddAddr(_ netip.Prefix, _ connection.Connection) error { - return errors.New("not implemented") -} - -func (r *notImplementedRouter) DelAddr(_ netip.Prefix) error { - return errors.New("not implemented") -} - -func (r *notImplementedRouter) AddRoute(dst netip.Prefix) error { - return errors.New("not implemented") -} - -func (r *notImplementedRouter) DelRoute(dst netip.Prefix) error { - return errors.New("not implemented") -} - -func (r *notImplementedRouter) Close() error { - return nil -}