linux/samples/bpf/xdp_tx_iptunnel_kern.c
<<
>>
Prefs
   1/* Copyright (c) 2016 Facebook
   2 *
   3 * This program is free software; you can redistribute it and/or
   4 * modify it under the terms of version 2 of the GNU General Public
   5 * License as published by the Free Software Foundation.
   6 *
   7 * This program shows how to use bpf_xdp_adjust_head() by
   8 * encapsulating the incoming packet in an IPv4/v6 header
   9 * and then XDP_TX it out.
  10 */
  11#define KBUILD_MODNAME "foo"
  12#include <uapi/linux/bpf.h>
  13#include <linux/in.h>
  14#include <linux/if_ether.h>
  15#include <linux/if_packet.h>
  16#include <linux/if_vlan.h>
  17#include <linux/ip.h>
  18#include <linux/ipv6.h>
  19#include "bpf_helpers.h"
  20#include "xdp_tx_iptunnel_common.h"
  21
  22struct bpf_map_def SEC("maps") rxcnt = {
  23        .type = BPF_MAP_TYPE_PERCPU_ARRAY,
  24        .key_size = sizeof(__u32),
  25        .value_size = sizeof(__u64),
  26        .max_entries = 256,
  27};
  28
  29struct bpf_map_def SEC("maps") vip2tnl = {
  30        .type = BPF_MAP_TYPE_HASH,
  31        .key_size = sizeof(struct vip),
  32        .value_size = sizeof(struct iptnl_info),
  33        .max_entries = MAX_IPTNL_ENTRIES,
  34};
  35
  36static __always_inline void count_tx(u32 protocol)
  37{
  38        u64 *rxcnt_count;
  39
  40        rxcnt_count = bpf_map_lookup_elem(&rxcnt, &protocol);
  41        if (rxcnt_count)
  42                *rxcnt_count += 1;
  43}
  44
  45static __always_inline int get_dport(void *trans_data, void *data_end,
  46                                     u8 protocol)
  47{
  48        struct tcphdr *th;
  49        struct udphdr *uh;
  50
  51        switch (protocol) {
  52        case IPPROTO_TCP:
  53                th = (struct tcphdr *)trans_data;
  54                if (th + 1 > data_end)
  55                        return -1;
  56                return th->dest;
  57        case IPPROTO_UDP:
  58                uh = (struct udphdr *)trans_data;
  59                if (uh + 1 > data_end)
  60                        return -1;
  61                return uh->dest;
  62        default:
  63                return 0;
  64        }
  65}
  66
  67static __always_inline void set_ethhdr(struct ethhdr *new_eth,
  68                                       const struct ethhdr *old_eth,
  69                                       const struct iptnl_info *tnl,
  70                                       __be16 h_proto)
  71{
  72        memcpy(new_eth->h_source, old_eth->h_dest, sizeof(new_eth->h_source));
  73        memcpy(new_eth->h_dest, tnl->dmac, sizeof(new_eth->h_dest));
  74        new_eth->h_proto = h_proto;
  75}
  76
  77static __always_inline int handle_ipv4(struct xdp_md *xdp)
  78{
  79        void *data_end = (void *)(long)xdp->data_end;
  80        void *data = (void *)(long)xdp->data;
  81        struct iptnl_info *tnl;
  82        struct ethhdr *new_eth;
  83        struct ethhdr *old_eth;
  84        struct iphdr *iph = data + sizeof(struct ethhdr);
  85        u16 *next_iph_u16;
  86        u16 payload_len;
  87        struct vip vip = {};
  88        int dport;
  89        u32 csum = 0;
  90        int i;
  91
  92        if (iph + 1 > data_end)
  93                return XDP_DROP;
  94
  95        dport = get_dport(iph + 1, data_end, iph->protocol);
  96        if (dport == -1)
  97                return XDP_DROP;
  98
  99        vip.protocol = iph->protocol;
 100        vip.family = AF_INET;
 101        vip.daddr.v4 = iph->daddr;
 102        vip.dport = dport;
 103        payload_len = ntohs(iph->tot_len);
 104
 105        tnl = bpf_map_lookup_elem(&vip2tnl, &vip);
 106        /* It only does v4-in-v4 */
 107        if (!tnl || tnl->family != AF_INET)
 108                return XDP_PASS;
 109
 110        /* The vip key is found.  Add an IP header and send it out */
 111
 112        if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct iphdr)))
 113                return XDP_DROP;
 114
 115        data = (void *)(long)xdp->data;
 116        data_end = (void *)(long)xdp->data_end;
 117
 118        new_eth = data;
 119        iph = data + sizeof(*new_eth);
 120        old_eth = data + sizeof(*iph);
 121
 122        if (new_eth + 1 > data_end ||
 123            old_eth + 1 > data_end ||
 124            iph + 1 > data_end)
 125                return XDP_DROP;
 126
 127        set_ethhdr(new_eth, old_eth, tnl, htons(ETH_P_IP));
 128
 129        iph->version = 4;
 130        iph->ihl = sizeof(*iph) >> 2;
 131        iph->frag_off = 0;
 132        iph->protocol = IPPROTO_IPIP;
 133        iph->check = 0;
 134        iph->tos = 0;
 135        iph->tot_len = htons(payload_len + sizeof(*iph));
 136        iph->daddr = tnl->daddr.v4;
 137        iph->saddr = tnl->saddr.v4;
 138        iph->ttl = 8;
 139
 140        next_iph_u16 = (u16 *)iph;
 141#pragma clang loop unroll(full)
 142        for (i = 0; i < sizeof(*iph) >> 1; i++)
 143                csum += *next_iph_u16++;
 144
 145        iph->check = ~((csum & 0xffff) + (csum >> 16));
 146
 147        count_tx(vip.protocol);
 148
 149        return XDP_TX;
 150}
 151
 152static __always_inline int handle_ipv6(struct xdp_md *xdp)
 153{
 154        void *data_end = (void *)(long)xdp->data_end;
 155        void *data = (void *)(long)xdp->data;
 156        struct iptnl_info *tnl;
 157        struct ethhdr *new_eth;
 158        struct ethhdr *old_eth;
 159        struct ipv6hdr *ip6h = data + sizeof(struct ethhdr);
 160        __u16 payload_len;
 161        struct vip vip = {};
 162        int dport;
 163
 164        if (ip6h + 1 > data_end)
 165                return XDP_DROP;
 166
 167        dport = get_dport(ip6h + 1, data_end, ip6h->nexthdr);
 168        if (dport == -1)
 169                return XDP_DROP;
 170
 171        vip.protocol = ip6h->nexthdr;
 172        vip.family = AF_INET6;
 173        memcpy(vip.daddr.v6, ip6h->daddr.s6_addr32, sizeof(vip.daddr));
 174        vip.dport = dport;
 175        payload_len = ip6h->payload_len;
 176
 177        tnl = bpf_map_lookup_elem(&vip2tnl, &vip);
 178        /* It only does v6-in-v6 */
 179        if (!tnl || tnl->family != AF_INET6)
 180                return XDP_PASS;
 181
 182        /* The vip key is found.  Add an IP header and send it out */
 183
 184        if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct ipv6hdr)))
 185                return XDP_DROP;
 186
 187        data = (void *)(long)xdp->data;
 188        data_end = (void *)(long)xdp->data_end;
 189
 190        new_eth = data;
 191        ip6h = data + sizeof(*new_eth);
 192        old_eth = data + sizeof(*ip6h);
 193
 194        if (new_eth + 1 > data_end ||
 195            old_eth + 1 > data_end ||
 196            ip6h + 1 > data_end)
 197                return XDP_DROP;
 198
 199        set_ethhdr(new_eth, old_eth, tnl, htons(ETH_P_IPV6));
 200
 201        ip6h->version = 6;
 202        ip6h->priority = 0;
 203        memset(ip6h->flow_lbl, 0, sizeof(ip6h->flow_lbl));
 204        ip6h->payload_len = htons(ntohs(payload_len) + sizeof(*ip6h));
 205        ip6h->nexthdr = IPPROTO_IPV6;
 206        ip6h->hop_limit = 8;
 207        memcpy(ip6h->saddr.s6_addr32, tnl->saddr.v6, sizeof(tnl->saddr.v6));
 208        memcpy(ip6h->daddr.s6_addr32, tnl->daddr.v6, sizeof(tnl->daddr.v6));
 209
 210        count_tx(vip.protocol);
 211
 212        return XDP_TX;
 213}
 214
 215SEC("xdp_tx_iptunnel")
 216int _xdp_tx_iptunnel(struct xdp_md *xdp)
 217{
 218        void *data_end = (void *)(long)xdp->data_end;
 219        void *data = (void *)(long)xdp->data;
 220        struct ethhdr *eth = data;
 221        __u16 h_proto;
 222
 223        if (eth + 1 > data_end)
 224                return XDP_DROP;
 225
 226        h_proto = eth->h_proto;
 227
 228        if (h_proto == htons(ETH_P_IP))
 229                return handle_ipv4(xdp);
 230        else if (h_proto == htons(ETH_P_IPV6))
 231
 232                return handle_ipv6(xdp);
 233        else
 234                return XDP_PASS;
 235}
 236
 237char _license[] SEC("license") = "GPL";
 238