Skip to content

Commit fa296e0

Browse files
committed
[tunnel] Send 0.0.0.0/0 ::/0 routes if NAT GW is enabled
1 parent 5bbb2a6 commit fa296e0

File tree

4 files changed

+71
-73
lines changed

4 files changed

+71
-73
lines changed

cmd/tunnelproxy/main.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ package main
55
import (
66
"flag"
77
"fmt"
8+
"net"
9+
"net/netip"
10+
"os"
811
"strings"
912
"time"
1013

@@ -44,6 +47,8 @@ var (
4447

4548
apiServerAddr = flag.String("apiserver_addr", "host.docker.internal:8443", "APIServer address.")
4649
jwksURLs = flag.String("jwks_urls", "", "Comma-separated URLs of the JWKS endpoints.")
50+
51+
extIPv6SubnetSize = flag.Int("ext_ipv6_subnet_size", 64, "IPv6 subnet size.")
4752
)
4853

4954
func main() {
@@ -97,7 +102,20 @@ func main() {
97102
log.Fatalf("Failed to create JWT validator: %v", err)
98103
}
99104

100-
r, err := router.NewNetlinkRouter()
105+
// Resolve external IPv6 address from hostname
106+
extAddr, err := net.ResolveIPAddr("ip6", os.Getenv("HOSTNAME"))
107+
if err != nil {
108+
log.Fatalf("Failed to resolve external IPv6 address: %v", err)
109+
}
110+
extIPv6, ok := netip.AddrFromSlice(extAddr.IP)
111+
if !ok {
112+
log.Fatalf("Invalid IPv6 address resolved: %s", extAddr.IP.String())
113+
}
114+
extIPv6Prefix := netip.PrefixFrom(extIPv6, *extIPv6SubnetSize)
115+
116+
r, err := router.NewNetlinkRouter(
117+
router.WithExternalIPv6Prefix(extIPv6Prefix),
118+
)
101119
if err != nil {
102120
log.Fatalf("Failed to create netlink router: %v", err)
103121
}
@@ -106,6 +124,7 @@ func main() {
106124
mgr.GetClient(),
107125
jwtValidator,
108126
r,
127+
tunnel.WithExternalIPv6Prefix(extIPv6Prefix),
109128
)
110129
g.Go(func() error {
111130
log.Infof("Starting Tunnel Proxy server")

pkg/tunnel/router/options.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type Option func(*routerOptions)
1111

1212
type routerOptions struct {
13+
extIPv6Prefix netip.Prefix
1314
localAddresses []netip.Prefix
1415
resolveConf *network.ResolveConfig // If not set system default resolver is used
1516
pcapPath string
@@ -20,6 +21,7 @@ type routerOptions struct {
2021

2122
func defaultOptions() *routerOptions {
2223
return &routerOptions{
24+
extIPv6Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 64),
2325
extIfaceName: "eth0",
2426
tunIfaceName: "tun0",
2527
socksListenAddr: "localhost:1080",
@@ -33,6 +35,13 @@ func WithLocalAddresses(localAddresses []netip.Prefix) Option {
3335
}
3436
}
3537

38+
// WithExternalIPv6Prefix sets the external IPv6 prefix for the router.
39+
func WithExternalIPv6Prefix(prefix netip.Prefix) Option {
40+
return func(o *routerOptions) {
41+
o.extIPv6Prefix = prefix
42+
}
43+
}
44+
3645
// WithPcapPath sets the optional path to a packet capture file for the netstack router.
3746
func WithPcapPath(path string) Option {
3847
return func(o *routerOptions) {

pkg/tunnel/router/server_netlink_linux.go

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"net"
1111
"net/netip"
1212
"os"
13-
"slices"
14-
"strings"
1513
"sync"
1614

1715
"github.com/vishvananda/netlink"
@@ -32,59 +30,19 @@ var (
3230

3331
// NetlinkRouter implements Router using Linux's netlink subsystem.
3432
type NetlinkRouter struct {
35-
extLink netlink.Link
33+
extLink netlink.Link
34+
extIPv6Prefix netip.Prefix
35+
3636
tunDev tun.Device
3737
tunLink netlink.Link
3838

39-
extAddr netip.Addr
40-
extPrefixes []netip.Prefix
41-
ipt utiliptables.Interface
39+
ipt utiliptables.Interface
4240

4341
mux *connection.MuxedConn
4442

4543
closeOnce sync.Once
4644
}
4745

48-
func extPrefixes(link netlink.Link) (netip.Addr, []netip.Prefix, error) {
49-
slog.Info("Checking link", slog.String("name", link.Attrs().Name))
50-
51-
addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
52-
if err != nil {
53-
return netip.Addr{}, nil, fmt.Errorf("failed to get addresses for link: %w", err)
54-
}
55-
56-
var extAddr netip.Addr
57-
var prefixes []netip.Prefix
58-
for _, addr := range addrs {
59-
slog.Debug("Checking address", slog.String("addr", addr.String()))
60-
// Skip loopback addresses
61-
ip, ok := netip.AddrFromSlice(addr.IP)
62-
if !ok {
63-
slog.Warn("Failed to convert IP address", slog.String("ip", addr.IP.String()))
64-
continue
65-
}
66-
if !ip.Is6() {
67-
slog.Warn("Skipping non-IPv6 address", slog.String("ip", addr.IP.String()))
68-
continue
69-
}
70-
if !ip.IsGlobalUnicast() { // Skip non-global unicast addresses.
71-
slog.Debug("Skipping non-global unicast address", slog.String("ip", addr.IP.String()))
72-
continue
73-
}
74-
75-
slog.Info("Found IPv6 address", slog.String("ip", addr.IP.String()), slog.String("mask", addr.Mask.String()))
76-
77-
if !extAddr.IsValid() {
78-
extAddr = ip
79-
}
80-
81-
bits, _ := addr.Mask.Size()
82-
prefixes = append(prefixes, netip.PrefixFrom(ip, bits))
83-
}
84-
85-
return extAddr, prefixes, nil
86-
}
87-
8846
// NewNetlinkRouter creates a new netlink-based tunnel router.
8947
func NewNetlinkRouter(opts ...Option) (*NetlinkRouter, error) {
9048
options := defaultOptions()
@@ -96,10 +54,6 @@ func NewNetlinkRouter(opts ...Option) (*NetlinkRouter, error) {
9654
if err != nil {
9755
return nil, fmt.Errorf("failed to get external interface: %w", err)
9856
}
99-
extAddr, lrs, err := extPrefixes(extLink)
100-
if err != nil {
101-
return nil, fmt.Errorf("failed to get local routes: %w", err)
102-
}
10357

10458
tunDev, err := tun.CreateTUN(options.tunIfaceName, netstack.IPv6MinMTU)
10559
if err != nil {
@@ -157,9 +111,7 @@ func NewNetlinkRouter(opts ...Option) (*NetlinkRouter, error) {
157111
tunDev: tunDev,
158112
tunLink: tunLink,
159113

160-
extAddr: extAddr,
161-
extPrefixes: lrs,
162-
ipt: utiliptables.New(utilexec.New(), utiliptables.ProtocolIPv6),
114+
ipt: utiliptables.New(utilexec.New(), utiliptables.ProtocolIPv6),
163115

164116
mux: connection.NewMuxedConn(),
165117
}, nil
@@ -181,14 +133,12 @@ func (r *NetlinkRouter) setupDNAT() error {
181133
extName := r.extLink.Attrs().Name
182134
tunName := r.tunLink.Attrs().Name
183135

184-
// Setup jump rule to our custom chain.
185-
var dsts []string
186-
for _, prefix := range r.extPrefixes {
187-
dsts = append(dsts, prefix.Addr().String())
188-
}
189-
slices.Sort(dsts)
190-
slog.Info("Setting up jump rule", slog.String("ext_iface", extName), slog.String("tun_iface", tunName), slog.String("dsts", strings.Join(dsts, ",")))
191-
jRuleSpec := []string{"-d", strings.Join(dsts, ","), "-i", extName, "-j", string(ChainA3yTunRules)}
136+
slog.Info("Setting up jump rule",
137+
slog.String("ext_iface", extName),
138+
slog.String("ext_addr", r.extIPv6Prefix.Addr().String()))
139+
140+
// Traffic arriving at the designated external interface will be processed by the A3Y-TUN-RULES chain.
141+
jRuleSpec := []string{"-d", r.extIPv6Prefix.Addr().String(), "-i", extName, "-j", string(ChainA3yTunRules)}
192142
if _, err := r.ipt.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPrerouting, jRuleSpec...); err != nil {
193143
return fmt.Errorf("failed to ensure jump rule: %w", err)
194144
}

pkg/tunnel/server.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,22 @@ var (
4444
type TunnelServerOption func(*tunnelServerOptions)
4545

4646
type tunnelServerOptions struct {
47-
proxyAddr string
48-
ulaPrefix netip.Prefix
49-
certPath string
50-
keyPath string
51-
ipam tunnet.IPAM
47+
proxyAddr string
48+
ulaPrefix netip.Prefix
49+
certPath string
50+
keyPath string
51+
ipam tunnet.IPAM
52+
extIPv6Prefix netip.Prefix
5253
}
5354

5455
func defaultServerOptions() *tunnelServerOptions {
5556
return &tunnelServerOptions{
56-
proxyAddr: "0.0.0.0:9443",
57-
ulaPrefix: netip.MustParsePrefix("fd00::/64"),
58-
certPath: "/etc/apoxy/certs/tunnelproxy.crt",
59-
keyPath: "/etc/apoxy/certs/tunnelproxy.key",
60-
ipam: tunnet.NewRandomULA(),
57+
proxyAddr: "0.0.0.0:9443",
58+
ulaPrefix: netip.MustParsePrefix("fd00::/64"),
59+
certPath: "/etc/apoxy/certs/tunnelproxy.crt",
60+
keyPath: "/etc/apoxy/certs/tunnelproxy.key",
61+
ipam: tunnet.NewRandomULA(),
62+
extIPv6Prefix: netip.MustParsePrefix("fd00::/64"),
6163
}
6264
}
6365

@@ -96,6 +98,14 @@ func WithIPAM(ipam tunnet.IPAM) TunnelServerOption {
9698
}
9799
}
98100

101+
// WithExternalIPv6Prefix sets the external IPv6 prefix. This is the IPv6 prefix used to
102+
// send traffic through the tunnel.
103+
func WithExternalIPv6Prefix(prefix netip.Prefix) TunnelServerOption {
104+
return func(o *tunnelServerOptions) {
105+
o.extIPv6Prefix = prefix
106+
}
107+
}
108+
99109
type TunnelServer struct {
100110
http3.Server
101111
client.Client
@@ -318,7 +328,17 @@ func (t *TunnelServer) handleConnect(w http.ResponseWriter, r *http.Request) {
318328
return
319329
}
320330

321-
advRoutes := []netip.Prefix{} // TODO: Implement route advertisement logic from TunnelNode and config.
331+
advRoutes := []netip.Prefix{
332+
t.options.extIPv6Prefix,
333+
}
334+
// If egress gateway is enabled, route 0.0.0.0/0 via the tunnel.
335+
if tn.Spec.EgressGateway != nil && tn.Spec.EgressGateway.Enabled {
336+
advRoutes = append(advRoutes,
337+
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
338+
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
339+
)
340+
}
341+
322342
logger.Info("Advertising routes", slog.Any("routes", advRoutes))
323343

324344
if err := conn.AdvertiseRoute(r.Context(), iproutesFromPrefixes(advRoutes)); err != nil {

0 commit comments

Comments
 (0)