From 7d5e58a383c1228e7e3534e2d31dd1d6c8a45ee6 Mon Sep 17 00:00:00 2001 From: Jeroen Wijenbergh Date: Mon, 23 Feb 2026 12:08:51 +0100 Subject: Failover: Support v6 gateway Useful for v6 only VPNs --- internal/failover/monitor.go | 4 ---- internal/failover/monitor_test.go | 4 +++- internal/failover/ping.go | 32 ++++++++++++++++++++++++++------ internal/failover/ping_default.go | 20 ++++++++++++++++++-- internal/failover/ping_windows.go | 20 ++++++++++++++++++-- 5 files changed, 65 insertions(+), 15 deletions(-) (limited to 'internal') diff --git a/internal/failover/monitor.go b/internal/failover/monitor.go index 0d319d6..2c14980 100644 --- a/internal/failover/monitor.go +++ b/internal/failover/monitor.go @@ -51,10 +51,6 @@ func (m *DroppedConMon) dropped(startBytes int64) (bool, error) { // This does not check Rx bytes every tick, but rather when pAlive or pDropped is reached // It returns an error if there was an invalid input or a ping was failed to be sent func (m *DroppedConMon) Start(ctx context.Context, gateway string, mtuSize int) (bool, error) { - if mtuSize < mtuOverhead { - return false, fmt.Errorf("invalid MTU size given, MTU has to be at least: %v bytes", mtuOverhead) - } - // Create a ping struct with our mtu size p, err := m.newPinger(gateway, mtuSize) if err != nil { diff --git a/internal/failover/monitor_test.go b/internal/failover/monitor_test.go index 33a93a7..1b41931 100644 --- a/internal/failover/monitor_test.go +++ b/internal/failover/monitor_test.go @@ -108,7 +108,9 @@ func TestMonitor(t *testing.T) { } } dcm := NewDroppedMonitor(c.interval, c.pDropped, c.readRxBytes) - dcm.newPinger = c.mockedPinger + if c.mockedPinger != nil { + dcm.newPinger = c.mockedPinger + } dropped, err := dcm.Start(context.Background(), c.gateway, c.mtuSize) if dropped != c.wantDropped { t.Fatalf("dropped is not equal to want dropped, got: %v, want: %v", dropped, c.wantDropped) diff --git a/internal/failover/ping.go b/internal/failover/ping.go index 59dbcc9..b37685c 100644 --- a/internal/failover/ping.go +++ b/internal/failover/ping.go @@ -8,16 +8,22 @@ import ( "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) -// mtuOverhead defines the total MTU overhead for an ICMP ECHO message: 20 bytes IP header + 8 bytes ICMP header -var mtuOverhead = 28 +var ( + // mtuV4Overhead defines the total MTU overhead for an ICMP ECHO message: 20 bytes IP header + 8 bytes ICMP header + mtuV4Overhead = 28 + // mtuV6Overhead defines the total MTU v6 overhead for an ICMP ECHO message: 40 bytes IP header + 8 bytes ICMP header + mtuV6Overhead = 48 +) // Pinger sends pings type Pinger struct { listener net.PacketConn buffer []byte gateway net.Addr + v4 bool } // Read reads from the ping listener with deadline `deadline` @@ -33,12 +39,18 @@ func (p Pinger) Read(deadline time.Time) error { if err != nil { return err } - got, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), r[:n]) + var t icmp.Type + if p.v4 { + t = ipv4.ICMPTypeEchoReply + } else { + t = ipv6.ICMPTypeEchoReply + } + got, err := icmp.ParseMessage(t.Protocol(), r[:n]) if err != nil { return err } switch got.Type { - case ipv4.ICMPTypeEchoReply: + case t: return nil default: return fmt.Errorf("not a ping echo reply, got: %+v", got) @@ -49,10 +61,18 @@ func (p Pinger) Read(deadline time.Time) error { func (p Pinger) Send(seq int) error { errorMessage := fmt.Sprintf("failed sending ping, seq %d", seq) // Make a new ICMP message + var t icmp.Type + if p.v4 { + t = ipv4.ICMPTypeEcho + } else { + t = ipv6.ICMPTypeEchoRequest + } m := icmp.Message{ - Type: ipv4.ICMPTypeEcho, Code: 0, + Type: t, + Code: 0, Body: &icmp.Echo{ - ID: os.Getpid() & 0xffff, Seq: seq, + ID: os.Getpid() & 0xffff, + Seq: seq, Data: p.buffer, }, } diff --git a/internal/failover/ping_default.go b/internal/failover/ping_default.go index 11401bb..181f3fc 100644 --- a/internal/failover/ping_default.go +++ b/internal/failover/ping_default.go @@ -11,13 +11,29 @@ import ( // NewPinger creates a new pinger with gateway `gateway` and size `size` func NewPinger(gateway string, size int) (*Pinger, error) { - l, err := icmp.ListenPacket("udp4", "0.0.0.0") + gip := net.ParseIP(gateway) + isV4 := gip.To4() != nil + mtuOverhead := mtuV6Overhead + if isV4 { + mtuOverhead = mtuV4Overhead + } + if size < mtuOverhead { + return nil, fmt.Errorf("invalid MTU size given, MTU has to be at least: %v bytes", mtuOverhead) + } + var l *icmp.PacketConn + var err error + if isV4 { + l, err = icmp.ListenPacket("udp4", "0.0.0.0") + } else { + l, err = icmp.ListenPacket("udp6", "::") + } if err != nil { return nil, fmt.Errorf("failed creating ping with error: %w", err) } return &Pinger{ listener: l, buffer: make([]byte, size-mtuOverhead), - gateway: &net.UDPAddr{IP: net.ParseIP(gateway)}, + gateway: &net.UDPAddr{IP: gip}, + v4: isV4, }, nil } diff --git a/internal/failover/ping_windows.go b/internal/failover/ping_windows.go index 3f181f5..eb801d8 100644 --- a/internal/failover/ping_windows.go +++ b/internal/failover/ping_windows.go @@ -8,13 +8,29 @@ import ( ) func NewPinger(gateway string, size int) (*Pinger, error) { - l, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") + gip := net.ParseIP(gateway) + isV4 := gip.To4() != nil + mtuOverhead := mtuV6Overhead + if isV4 { + mtuOverhead = mtuV4Overhead + } + if size < mtuOverhead { + return nil, fmt.Errorf("invalid MTU size given, MTU has to be at least: %v bytes", mtuOverhead) + } + var l *icmp.PacketConn + var err error + if isV4 { + l, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0") + } else { + l, err = icmp.ListenPacket("ip6:icmp", "::") + } if err != nil { return nil, fmt.Errorf("failed creating ping with error: %w", err) } return &Pinger{ listener: l, buffer: make([]byte, size-mtuOverhead), - gateway: &net.IPAddr{IP: net.ParseIP(gateway)}, + gateway: &net.IPAddr{IP: gip}, + v4: isV4, }, nil } -- cgit v1.2.3