TUN-6741: ICMP proxy tries to listen on specific IPv4 & IPv6 when possible

If it cannot determine the correct interface IP, it will fallback to all interfaces.
This commit also introduces the icmpv4-src and icmpv6-src flags
This commit is contained in:
cthuang
2022-09-20 11:39:51 +01:00
parent 3449ea35f2
commit be0305ec58
22 changed files with 262 additions and 109 deletions

View File

@@ -2,18 +2,11 @@ package packet
import (
"context"
"net"
"net/netip"
"github.com/rs/zerolog"
)
var (
// Source IP in documentation range to return ICMP error messages if we can't determine the IP of this machine
icmpv4ErrFallbackSrc = netip.MustParseAddr("192.0.2.30")
icmpv6ErrFallbackSrc = netip.MustParseAddr("2001:db8::")
)
// ICMPRouter sends ICMP messages and listens for their responses
type ICMPRouter interface {
// Serve starts listening for responses to the requests until context is done
@@ -28,32 +21,31 @@ type Upstream interface {
ReceivePacket(ctx context.Context) (RawPacket, error)
}
// Router routes packets between Upstream and ICMPRouter. Currently it rejects all other type of ICMP packets
type Router struct {
upstream Upstream
returnPipe FunnelUniPipe
icmpProxy ICMPRouter
icmpRouter ICMPRouter
ipv4Src netip.Addr
ipv6Src netip.Addr
logger *zerolog.Logger
}
func NewRouter(upstream Upstream, returnPipe FunnelUniPipe, icmpProxy ICMPRouter, logger *zerolog.Logger) *Router {
ipv4Src, err := findLocalAddr(net.ParseIP("1.1.1.1"), 53)
if err != nil {
logger.Warn().Err(err).Msgf("Failed to determine the IPv4 for this machine. It will use %s as source IP for error messages such as ICMP TTL exceed", icmpv4ErrFallbackSrc)
ipv4Src = icmpv4ErrFallbackSrc
}
ipv6Src, err := findLocalAddr(net.ParseIP("2606:4700:4700::1111"), 53)
if err != nil {
logger.Warn().Err(err).Msgf("Failed to determine the IPv6 for this machine. It will use %s as source IP for error messages such as ICMP TTL exceed", icmpv6ErrFallbackSrc)
ipv6Src = icmpv6ErrFallbackSrc
}
// GlobalRouterConfig is the configuration shared by all instance of Router.
type GlobalRouterConfig struct {
ICMPRouter ICMPRouter
IPv4Src netip.Addr
IPv6Src netip.Addr
Zone string
}
func NewRouter(globalConfig *GlobalRouterConfig, upstream Upstream, returnPipe FunnelUniPipe, logger *zerolog.Logger) *Router {
return &Router{
upstream: upstream,
returnPipe: returnPipe,
icmpProxy: icmpProxy,
ipv4Src: ipv4Src,
ipv6Src: ipv6Src,
icmpRouter: globalConfig.ICMPRouter,
ipv4Src: globalConfig.IPv4Src,
ipv6Src: globalConfig.IPv6Src,
logger: logger,
}
}
@@ -80,7 +72,7 @@ func (r *Router) Serve(ctx context.Context) error {
}
icmpPacket.TTL--
if err := r.icmpProxy.Request(icmpPacket, r.returnPipe); err != nil {
if err := r.icmpRouter.Request(icmpPacket, r.returnPipe); err != nil {
r.logger.Err(err).
Str("src", icmpPacket.Src.String()).
Str("dst", icmpPacket.Dst.String()).
@@ -106,21 +98,3 @@ func (r *Router) sendTTLExceedMsg(pk *ICMP, rawPacket RawPacket, encoder *Encode
}
return r.returnPipe.SendPacket(pk.Src, encodedTTLExceed)
}
// findLocalAddr tries to dial UDP and returns the local address picked by the OS
func findLocalAddr(dst net.IP, port int) (netip.Addr, error) {
udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: dst,
Port: port,
})
if err != nil {
return netip.Addr{}, err
}
defer udpConn.Close()
localAddrPort, err := netip.ParseAddrPort(udpConn.LocalAddr().String())
if err != nil {
return netip.Addr{}, err
}
localAddr := localAddrPort.Addr()
return localAddr, nil
}

View File

@@ -26,7 +26,12 @@ func TestRouterReturnTTLExceed(t *testing.T) {
returnPipe := &mockFunnelUniPipe{
uniPipe: make(chan RawPacket),
}
router := NewRouter(upstream, returnPipe, &mockICMPRouter{}, &noopLogger)
packetConfig := &GlobalRouterConfig{
ICMPRouter: &mockICMPRouter{},
IPv4Src: netip.MustParseAddr("172.16.0.1"),
IPv6Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
}
router := NewRouter(packetConfig, upstream, returnPipe, &noopLogger)
ctx, cancel := context.WithCancel(context.Background())
routerStopped := make(chan struct{})
go func() {