From 13c8bff7c25f69b5392886f1dc4ab05fb47132ad Mon Sep 17 00:00:00 2001 From: nvksie Date: Tue, 3 Dec 2024 17:50:29 +0800 Subject: [PATCH 1/2] filter icmp id with sock_filter, to optimize rawsocket preformance Signed-off-by: nvksie --- packetconn.go | 1 + ping.go | 10 ++++++++-- ping_test.go | 1 + utils_linux.go | 38 ++++++++++++++++++++++++++++++++++++++ utils_other.go | 8 ++++++++ utils_windows.go | 8 ++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packetconn.go b/packetconn.go index b923a19..156f143 100644 --- a/packetconn.go +++ b/packetconn.go @@ -23,6 +23,7 @@ type packetConn interface { SetBroadcastFlag() error SetIfIndex(ifIndex int) SetTrafficClass(uint8) error + InstallICMPIDFilter(id int) error } type icmpConn struct { diff --git a/ping.go b/ping.go index c12f2cb..e832967 100644 --- a/ping.go +++ b/ping.go @@ -88,8 +88,9 @@ var ( ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"} ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"} - ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform") - ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform") + ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform") + ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform") + ErrSockFilterNotSupport = errors.New("setting SO_ATTACH_FILTER socket option is not supported on this platform") ) // New returns a new Pinger struct pointer. @@ -979,6 +980,11 @@ func (p *Pinger) listen() (packetConn, error) { p.Stop() return nil, err } + + if p.Privileged() { + conn.InstallICMPIDFilter(p.id) + } + return conn, nil } diff --git a/ping_test.go b/ping_test.go index 2b2ba9b..975e07b 100644 --- a/ping_test.go +++ b/ping_test.go @@ -677,6 +677,7 @@ func (c testPacketConn) SetTTL(t int) {} func (c testPacketConn) SetMark(m uint) error { return nil } func (c testPacketConn) SetDoNotFragment() error { return nil } func (c testPacketConn) SetBroadcastFlag() error { return nil } +func (c testPacketConn) InstallICMPIDFilter(id int) error { return nil } func (c testPacketConn) SetIfIndex(ifIndex int) {} func (c testPacketConn) SetTrafficClass(uint8) error { return nil } diff --git a/utils_linux.go b/utils_linux.go index 7810785..c5e9459 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -9,7 +9,10 @@ import ( "reflect" "syscall" + "golang.org/x/net/bpf" "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) // Returns the length of an ICMP message. @@ -137,6 +140,41 @@ func (c *icmpV6Conn) SetBroadcastFlag() error { ) } +// InstallICMPIDFilter attaches a BPF program to the connection to filter ICMP packets id. +func (c *icmpv4Conn) InstallICMPIDFilter(id int) error { + filter, err := bpf.Assemble([]bpf.Instruction{ + bpf.LoadMemShift{Off: 0}, // Skip IP header + bpf.LoadIndirect{Off: 4, Size: 2}, // Load ICMP echo ident + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(id), SkipTrue: 0, SkipFalse: 1}, // Jump on ICMP Echo Request (ID check) + bpf.RetConstant{Val: ^uint32(0)}, // If our ID, accept the packet + bpf.LoadIndirect{Off: 0, Size: 1}, // Load ICMP type + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(ipv4.ICMPTypeEchoReply), SkipTrue: 1, SkipFalse: 0}, // Check if ICMP Echo Reply + bpf.RetConstant{Val: 0xFFFFFFF}, // Accept packet if it's not Echo Reply + bpf.RetConstant{Val: 0}, // Reject Echo packet with wrong identifier + }) + if err != nil { + return err + } + return c.c.IPv4PacketConn().SetBPF(filter) +} + +// InstallICMPIDFilter attaches a BPF program to the connection to filter ICMPv6 packets id. +func (c *icmpV6Conn) InstallICMPIDFilter(id int) error { + filter, err := bpf.Assemble([]bpf.Instruction{ + bpf.LoadAbsolute{Off: 4, Size: 2}, // Load ICMP echo identifier + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(id), SkipTrue: 0, SkipFalse: 1}, // Check if it matches our identifier + bpf.RetConstant{Val: ^uint32(0)}, // Accept if true + bpf.LoadAbsolute{Off: 0, Size: 1}, // Load ICMP type + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(ipv6.ICMPTypeEchoReply), SkipTrue: 1, SkipFalse: 0}, // Check if it is an ICMP6 echo reply + bpf.RetConstant{Val: ^uint32(0)}, // Accept if false + bpf.RetConstant{Val: 0}, // Reject if echo with wrong identifier + }) + if err != nil { + return err + } + return c.c.IPv6PacketConn().SetBPF(filter) +} + // getFD gets the system file descriptor for an icmp.PacketConn func getFD(c *icmp.PacketConn) (uintptr, error) { v := reflect.ValueOf(c).Elem().FieldByName("c").Elem() diff --git a/utils_other.go b/utils_other.go index 03fea8b..145ea68 100644 --- a/utils_other.go +++ b/utils_other.go @@ -58,3 +58,11 @@ func (c *icmpv4Conn) SetBroadcastFlag() error { func (c *icmpV6Conn) SetBroadcastFlag() error { return nil } + +func (c *icmpv4Conn) InstallICMPIDFilter(id int) error { + return ErrSockFilterNotSupport +} + +func (c *icmpV6Conn) InstallICMPIDFilter(id int) error { + return ErrSockFilterNotSupport +} diff --git a/utils_windows.go b/utils_windows.go index cefb239..f886d7e 100644 --- a/utils_windows.go +++ b/utils_windows.go @@ -86,3 +86,11 @@ func (c *icmpv4Conn) SetBroadcastFlag() error { func (c *icmpV6Conn) SetBroadcastFlag() error { return nil } + +func (c *icmpv4Conn) InstallICMPIDFilter(id int) error { + return ErrSockFilterNotSupport +} + +func (c *icmpV6Conn) InstallICMPIDFilter(id int) error { + return ErrSockFilterNotSupport +} From e6f961de6b7652846981735904464a200372193b Mon Sep 17 00:00:00 2001 From: nvksie Date: Wed, 1 Jan 2025 22:47:22 +0800 Subject: [PATCH 2/2] Add error handler for InstallICMPIDFilter Signed-off-by: nvksie --- ping.go | 9 +++++---- utils_other.go | 4 ++-- utils_windows.go | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ping.go b/ping.go index e832967..81bf0ad 100644 --- a/ping.go +++ b/ping.go @@ -88,9 +88,8 @@ var ( ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"} ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"} - ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform") - ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform") - ErrSockFilterNotSupport = errors.New("setting SO_ATTACH_FILTER socket option is not supported on this platform") + ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform") + ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform") ) // New returns a new Pinger struct pointer. @@ -982,7 +981,9 @@ func (p *Pinger) listen() (packetConn, error) { } if p.Privileged() { - conn.InstallICMPIDFilter(p.id) + if err := conn.InstallICMPIDFilter(p.id); err != nil { + p.logger.Warnf("error installing icmp filter, %v", err) + } } return conn, nil diff --git a/utils_other.go b/utils_other.go index 145ea68..c358572 100644 --- a/utils_other.go +++ b/utils_other.go @@ -60,9 +60,9 @@ func (c *icmpV6Conn) SetBroadcastFlag() error { } func (c *icmpv4Conn) InstallICMPIDFilter(id int) error { - return ErrSockFilterNotSupport + return nil } func (c *icmpV6Conn) InstallICMPIDFilter(id int) error { - return ErrSockFilterNotSupport + return nil } diff --git a/utils_windows.go b/utils_windows.go index f886d7e..f507b7a 100644 --- a/utils_windows.go +++ b/utils_windows.go @@ -88,9 +88,9 @@ func (c *icmpV6Conn) SetBroadcastFlag() error { } func (c *icmpv4Conn) InstallICMPIDFilter(id int) error { - return ErrSockFilterNotSupport + return nil } func (c *icmpV6Conn) InstallICMPIDFilter(id int) error { - return ErrSockFilterNotSupport + return nil }