@@ -4,7 +4,7 @@ use socket2::{Domain, Protocol, Type};
44use std:: {
55 io,
66 mem:: MaybeUninit ,
7- net:: { IpAddr , Ipv6Addr , SocketAddr } ,
7+ net:: { IpAddr , Ipv6Addr , SocketAddr , UdpSocket } ,
88 slice,
99} ;
1010
@@ -18,20 +18,27 @@ pub struct IcmpSocket {
1818 /// actual underlying icmp socket
1919 socket : socket2:: Socket ,
2020 /// udp socket that is kept alive for avoiding duplicate port
21- _udp_socket : std:: net:: UdpSocket ,
22- /// address of udp socket same as `udp_socket.local_addr()`
23- udp_socket_addr : SocketAddr ,
21+ _udp_socket : UdpSocket ,
22+ addr : SocketAddr ,
2423}
2524
2625impl IcmpSocket {
2726 pub fn bind ( addr : & SocketAddr ) -> io:: Result < Self > {
28- let udp_socket = std :: net :: UdpSocket :: bind ( addr) ?;
27+ let udp_socket = UdpSocket :: bind ( addr) ?;
2928 let udp_socket_addr = udp_socket. local_addr ( ) ?;
3029 let socket = IcmpSocket :: inner_bind ( * addr) ?;
3130
31+ #[ cfg( target_os = "linux" ) ]
32+ {
33+ let filter = create_bfp_seq_filter ( udp_socket_addr. is_ipv6 ( ) , udp_socket_addr. port ( ) ) ;
34+ if let Err ( error) = socket. attach_filter ( & filter) {
35+ log:: warn!( "couldn't attach bpf filter: {error:?}" ) ;
36+ }
37+ }
38+
3239 Ok ( IcmpSocket {
3340 _udp_socket : udp_socket,
34- udp_socket_addr,
41+ addr : udp_socket_addr,
3542 socket,
3643 } )
3744 }
@@ -50,30 +57,29 @@ impl IcmpSocket {
5057impl SocketTrait for IcmpSocket {
5158 fn send_to ( & self , buffer : & mut [ u8 ] , to : & SocketAddr ) -> io:: Result < usize > {
5259 let buffer_with_header = unsafe { slice_sub ( buffer, ICMP_HEADER_LEN ) } ;
53- craft_icmp_packet ( buffer_with_header, & self . udp_socket_addr , to, true ) ;
60+ craft_icmp_packet ( buffer_with_header, & self . addr , to, true ) ;
5461 let mut to_addr = * to;
5562 // in linux `send_to` on icmpv6 socket requires destination port to be zero
5663 to_addr. set_port ( 0 ) ;
5764 self . socket . send_to ( buffer_with_header, & to_addr. into ( ) )
5865 }
5966
6067 fn recv_from ( & self , buffer : & mut [ u8 ] ) -> io:: Result < ( usize , SocketAddr ) > {
61- let local_addr = self . udp_socket_addr ;
62- loop {
63- let icmp_header_offset = header_offset ( local_addr. is_ipv6 ( ) ) ;
68+ let is_ipv6 = self . addr . is_ipv6 ( ) ;
69+ let icmp_header_offset = header_offset ( is_ipv6) ;
70+ // aligning in a way that the icmp payload gets written into buffer
71+ let payload_offset = icmp_header_offset + ICMP_HEADER_LEN ;
6472
65- // aligning in a way that the icmp payload gets written into buffer
66- let payload_offset = icmp_header_offset + ICMP_HEADER_LEN ;
73+ loop {
6774 let buffer_with_header = unsafe { slice_sub ( buffer, payload_offset) } ;
68-
6975 let ( size, from_addr) = self
7076 . socket
7177 . recv_from ( cast_maybe_uninit ( buffer_with_header) ) ?;
7278 let icmp_packet = & mut buffer_with_header[ icmp_header_offset..size] ;
73- let Some ( packet) = parse_icmp_packet ( icmp_packet, local_addr . is_ipv6 ( ) , false ) else {
79+ let Some ( packet) = parse_icmp_packet ( icmp_packet, is_ipv6, false ) else {
7480 continue ;
7581 } ;
76- if packet. dst_port != local_addr . port ( ) {
82+ if packet. dst_port != self . addr . port ( ) {
7783 continue ;
7884 }
7985 // doesn't panic because from_addr is either ipv6 or ipv4
@@ -85,13 +91,16 @@ impl SocketTrait for IcmpSocket {
8591 }
8692
8793 fn local_addr ( & self ) -> io:: Result < SocketAddr > {
88- Ok ( self . udp_socket_addr )
94+ Ok ( self . addr )
8995 }
9096}
9197
9298#[ derive( Debug ) ]
9399pub struct NonBlockingIcmpSocket {
94- icmp_socket : IcmpSocket ,
100+ socket : socket2:: Socket ,
101+ /// udp socket that is kept alive for avoiding duplicate port
102+ _udp_socket : UdpSocket ,
103+ addr : SocketAddr ,
95104 // we need to have a copy of connected addr because we
96105 // need it to craft packet, in ipv6 we need addr + port and
97106 // in ipv4 we need port
@@ -100,11 +109,15 @@ pub struct NonBlockingIcmpSocket {
100109
101110impl NonBlockingIcmpSocket {
102111 pub fn bind ( addr : & SocketAddr ) -> io:: Result < Self > {
103- let icmp_socket = IcmpSocket :: bind ( addr) ?;
104- icmp_socket. socket . set_nonblocking ( true ) ?;
112+ let udp_socket = UdpSocket :: bind ( addr) ?;
113+ let addr = udp_socket. local_addr ( ) ?;
114+ let socket = IcmpSocket :: inner_bind ( addr) ?;
115+ socket. set_nonblocking ( true ) ?;
105116 Ok ( Self {
106- icmp_socket ,
117+ socket ,
107118 connected_addr : None ,
119+ _udp_socket : udp_socket,
120+ addr,
108121 } )
109122 }
110123}
@@ -116,26 +129,21 @@ impl NonBlockingSocketTrait for NonBlockingIcmpSocket {
116129 . ok_or_else ( || Into :: < io:: Error > :: into ( io:: ErrorKind :: NotConnected ) ) ?;
117130 // it's safe because the main buffer has reserved bytes
118131 let buffer_with_header = unsafe { slice_sub ( buffer, ICMP_HEADER_LEN ) } ;
119- craft_icmp_packet (
120- buffer_with_header,
121- & self . icmp_socket . udp_socket_addr ,
122- & dst_addr,
123- false ,
124- ) ;
125- self . icmp_socket . socket . send ( buffer_with_header)
132+ craft_icmp_packet ( buffer_with_header, & self . addr , & dst_addr, false ) ;
133+ self . socket . send ( buffer_with_header)
126134 }
127135
128136 fn connect ( & mut self , addr : & SocketAddr ) -> io:: Result < ( ) > {
129137 self . connected_addr = Some ( * addr) ;
130138 let mut addr = * addr;
131139 // in linux icmpv6 socket requires destination port to be zero
132140 addr. set_port ( 0 ) ;
133- self . icmp_socket . socket . connect ( & addr. into ( ) ) ?;
141+ self . socket . connect ( & addr. into ( ) ) ?;
134142 Ok ( ( ) )
135143 }
136144
137145 fn local_addr ( & self ) -> io:: Result < SocketAddr > {
138- self . icmp_socket . local_addr ( )
146+ Ok ( self . addr )
139147 }
140148}
141149
@@ -241,3 +249,19 @@ fn as_ipv6(ip: &IpAddr) -> &Ipv6Addr {
241249 _ => panic ! ( ) ,
242250 }
243251}
252+
253+ // bpf filter is really useful when having multiple forwarder instances
254+ // on same kernel, by default all icmp sockets receives all packets and then
255+ // we in user mode filter them but with this the packets get filtered on kernel
256+ #[ cfg( target_os = "linux" ) ]
257+ pub fn create_bfp_seq_filter ( is_ipv6 : bool , value : u16 ) -> [ libc:: sock_filter ; 4 ] {
258+ let icmp_header_offset = header_offset ( is_ipv6) ;
259+ let offset = icmp_header_offset + 6 ;
260+ [
261+ ( 0x28 , 0 , 0 , offset as u32 ) , // ldh [offset]
262+ ( 0x15 , 0 , 1 , value as u32 ) , // jne val, drop
263+ ( 0x06 , 0 , 0 , 0xffffffff ) , // ret #-1
264+ ( 0x06 , 0 , 0 , 0000000000 ) , // drop: ret #0
265+ ]
266+ . map ( |( code, jt, jf, k) | libc:: sock_filter { code, jt, jf, k } )
267+ }
0 commit comments