// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2019 Facebook */ #include <linux/bpf.h> #include <netinet/in.h> #include <stdbool.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #include "bpf_tcp_helpers.h" enum bpf_linum_array_idx { EGRESS_LINUM_IDX, INGRESS_LINUM_IDX, READ_SK_DST_PORT_LINUM_IDX, __NR_BPF_LINUM_ARRAY_IDX, }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, __NR_BPF_LINUM_ARRAY_IDX); __type(key, __u32); __type(value, __u32); } linum_map SEC(".maps"); struct bpf_spinlock_cnt { struct bpf_spin_lock lock; __u32 cnt; }; struct { __uint(type, BPF_MAP_TYPE_SK_STORAGE); __uint(map_flags, BPF_F_NO_PREALLOC); __type(key, int); __type(value, struct bpf_spinlock_cnt); } sk_pkt_out_cnt SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_SK_STORAGE); __uint(map_flags, BPF_F_NO_PREALLOC); __type(key, int); __type(value, struct bpf_spinlock_cnt); } sk_pkt_out_cnt10 SEC(".maps"); struct bpf_tcp_sock listen_tp = {}; struct sockaddr_in6 srv_sa6 = {}; struct bpf_tcp_sock cli_tp = {}; struct bpf_tcp_sock srv_tp = {}; struct bpf_sock listen_sk = {}; struct bpf_sock srv_sk = {}; struct bpf_sock cli_sk = {}; __u64 parent_cg_id = 0; __u64 child_cg_id = 0; __u64 lsndtime = 0; static bool is_loopback6(__u32 *a6) { return !a6[0] && !a6[1] && !a6[2] && a6[3] == bpf_htonl(1); } static void skcpy(struct bpf_sock *dst, const struct bpf_sock *src) { dst->bound_dev_if = src->bound_dev_if; dst->family = src->family; dst->type = src->type; dst->protocol = src->protocol; dst->mark = src->mark; dst->priority = src->priority; dst->src_ip4 = src->src_ip4; dst->src_ip6[0] = src->src_ip6[0]; dst->src_ip6[1] = src->src_ip6[1]; dst->src_ip6[2] = src->src_ip6[2]; dst->src_ip6[3] = src->src_ip6[3]; dst->src_port = src->src_port; dst->dst_ip4 = src->dst_ip4; dst->dst_ip6[0] = src->dst_ip6[0]; dst->dst_ip6[1] = src->dst_ip6[1]; dst->dst_ip6[2] = src->dst_ip6[2]; dst->dst_ip6[3] = src->dst_ip6[3]; dst->dst_port = src->dst_port; dst->state = src->state; } static void tpcpy(struct bpf_tcp_sock *dst, const struct bpf_tcp_sock *src) { dst->snd_cwnd = src->snd_cwnd; dst->srtt_us = src->srtt_us; dst->rtt_min = src->rtt_min; dst->snd_ssthresh = src->snd_ssthresh; dst->rcv_nxt = src->rcv_nxt; dst->snd_nxt = src->snd_nxt; dst->snd_una = src->snd_una; dst->mss_cache = src->mss_cache; dst->ecn_flags = src->ecn_flags; dst->rate_delivered = src->rate_delivered; dst->rate_interval_us = src->rate_interval_us; dst->packets_out = src->packets_out; dst->retrans_out = src->retrans_out; dst->total_retrans = src->total_retrans; dst->segs_in = src->segs_in; dst->data_segs_in = src->data_segs_in; dst->segs_out = src->segs_out; dst->data_segs_out = src->data_segs_out; dst->lost_out = src->lost_out; dst->sacked_out = src->sacked_out; dst->bytes_received = src->bytes_received; dst->bytes_acked = src->bytes_acked; } /* Always return CG_OK so that no pkt will be filtered out */ #define CG_OK 1 #define RET_LOG() ({ \ linum = __LINE__; \ bpf_map_update_elem(&linum_map, &linum_idx, &linum, BPF_ANY); \ return CG_OK; \ }) SEC("cgroup_skb/egress") int egress_read_sock_fields(struct __sk_buff *skb) { struct bpf_spinlock_cnt cli_cnt_init = { .lock = {}, .cnt = 0xeB9F }; struct bpf_spinlock_cnt *pkt_out_cnt, *pkt_out_cnt10; struct bpf_tcp_sock *tp, *tp_ret; struct bpf_sock *sk, *sk_ret; __u32 linum, linum_idx; struct tcp_sock *ktp; linum_idx = EGRESS_LINUM_IDX; sk = skb->sk; if (!sk) RET_LOG(); /* Not testing the egress traffic or the listening socket, * which are covered by the cgroup_skb/ingress test program. */ if (sk->family != AF_INET6 || !is_loopback6(sk->src_ip6) || sk->state == BPF_TCP_LISTEN) return CG_OK; if (sk->src_port == bpf_ntohs(srv_sa6.sin6_port)) { /* Server socket */ sk_ret = &srv_sk; tp_ret = &srv_tp; } else if (sk->dst_port == srv_sa6.sin6_port) { /* Client socket */ sk_ret = &cli_sk; tp_ret = &cli_tp; } else { /* Not the testing egress traffic */ return CG_OK; } /* It must be a fullsock for cgroup_skb/egress prog */ sk = bpf_sk_fullsock(sk); if (!sk) RET_LOG(); /* Not the testing egress traffic */ if (sk->protocol != IPPROTO_TCP) return CG_OK; tp = bpf_tcp_sock(sk); if (!tp) RET_LOG(); skcpy(sk_ret, sk); tpcpy(tp_ret, tp); if (sk_ret == &srv_sk) { ktp = bpf_skc_to_tcp_sock(sk); if (!ktp) RET_LOG(); lsndtime = ktp->lsndtime; child_cg_id = bpf_sk_cgroup_id(ktp); if (!child_cg_id) RET_LOG(); parent_cg_id = bpf_sk_ancestor_cgroup_id(ktp, 2); if (!parent_cg_id) RET_LOG(); /* The userspace has created it for srv sk */ pkt_out_cnt = bpf_sk_storage_get(&sk_pkt_out_cnt, ktp, 0, 0); pkt_out_cnt10 = bpf_sk_storage_get(&sk_pkt_out_cnt10, ktp, 0, 0); } else { pkt_out_cnt = bpf_sk_storage_get(&sk_pkt_out_cnt, sk, &cli_cnt_init, BPF_SK_STORAGE_GET_F_CREATE); pkt_out_cnt10 = bpf_sk_storage_get(&sk_pkt_out_cnt10, sk, &cli_cnt_init, BPF_SK_STORAGE_GET_F_CREATE); } if (!pkt_out_cnt || !pkt_out_cnt10) RET_LOG(); /* Even both cnt and cnt10 have lock defined in their BTF, * intentionally one cnt takes lock while one does not * as a test for the spinlock support in BPF_MAP_TYPE_SK_STORAGE. */ pkt_out_cnt->cnt += 1; bpf_spin_lock(&pkt_out_cnt10->lock); pkt_out_cnt10->cnt += 10; bpf_spin_unlock(&pkt_out_cnt10->lock); return CG_OK; } SEC("cgroup_skb/ingress") int ingress_read_sock_fields(struct __sk_buff *skb) { struct bpf_tcp_sock *tp; __u32 linum, linum_idx; struct bpf_sock *sk; linum_idx = INGRESS_LINUM_IDX; sk = skb->sk; if (!sk) RET_LOG(); /* Not the testing ingress traffic to the server */ if (sk->family != AF_INET6 || !is_loopback6(sk->src_ip6) || sk->src_port != bpf_ntohs(srv_sa6.sin6_port)) return CG_OK; /* Only interested in the listening socket */ if (sk->state != BPF_TCP_LISTEN) return CG_OK; /* It must be a fullsock for cgroup_skb/ingress prog */ sk = bpf_sk_fullsock(sk); if (!sk) RET_LOG(); tp = bpf_tcp_sock(sk); if (!tp) RET_LOG(); skcpy(&listen_sk, sk); tpcpy(&listen_tp, tp); return CG_OK; } /* * NOTE: 4-byte load from bpf_sock at dst_port offset is quirky. It * gets rewritten by the access converter to a 2-byte load for * backward compatibility. Treating the load result as a be16 value * makes the code portable across little- and big-endian platforms. */ static __noinline bool sk_dst_port__load_word(struct bpf_sock *sk) { __u32 *word = (__u32 *)&sk->dst_port; return word[0] == bpf_htons(0xcafe); } static __noinline bool sk_dst_port__load_half(struct bpf_sock *sk) { __u16 *half; asm volatile (""); half = (__u16 *)&sk->dst_port; return half[0] == bpf_htons(0xcafe); } static __noinline bool sk_dst_port__load_byte(struct bpf_sock *sk) { __u8 *byte = (__u8 *)&sk->dst_port; return byte[0] == 0xca && byte[1] == 0xfe; } SEC("cgroup_skb/egress") int read_sk_dst_port(struct __sk_buff *skb) { __u32 linum, linum_idx; struct bpf_sock *sk; linum_idx = READ_SK_DST_PORT_LINUM_IDX; sk = skb->sk; if (!sk) RET_LOG(); /* Ignore everything but the SYN from the client socket */ if (sk->state != BPF_TCP_SYN_SENT) return CG_OK; if (!sk_dst_port__load_word(sk)) RET_LOG(); if (!sk_dst_port__load_half(sk)) RET_LOG(); if (!sk_dst_port__load_byte(sk)) RET_LOG(); return CG_OK; } char _license[] SEC("license") = "GPL";