aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/receive.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/receive.c')
-rw-r--r--src/receive.c301
1 files changed, 301 insertions, 0 deletions
diff --git a/src/receive.c b/src/receive.c
new file mode 100644
index 0000000..293b764
--- /dev/null
+++ b/src/receive.c
@@ -0,0 +1,301 @@
+/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+#include "wireguard.h"
+#include "packets.h"
+#include "device.h"
+#include "timers.h"
+#include "messages.h"
+#include "cookie.h"
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+
+static inline void rx_stats(struct wireguard_peer *peer, size_t len)
+{
+ struct pcpu_sw_netstats *tstats = get_cpu_ptr(netdev_pub(peer->device)->tstats);
+ u64_stats_update_begin(&tstats->syncp);
+ tstats->rx_bytes += len;
+ ++tstats->rx_packets;
+ u64_stats_update_end(&tstats->syncp);
+ put_cpu_ptr(tstats);
+ peer->rx_bytes += len;
+}
+
+static inline void update_latest_addr(struct wireguard_peer *peer, struct sk_buff *skb)
+{
+ struct sockaddr_storage addr = { 0 };
+ if (!socket_addr_from_skb(&addr, skb))
+ socket_set_peer_addr(peer, &addr);
+}
+
+static inline int skb_data_offset(struct sk_buff *skb, size_t *data_offset, size_t *data_len)
+{
+ struct udphdr *udp;
+#ifdef DEBUG
+ struct sockaddr_storage addr = { 0 };
+ socket_addr_from_skb(&addr, skb);
+#else
+ static const u8 addr;
+#endif
+
+ if (unlikely(skb->len < sizeof(struct iphdr)))
+ return -EINVAL;
+ if (unlikely(ip_hdr(skb)->version != 4 && ip_hdr(skb)->version != 6))
+ return -EINVAL;
+ if (unlikely(ip_hdr(skb)->version == 6 && skb->len < sizeof(struct ipv6hdr)))
+ return -EINVAL;
+
+ udp = udp_hdr(skb);
+ *data_offset = (u8 *)udp - skb->data;
+ if (*data_offset + sizeof(struct udphdr) > skb->len) {
+ net_dbg_ratelimited("Packet isn't big enough to have UDP fields from %pISpfsc\n", &addr);
+ return -EINVAL;
+ }
+ *data_len = ntohs(udp->len);
+ if (*data_len < sizeof(struct udphdr)) {
+ net_dbg_ratelimited("UDP packet is reporting too small of a size from %pISpfsc\n", &addr);
+ return -EINVAL;
+ }
+ if (*data_len > skb->len - *data_offset) {
+ net_dbg_ratelimited("UDP packet is lying about its size from %pISpfsc\n", &addr);
+ return -EINVAL;
+ }
+ *data_len -= sizeof(struct udphdr);
+ *data_offset = (u8 *)udp + sizeof(struct udphdr) - skb->data;
+ if (!pskb_may_pull(skb, *data_offset + sizeof(struct message_header))) {
+ net_dbg_ratelimited("Could not pull header into data section from %pISpfsc\n", &addr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void receive_handshake_packet(struct wireguard_device *wg, void *data, size_t len, struct sk_buff *skb)
+{
+ struct wireguard_peer *peer = NULL;
+ enum message_type message_type;
+ bool under_load;
+ enum cookie_mac_state mac_state;
+ bool packet_needs_cookie;
+
+#ifdef DEBUG
+ struct sockaddr_storage addr = { 0 };
+ socket_addr_from_skb(&addr, skb);
+#else
+ static const u8 addr;
+#endif
+
+ message_type = message_determine_type(data, len);
+
+ if (message_type == MESSAGE_HANDSHAKE_COOKIE) {
+ net_dbg_ratelimited("Receiving cookie response from %pISpfsc\n", &addr);
+ cookie_message_consume(data, wg);
+ return;
+ }
+
+ under_load = skb_queue_len(&wg->incoming_handshakes) >= MAX_QUEUED_HANDSHAKES / 2;
+ mac_state = cookie_validate_packet(&wg->cookie_checker, skb, data, len, under_load);
+ if ((under_load && mac_state == VALID_MAC_WITH_COOKIE) || (!under_load && mac_state == VALID_MAC_BUT_NO_COOKIE))
+ packet_needs_cookie = false;
+ else if (under_load && mac_state == VALID_MAC_BUT_NO_COOKIE)
+ packet_needs_cookie = true;
+ else {
+ net_dbg_ratelimited("Invalid MAC of handshake, dropping packet from %pISpfsc\n", &addr);
+ return;
+ }
+
+ switch (message_type) {
+ case MESSAGE_HANDSHAKE_INITIATION:
+ if (packet_needs_cookie) {
+ struct message_handshake_initiation *message = data;
+ packet_send_handshake_cookie(wg, skb, message, sizeof(*message), message->sender_index);
+ return;
+ }
+ peer = noise_handshake_consume_initiation(data, wg);
+ if (unlikely(!peer)) {
+ net_dbg_ratelimited("Invalid handshake initiation from %pISpfsc\n", &addr);
+ return;
+ }
+ net_dbg_ratelimited("Receiving handshake initiation from peer %Lu (%pISpfsc)\n", peer->internal_id, &addr);
+ update_latest_addr(peer, skb);
+ packet_send_handshake_response(peer);
+ break;
+ case MESSAGE_HANDSHAKE_RESPONSE:
+ if (packet_needs_cookie) {
+ struct message_handshake_response *message = data;
+ packet_send_handshake_cookie(wg, skb, message, sizeof(*message), message->sender_index);
+ return;
+ }
+ peer = noise_handshake_consume_response(data, wg);
+ if (unlikely(!peer)) {
+ net_dbg_ratelimited("Invalid handshake response from %pISpfsc\n", &addr);
+ return;
+ }
+ net_dbg_ratelimited("Receiving handshake response from peer %Lu (%pISpfsc)\n", peer->internal_id, &addr);
+ if (noise_handshake_begin_session(&peer->handshake, &peer->keypairs, true)) {
+ timers_ephemeral_key_created(peer);
+ timers_handshake_complete(peer);
+ packet_send_queue(peer);
+ }
+ break;
+ default:
+ net_err_ratelimited("Somehow a wrong type of packet wound up in the handshake queue from %pISpfsc!\n", &addr);
+ BUG();
+ return;
+ }
+
+ BUG_ON(!peer);
+
+ rx_stats(peer, len);
+ timers_any_authorized_packet_received(peer);
+ update_latest_addr(peer, skb);
+ peer_put(peer);
+}
+
+void packet_process_queued_handshake_packets(struct work_struct *work)
+{
+ struct wireguard_device *wg = container_of(work, struct wireguard_device, incoming_handshakes_work);
+ struct sk_buff *skb;
+ size_t len, offset;
+ size_t num_processed = 0;
+
+ while ((skb = skb_dequeue(&wg->incoming_handshakes)) != NULL) {
+ if (!skb_data_offset(skb, &offset, &len))
+ receive_handshake_packet(wg, skb->data + offset, len, skb);
+ dev_kfree_skb(skb);
+ if (++num_processed == MAX_BURST_HANDSHAKES) {
+ queue_work(wg->workqueue, &wg->incoming_handshakes_work);
+ return;
+ }
+ }
+}
+
+static void receive_data_packet(struct sk_buff *skb, struct wireguard_peer *peer, struct sockaddr_storage *addr, bool used_new_key, int err)
+{
+ struct net_device *dev;
+ struct wireguard_peer *routed_peer;
+ struct wireguard_device *wg;
+
+ if (unlikely(err < 0 || !peer || !addr)) {
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ wg = peer->device;
+ dev = netdev_pub(wg);
+
+ if (unlikely(used_new_key))
+ packet_send_queue(peer);
+
+ /* A packet with length 0 is a keep alive packet */
+ if (unlikely(!skb->len)) {
+ net_dbg_ratelimited("Receiving keepalive packet from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ goto packet_processed;
+ }
+
+ if (unlikely(skb->len < sizeof(struct iphdr))) {
+ ++dev->stats.rx_errors;
+ ++dev->stats.rx_length_errors;
+ net_dbg_ratelimited("Packet missing ip header from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ goto packet_processed;
+ }
+
+ if (!pskb_may_pull(skb, 1 /* For checking the ip version below */)) {
+ ++dev->stats.rx_errors;
+ ++dev->stats.rx_length_errors;
+ net_dbg_ratelimited("Packet missing IP version from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ goto packet_processed;
+ }
+
+ skb->dev = dev;
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (ip_hdr(skb)->version == 4)
+ skb->protocol = htons(ETH_P_IP);
+ else if (ip_hdr(skb)->version == 6) {
+ if (unlikely(skb->len < sizeof(struct ipv6hdr))) {
+ ++dev->stats.rx_errors;
+ ++dev->stats.rx_length_errors;
+ net_dbg_ratelimited("Packet missing ipv6 header from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ goto packet_processed;
+ }
+ skb->protocol = htons(ETH_P_IPV6);
+ } else {
+ ++dev->stats.rx_errors;
+ ++dev->stats.rx_length_errors;
+ net_dbg_ratelimited("Packet neither ipv4 nor ipv6 from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ goto packet_processed;
+ }
+
+ timers_data_received(peer);
+
+ routed_peer = routing_table_lookup_src(&wg->peer_routing_table, skb);
+ peer_put(routed_peer); /* We don't need the extra reference. */
+
+ if (unlikely(routed_peer != peer)) {
+#ifdef DEBUG
+ struct sockaddr_storage unencrypted_addr = { 0 };
+ socket_addr_from_skb(&unencrypted_addr, skb);
+ net_dbg_ratelimited("Packet has unallowed src IP (%pISc) from peer %Lu (%pISpfsc)\n", &unencrypted_addr, peer->internal_id, addr);
+#endif
+ ++dev->stats.rx_errors;
+ ++dev->stats.rx_frame_errors;
+ goto packet_processed;
+ }
+
+ dev->last_rx = jiffies;
+ if (netif_rx(skb) == NET_RX_SUCCESS)
+ rx_stats(peer, skb->len);
+ else {
+ ++dev->stats.rx_dropped;
+ net_dbg_ratelimited("Failed to give packet to userspace from peer %Lu (%pISpfsc)\n", peer->internal_id, addr);
+ }
+ goto continue_processing;
+
+packet_processed:
+ dev_kfree_skb(skb);
+continue_processing:
+ timers_any_authorized_packet_received(peer);
+ socket_set_peer_addr(peer, addr);
+ peer_put(peer);
+}
+
+void packet_receive(struct wireguard_device *wg, struct sk_buff *skb)
+{
+ size_t len, offset;
+#ifdef DEBUG
+ struct sockaddr_storage addr = { 0 };
+ socket_addr_from_skb(&addr, skb);
+#else
+ static const u8 addr;
+#endif
+
+ if (skb_data_offset(skb, &offset, &len) < 0)
+ goto err;
+ switch (message_determine_type(skb->data + offset, len)) {
+ case MESSAGE_HANDSHAKE_INITIATION:
+ case MESSAGE_HANDSHAKE_RESPONSE:
+ case MESSAGE_HANDSHAKE_COOKIE:
+ if (skb_queue_len(&wg->incoming_handshakes) > MAX_QUEUED_HANDSHAKES) {
+ net_dbg_ratelimited("Too many handshakes queued, dropping packet from %pISpfsc\n", &addr);
+ goto err;
+ }
+ if (skb_linearize(skb) < 0) {
+ net_dbg_ratelimited("Unable to linearize handshake skb from %pISpfsc\n", &addr);
+ goto err;
+ }
+ skb_queue_tail(&wg->incoming_handshakes, skb);
+ /* Queues up a call to packet_process_queued_handshake_packets(skb): */
+ queue_work(wg->workqueue, &wg->incoming_handshakes_work);
+ break;
+ case MESSAGE_DATA:
+ packet_consume_data(skb, offset, wg, receive_data_packet);
+ break;
+ default:
+ net_dbg_ratelimited("Invalid packet from %pISpfsc\n", &addr);
+ goto err;
+ }
+ return;
+
+err:
+ dev_kfree_skb(skb);
+}