code:
commands_for_packet03_user_script.txt
sudo ./xdp_prog_user -d right -r left --src-mac f2:7d:79:9a:77:6f --dest-mac 26:cf:a1:79:bc:86
sudo ./xdp_prog_user -d left -r right --src-mac 26:cf:a1:79:bc:86 --dest-mac f2:7d:79:9a:77:6f
xdp_prog_kern.c
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/bpf.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// The parsing helper functions from the packet01 lesson have moved here
#include "../common/parsing_helpers.h"
//#include "../common/rewrite_helpers.h"
/* Defines xdp_stats_map */
#include "../common/xdp_stats_kern_user.h"
#include "../common/xdp_stats_kern.h"
#ifndef memcpy
#define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n))
#endif
#define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF)
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__type(key, int);
__type(value, int);
__uint(max_entries, 256);
} tx_port SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, unsigned char[ETH_ALEN]);
__type(value, unsigned char[ETH_ALEN]);
__uint(max_entries, 1);
} redirect_params SEC(".maps");
static __always_inline void swap_src_dst_mac(struct ethhdr *eth)
{
/* Assignment 1: swap source and destination addresses in the eth.
* For simplicity you can use the memcpy macro defined above */
__u8 h_tmp[ETH_ALEN];
__builtin_memcpy(h_tmp, eth->h_source, ETH_ALEN);
__builtin_memcpy(eth->h_source, eth->h_dest, ETH_ALEN);
__builtin_memcpy(eth->h_dest, h_tmp, ETH_ALEN);
}
static __always_inline void swap_src_dst_ipv6(struct ipv6hdr *ipv6)
{
/* Assignment 1: swap source and destination addresses in the iphv6dr */
struct in6_addr tmp = ipv6->saddr;
ipv6->saddr = ipv6->daddr;
ipv6->daddr = tmp;
}
static __always_inline void swap_src_dst_ipv4(struct iphdr *iphdr)
{
/* Assignment 1: swap source and destination addresses in the iphdr */
__be32 tmp = iphdr->saddr;
iphdr->saddr = iphdr->daddr;
iphdr->daddr = tmp;
}
static __always_inline __u16 csum_fold_helper(__u32 csum)
{
__u32 sum;
sum = (csum >> 16) + (csum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
static __always_inline __u16 icmp_checksum_diff(
__u16 seed,
struct icmphdr_common *icmphdr_new,
struct icmphdr_common *icmphdr_old)
{
__u32 csum, size = sizeof(struct icmphdr_common);
csum = bpf_csum_diff((__be32 *)icmphdr_old, size, (__be32 *)icmphdr_new, size, seed);
return csum_fold_helper(csum);
}
/* Implement packet03/assignment-1 in this section */
SEC("xdp")
int xdp_icmp_echo_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct hdr_cursor nh;
struct ethhdr *eth;
int eth_type;
int ip_type;
int icmp_type;
struct iphdr *iphdr;
struct ipv6hdr *ipv6hdr;
__u16 echo_reply, old_csum;
struct icmphdr_common *icmphdr;
struct icmphdr_common icmphdr_old;
__u32 action = XDP_PASS;
/* These keep track of the next header type and iterator pointer */
nh.pos = data;
/* Parse Ethernet and IP/IPv6 headers */
eth_type = parse_ethhdr(&nh, data_end, ð);
if (eth_type == bpf_htons(ETH_P_IP)) {
ip_type = parse_iphdr(&nh, data_end, &iphdr);
if (ip_type != IPPROTO_ICMP)
goto out;
} else if (eth_type == bpf_htons(ETH_P_IPV6)) {
ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr);
if (ip_type != IPPROTO_ICMPV6)
goto out;
} else {
goto out;
}
/*
* We are using a special parser here which returns a stucture
* containing the "protocol-independent" part of an ICMP or ICMPv6
* header. For purposes of this Assignment we are not interested in
* the rest of the structure.
*/
icmp_type = parse_icmphdr_common(&nh, data_end, &icmphdr);
if (eth_type == bpf_htons(ETH_P_IP) && icmp_type == ICMP_ECHO) {
/* Swap IP source and destination */
swap_src_dst_ipv4(iphdr);
echo_reply = ICMP_ECHOREPLY;
} else if (eth_type == bpf_htons(ETH_P_IPV6)
&& icmp_type == ICMPV6_ECHO_REQUEST) {
/* Swap IPv6 source and destination */
swap_src_dst_ipv6(ipv6hdr);
echo_reply = ICMPV6_ECHO_REPLY;
} else {
goto out;
}
/* Swap Ethernet source and destination */
swap_src_dst_mac(eth);
/* Assignment 1: patch the packet and update the checksum. You can use
* the echo_reply variable defined above to fix the ICMP Type field. */
icmphdr->type = echo_reply;
icmphdr->code = 25;
old_csum = icmphdr->cksum;
icmphdr->cksum = 0;
icmphdr_old = *icmphdr;
icmphdr->type = echo_reply;
icmphdr->cksum = icmp_checksum_diff(~old_csum, icmphdr, &icmphdr_old);
bpf_printk("echo_reply: %d", echo_reply);
action = XDP_TX;
out:
return xdp_stats_record_action(ctx, action);
}
/* Assignment 2 */
SEC("xdp")
int xdp_redirect_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct hdr_cursor nh;
struct ethhdr *eth;
int eth_type;
int action = XDP_PASS;
unsigned char dst[ETH_ALEN] = {0x3e, 0x84, 0xde, 0xed, 0x84, 0x0c}; /* Assignment 2: fill in with the MAC address of the left inner interface */
unsigned ifindex = 5; /* Assignment 2: fill in with the ifindex of the left interface */
/* These keep track of the next header type and iterator pointer */
nh.pos = data;
/* Parse Ethernet and IP/IPv6 headers */
eth_type = parse_ethhdr(&nh, data_end, ð);
if (eth_type == -1)
goto out;
/* Assignment 2: set a proper destination address and call the
* bpf_redirect() with proper parameters, action = bpf_redirect(...) */
/* Set a proper destination address */
memcpy(eth->h_dest, dst, ETH_ALEN);
action = bpf_redirect(ifindex, 0);
out:
return xdp_stats_record_action(ctx, action);
}
/* Assignment 3: nothing to do here, patch the xdp_prog_user.c program */
SEC("xdp")
int xdp_redirect_map_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct hdr_cursor nh;
struct ethhdr *eth;
int eth_type;
int action = XDP_PASS;
unsigned char *dst;
/* These keep track of the next header type and iterator pointer */
nh.pos = data;
/* Parse Ethernet and IP/IPv6 headers */
eth_type = parse_ethhdr(&nh, data_end, ð);
if (eth_type == -1)
goto out;
/* Do we know where to redirect this packet? */
dst = bpf_map_lookup_elem(&redirect_params, eth->h_source);
if (!dst)
goto out;
/* Set a proper destination address */
memcpy(eth->h_dest, dst, ETH_ALEN);
action = bpf_redirect_map(&tx_port, 0, 0);
out:
return xdp_stats_record_action(ctx, action);
}
/* from include/net/ip.h */
static __always_inline int ip_decrease_ttl(struct iphdr *iph)
{
/* Assignment 4: see samples/bpf/xdp_fwd_kern.c from the kernel */
return --iph->ttl;
}
/* Solution to packet03/assignment-4 */
SEC("xdp")
int xdp_router_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct bpf_fib_lookup fib_params = {};
struct ethhdr *eth = data;
struct ipv6hdr *ip6h;
struct iphdr *iph;
__u16 h_proto;
__u64 nh_off;
int rc;
int action = XDP_PASS;
nh_off = sizeof(*eth);
if (data + nh_off > data_end) {
action = XDP_DROP;
goto out;
}
h_proto = eth->h_proto;
if (h_proto == bpf_htons(ETH_P_IP)) {
iph = data + nh_off;
if (iph + 1 > data_end) {
action = XDP_DROP;
goto out;
}
if (iph->ttl <= 1)
goto out;
fib_params.family = AF_INET;
fib_params.tos = iph->tos;
fib_params.l4_protocol = iph->protocol;
fib_params.sport = 0;
fib_params.dport = 0;
fib_params.tot_len = bpf_ntohs(iph->tot_len);
fib_params.ipv4_src = iph->saddr;
fib_params.ipv4_dst = iph->daddr;
} else if (h_proto == bpf_htons(ETH_P_IPV6)) {
struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src;
struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst;
ip6h = data + nh_off;
if (ip6h + 1 > data_end) {
action = XDP_DROP;
goto out;
}
if (ip6h->hop_limit <= 1)
goto out;
fib_params.family = AF_INET6;
fib_params.flowinfo = *(__be32 *) ip6h & IPV6_FLOWINFO_MASK;
fib_params.l4_protocol = ip6h->nexthdr;
fib_params.sport = 0;
fib_params.dport = 0;
fib_params.tot_len = bpf_ntohs(ip6h->payload_len);
*src = ip6h->saddr;
*dst = ip6h->daddr;
} else {
goto out;
}
fib_params.ifindex = ctx->ingress_ifindex;
rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0);
switch (rc) {
case BPF_FIB_LKUP_RET_SUCCESS: /* lookup successful */
if (h_proto == bpf_htons(ETH_P_IP))
ip_decrease_ttl(iph);
else if (h_proto == bpf_htons(ETH_P_IPV6))
ip6h->hop_limit--;
memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN);
memcpy(eth->h_source, fib_params.smac, ETH_ALEN);
action = bpf_redirect(fib_params.ifindex, 0);
break;
case BPF_FIB_LKUP_RET_BLACKHOLE: /* dest is blackholed; can be dropped */
case BPF_FIB_LKUP_RET_UNREACHABLE: /* dest is unreachable; can be dropped */
case BPF_FIB_LKUP_RET_PROHIBIT: /* dest not allowed; can be dropped */
action = XDP_DROP;
break;
case BPF_FIB_LKUP_RET_NOT_FWDED: /* packet is not forwarded */
case BPF_FIB_LKUP_RET_FWD_DISABLED: /* fwding is not enabled on ingress */
case BPF_FIB_LKUP_RET_UNSUPP_LWT: /* fwd requires encapsulation */
case BPF_FIB_LKUP_RET_NO_NEIGH: /* no neighbor entry for nh */
case BPF_FIB_LKUP_RET_FRAG_NEEDED: /* fragmentation required to fwd */
/* PASS */
break;
}
out:
return xdp_stats_record_action(ctx, action);
}
#if 0
/* Assignment 4: Complete this router program */
SEC("xdp")
int xdp_router_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct bpf_fib_lookup fib_params = {};
struct ethhdr *eth = data;
struct ipv6hdr *ip6h;
struct iphdr *iph;
__u16 h_proto;
__u64 nh_off;
int rc;
int action = XDP_PASS;
nh_off = sizeof(*eth);
if (data + nh_off > data_end) {
action = XDP_DROP;
goto out;
}
h_proto = eth->h_proto;
if (h_proto == bpf_htons(ETH_P_IP)) {
iph = data + nh_off;
if (iph + 1 > data_end) {
action = XDP_DROP;
goto out;
}
if (iph->ttl <= 1)
goto out;
/* Assignment 4: fill the fib_params structure for the AF_INET case */
} else if (h_proto == bpf_htons(ETH_P_IPV6)) {
/* These pointers can be used to assign structures instead of executing memcpy: */
/* struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src; */
/* struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst; */
ip6h = data + nh_off;
if (ip6h + 1 > data_end) {
action = XDP_DROP;
goto out;
}
if (ip6h->hop_limit <= 1)
goto out;
/* Assignment 4: fill the fib_params structure for the AF_INET6 case */
} else {
goto out;
}
fib_params.ifindex = ctx->ingress_ifindex;
rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0);
switch (rc) {
case BPF_FIB_LKUP_RET_SUCCESS: /* lookup successful */
if (h_proto == bpf_htons(ETH_P_IP))
ip_decrease_ttl(iph);
else if (h_proto == bpf_htons(ETH_P_IPV6))
ip6h->hop_limit--;
/* Assignment 4: fill in the eth destination and source
* addresses and call the bpf_redirect function */
/* memcpy(eth->h_dest, ???, ETH_ALEN); */
/* memcpy(eth->h_source, ???, ETH_ALEN); */
/* action = bpf_redirect(???, 0); */
break;
case BPF_FIB_LKUP_RET_BLACKHOLE: /* dest is blackholed; can be dropped */
case BPF_FIB_LKUP_RET_UNREACHABLE: /* dest is unreachable; can be dropped */
case BPF_FIB_LKUP_RET_PROHIBIT: /* dest not allowed; can be dropped */
action = XDP_DROP;
break;
case BPF_FIB_LKUP_RET_NOT_FWDED: /* packet is not forwarded */
case BPF_FIB_LKUP_RET_FWD_DISABLED: /* fwding is not enabled on ingress */
case BPF_FIB_LKUP_RET_UNSUPP_LWT: /* fwd requires encapsulation */
case BPF_FIB_LKUP_RET_NO_NEIGH: /* no neighbor entry for nh */
case BPF_FIB_LKUP_RET_FRAG_NEEDED: /* fragmentation required to fwd */
/* PASS */
break;
}
out:
return xdp_stats_record_action(ctx, action);
}
#endif
SEC("xdp")
int xdp_pass_func(struct xdp_md *ctx)
{
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
xdp_prog_user.c
/* SPDX-License-Identifier: GPL-2.0 */
static const char *__doc__ = "XDP redirect helper\n"
" - Allows to populate/query tx_port and redirect_params maps\n";
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <locale.h>
#include <unistd.h>
#include <time.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_link.h> /* depend on kernel-headers installed */
#include "../common/common_params.h"
#include "../common/common_libbpf.h"
#include "../common/common_user_bpf_xdp.h"
#include "../common/xdp_stats_kern_user.h"
#define PATH_MAX 4096
int open_bpf_map_file(const char *pin_dir,
const char *mapname,
struct bpf_map_info *info)
{
char filename[PATH_MAX];
int err, len, fd;
__u32 info_len = sizeof(*info);
len = snprintf(filename, PATH_MAX, "%s/%s", pin_dir, mapname);
if (len < 0) {
fprintf(stderr, "ERR: constructing full mapname path\n");
return -1;
}
fd = bpf_obj_get(filename);
if (fd < 0) {
fprintf(stderr,
"WARN: Failed to open bpf map file:%s err(%d):%s\n",
filename, errno, strerror(errno));
return fd;
}
if (info) {
err = bpf_obj_get_info_by_fd(fd, info, &info_len);
if (err) {
fprintf(stderr, "ERR: %s() can't get info - %s\n",
__func__, strerror(errno));
return EXIT_FAIL_BPF;
}
}
return fd;
}
static const struct option_wrapper long_options[] = {
{{"help", no_argument, NULL, 'h' },
"Show help", false},
{{"dev", required_argument, NULL, 'd' },
"Operate on device <ifname>", "<ifname>", true},
{{"redirect-dev", required_argument, NULL, 'r' },
"Redirect to device <ifname>", "<ifname>", true},
{{"src-mac", required_argument, NULL, 'L' },
"Source MAC address of <dev>", "<mac>", true },
{{"dest-mac", required_argument, NULL, 'R' },
"Destination MAC address of <redirect-dev>", "<mac>", true },
{{"quiet", no_argument, NULL, 'q' },
"Quiet mode (no output)"},
{{0, 0, NULL, 0 }, NULL, false}
};
static int parse_u8(char *str, unsigned char *x)
{
unsigned long z;
z = strtoul(str, 0, 16);
if (z > 0xff)
return -1;
if (x)
*x = z;
return 0;
}
static int parse_mac(char *str, unsigned char mac[ETH_ALEN])
{
if (parse_u8(str, &mac[0]) < 0)
return -1;
if (parse_u8(str + 3, &mac[1]) < 0)
return -1;
if (parse_u8(str + 6, &mac[2]) < 0)
return -1;
if (parse_u8(str + 9, &mac[3]) < 0)
return -1;
if (parse_u8(str + 12, &mac[4]) < 0)
return -1;
if (parse_u8(str + 15, &mac[5]) < 0)
return -1;
return 0;
}
static int write_iface_params(int map_fd, unsigned char *src, unsigned char *dest)
{
if (bpf_map_update_elem(map_fd, src, dest, 0) < 0) {
fprintf(stderr,
"WARN: Failed to update bpf map file: err(%d):%s\n",
errno, strerror(errno));
return -1;
}
printf("forward: %02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x\n",
src[0], src[1], src[2], src[3], src[4], src[5],
dest[0], dest[1], dest[2], dest[3], dest[4], dest[5]
);
return 0;
}
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
const char *pin_basedir = "/sys/fs/bpf";
int main(int argc, char **argv)
{
int i;
int len;
int map_fd;
bool redirect_map;
char pin_dir[PATH_MAX];
unsigned char src[ETH_ALEN];
unsigned char dest[ETH_ALEN];
struct config cfg = {
.ifindex = -1,
.redirect_ifindex = -1,
};
/* Cmdline options can change progname */
parse_cmdline_args(argc, argv, long_options, &cfg, __doc__);
redirect_map = (cfg.ifindex > 0) && (cfg.redirect_ifindex > 0);
if (cfg.redirect_ifindex > 0 && cfg.ifindex == -1) {
fprintf(stderr, "ERR: required option --dev missing\n\n");
usage(argv[0], __doc__, long_options, (argc == 1));
return EXIT_FAIL_OPTION;
}
len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname);
if (len < 0) {
fprintf(stderr, "ERR: creating pin dirname\n");
return EXIT_FAIL_OPTION;
}
if (parse_mac(cfg.src_mac, src) < 0) {
fprintf(stderr, "ERR: can't parse mac address %s\n", cfg.src_mac);
return EXIT_FAIL_OPTION;
}
if (parse_mac(cfg.dest_mac, dest) < 0) {
fprintf(stderr, "ERR: can't parse mac address %s\n", cfg.dest_mac);
return EXIT_FAIL_OPTION;
}
/* Open the tx_port map corresponding to the cfg.ifname interface */
map_fd = open_bpf_map_file(pin_dir, "tx_port", NULL);
if (map_fd < 0) {
return EXIT_FAIL_BPF;
}
printf("map dir: %s\n", pin_dir);
if (redirect_map) {
/* setup a virtual port for the static redirect */
i = 0;
bpf_map_update_elem(map_fd, &i, &cfg.redirect_ifindex, 0);
printf("redirect from ifnum=%d to ifnum=%d\n", cfg.ifindex, cfg.redirect_ifindex);
/* Open the redirect_params map */
map_fd = open_bpf_map_file(pin_dir, "redirect_params", NULL);
if (map_fd < 0) {
return EXIT_FAIL_BPF;
}
/* Setup the mapping containing MAC addresses */
if (write_iface_params(map_fd, src, dest) < 0) {
fprintf(stderr, "can't write iface params\n");
return 1;
}
}
return EXIT_OK;
}