From 6bf5a3c48be01eb43bfbb1ab154b8ff8cfd6016e Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 24 Feb 2019 19:27:21 +0100 Subject: net: implement ECN handling, rfc6040 style To decide whether we should use the compatibility mode or the normal mode with a peer, we use the handshake messages as a signaling channel. If we receive the expected ECN bits, it most likely means they're running a compatible version. Signed-off-by: Florent Daigniere --- src/messages.h | 2 +- src/peer.h | 1 + src/receive.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- src/send.c | 8 +++---- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/messages.h b/src/messages.h index 3cfd1c5..93dea49 100644 --- a/src/messages.h +++ b/src/messages.h @@ -123,6 +123,6 @@ enum message_alignments { #define DATA_PACKET_HEAD_ROOM \ ALIGN(sizeof(struct message_data) + SKB_HEADER_LEN, 4) -enum { HANDSHAKE_DSCP = 0x88 /* AF41, plus 00 ECN */ }; +enum { HANDSHAKE_DSCP = 0x8A /* AF41, plus 10 ECN */ }; #endif /* _WG_MESSAGES_H */ diff --git a/src/peer.h b/src/peer.h index 2e04262..06b5b1e 100644 --- a/src/peer.h +++ b/src/peer.h @@ -63,6 +63,7 @@ struct wg_peer { u64 internal_id; struct napi_struct napi; bool is_dead; + bool is_ecn_aware; }; struct wg_peer *wg_peer_create(struct wg_device *wg, diff --git a/src/receive.c b/src/receive.c index 51d06d3..8a8a7d1 100644 --- a/src/receive.c +++ b/src/receive.c @@ -153,6 +153,7 @@ static void wg_receive_handshake_packet(struct wg_device *wg, return; } wg_socket_set_peer_endpoint_from_skb(peer, skb); + peer->is_ecn_aware = INET_ECN_is_capable(PACKET_CB(skb)->ds); net_dbg_ratelimited("%s: Receiving handshake initiation from peer %llu (%pISpfsc)\n", wg->dev->name, peer->internal_id, &peer->endpoint.addr); @@ -175,6 +176,7 @@ static void wg_receive_handshake_packet(struct wg_device *wg, return; } wg_socket_set_peer_endpoint_from_skb(peer, skb); + peer->is_ecn_aware = INET_ECN_is_capable(PACKET_CB(skb)->ds); net_dbg_ratelimited("%s: Receiving handshake response from peer %llu (%pISpfsc)\n", wg->dev->name, peer->internal_id, &peer->endpoint.addr); @@ -297,6 +299,59 @@ static bool decrypt_packet(struct sk_buff *skb, struct noise_symmetric_key *key, return true; } +/* + * RFC 6040 4.2 + * To decapsulate the inner header at the tunnel egress, a compliant + * tunnel egress MUST set the outgoing ECN field to the codepoint at the + * intersection of the appropriate arriving inner header (row) and outer + * header (column) in Figure 4 + * + * +---------+------------------------------------------------+ + * |Arriving | Arriving Outer Header | + * | Inner +---------+------------+------------+------------+ + * | Header | Not-ECT | ECT(0) | ECT(1) | CE | + * +---------+---------+------------+------------+------------+ + * | Not-ECT | Not-ECT |Not-ECT(!!!)|Not-ECT(!!!)| (!!!)| + * | ECT(0) | ECT(0) | ECT(0) | ECT(1) | CE | + * | ECT(1) | ECT(1) | ECT(1) (!) | ECT(1) | CE | + * | CE | CE | CE | CE(!!!)| CE | + * +---------+---------+------------+------------+------------+ + * + * Figure 4: New IP in IP Decapsulation Behaviour + * + * returns 0 on success + * 1 if the packet should be dropped + */ +static inline int ECN_RFC6040_decapsulate(struct sk_buff *skb) +{ + __u8 outer = PACKET_CB(skb)->ds; + __u8 inner = ip_tunnel_get_dsfield(ip_hdr(skb), skb); + + switch (outer & INET_ECN_MASK) { + case INET_ECN_CE: + PACKET_CB(skb)->ds = + (inner & ~INET_ECN_MASK) | + INET_ECN_CE; + switch (inner & INET_ECN_MASK) { + case INET_ECN_NOT_ECT: + return 1; + } + return 0; + case INET_ECN_ECT_1: + switch (inner & INET_ECN_MASK) { + case INET_ECN_ECT_0: + PACKET_CB(skb)->ds = + (inner & ~INET_ECN_MASK) | + INET_ECN_ECT_1; + return 0; + } + } + + PACKET_CB(skb)->ds = inner; + + return 0; +} + /* This is RFC6479, a replay detection bitmap algorithm that avoids bitshifts */ static bool counter_validate(union noise_counter *counter, u64 their_counter) { @@ -394,13 +449,24 @@ static void wg_packet_consume_data_done(struct wg_peer *peer, len = ntohs(ip_hdr(skb)->tot_len); if (unlikely(len < sizeof(struct iphdr))) goto dishonest_packet_size; - if (INET_ECN_is_ce(PACKET_CB(skb)->ds)) - IP_ECN_set_ce(ip_hdr(skb)); + + if (ECN_RFC6040_decapsulate(skb)) { + net_dbg_ratelimited("%s: Dropping packet from peer %llu (%pISpfsc) - ECN\n", + dev->name, peer->internal_id, + &peer->endpoint.addr); + ++dev->stats.rx_dropped; + goto packet_processed; + } } else if (skb->protocol == htons(ETH_P_IPV6)) { len = ntohs(ipv6_hdr(skb)->payload_len) + sizeof(struct ipv6hdr); - if (INET_ECN_is_ce(PACKET_CB(skb)->ds)) - IP6_ECN_set_ce(skb, ipv6_hdr(skb)); + if (ECN_RFC6040_decapsulate(skb)) { + net_dbg_ratelimited("%s: Dropping packet from peer %llu (%pISpfsc) - ECN\n", + dev->name, peer->internal_id, + &peer->endpoint.addr); + ++dev->stats.rx_dropped; + goto packet_processed; + } } else { goto dishonest_packet_type; } @@ -582,6 +648,7 @@ void wg_packet_receive(struct wg_device *wg, struct sk_buff *skb) goto err; } skb_queue_tail(&wg->incoming_handshakes, skb); + PACKET_CB(skb)->ds = ip_tunnel_get_dsfield(ip_hdr(skb), skb); /* Queues up a call to packet_process_queued_handshake_ * packets(skb): */ diff --git a/src/send.c b/src/send.c index b0df5c7..11c4052 100644 --- a/src/send.c +++ b/src/send.c @@ -389,10 +389,10 @@ void wg_packet_send_staged_packets(struct wg_peer *peer) * handshake. */ skb_queue_walk(&packets, skb) { - /* 0 for no outer TOS: no leak. TODO: at some later point, we - * might consider using flowi->tos as outer instead. - */ - PACKET_CB(skb)->ds = ip_tunnel_ecn_encap(0, ip_hdr(skb), skb); + PACKET_CB(skb)->ds = ip_tunnel_get_dsfield(ip_hdr(skb), skb); + if(!peer->is_ecn_aware) { + PACKET_CB(skb)->ds &= ~INET_ECN_MASK; + } PACKET_CB(skb)->nonce = atomic64_inc_return(&key->counter.counter) - 1; if (unlikely(PACKET_CB(skb)->nonce >= REJECT_AFTER_MESSAGES)) -- cgit v1.2.3-59-g8ed1b