Skip to content

Commit aba89d3

Browse files
committed
icmp support nat
1 parent 3667ec0 commit aba89d3

4 files changed

Lines changed: 57 additions & 18 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,3 @@ forwarder -l 0.0.0.0:1001/udp -r 127.0.0.1:1050/icmp
2525
forwarder -l 127.0.0.1:1050/icmp -r 127.0.0.1:1002/udp
2626
```
2727
![Screenshot_2024-01-19_1705683004](https://github.com/Arian8j2/forwarder/assets/56799194/bafe0681-abec-48cb-8ea7-1651d983c9e6)
28-
> [!WARNING]
29-
> UDP over ICMP currently may not work behind NAT or NAPT, because forwarder doesn't try to simulate real icmp handshake (request, reply) and only sends echo request and also the sequence and id of icmp packet is used as source and destination port to avoid further MTU issues, also i'm using forwarder only on servers so the main reason for this behavior is that.

forwarder/src/poll/icmp.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ impl Poll for IcmpPoll {
3333
let Ok(size) = socket.recv(cast_maybe_uninit(&mut buffer)) else {
3434
continue;
3535
};
36-
let Some(icmp_packet) = parse_icmp_packet(&buffer[header_offset..size], self.is_ipv6)
36+
let Some(icmp_packet) =
37+
parse_icmp_packet(&buffer[header_offset..size], self.is_ipv6, true)
3738
else {
3839
continue;
3940
};

forwarder/src/socket/icmp.rs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl IcmpSocket {
5050
impl SocketTrait for IcmpSocket {
5151
fn send_to(&self, buffer: &mut [u8], to: &SocketAddr) -> io::Result<usize> {
5252
let buffer_with_header = unsafe { slice_sub(buffer, ICMP_HEADER_LEN) };
53-
craft_icmp_packet(buffer_with_header, &self.udp_socket_addr, to);
53+
craft_icmp_packet(buffer_with_header, &self.udp_socket_addr, to, true);
5454
let mut to_addr = *to;
5555
// in linux `send_to` on icmpv6 socket requires destination port to be zero
5656
to_addr.set_port(0);
@@ -70,7 +70,7 @@ impl SocketTrait for IcmpSocket {
7070
.socket
7171
.recv_from(cast_maybe_uninit(buffer_with_header))?;
7272
let icmp_packet = &mut buffer_with_header[icmp_header_offset..size];
73-
let Some(packet) = parse_icmp_packet(icmp_packet, local_addr.is_ipv6()) else {
73+
let Some(packet) = parse_icmp_packet(icmp_packet, local_addr.is_ipv6(), false) else {
7474
continue;
7575
};
7676
if packet.dst_port != local_addr.port() {
@@ -120,6 +120,7 @@ impl NonBlockingSocketTrait for NonBlockingIcmpSocket {
120120
buffer_with_header,
121121
&self.icmp_socket.udp_socket_addr,
122122
&dst_addr,
123+
false,
123124
);
124125
self.icmp_socket.socket.send(buffer_with_header)
125126
}
@@ -140,21 +141,37 @@ impl NonBlockingSocketTrait for NonBlockingIcmpSocket {
140141

141142
fn craft_icmp_packet(
142143
buffer_with_header: &mut [u8],
143-
source_addr: &SocketAddr,
144+
src_addr: &SocketAddr,
144145
dst_addr: &SocketAddr,
146+
is_echo_reply: bool,
145147
) {
146148
let mut icmp_packet = Icmpv4Packet::new_unchecked(buffer_with_header);
147-
icmp_packet.set_echo_ident(dst_addr.port());
148-
icmp_packet.set_echo_seq_no(source_addr.port());
149+
// point of this is to make sure echo ident of request and corresponding reply
150+
// remains the same, so nat could figure out who are we talking to
151+
let (ident, seq) = if is_echo_reply {
152+
(dst_addr.port(), src_addr.port())
153+
} else {
154+
(src_addr.port(), dst_addr.port())
155+
};
156+
icmp_packet.set_echo_ident(ident);
157+
icmp_packet.set_echo_seq_no(seq);
149158
icmp_packet.set_msg_code(0);
150159

151-
if source_addr.is_ipv4() {
152-
icmp_packet.set_msg_type(Icmpv4Message::EchoRequest);
160+
if src_addr.is_ipv4() {
161+
icmp_packet.set_msg_type(if is_echo_reply {
162+
Icmpv4Message::EchoReply
163+
} else {
164+
Icmpv4Message::EchoRequest
165+
});
153166
icmp_packet.fill_checksum();
154167
} else {
155168
let mut icmp_packet = Icmpv6Packet::new_unchecked(icmp_packet.into_inner());
156-
icmp_packet.set_msg_type(Icmpv6Message::EchoRequest);
157-
icmp_packet.fill_checksum(as_ipv6(&source_addr.ip()), as_ipv6(&dst_addr.ip()));
169+
icmp_packet.set_msg_type(if is_echo_reply {
170+
Icmpv6Message::EchoReply
171+
} else {
172+
Icmpv6Message::EchoRequest
173+
});
174+
icmp_packet.fill_checksum(as_ipv6(&src_addr.ip()), as_ipv6(&dst_addr.ip()));
158175
}
159176
}
160177

@@ -163,14 +180,22 @@ pub struct IcmpPacket {
163180
pub dst_port: u16,
164181
}
165182

166-
pub fn parse_icmp_packet(packet: &[u8], is_ipv6: bool) -> Option<IcmpPacket> {
183+
pub fn parse_icmp_packet(packet: &[u8], is_ipv6: bool, is_echo_reply: bool) -> Option<IcmpPacket> {
167184
let icmp_packet = Icmpv4Packet::new_checked(packet).ok()?;
168185

169186
// we only work with icmp echo requests so if any other type of icmp
170187
// packet we receive we just ignore it
188+
// TODO: maybe always check for both reply or request
171189
let correct_type = if is_ipv6 {
172-
// icmpv6 echo request
173-
Icmpv4Message::Unknown(0x80)
190+
if is_echo_reply {
191+
// icmpv6 echo reply
192+
Icmpv4Message::Unknown(0x81)
193+
} else {
194+
// icmpv6 echo request
195+
Icmpv4Message::Unknown(0x80)
196+
}
197+
} else if is_echo_reply {
198+
Icmpv4Message::EchoReply
174199
} else {
175200
Icmpv4Message::EchoRequest
176201
};
@@ -180,8 +205,14 @@ pub fn parse_icmp_packet(packet: &[u8], is_ipv6: bool) -> Option<IcmpPacket> {
180205

181206
// icmp is on layer 3 so it has no idea about ports
182207
// we use identifier and sequence number of icmp packet as ports
183-
let dst_port = icmp_packet.echo_ident();
184-
let src_port = icmp_packet.echo_seq_no();
208+
let ident = icmp_packet.echo_ident();
209+
let seq = icmp_packet.echo_seq_no();
210+
211+
let (src_port, dst_port) = if is_echo_reply {
212+
(seq, ident)
213+
} else {
214+
(ident, seq)
215+
};
185216
Some(IcmpPacket { src_port, dst_port })
186217
}
187218

test_icmp.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
#!/bin/bash
2+
13
set -e
24

35
cargo t --no-run
46
bin_name=$(cargo t --no-run 2>&1 | grep -oP '\(\Ktarget/debug/deps/server-.+(?=\))')
57
sudo setcap cap_net_admin,cap_net_raw=eip "$bin_name"
6-
./$bin_name --nocapture --color always --ignored icmp
8+
9+
echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all &>/dev/null
10+
echo 1 | sudo tee /proc/sys/net/ipv6/icmp/echo_ignore_all &>/dev/null
11+
12+
./$bin_name --nocapture --color always --ignored icmp || echo
13+
14+
echo 0 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all &>/dev/null
15+
echo 0 | sudo tee /proc/sys/net/ipv6/icmp/echo_ignore_all &>/dev/null

0 commit comments

Comments
 (0)