From 3abb86ce332d0beae388430d96096cea920dded3 Mon Sep 17 00:00:00 2001 From: Simone Magnani Date: Sat, 24 Feb 2024 13:10:50 +0100 Subject: [PATCH] simplified example by removing xdp and batch hash map delete Signed-off-by: Simone Magnani --- examples/README.md | 4 +- examples/shared_xdp_tc/bpf_bpfeb.o | Bin 7536 -> 0 bytes examples/shared_xdp_tc/bpf_bpfel.o | Bin 7536 -> 0 bytes examples/shared_xdp_tc/main.go | 155 ------------------- examples/shared_xdp_tc/xdp_tcx.c | 149 ------------------ examples/{shared_xdp_tc => tcx}/bpf_bpfeb.go | 0 examples/tcx/bpf_bpfeb.o | Bin 0 -> 7112 bytes examples/{shared_xdp_tc => tcx}/bpf_bpfel.go | 0 examples/tcx/bpf_bpfel.o | Bin 0 -> 7112 bytes examples/tcx/main.go | 120 ++++++++++++++ examples/tcx/tcx.c | 155 +++++++++++++++++++ 11 files changed, 277 insertions(+), 306 deletions(-) delete mode 100644 examples/shared_xdp_tc/bpf_bpfeb.o delete mode 100644 examples/shared_xdp_tc/bpf_bpfel.o delete mode 100644 examples/shared_xdp_tc/main.go delete mode 100644 examples/shared_xdp_tc/xdp_tcx.c rename examples/{shared_xdp_tc => tcx}/bpf_bpfeb.go (100%) create mode 100644 examples/tcx/bpf_bpfeb.o rename examples/{shared_xdp_tc => tcx}/bpf_bpfel.go (100%) create mode 100644 examples/tcx/bpf_bpfel.o create mode 100644 examples/tcx/main.go create mode 100644 examples/tcx/tcx.c diff --git a/examples/README.md b/examples/README.md index 12e6b73d7..444ff3cab 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,8 +16,8 @@ Like kprobes, but with better performance and usability, for kernels 5.5 and later. * [tcp_connect](fentry/) - Trace outgoing IPv4 TCP connections. * [tcp_close](tcprtt/) - Log RTT of IPv4 TCP connections using eBPF CO-RE helpers. -* TC and XDP - Attach a program to a network interface to process incoming (XDP) and outgoing (TC) packets. - * [shared_xdp_tc](./shared_xdp_tc/) - monitor and periodically reset the number of incoming and outgoing packets for each network flow identified with the traditional 5-tuple session identifier (IP addresses, L4 Ports, IP protocol). +* TCx - Attach a program to Linux TC (Traffic Control) to process incoming and outgoing packets. + * [tcx](./tcx/) - monitor the number of incoming and outgoing packets for each network flow identified with the traditional 5-tuple session identifier (IP addresses, L4 Ports, IP protocol). * XDP - Attach a program to a network interface to process incoming packets. * [xdp](xdp/) - Print packet counts by IPv4 source address. * Add your use case(s) here! diff --git a/examples/shared_xdp_tc/bpf_bpfeb.o b/examples/shared_xdp_tc/bpf_bpfeb.o deleted file mode 100644 index 0c8e967aabc23faa5eef45114aed1d5525a3dd11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7536 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 NJIw~w#?g-7^KZASz?}d9 diff --git a/examples/shared_xdp_tc/bpf_bpfel.o b/examples/shared_xdp_tc/bpf_bpfel.o deleted file mode 100644 index 3dcbae5bb3acd73ea02e2c3239df6d88caca9b85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7536 zcmcgxU2I%O6+XLb$2N&w+li@@mU8n`XYJaKH;tS$CDG=Wq)lPfI8{omU<5)DRH{` zO57?K^~KhU{`SRMiMwznwu9Dz-`phaZKyX6ab(9W=2hqK=KIdudJ$L8>;EoaA`Ef9m~_65R?>OvnUfQ{KAMsVlji$Azy0S3^(*T# zmH?Oayip`Yc~|iIV8$!NszJG7crCG>ebm;|e$WVt)|+dr)2%(QBzceXQGzyFSpO(W z2MW_;C@fnj2^5~;B+6bT^9ntH(up#Oas&m`{sBBr3bEFAdQReY-d&$iHXZ}V%t_qN z=TJ$?;soTrDT=b+r%_O+h4q(FbU!%MydI+To`Arl) zX_mm>K@szb1=5DXvc-#qIZ6_8x6)UY9%@9DzNk)hb)%kbOqiNhvDe@zPe`f9Gs<8(lvl{Ovs9k7U#z$l*Ot(2v^v3T ztpkzBuhU@%V+Jm$8Nhwxw0r(pG_ta=zX$XbtCi9ze2 z#c{Z~LF;Ad1l!2LtS!=GZ9QO}FHPCmpjhzZ$0knLDf?tywxo`2B`oU9f1 zN+SnFgHACoDmQi~aNb>=IvRZ{nPxs1plTW8jPoKHYz&5x3m>uml+WY4rIXO{%n1*% zP7M#CV-M8`FI>0;Go{@%rC8HY$}sFvGwGo4ujzD{daqzIQYRLvH3WusdH%6d?R>GA ztCVav@6N;rY72PS8bC|jMNaTmRFU)Bw8esZ(Uy&hCDEaqu9UC}+~!RqfT}iZ9Zns$ z4}I*Mn2-+J_U8(YJ>??9cDj(UvBTp7Xny3N-h#teVozf0`Z@c2WonA@Zh=aR-o%+>q-zfp4RfQQ7U`IvKP#e-k5=-wxn~OvS~j{P&oNC zj-d<{(k0IZpGwbo`8g`PGewNnEA63t(MjipR8x-El;he{99Pm+hyiAQrbHLLvTO5@ zrv0MryVJfDyq8>>$`>#4;pY=iaUEfSj_;`-W4zlfevqFNo_^8E;Q$jyjyN8~0TTQd z&y2*dHF3l~bY#N*_|xy1M}4NsJc>itw)39j7JQdRku{$*BEI|byMk$lDtGY4Srcy^ z@gAZ9yhQLP=!1Ap%lD1IT!-(2?8EO!rXk4VD9L?M@x7z*PGG)2G5!il-%leHSNI3O z8?O=V3*mLZNri92z8)8@6Cu0~IHmC0u=m~}nhfD}zrjiz|Er_DAkSD5>ye;C0Ip zI;-$Ef%{h?gf5AA=!PIPDe%u=kG&D0>k7XG+_E7`iwbjoqsS+(1^MfMtoKRaWU8Kb z#yf%g(fa>0?^zX3=RLd*IPaH~UFUs1ggNim6xMlP3}MdulEPquR`6_=cb(^O-aA8i z*Xx4RyfW@YQgohmUimi=ZeQnhOktf@ooAibl(Ng-Q7z9puk*_ObL6L`B}xklbH1X; z*LzV+UP~L?wl^A*U(p!V&PlAGX7BT zRx!4)|Cf}0C9H&eRp~kRVUoWqw2GXE^{)xgN>_~jJPmzsXnt-f`!?u%#EB?du_U7x zz0itf3=c! zuKE!aCU)bGq7(xVW42GCFlo$tjY;FGKy`e@zzYVxZr~*Y-!|}yf$thvbP_tM$e9OSg23|Grnt|K7#drUF_}Q`gYe@B1bEZG*OcC)R4-c3kkY zO9LevP#vhW)D-+1*uSf<-y^z0yA~AZ-{Sc9vZTVA&8z?SSW3ZXK95t^?^gN~;LYN< ztNo$Fa=#{N9h9x>p3NqSR: : Proto: => Ingress: Egress: -// Every nth calls to this function, the entire content of the Hash map is erased -// (lru map would automatically remove old keys, but can also remove additional keys -// so we use hash map to keep constant behaviour) -func handleMapContents(m *ebpf.Map) { - var ( - sb strings.Builder - key bpfSessionKey - val bpfSessionValue - keys []bpfSessionKey - ) - currIter++ - needsErase := currIter%eraseEvery == 0 - - if needsErase { - keys = make([]bpfSessionKey, 0) - } - - iter := m.Iterate() - for iter.Next(&key, &val) { - sb.WriteString(fmt.Sprintf("\t%s:%d - %s:%d Proto:%s => Ingress:%d Egress:%d\n", - intToIp(key.Saddr), portToLE(key.Sport), intToIp(key.Daddr), portToLE(key.Dport), - protoMap[key.Proto], val.InCount, val.EgCount)) - if needsErase { - keys = append(keys, key) - } - } - if iter.Err() != nil { - log.Printf("Error reading map: %s", iter.Err()) - return - } - - log.Printf("Map contents:\n%s", sb.String()) - - if !needsErase { - return - } - - n, err := m.BatchDelete(keys, nil) - if err != nil { - log.Printf("Error erasing map: %s", err) - return - } - log.Printf("Successfully Erased Map content (%d elements) at Iteration n. %d\n", n, currIter) -} - -// intToIp convert an int32 value retrieved from the network -// traffic (big endian) into a netip.Addr -func intToIp(val uint32) netip.Addr { - a4 := [4]byte{} - binary.LittleEndian.PutUint32(a4[:], val) - return netip.AddrFrom4(a4) -} - -// portToLE convert a uint16 value retrieved from the network -// traffic (big endian) into a little endian -func portToLE(val uint16) uint16 { - p2 := [2]byte{} - binary.LittleEndian.PutUint16(p2[:], val) - return binary.LittleEndian.Uint16(p2[:]) -} diff --git a/examples/shared_xdp_tc/xdp_tcx.c b/examples/shared_xdp_tc/xdp_tcx.c deleted file mode 100644 index eacdf2d6e..000000000 --- a/examples/shared_xdp_tc/xdp_tcx.c +++ /dev/null @@ -1,149 +0,0 @@ -//go:build ignore - -#include "common.h" - -char __license[] SEC("license") = "Dual MIT/GPL"; - -// Session identifier -struct session_key { - __u32 saddr; // IP source address - __u32 daddr; // IP dest address - __u16 sport; // Source port (if ICMP then 0) - __u16 dport; // Dest port (if ICMP then 0) - __u8 proto; // Protocol ID -}; - -// Session value -struct session_value { - __u32 in_count; - __u32 eg_count; -}; - -#define MAX_MAP_ENTRIES 16 - -/* -Define an Hash map for storing packet Ingress and Egress count by 5-tuple session identifier -User-space logic is responsible for cleaning the map, if potentially new entries needs to be monitored. -*/ -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, MAX_MAP_ENTRIES); - __type(key, struct session_key); - __type(value, struct session_value); -} stats_map SEC(".maps"); - -/* -Attempt to parse the 5-tuple session identifierfrom the packet. -Returns 0 if there is no IPv4 header field or if L4 is not a UDP, TCP or ICMP packet; otherwise returns non-zero. -*/ -static __always_inline int parse_session_identifier(void *data, void *data_end, struct session_key *key, __u8 is_ingress) { - // First, parse the ethernet header. - struct ethhdr *eth = data; - if ((void *)(eth + 1) > data_end) { - return 0; - } - - if (eth->h_proto != bpf_htons(ETH_P_IP)) { - // The protocol is not IPv4, so we can't parse an IPv4 source address. - return 0; - } - - // Then parse the IP header. - struct iphdr *ip = (void *)(eth + 1); - if ((void *)(ip + 1) > data_end) { - return 0; - } - - // Then parse the L4 header. - switch (ip->protocol) { - case IPPROTO_TCP: { - // TCP protocol carried, parse TCP header. - struct tcphdr *tcp = (void *)(ip + 1); - if ((void *)(tcp + 1) > data_end) - return 0; - key->sport = (__u16)(tcp->source); - key->dport = (__u16)(tcp->dest); - break; - } - case IPPROTO_UDP: { - // UDP protocol carried, parse TCP header. - struct udphdr *udp = (void *)(ip + 1); - if ((void *)(udp + 1) > data_end) - return 0; - key->sport = (__u16)(udp->source); - key->dport = (__u16)(udp->dest); - break; - } - case IPPROTO_ICMP: { - // ICMP protocol carried, no source/dest port. - break; - } - // Unchecked protocols, ignore them - default: { - return 0; - } - } - - // Fill session key with IP header data - key->proto = (__u8)(ip->protocol); - key->saddr = (__u32)(ip->saddr); - key->daddr = (__u32)(ip->daddr); - - // In case the function is called from Egress hook, swap IP addresses and L4 ports before - // doing the map lookup - if (!is_ingress) { - __u32 tmp = key->saddr; - key->saddr = key->daddr; - key->daddr = tmp; - __u16 tmp2 = key->sport; - key->sport = key->dport; - key->dport = tmp2; - } - return 1; -} - -/* -Main program logic shared by either XDP and TC hook. The function attempts to update the entry -in the LRU map corresponding to the 5-tuple identifier; it increases either the ingress or egress -packet counter value. In case of a non IP, TCP, UDP, ICMP packet, the program ignores the packet. -*/ -static __always_inline int prog_logic(void *data, void *data_end, __u8 is_ingress, int ret_code) { - struct session_key key = {}; - if (!parse_session_identifier(data, data_end, &key, is_ingress)) { - // Not an IPv4 packet, so don't count it. - goto done; - } - - struct session_value *val = bpf_map_lookup_elem(&stats_map, &key); - if (!val) { - // No entry in the map for this 5-tuple identifier yet, so set the initial value to 1. - struct session_value new_val = {}; - if (is_ingress) - new_val.in_count = 1; - else - new_val.eg_count = 1; - bpf_map_update_elem(&stats_map, &key, &new_val, BPF_ANY); - } else { - // Entry already exists for this 5-tuple identifier, so increment it atomically using an LLVM built-in. - if (is_ingress) - __sync_fetch_and_add(&val->in_count, 1); - else - __sync_fetch_and_add(&val->eg_count, 1); - } - -done: - // Return code corresponds to the OK action within either XDP or TC - return ret_code; -} - -// XDP Ingress hook -SEC("xdp") -int ingress_prog_func(struct xdp_md *ctx) { - return prog_logic((void *)(long)ctx->data, (void *)(long)ctx->data_end, 0, XDP_PASS); -} - -// TC Egress hook -SEC("tc") -int egress_prog_func(struct __sk_buff *ctx) { - return prog_logic((void *)(long)ctx->data, (void *)(long)ctx->data_end, 1, TC_ACT_OK); -} diff --git a/examples/shared_xdp_tc/bpf_bpfeb.go b/examples/tcx/bpf_bpfeb.go similarity index 100% rename from examples/shared_xdp_tc/bpf_bpfeb.go rename to examples/tcx/bpf_bpfeb.go diff --git a/examples/tcx/bpf_bpfeb.o b/examples/tcx/bpf_bpfeb.o new file mode 100644 index 0000000000000000000000000000000000000000..9e23b089d261edc9777524a92e3e0eb059828417 GIT binary patch literal 7112 zcmcIoZ)_Y#6@R;TZjwSu(tmMFp_?>~&S`wjrBP~V8(q>QBvL@8I4D7t)|d0zxA=T_ zz1>TkhEji_%7-Eq2_#Fcz)7eus1ku9;v%F3`o%~f6(l4JD9uMpfRqnWu}~%R`_0Vm zt*3So5i~bn-ol|Z)nLmA$q=+-8p}8d66!5oNJ%e z4Y_WJyJ)qOt7UCB@mh^W=RyBb`Fnu&@PN)&_(h#ZJ73t3Do^9z*ZHkp)cG5KUFR41 z{BQHe@u%zif6QB6^}`C3X_AbTiDjN2%$(njq<)6aUi+=oEeqC)+{C>u~nIQvTW zz6d;Ha6ZX&l($tz&^)~?GDH-Vp#iT@UgxBbOl$8F?7;DI-roK5gU~$X_+`b;!>d z`3B^#X_>=yUP5&*c9jSDTSlIN{3>LOGf({wj9iBNQzH+9lQ6gZ6!gD@jJrF39rD|d z(U$3Perse8oP_(NrwsicA=76HJiDi%Q~74&)%4s8jLgNvzH$Bv-b)?Hn4%jK}>#Cv}+rV$2tE<*3?X;6Km3k+@Q2c7MgYg7oMu^X~0-2~x`avUZ zhe0G9jdqUWN*wtUl@{VgsGgQuC8`M&)p`YMHiCtU zE2_eSm5H!^PTIjlGYDH*{Yn~gD#l|~xw<|)NTi;u?D$zoMDoN0z5-cC-KbM>C`3R=@wV7NYn=-;1MpUAp< zq&3~)8pZ8SHTE*w(8EN~RDVAxtUUUW8$t2M6>w)*!cTS+xuIh?rq;pVborB7#M zH6KjSMCQ`)y!5T?3^P|g>OdNw$M$F^rRz&P)M2$el#U+O+NuNP?F2&uE)dR(UvD0#UD?Z5ySgBjS0CVBx@jo1jz`a0J|yyYzSUmIV19~c46cc@iFVAH{pW~jpVJtUrUhev zS)3t!TjhqqSHwBVZ}?&c(|*(7KZx@z?MoTV&%0T_oQdabGWJ{IyoUFJQ_wg+AkGZt z>y!;XEY55%u$gcEtT=Dr^Y2U>`l?a6#i{@nH2U*!{a7jsbSZSv<9G8p4`%LbeL zxq}&unC`H_CVy@#gK3}CSPZ!rjNPoa`-;IPf9|xwCSUH1#`&A#ddQbMt8vc;aUUSQ zV(>O`3&e8}j%M^v<047sWz_1fyMHtO*U0!$ z^iWQ8{goe7JU6lj{&@1QAmp6HbF6;*nw;>QW-<3c7BkOToNpPN2M6W34*X4Fuyh=JxA2067cIQB67ycn#&dctT(Izf zg^ybJq=jo1p0w}<3r|~k*1~fZp11IVg%>Tnv=Z~4%+8xW*lMZCG=%4sP^=Oz?4-O2HT2TT5E$aZU1@&`0htmHqgkzyskv`0hDflCJSz4*VN zQ3SE><0R}B{uf00jb>@hyCxyZ3AXR=a^ht1 zx{LdUR7SyHH|ac+_WVvHcQ?e;P8pP&)m(iWjHp;LUV^((5^Swwj{B12sk&Pw{ZsQi zO47dTp`;x5E$H@VG*9d7`kjGxt@)ews(;QOZ!$$m|C~SH{cH4p2fuTm+@j`bo$a47 z*Xn;sx5!-wXa9*`w*T$T<>PAIrr@xWwj}G9?T;?m@iu_nmF*IAXRnr=DR8#o7)VL~ Gz5fQ29g&6r literal 0 HcmV?d00001 diff --git a/examples/shared_xdp_tc/bpf_bpfel.go b/examples/tcx/bpf_bpfel.go similarity index 100% rename from examples/shared_xdp_tc/bpf_bpfel.go rename to examples/tcx/bpf_bpfel.go diff --git a/examples/tcx/bpf_bpfel.o b/examples/tcx/bpf_bpfel.o new file mode 100644 index 0000000000000000000000000000000000000000..314606148bef7e2929085d27997321d8aa36ef01 GIT binary patch literal 7112 zcmcIpZ)hCH6@PbUOLA>Fwrt0WohEBraV1$gTaHmwCl+VNcBJAK;mSo0fz|1>(rtCR zJKyfvQq(l}r9mH3aG?c3LMswVl@h4ilv*tTtMwNZ+7B%hME}Jf1#Q6}Oe%`O{k@qt zr`6uOyC89r&HS%1rfJh`J$mwm3?$mGnTO_I71|QrZ0`&O$WNuE*&&wFv%~8h3E| zq2lSL14^H1zpwG`r!+qBK+o?T1*twxbpO$#N6psaeFJ7zU7_vD@iqYCnFFgeo}DTa_C1+HTdYWs|xcQUROS?X$}6@6~nk_v!lCbxKQpCcdESnmM5BHu18qYo`6b zt()Gb(YpSB>!%meuHX4`I_G?`fAIKA1Nxxp<=yo9y}!lC9N8&!$?aEC69mQibnrZw zaYm;K^}*r2#d`LUFyjY}qBx&FA$8bHjBG(R#rbSMfrdV|u>KjeZD>r#&{%fRy3qK9 zq|lxdneQxT&>GQ((OyIYHGBq_ekE4NPG8Zufvfe3*myjgb3)?=-q%Gft2-W#O(@QO zUq?gC7S_LsX2!{9$cz&rugew({JUtfhV(9EUZ)h=kI?GTDrg^|J%+Y~_H#7QPQHJc zIEZn+h22GS(C;GnZ_!Nd--!$x{ZZr;_(viSga1S13i!t&&x0qBg!@@S+adBj$ooa+ zK{z5aKOu4oyhr3=@Uv+95LW>|FM0a@U6UW{tCgIJB|m%dH=6M58`b>A8WbcAmoQy zj&W=XjR`8%*JD^4eb>`^T;u%oX@VZ4`eFh8jjTF|3qNk328V9P(`*FU_Kz@VwjYA+ z`+8#LJV32T&HV^Oo*$pTA8NmyEs)vir@-L6wW*0OAP4Oi5&bi)1aUn1`H)=q?B&a6 zuex9EfBAwtcy-`{>r&tafnUhG6W%lh>Cw>=jjC%_qDLO5OYXfi$Jdik*Adjaui zv~v)q!@!+P7twDp;f|EY#wh3IsW=h35>}d^Yh|zOu~*U0kCPg2rjVoSxpdwwmb?J{ zY7l08qvbgjOMao`htn9-INSo)AM^90-V_-gW?Zevj8HZmWC@B)E{!!C#X^N?b-}Pl zDW5L-F0^CmNk2DDC2z72dahqQMY%#Iol{a(*;+Ep(-dUq#1qzJJFlSR7W zmpqrJJ{}a@z#9*=;J@n8Sgvq`D;Us7am-yuC`I?F^;+Z#`EiFY6ig`3ppcn>JW`!L zR&LxKE9WzqCnb+3kB6D5WQMj~FZiQQJ5QMB>YQ;}wWh6=yK6%ioxR5TeAL}x+SDDc zL0BqhLMO5f9ZUovGq1twk2$T?R9={MjyYXzPOoEfwK+4|3AWDOENYFaI)~0Uybjs0 zkPlif4EDJLZvQ}A)wkNAU#!_*aKjHXS*O)6cJ`XBq=GjNC(L{@oVIQBDUa6j!33pe zUK&0xZ5xwe=E_GMNb!00q;^ueHshfVtL33|>|w2~I#5nK7y@{Ka9-S8p)gS{x?awk zY&}{%Y#q)~41oLK3LHX+UVxfC=Di!P?sO(;&Nec1aRIlPJmn&4=GD%FY|=-Eb8g_G zd-h9LIW-RabUx#bd8me)&X2mdF|{5=rp{iIrUTpId3p_Z5_Njlw=b_>Qr*LN`!P_r zGW4npe2c3Z7>GeRu_2r+o-{h+3>{RpS2el@=C)?m;8W&dT=hKEz0!4ae#XlO9wkwq zfD9tycS?uve*z>67h7oI)*EMjud7VwZb49@RYY kbP94W+R;i( z#W9{_jF$m^elngz+c&FoGX5Fbp11LyB6h}gz&(Qh1iby0evYy~<2v9zvGZ@Lre&g` z2(AMj7JLl$oqs0EMsOUsBKSP)d;d;!GlJ`Z=LKikzAHvZsm9N^9(YkOn4%S2d3C-^VGv6YyrDaWh&J-~d$uIhI`MhH>)Rs9~u zXj!nSAG%O>Q@?wHP5o9QnCpiwmA|PUZ!pf!nCrJou&JLD!Cb!{!KQwF5zO@)5^U<1 zjbN@{Q81QWQAMz+->hI$zng+hy?7mXy!WwwjTqd#;B9zUYJ4(I3xW?bZjI9|!QG5o z;U$zi_XMl@BGH=Qe#AGw5LfTgoL|*96{i8L8{-++4GJclT~F8Yc! zowr2KHILFaMbEX4lK#z>pgybr7va~(eB4(@`wlMDjCS!MGd*O-`b{A>jCOM3?z6RSDq6a$dKeHvErkcIgk z$z=SeG_2xT3(r}2!NR8AroUy&zGC533$IzYLDtLk*KFZd3-?&K&%#3%&RTfN!m}2h zv+#n27cIPO;S~$7T6oRE4YEI~^S3Z=Gm4D8$HILUHs{6oXD$1bg=Z~1XW<14FIsrn z!YdYDweXsS8{|Bg@itqy)xtd%Hs{^MoBNBwS<8RQ!m}2hv+#n27cIPO;S~!%-u%RV zN`~H4NXe3yOP(9Nm{dT$vsK@tDVfV%pX3+U5`H%0C#-p!;~@ORGCw=lUtB-&CY!`N zcbFcbWH3FcxTK>?=gxJxpJ#SLvo5zsBgMMh=QL8R%k4U;q2|C{g89?m|4d`(L6&Gk zUmx-ZA<(Zit6t@}Y{0Cmt7^26vao@?52BO;__TPlUrbDc5rn@0&fn@%f(@IK|4)p5 z2Mjz<6TeFw`TouM?KLp_%sf}bUHKZBIe&w!6>s*X!%h)0`HkvT2pn&d*|(SZqiuxj z^}7yRt@*FW{2htq`SXV_li$prza7`g-@zY!L95on*!j87TKU%`|B}RWe)hBTw?LP; p^r!$_0o7J=Q!T Ingress:%10d Egress:%10d\n", + intToIp(key.Saddr), portToLittleEndian(key.Sport), + intToIp(key.Daddr), portToLittleEndian(key.Dport), + protoMap[key.Proto], val.InCount, val.EgCount)) + } + + return sb.String(), iter.Err() +} + +// intToIp convert an int32 value retrieved from the network traffic (big endian) into a netip.Addr +func intToIp(val uint32) netip.Addr { + a4 := [4]byte{} + binary.LittleEndian.PutUint32(a4[:], val) + return netip.AddrFrom4(a4) +} + +// portToLittleEndian convert a uint16 value retrieved from the network traffic (big endian) into a little endian +func portToLittleEndian(val uint16) uint16 { + p2 := [2]byte{} + binary.LittleEndian.PutUint16(p2[:], val) + return binary.LittleEndian.Uint16(p2[:]) +} diff --git a/examples/tcx/tcx.c b/examples/tcx/tcx.c new file mode 100644 index 000000000..d6b00c117 --- /dev/null +++ b/examples/tcx/tcx.c @@ -0,0 +1,155 @@ +//go:build ignore + +#include "common.h" +#include "bpf_endian.h" + +char __license[] SEC("license") = "Dual MIT/GPL"; + +// Session identifier +struct session_key { + __u32 saddr; // IP source address + __u32 daddr; // IP dest address + __u16 sport; // Source port (set to 0 if ICMP) + __u16 dport; // Dest port (set to 0 if ICMP) + __u8 proto; // Protocol ID +}; + +// Session value +struct session_value { + __u32 in_count; // Ingress packet count + __u32 eg_count; // Egress packet count +}; + +#define MAX_MAP_ENTRIES 16 + +// Define an Hash map for storing packet Ingress and Egress count by 5-tuple session identifier +// User-space logic is responsible for cleaning the map, if potentially new entries needs to be monitored. +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_MAP_ENTRIES); + __type(key, struct session_key); + __type(value, struct session_value); +} stats_map SEC(".maps"); + +// Attempt to parse the 5-tuple session identifier from the packet. +// Returns 0 if the operation failed, i.e. not IPv4 packet or not UDP, TCP or ICMP. +static __always_inline int parse_session_identifier(void *data, void *data_end, struct session_key *key) { + // First, parse the ethernet header. + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) { + return 0; + } + + // Check for IPv4 packet. + if (eth->h_proto != bpf_htons(ETH_P_IP)) { + return 0; + } + + // Then parse the IP header. + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) { + return 0; + } + + // Then parse the L4 header. + switch (ip->protocol) { + case IPPROTO_TCP: { + // TCP protocol carried, parse TCP header. + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return 0; + key->sport = (__u16)(tcp->source); + key->dport = (__u16)(tcp->dest); + break; + } + case IPPROTO_UDP: { + // UDP protocol carried, parse UDP header. + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return 0; + key->sport = (__u16)(udp->source); + key->dport = (__u16)(udp->dest); + break; + } + case IPPROTO_ICMP: { + // ICMP protocol carried, no source/dest port. + break; + } + // Unchecked protocols, ignore packet and return. + default: { + return 0; + } + } + + // Fill session key with IP header data + key->proto = (__u8)(ip->protocol); + key->saddr = (__u32)(ip->saddr); + key->daddr = (__u32)(ip->daddr); + + return 1; +} + +// TC Ingress hook, to monitoring TCP/UDP/ICMP network connections and count packets. +SEC("tc") +int ingress_prog_func(struct __sk_buff *skb) { + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + + struct session_key key = {}; + if (!parse_session_identifier(data, data_end, &key)) { + goto ingress_done; + } + + struct session_value *val = bpf_map_lookup_elem(&stats_map, &key); + if (!val) { + // No entry in the map for this 5-tuple identifier yet, so set the initial value to 1. + struct session_value new_val = {.in_count = 1}; + bpf_map_update_elem(&stats_map, &key, &new_val, BPF_ANY); + goto ingress_done; + } + + // Entry already exists for this 5-tuple identifier, so increment it atomically using an LLVM built-in. + __sync_fetch_and_add(&val->in_count, 1); + +ingress_done: + + // Return code corresponds to the PASS action in TC + return TC_ACT_OK; +} + +// TC Egress hook, same as Ingress but with IPs and Ports inverted in the key. +// This way, the connections match the same entry for the Ingress in the bpf map. +SEC("tc") +int egress_prog_func(struct __sk_buff *skb) { + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + + struct session_key key = {}; + if (!parse_session_identifier(data, data_end, &key)) { + goto egress_done; + } + + // Swap addresses and L4 port before doing the map lookup. + __u32 tmp = key.saddr; + __u16 tmp2 = key.sport; + key.saddr = key.daddr; + key.sport = key.dport; + key.daddr = tmp; + key.dport = tmp2; + + struct session_value *val = bpf_map_lookup_elem(&stats_map, &key); + if (!val) { + // No entry in the map for this 5-tuple identifier yet, so set the initial value to 1. + struct session_value new_val = {.eg_count = 1}; + bpf_map_update_elem(&stats_map, &key, &new_val, BPF_ANY); + goto egress_done; + } + + // Entry already exists for this 5-tuple identifier, so increment it atomically using an LLVM built-in. + __sync_fetch_and_add(&val->eg_count, 1); + +egress_done: + + // Return code corresponds to the PASS action in TC + return TC_ACT_OK; +}