diff --git a/config/config.yml b/config/config.yml index 734a14ae..437be8bd 100644 --- a/config/config.yml +++ b/config/config.yml @@ -15,7 +15,7 @@ gnodeb: sd: "000001" # optional, can be removed if not used ue: - msin: "0000000120" + msin: "0000001000" key: "00112233445566778899AABBCCDDEEFF" opc: "00112233445566778899AABBCCDDEEFF" amf: "8000" diff --git a/go.mod b/go.mod index 32f068df..d2a1c883 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module my5G-RANTester go 1.21 require ( + github.com/RoaringBitmap/roaring v1.3.0 + github.com/cilium/ebpf v0.12.3 github.com/davecgh/go-spew v1.1.1 github.com/free5gc/go-gtp5gnl v1.4.5 github.com/free5gc/nas v1.1.1 @@ -12,29 +14,41 @@ require ( github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 github.com/khirono/go-nl v1.0.4 github.com/khirono/go-rtnllink v1.1.1 + github.com/lorenzosaino/go-sysctl v0.3.1 github.com/mitchellh/mapstructure v1.4.2 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 + github.com/rs/zerolog v1.31.0 github.com/sirupsen/logrus v1.9.2 github.com/stretchr/testify v1.7.0 github.com/tetratelabs/wazero v1.3.0 github.com/urfave/cli/v2 v2.25.5 github.com/vishvananda/netlink v1.1.0 github.com/wmnsk/go-gtp v0.8.6 - golang.org/x/net v0.16.0 + golang.org/x/net v0.18.0 + golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c gopkg.in/yaml.v2 v2.4.0 ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect github.com/antonfisher/nested-logrus-formatter v1.3.1 // indirect + github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/khirono/go-genl v1.0.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mschoch/smat v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/vishvananda/netns v0.0.4 // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect + golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/tools v0.2.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index 474cd0e3..7bccf7df 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,23 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/RoaringBitmap/roaring v1.3.0 h1:aQmu9zQxDU0uhwR8SXOH/OrqEf+X8A0LQmwW3JX8Lcg= +github.com/RoaringBitmap/roaring v1.3.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY= github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg= github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/free5gc/go-gtp5gnl v1.4.5 h1:w4mHuEue4hwpnQdYc19C5sXmxvYLZ0s2vJyDNdmt2wk= github.com/free5gc/go-gtp5gnl v1.4.5/go.mod h1:TT5aXB90NuSPMehuIK9lV2yJFnq6Qjw37ZqNB1QAKh0= github.com/free5gc/nas v1.1.1 h1:xUsqOOrb3kH38TQCzwZY7WN6WJkIerjERNjORDtnCbo= @@ -16,10 +26,11 @@ github.com/free5gc/openapi v1.0.6 h1:ytRjU/YZRI8UhKKyfajXSyGB6s1YDFkJ1weeAGJ8LXw github.com/free5gc/openapi v1.0.6/go.mod h1:iw/N0E+FlX44EEx24IBi2EdZW8v+bkj3ETWPGnlK9DI= github.com/free5gc/util v1.0.4 h1:GxliLpjI3NHMrKck3PDfx4OLJUuBjU182sVj+zbZK50= github.com/free5gc/util v1.0.4/go.mod h1:H0DjCCEcHsdokJC7+/fowtZ85QgT6UndyaiA/NSH/LM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -34,18 +45,33 @@ github.com/khirono/go-nl v1.0.4 h1:MKdgv6HiJyFvM5qb8r7Sy6LbcUmK4yB7542u6JM0sBE= github.com/khirono/go-nl v1.0.4/go.mod h1:PzYeSjD38fzV7mX5DuaCZvnwx2kD/o7XgHyzuJxoi7U= github.com/khirono/go-rtnllink v1.1.1 h1:VsJbbW2HbqIZ62qit5FsehxR0gnrfDp7GE9fSTqHUDY= github.com/khirono/go-rtnllink v1.1.1/go.mod h1:FqrOS6/iGjmK30oNB3snEtXXd0JRT9nJ/98/605IiO0= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lorenzosaino/go-sysctl v0.3.1 h1:3phX80tdITw2fJjZlwbXQnDWs4S30beNcMbw0cn0HtY= +github.com/lorenzosaino/go-sysctl v0.3.1/go.mod h1:5grcsBRpspKknNS1qzt1eIeRDLrhpKZAtz8Fcuvs1Rc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= @@ -64,29 +90,43 @@ github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6f github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/wmnsk/go-gtp v0.8.6 h1:Z2lHnNlQ49jdySoWpcZPOsGPFjDb+5BQm9rO9Nn47E0= github.com/wmnsk/go-gtp v0.8.6/go.mod h1:7Ja5ik6qMx451Oa4LXdXYl53277fL/X7Ob8nCb5T6TY= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d h1:+W8Qf4iJtMGKkyAygcKohjxTk4JPsL9DpzApJ22m5Ic= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c h1:3kC/TjQ+xzIblQv39bCOyRk8fbEeJcDHwbyxPUU2BpA= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= @@ -96,3 +136,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= +honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= diff --git a/internal/control_test_engine/ue/gtp/service/service.go b/internal/control_test_engine/ue/gtp/service/service.go index 7055c65d..9e32f05f 100644 --- a/internal/control_test_engine/ue/gtp/service/service.go +++ b/internal/control_test_engine/ue/gtp/service/service.go @@ -5,22 +5,285 @@ package service import ( + "encoding/binary" "fmt" gtpLink "my5G-RANTester/internal/cmd/gogtp5g-link" gtpTunnel "my5G-RANTester/internal/cmd/gogtp5g-tunnel" gnbContext "my5G-RANTester/internal/control_test_engine/gnb/context" "my5G-RANTester/internal/control_test_engine/ue/context" - - log "github.com/sirupsen/logrus" - "github.com/vishvananda/netlink" - + "my5G-RANTester/lib/eupf/ebpf" "net" + "os/exec" "strconv" "strings" "time" + "unsafe" + + "github.com/cilium/ebpf/link" + "github.com/lorenzosaino/go-sysctl" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" ) func SetupGtpInterface(ue *context.UEContext, msg gnbContext.UEMessage) { + + gnbPduSession := msg.GNBPduSessions[0] + pduSession, err := ue.GetPduSession(uint8(gnbPduSession.GetPduSessionId())) + pduSession.GnbPduSession = gnbPduSession + if err != nil { + log.Error("[GNB][GTP] ", err) + return + } + + if !ue.IsTunnelEnabled() { + log.Info(fmt.Sprintf("[UE][GTP] Interface for UE %s has not been created. Tunnel has been disabled.", ue.GetMsin())) + return + } + + if err := ebpf.IncreaseResourceLimits(); err != nil { + log.Fatalf("Can't increase resource limits: %s", err.Error()) + } + + bpfObjects := ebpf.NewBpfObjects() + if err := bpfObjects.Load(); err != nil { + log.Fatalf("Loading bpf objects failed: %s", err.Error()) + } + + /*if err := bpfObjects.ResizeAllMaps(1024, 1024, 1024); err != nil { + log.Fatalf("Resizing bpf objects failed: %s", err.Error()) + }*/ + + if err := sysctl.Set("net.ipv4.ip_forward", "1"); err != nil { + log.Fatalf("Unable to enable IP forwarding: %s", err.Error()) + } + + tableId, _ := strconv.ParseInt(fmt.Sprint(gnbPduSession.GetTeidUplink()), 10, 32) + ueVrfInf := fmt.Sprintf("vrf%s", ue.GetMsin()) + ueIp := pduSession.GetIp() + + /* + ueTunInf := fmt.Sprintf("ue%s", ue.GetMsin()) + ueTunDevice := &netlink.Tuntap{ + LinkAttrs: netlink.LinkAttrs{ + Name: ueTunInf, + MTU: 1400, + }, + Mode: netlink.TUNTAP_MODE_TAP, + } + + if err := netlink.LinkDel(ueTunDevice); err != nil { + log.Warn("[UE][DATA] Unable to delete tun for UE", err) + } + + if err := netlink.LinkAdd(ueTunDevice); err != nil { + log.Fatal("[UE][DATA] Unable to create tun for UE", err) + return + } + ueTunDevice2, _ := netlink.LinkByName(ueTunInf) + + pduSession.SetTunInterface(ueTunDevice2)*/ + + netUeIp := net.ParseIP(ueIp).To4() + upfIp := net.ParseIP(pduSession.GnbPduSession.GetUpfIp()).To4() + gnbIp := net.ParseIP(msg.GnbIp).To4() + + ueVrfDevice := &netlink.Vrf{ + LinkAttrs: netlink.LinkAttrs{ + Name: ueVrfInf, + MTU: 1400, + }, + Table: uint32(tableId), + } + _ = netlink.LinkDel(ueVrfDevice) + + if err := netlink.LinkAdd(ueVrfDevice); err != nil { + log.Fatal("[UE][DATA] Unable to create VRF for UE", err) + return + } + + // add an IP address to a n3Inf device. + addrTun := &netlink.Addr{ + IPNet: &net.IPNet{ + IP: netUeIp.To4(), + Mask: net.IPv4Mask(255, 255, 255, 255), + }, + } + + if err := netlink.AddrAdd(ueVrfDevice, addrTun); err != nil { + log.Fatal("[UE][DATA] Error in adding IP for virtual interface", err) + return + } + + /* + if err := netlink.LinkSetUp(ueTunDevice2); err != nil { + log.Fatal("[UE][DATA] Unable to set interface vEth UP", err) + return + } + + if err := netlink.LinkSetMaster(ueTunDevice2, ueVrfDevice); err != nil { + log.Fatal("[UE][DATA] Unable to set vEth tunnel as slave of VRF interface", err) + return + } + */ + + if err := netlink.LinkSetUp(ueVrfDevice); err != nil { + log.Fatal("[UE][DATA] Unable to set interface VRF UP", err) + return + } + pduSession.SetVrfDevice(ueVrfDevice) + + route := &netlink.Route{ + Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}, // default + LinkIndex: ueVrfDevice.Attrs().Index, // dev gtp- + Scope: netlink.SCOPE_LINK, // scope n3Inf + Protocol: 4, // proto static + Priority: 1, // metric 1 + Table: int(tableId), // table + } + + if err := netlink.RouteReplace(route); err != nil { + log.Fatal("[GNB][GTP] Unable to create Kernel Route ", err) + } + pduSession.SetTunRoute(route) + + route = &netlink.Route{ + Dst: &net.IPNet{IP: netUeIp, Mask: net.CIDRMask(32, 32)}, // default + LinkIndex: ueVrfDevice.Attrs().Index, // dev gtp- + Scope: netlink.SCOPE_HOST, + Protocol: 4, // proto static + Priority: 1, // metric 1 + } + + if err := netlink.RouteReplace(route); err != nil { + log.Fatal("[GNB][GTP] Unable to create Kernel Route ", err) + } + + links, err := netlink.LinkList() + var n3Device netlink.Link + for _, link := range links { + list, err := netlink.AddrList(link, 0) + if err != nil { + log.Fatal(err) + } + for _, ip := range list { + if ip.Contains(gnbIp) { + n3Device = link + } + } + } + if n3Device == nil { + log.Fatal("[UE][GTP] Unable to determine the N3 interface from ip ", gnbIp) + } + route = &netlink.Route{ + Dst: &net.IPNet{IP: gnbIp, Mask: net.CIDRMask(32, 32)}, // default + LinkIndex: n3Device.Attrs().Index, // dev gtp- + Protocol: 4, // proto static + Priority: 1, // metric 1 + } + + if err := netlink.RouteReplace(route); err != nil { + log.Fatal("[GNB][GTP] Unable to create Kernel Route ", err) + } + + //cmdAddFar := []string{nameInf, "1", "--action", "2"} + //cmdAddFar = []string{nameInf, "2", "--action", "2", "--hdr-creation", "0", fmt.Sprint(gnbPduSession.GetTeidUplink()), msg.UpfIp, "2152"} + //cmdAddPdr := []string{nameInf, "1", "--pcd", "1", "--hdr-rm", "0", "--ue-ipv4", ueIp, "--f-teid", fmt.Sprint(gnbPduSession.GetTeidDownlink()), msg.GnbIp, "--far-id", "1"} + //cmdAddPdr = []string{nameInf, "2", "--pcd", "2", "--ue-ipv4", ueIp, "--far-id", "2"} + + qer := ebpf.QerInfo{GateStatusUL: 0, GateStatusDL: 0, Qfi: 5, MaxBitrateUL: 1000000, MaxBitrateDL: 100000, StartUL: 0, StartDL: 0} + qerId, err := bpfObjects.NewQer(qer) + if err != nil { + log.Fatalf("Could not add FAR: %s", err.Error()) + } + + // Downlink + farDownlink := ebpf.FarInfo{ + Action: 2, + OuterHeaderCreation: 0, + Teid: gnbPduSession.GetTeidDownlink(), + RemoteIP: binary.LittleEndian.Uint32(upfIp), + LocalIP: binary.LittleEndian.Uint32(gnbIp), + IfIndex: uint32(n3Device.Attrs().Index), + TransportLevelMarking: 0, + } + farDownlinkId, err := bpfObjects.NewFar(farDownlink) + if err != nil { + log.Fatalf("Could not add FAR: %s", err.Error()) + } + + pdrDownlink := ebpf.PdrInfo{ + OuterHeaderRemoval: 0, + FarId: farDownlinkId, + QerId: qerId, + SdfFilter: nil, + } + + bpfObjects.PutPdrUplink(gnbPduSession.GetTeidDownlink(), pdrDownlink) + + // Uplink + farUplink := ebpf.FarInfo{ + Action: 2, + OuterHeaderCreation: 0x01, + Teid: gnbPduSession.GetTeidUplink(), + RemoteIP: binary.LittleEndian.Uint32(upfIp), + LocalIP: binary.LittleEndian.Uint32(gnbIp), + IfIndex: uint32(n3Device.Attrs().Index), + TransportLevelMarking: 0, + } + farUplinkId, err := bpfObjects.NewFar(farUplink) + if err != nil { + log.Fatalf("Could not add FAR: %s", err.Error()) + } + + pdrUplink := ebpf.IpEntrypointPdrInfo{ + OuterHeaderRemoval: 0, + FarId: farUplinkId, + QerId: qerId, + } + + bpfObjects.IpEntrypointObjects.PdrMapDownlinkIp4.Put(netUeIp.To4(), unsafe.Pointer(&pdrUplink)) + + for _, ifname := range []string{n3Device.Attrs().Name, ueVrfInf} { + AttachXdpProgram(ifname, bpfObjects) + } + log.Warn(fmt.Sprintf("[UE][GTP] You are using PacketRusher's eBPF Preview mode:")) + log.Info(fmt.Sprintf("[UE][GTP] Interface %s has successfully been configured for UE %s", ueVrfInf, ueIp)) + log.Info(fmt.Sprintf("[UE][GTP] You can do traffic for this UE using VRF %s, eg:", ueVrfInf)) + log.Info(fmt.Sprintf("[UE][GTP] sudo ip vrf exec %s iperf3 -c IPERF_SERVER -p PORT -t 9000", ueVrfInf)) +} + +func AttachXdpProgram(ifaceName string, bpfObjects *ebpf.BpfObjects) { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + log.Fatalf("Lookup network iface %q: %s", ifaceName, err.Error()) + } + + go func() { + // Attach the program. + _, err = link.AttachXDP(link.XDPOptions{ + Program: bpfObjects.UpfIpEntrypointFunc, + Interface: iface.Index, + Flags: link.XDPGenericMode, + }) + if err != nil { + log.Fatalf("Could not attach XDP program: %s", err.Error()) + } + log.Infof("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index) + ch := make(chan string) + <-ch + }() + + _ = ebpf.UpfXdpActionStatistic{ + BpfObjects: bpfObjects, + } + + cmd := exec.Command("ethtool", "-K", ifaceName, "rx", "off", "tx", "off", "sg", "off", "tso", "off", "gso", "off", "gro", "off", "lro", "off") + if err = cmd.Run(); err != nil { + log.Fatal(err) + } +} + +func SetupGtpInterfaceOld(ue *context.UEContext, msg gnbContext.UEMessage) { gnbPduSession := msg.GNBPduSessions[0] pduSession, err := ue.GetPduSession(uint8(gnbPduSession.GetPduSessionId())) pduSession.GnbPduSession = gnbPduSession diff --git a/internal/templates/test-multi-ues-in-queue.go b/internal/templates/test-multi-ues-in-queue.go index e5a42f92..83f42d61 100644 --- a/internal/templates/test-multi-ues-in-queue.go +++ b/internal/templates/test-multi-ues-in-queue.go @@ -17,9 +17,6 @@ import ( ) func TestMultiUesInQueue(numUes int, tunnelEnabled bool, dedicatedGnb bool, loop bool, timeBetweenRegistration int, timeBeforeDeregistration int, timeBeforeHandover int, numPduSessions int) { - if tunnelEnabled && !dedicatedGnb { - log.Fatal("You cannot use the --tunnel option, without using the --dedicatedGnb option") - } if tunnelEnabled && timeBetweenRegistration < 500 { log.Fatal("When using the --tunnel option, --timeBetweenRegistration must be equal to at least 500 ms, or else gtp5g kernel module may crash if you create tunnels too rapidly.") } diff --git a/lib/eupf/ebpf/ipentrypoint_bpf.go b/lib/eupf/ebpf/ipentrypoint_bpf.go new file mode 100644 index 00000000..68886624 --- /dev/null +++ b/lib/eupf/ebpf/ipentrypoint_bpf.go @@ -0,0 +1,241 @@ +// Code generated by bpf2go; DO NOT EDIT. + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type IpEntrypointFarInfo struct { + Action uint8 + OuterHeaderCreation uint8 + _ [2]byte + Teid uint32 + Remoteip uint32 + Localip uint32 + IfIndex uint32 + TransportLevelMarking uint16 + _ [2]byte +} + +type IpEntrypointIn6Addr struct{ In6U struct{ U6Addr8 [16]uint8 } } + +type IpEntrypointPdrInfo struct { + FarId uint32 + QerId uint32 + OuterHeaderRemoval uint8 + SdfMode uint8 + _ [6]byte + SdfRules struct { + SdfFilter struct { + Protocol uint8 + _ [15]byte + SrcAddr struct { + Type uint8 + _ [15]byte + Ip [16]byte /* uint128 */ + Mask [16]byte /* uint128 */ + } + SrcPort struct { + LowerBound uint16 + UpperBound uint16 + } + _ [12]byte + DstAddr struct { + Type uint8 + _ [15]byte + Ip [16]byte /* uint128 */ + Mask [16]byte /* uint128 */ + } + DstPort struct { + LowerBound uint16 + UpperBound uint16 + } + _ [12]byte + } + OuterHeaderRemoval uint8 + _ [3]byte + FarId uint32 + QerId uint32 + _ [4]byte + } +} + +type IpEntrypointQerInfo struct { + UlGateStatus uint8 + DlGateStatus uint8 + Qfi uint8 + _ [1]byte + UlMaximumBitrate uint32 + DlMaximumBitrate uint32 + _ [4]byte + UlStart uint64 + DlStart uint64 +} + +type IpEntrypointRouteStat struct { + FibLookupIp4Cache uint64 + FibLookupIp4Ok uint64 + FibLookupIp4ErrorDrop uint64 + FibLookupIp4ErrorPass uint64 + FibLookupIp6Cache uint64 + FibLookupIp6Ok uint64 + FibLookupIp6ErrorDrop uint64 + FibLookupIp6ErrorPass uint64 +} + +type IpEntrypointUpfStatistic struct { + UpfCounters struct { + RxArp uint64 + RxIcmp uint64 + RxIcmp6 uint64 + RxIp4 uint64 + RxIp6 uint64 + RxTcp uint64 + RxUdp uint64 + RxOther uint64 + RxGtpEcho uint64 + RxGtpPdu uint64 + RxGtpOther uint64 + RxGtpUnexp uint64 + } + UpfN3N6Counter struct { + RxN3 uint64 + TxN3 uint64 + RxN6 uint64 + TxN6 uint64 + } + XdpActions [8]uint64 +} + +// LoadIpEntrypoint returns the embedded CollectionSpec for IpEntrypoint. +func LoadIpEntrypoint() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_IpEntrypointBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load IpEntrypoint: %w", err) + } + + return spec, err +} + +// LoadIpEntrypointObjects loads IpEntrypoint and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *IpEntrypointObjects +// *IpEntrypointPrograms +// *IpEntrypointMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func LoadIpEntrypointObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := LoadIpEntrypoint() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// IpEntrypointSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type IpEntrypointSpecs struct { + IpEntrypointProgramSpecs + IpEntrypointMapSpecs +} + +// IpEntrypointSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type IpEntrypointProgramSpecs struct { + UpfIpEntrypointFunc *ebpf.ProgramSpec `ebpf:"upf_ip_entrypoint_func"` +} + +// IpEntrypointMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type IpEntrypointMapSpecs struct { + FarMap *ebpf.MapSpec `ebpf:"far_map"` + PdrMapDownlinkIp4 *ebpf.MapSpec `ebpf:"pdr_map_downlink_ip4"` + PdrMapDownlinkIp6 *ebpf.MapSpec `ebpf:"pdr_map_downlink_ip6"` + PdrMapUplinkIp4 *ebpf.MapSpec `ebpf:"pdr_map_uplink_ip4"` + QerMap *ebpf.MapSpec `ebpf:"qer_map"` + UpfExtStat *ebpf.MapSpec `ebpf:"upf_ext_stat"` + UpfPipeline *ebpf.MapSpec `ebpf:"upf_pipeline"` + UpfRouteStat *ebpf.MapSpec `ebpf:"upf_route_stat"` +} + +// IpEntrypointObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to LoadIpEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type IpEntrypointObjects struct { + IpEntrypointPrograms + IpEntrypointMaps +} + +func (o *IpEntrypointObjects) Close() error { + return _IpEntrypointClose( + &o.IpEntrypointPrograms, + &o.IpEntrypointMaps, + ) +} + +// IpEntrypointMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to LoadIpEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type IpEntrypointMaps struct { + FarMap *ebpf.Map `ebpf:"far_map"` + PdrMapDownlinkIp4 *ebpf.Map `ebpf:"pdr_map_downlink_ip4"` + PdrMapDownlinkIp6 *ebpf.Map `ebpf:"pdr_map_downlink_ip6"` + PdrMapUplinkIp4 *ebpf.Map `ebpf:"pdr_map_uplink_ip4"` + QerMap *ebpf.Map `ebpf:"qer_map"` + UpfExtStat *ebpf.Map `ebpf:"upf_ext_stat"` + UpfPipeline *ebpf.Map `ebpf:"upf_pipeline"` + UpfRouteStat *ebpf.Map `ebpf:"upf_route_stat"` +} + +func (m *IpEntrypointMaps) Close() error { + return _IpEntrypointClose( + m.FarMap, + m.PdrMapDownlinkIp4, + m.PdrMapDownlinkIp6, + m.PdrMapUplinkIp4, + m.QerMap, + m.UpfExtStat, + m.UpfPipeline, + m.UpfRouteStat, + ) +} + +// IpEntrypointPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to LoadIpEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type IpEntrypointPrograms struct { + UpfIpEntrypointFunc *ebpf.Program `ebpf:"upf_ip_entrypoint_func"` +} + +func (p *IpEntrypointPrograms) Close() error { + return _IpEntrypointClose( + p.UpfIpEntrypointFunc, + ) +} + +func _IpEntrypointClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed ipentrypoint_bpf.o +var _IpEntrypointBytes []byte diff --git a/lib/eupf/ebpf/ipentrypoint_bpf.o b/lib/eupf/ebpf/ipentrypoint_bpf.o new file mode 100644 index 00000000..747ea170 Binary files /dev/null and b/lib/eupf/ebpf/ipentrypoint_bpf.o differ diff --git a/lib/eupf/ebpf/n3entrypoint_bpf.go b/lib/eupf/ebpf/n3entrypoint_bpf.go new file mode 100644 index 00000000..cc3902ee --- /dev/null +++ b/lib/eupf/ebpf/n3entrypoint_bpf.go @@ -0,0 +1,118 @@ +// Code generated by bpf2go; DO NOT EDIT. + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// LoadN3Entrypoint returns the embedded CollectionSpec for N3Entrypoint. +func LoadN3Entrypoint() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_N3EntrypointBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load N3Entrypoint: %w", err) + } + + return spec, err +} + +// LoadN3EntrypointObjects loads N3Entrypoint and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *N3EntrypointObjects +// *N3EntrypointPrograms +// *N3EntrypointMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func LoadN3EntrypointObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := LoadN3Entrypoint() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// N3EntrypointSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N3EntrypointSpecs struct { + N3EntrypointProgramSpecs + N3EntrypointMapSpecs +} + +// N3EntrypointSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N3EntrypointProgramSpecs struct { + UpfN3EntrypointFunc *ebpf.ProgramSpec `ebpf:"upf_n3_entrypoint_func"` +} + +// N3EntrypointMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N3EntrypointMapSpecs struct { + UpfPipeline *ebpf.MapSpec `ebpf:"upf_pipeline"` +} + +// N3EntrypointObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to LoadN3EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N3EntrypointObjects struct { + N3EntrypointPrograms + N3EntrypointMaps +} + +func (o *N3EntrypointObjects) Close() error { + return _N3EntrypointClose( + &o.N3EntrypointPrograms, + &o.N3EntrypointMaps, + ) +} + +// N3EntrypointMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to LoadN3EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N3EntrypointMaps struct { + UpfPipeline *ebpf.Map `ebpf:"upf_pipeline"` +} + +func (m *N3EntrypointMaps) Close() error { + return _N3EntrypointClose( + m.UpfPipeline, + ) +} + +// N3EntrypointPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to LoadN3EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N3EntrypointPrograms struct { + UpfN3EntrypointFunc *ebpf.Program `ebpf:"upf_n3_entrypoint_func"` +} + +func (p *N3EntrypointPrograms) Close() error { + return _N3EntrypointClose( + p.UpfN3EntrypointFunc, + ) +} + +func _N3EntrypointClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed n3entrypoint_bpf.o +var _N3EntrypointBytes []byte diff --git a/lib/eupf/ebpf/n3entrypoint_bpf.o b/lib/eupf/ebpf/n3entrypoint_bpf.o new file mode 100644 index 00000000..a33d9fb8 Binary files /dev/null and b/lib/eupf/ebpf/n3entrypoint_bpf.o differ diff --git a/lib/eupf/ebpf/n6entrypoint_bpf.go b/lib/eupf/ebpf/n6entrypoint_bpf.go new file mode 100644 index 00000000..2a8bf5d9 --- /dev/null +++ b/lib/eupf/ebpf/n6entrypoint_bpf.go @@ -0,0 +1,118 @@ +// Code generated by bpf2go; DO NOT EDIT. + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// LoadN6Entrypoint returns the embedded CollectionSpec for N6Entrypoint. +func LoadN6Entrypoint() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_N6EntrypointBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load N6Entrypoint: %w", err) + } + + return spec, err +} + +// LoadN6EntrypointObjects loads N6Entrypoint and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *N6EntrypointObjects +// *N6EntrypointPrograms +// *N6EntrypointMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func LoadN6EntrypointObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := LoadN6Entrypoint() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// N6EntrypointSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N6EntrypointSpecs struct { + N6EntrypointProgramSpecs + N6EntrypointMapSpecs +} + +// N6EntrypointSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N6EntrypointProgramSpecs struct { + UpfN6EntrypointFunc *ebpf.ProgramSpec `ebpf:"upf_n6_entrypoint_func"` +} + +// N6EntrypointMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type N6EntrypointMapSpecs struct { + UpfPipeline *ebpf.MapSpec `ebpf:"upf_pipeline"` +} + +// N6EntrypointObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to LoadN6EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N6EntrypointObjects struct { + N6EntrypointPrograms + N6EntrypointMaps +} + +func (o *N6EntrypointObjects) Close() error { + return _N6EntrypointClose( + &o.N6EntrypointPrograms, + &o.N6EntrypointMaps, + ) +} + +// N6EntrypointMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to LoadN6EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N6EntrypointMaps struct { + UpfPipeline *ebpf.Map `ebpf:"upf_pipeline"` +} + +func (m *N6EntrypointMaps) Close() error { + return _N6EntrypointClose( + m.UpfPipeline, + ) +} + +// N6EntrypointPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to LoadN6EntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type N6EntrypointPrograms struct { + UpfN6EntrypointFunc *ebpf.Program `ebpf:"upf_n6_entrypoint_func"` +} + +func (p *N6EntrypointPrograms) Close() error { + return _N6EntrypointClose( + p.UpfN6EntrypointFunc, + ) +} + +func _N6EntrypointClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed n6entrypoint_bpf.o +var _N6EntrypointBytes []byte diff --git a/lib/eupf/ebpf/n6entrypoint_bpf.o b/lib/eupf/ebpf/n6entrypoint_bpf.o new file mode 100644 index 00000000..593cc024 Binary files /dev/null and b/lib/eupf/ebpf/n6entrypoint_bpf.o differ diff --git a/lib/eupf/ebpf/objects.go b/lib/eupf/ebpf/objects.go new file mode 100644 index 00000000..d7fb540b --- /dev/null +++ b/lib/eupf/ebpf/objects.go @@ -0,0 +1,203 @@ +package ebpf + +import ( + "errors" + "io" + "os" + + "github.com/RoaringBitmap/roaring" + "github.com/rs/zerolog/log" + + "github.com/cilium/ebpf" +) + +// +// Supported BPF_CFLAGS: +// - ENABLE_LOG: +// - enables debug output to tracepipe (`bpftool prog tracelog`) +// - ENABLE_ROUTE_CACHE +// - enable routing decision cache +// + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "$BPF_CFLAGS" -target bpf IpEntrypoint xdp/n3n6_entrypoint.c -- -I. -O2 -Wall -g +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpf ZeroEntrypoint xdp/zero_entrypoint.c -- -I. -O2 -Wall +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpf N3Entrypoint xdp/n3_entrypoint.c -- -I. -O2 -Wall +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpf N6Entrypoint xdp/n6_entrypoint.c -- -I. -O2 -Wall + +type BpfObjects struct { + IpEntrypointObjects + + FarIdTracker *IdTracker + QerIdTracker *IdTracker +} + +func NewBpfObjects() *BpfObjects { + return &BpfObjects{ + FarIdTracker: NewIdTracker(1024), + QerIdTracker: NewIdTracker(1024), + } +} + +func (bpfObjects *BpfObjects) Load() error { + pinPath := "/sys/fs/bpf/upf_pipeline" + if err := os.MkdirAll(pinPath, os.ModePerm); err != nil { + log.Info().Msgf("failed to create bpf fs subpath: %+v", err) + return err + } + + collectionOptions := ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{ + // Pin the map to the BPF filesystem and configure the + // library to automatically re-write it in the BPF + // program, so it can be re-used if it already exists or + // create it if not + PinPath: pinPath, + }, + } + + return LoadAllObjects(&collectionOptions, + Loader{LoadIpEntrypointObjects, &bpfObjects.IpEntrypointObjects}) +} + +func (bpfObjects *BpfObjects) Close() error { + return CloseAllObjects( + &bpfObjects.IpEntrypointObjects, + ) +} + +type LoaderFunc func(obj interface{}, opts *ebpf.CollectionOptions) error +type Loader struct { + LoaderFunc + object interface{} +} + +func LoadAllObjects(opts *ebpf.CollectionOptions, loaders ...Loader) error { + for _, loader := range loaders { + if err := loader.LoaderFunc(loader.object, opts); err != nil { + return err + } + } + return nil +} + +func CloseAllObjects(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +func ResizeEbpfMap(eMap **ebpf.Map, eProg *ebpf.Program, newSize uint32) error { + mapInfo, err := (*eMap).Info() + if err != nil { + log.Info().Msgf("Failed get ebpf2 map info: %s", err) + return err + } + mapInfo.MaxEntries = newSize + // Create a new MapSpec using the information from MapInfo + mapSpec := &ebpf.MapSpec{ + Name: mapInfo.Name, + Type: mapInfo.Type, + KeySize: mapInfo.KeySize, + ValueSize: mapInfo.ValueSize, + MaxEntries: mapInfo.MaxEntries, + Flags: mapInfo.Flags, + } + if err != nil { + log.Info().Msgf("Failed to close old ebpf2 map: %s, %+v", err, *eMap) + return err + } + + // Unpin the old map + err = (*eMap).Unpin() + if err != nil { + log.Info().Msgf("Failed to unpin old ebpf2 map: %s, %+v", err, *eMap) + return err + } + + // Close the old map + err = (*eMap).Close() + if err != nil { + log.Info().Msgf("Failed to close old ebpf2 map: %s, %+v", err, *eMap) + return err + } + + // Old map will be garbage collected sometime after this point + + *eMap, err = ebpf.NewMapWithOptions(mapSpec, ebpf.MapOptions{}) + if err != nil { + log.Info().Msgf("Failed to create resized ebpf2 map: %s", err) + return err + } + err = eProg.BindMap(*eMap) + if err != nil { + log.Info().Msgf("Failed to bind resized ebpf2 map: %s", err) + return err + } + return nil +} + +func (bpfObjects *BpfObjects) ResizeAllMaps(qerMapSize uint32, farMapSize uint32, pdrMapSize uint32) error { + // QER + if err := ResizeEbpfMap(&bpfObjects.QerMap, bpfObjects.UpfIpEntrypointFunc, qerMapSize); err != nil { + log.Info().Msgf("Failed to resize qer map: %s", err) + return err + } + // FAR + if err := ResizeEbpfMap(&bpfObjects.FarMap, bpfObjects.UpfIpEntrypointFunc, farMapSize); err != nil { + log.Info().Msgf("Failed to resize far map: %s", err) + return err + } + // PDR + if err := ResizeEbpfMap(&bpfObjects.PdrMapDownlinkIp4, bpfObjects.UpfIpEntrypointFunc, pdrMapSize); err != nil { + log.Info().Msgf("Failed to resize pdr map: %s", err) + return err + } + if err := ResizeEbpfMap(&bpfObjects.PdrMapDownlinkIp6, bpfObjects.UpfIpEntrypointFunc, pdrMapSize); err != nil { + log.Info().Msgf("Failed to resize qer map: %s", err) + return err + } + if err := ResizeEbpfMap(&bpfObjects.PdrMapUplinkIp4, bpfObjects.UpfIpEntrypointFunc, pdrMapSize); err != nil { + log.Info().Msgf("Failed to resize qer map: %s", err) + return err + } + + return nil +} + +type IdTracker struct { + bitmap *roaring.Bitmap + maxSize uint32 +} + +func NewIdTracker(size uint32) *IdTracker { + newBitmap := roaring.NewBitmap() + newBitmap.Flip(0, uint64(size)) + + return &IdTracker{ + bitmap: newBitmap, + maxSize: size, + } +} + +func (t *IdTracker) GetNext() (next uint32, err error) { + + i := t.bitmap.Iterator() + if i.HasNext() { + next := i.Next() + t.bitmap.Remove(next) + return next, nil + } + + return 0, errors.New("pool is empty") +} + +func (t *IdTracker) Release(id uint32) { + if id >= t.maxSize { + return + } + + t.bitmap.Add(id) +} diff --git a/lib/eupf/ebpf/objects_test.go b/lib/eupf/ebpf/objects_test.go new file mode 100644 index 00000000..8440143d --- /dev/null +++ b/lib/eupf/ebpf/objects_test.go @@ -0,0 +1,545 @@ +// Copyright 2023 Edgecom LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ebpf + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "reflect" + "testing" + "unsafe" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +func testArp(bpfObjects *BpfObjects) error { + + packetArp := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packetArp, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeARP, + }, + &layers.ARP{}, + ); err != nil { + return fmt.Errorf("serializing input packet failed: %v", err) + } + + bpfRet, bufOut, err := bpfObjects.UpfIpEntrypointFunc.Test(packetArp.Bytes()) + if err != nil { + return fmt.Errorf("test run failed: %v", err) + } + if bpfRet != 2 { // XDP_PASS + return fmt.Errorf("unexpected return value: %d", bpfRet) + } + if !reflect.DeepEqual(bufOut, packetArp.Bytes()) { + return errors.New("unexpected data modification") + } + + return nil +} + +func testArpBenchmark(bpfObjects *BpfObjects, repeat int) (int64, error) { + + packetArp := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packetArp, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeARP, + }, + &layers.ARP{}, + ); err != nil { + return 0, fmt.Errorf("serializing input packet failed: %v", err) + } + + _, duration, err := bpfObjects.UpfIpEntrypointFunc.Benchmark(packetArp.Bytes(), repeat, func() {}) + if err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + + return duration.Nanoseconds(), nil +} + +func testGtpBenchmark(bpfObjects *BpfObjects, repeat int) (int64, error) { + + packet := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packet, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeIPv4, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{10, 3, 0, 10}, + SrcIP: net.IP{10, 3, 0, 20}, + Protocol: layers.IPProtocolUDP, + IHL: 5, + }, + &layers.UDP{ + DstPort: 2152, + SrcPort: 2152, + }, + &layers.GTPv1U{ + Version: 1, + MessageType: 255, // GTPU_G_PDU + TEID: 1, + SequenceNumber: 0, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{1, 1, 1, 1}, + SrcIP: net.IP{10, 60, 0, 1}, + Protocol: layers.IPProtocolICMPv4, + IHL: 5, + }, + &layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoRequest, + Id: 0, + Seq: 0, + }, + ); err != nil { + return 0, fmt.Errorf("serializing input packet failed: %v", err) + } + + _, duration, err := bpfObjects.UpfIpEntrypointFunc.Benchmark(packet.Bytes(), repeat, func() {}) + if err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + + return duration.Nanoseconds(), nil +} + +func testGtpWithPDRBenchmark(bpfObjects *BpfObjects, repeat int) (int64, error) { + + teid := uint32(1) + + packet := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packet, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeIPv4, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{10, 3, 0, 10}, + SrcIP: net.IP{10, 3, 0, 20}, + Protocol: layers.IPProtocolUDP, + IHL: 5, + }, + &layers.UDP{ + DstPort: 2152, + SrcPort: 2152, + }, + &layers.GTPv1U{ + Version: 1, + MessageType: 255, // GTPU_G_PDU + TEID: teid, + SequenceNumber: 0, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{1, 1, 1, 1}, + SrcIP: net.IP{10, 60, 0, 1}, + Protocol: layers.IPProtocolICMPv4, + IHL: 5, + }, + &layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoRequest, + Id: 0, + Seq: 0, + }, + ); err != nil { + return 0, fmt.Errorf("serializing input packet failed: %v", err) + } + + pdr := PdrInfo{OuterHeaderRemoval: 0, FarId: 1, QerId: 1} + far := FarInfo{Action: 2, OuterHeaderCreation: 1, RemoteIP: 1, LocalIP: 2, Teid: 2, TransportLevelMarking: 0} + qer := QerInfo{GateStatusUL: 0, GateStatusDL: 0, Qfi: 0, MaxBitrateUL: 1000000, MaxBitrateDL: 100000, StartUL: 0, StartDL: 0} + + if err := bpfObjects.FarMap.Put(uint32(1), unsafe.Pointer(&far)); err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + if err := bpfObjects.QerMap.Put(uint32(1), unsafe.Pointer(&qer)); err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + if err := bpfObjects.PdrMapUplinkIp4.Put(teid, unsafe.Pointer(&pdr)); err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + + _, duration, err := bpfObjects.UpfIpEntrypointFunc.Benchmark(packet.Bytes(), repeat, func() {}) + if err != nil { + return 0, fmt.Errorf("benchmark run failed: %v", err) + } + + return duration.Nanoseconds(), nil +} + +func testGtpEcho(bpfObjects *BpfObjects) error { + + packetArp := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packetArp, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeIPv4, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{10, 3, 0, 10}, + SrcIP: net.IP{10, 3, 0, 20}, + Protocol: layers.IPProtocolUDP, + IHL: 5, + }, + &layers.UDP{ + DstPort: 2152, + SrcPort: 2152, + }, + &layers.GTPv1U{ + Version: 1, + MessageType: 1, // GTPU_ECHO_REQUEST + TEID: 0, + SequenceNumber: 0, + }, + ); err != nil { + return fmt.Errorf("serializing input packet failed: %v", err) + } + + bpfRet, bufOut, err := bpfObjects.UpfIpEntrypointFunc.Test(packetArp.Bytes()) + if err != nil { + return fmt.Errorf("test run failed: %v", err) + } + if bpfRet != 3 { // XDP_TX + return fmt.Errorf("unexpected return value: %d", bpfRet) + } + + response := gopacket.NewPacket(bufOut, layers.LayerTypeEthernet, gopacket.Default) + if gtpLayer := response.Layer(layers.LayerTypeGTPv1U); gtpLayer != nil { + gtp, _ := gtpLayer.(*layers.GTPv1U) + + if gtp.MessageType != 2 { //GTPU_ECHO_RESPONSE + return fmt.Errorf("unexpected gtp response: %d", gtp.MessageType) + } + if gtp.SequenceNumber != 0 { + return fmt.Errorf("unexpected gtp sequence: %d", gtp.SequenceNumber) + } + if gtp.TEID != 0 { + return fmt.Errorf("unexpected gtp TEID: %d", gtp.TEID) + } + } else { + return errors.New("unexpected response") + } + + return nil +} + +func testGtpWithSDFFilter(bpfObjects *BpfObjects) error { + + teid := uint32(1) + + packet := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packet, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeIPv4, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{10, 3, 0, 10}, + SrcIP: net.IP{10, 3, 0, 20}, + Protocol: layers.IPProtocolUDP, + IHL: 5, + }, + &layers.UDP{ + DstPort: 2152, + SrcPort: 2152, + }, + &layers.GTPv1U{ + Version: 1, + MessageType: 255, // GTPU_G_PDU + TEID: teid, + SequenceNumber: 0, + }, + &layers.IPv4{ + Version: 4, + DstIP: net.IP{1, 1, 1, 1}, + SrcIP: net.IP{10, 60, 0, 1}, + Protocol: layers.IPProtocolICMPv4, + IHL: 5, + }, + &layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoRequest, + Id: 0, + Seq: 0, + }, + ); err != nil { + return fmt.Errorf("serializing input packet failed: %v", err) + } + + pdr := PdrInfo{OuterHeaderRemoval: 0, FarId: 1, QerId: 1} + farForward := FarInfo{Action: 2, OuterHeaderCreation: 1, RemoteIP: 1, LocalIP: 2, Teid: 2, TransportLevelMarking: 0} + farDrop := FarInfo{Action: 1, OuterHeaderCreation: 1, RemoteIP: 1, LocalIP: 2, Teid: 2, TransportLevelMarking: 0} + qer := QerInfo{GateStatusUL: 0, GateStatusDL: 0, Qfi: 0, MaxBitrateUL: 1000000, MaxBitrateDL: 100000, StartUL: 0, StartDL: 0} + + if err := bpfObjects.FarMap.Put(uint32(1), unsafe.Pointer(&farForward)); err != nil { + return fmt.Errorf("can't set FAR: %v", err) + } + if err := bpfObjects.FarMap.Put(uint32(2), unsafe.Pointer(&farDrop)); err != nil { + return fmt.Errorf("can't set FAR: %v", err) + } + if err := bpfObjects.QerMap.Put(uint32(1), unsafe.Pointer(&qer)); err != nil { + return fmt.Errorf("can't set QER: %v", err) + } + + if err := bpfObjects.PutPdrUplink(teid, pdr); err != nil { + return fmt.Errorf("can't set uplink PDR: %v", err) + } + + sdf := SdfFilter{ + Protocol: 1, + SrcAddress: IpWMask{Type: 1, Ip: net.IP{10, 60, 0, 1}, Mask: net.IPMask{255, 255, 255, 255}}, + DstAddress: IpWMask{Type: 1, Ip: net.IP{1, 1, 1, 1}, Mask: net.IPMask{255, 255, 255, 255}}, + SrcPortRange: PortRange{LowerBound: 0, UpperBound: 65535}, + DstPortRange: PortRange{LowerBound: 0, UpperBound: 65535}, + } + pdr.SdfFilter = &sdf + pdr.FarId = 2 + if err := bpfObjects.PutPdrUplink(teid, pdr); err != nil { + return fmt.Errorf("can't set uplink PDR: %v", err) + } + + bpfRet, _, err := bpfObjects.UpfIpEntrypointFunc.Test(packet.Bytes()) + if err != nil { + return fmt.Errorf("ebpf2 run failed: %v", err) + } + + if bpfRet != 1 { // XDP_DROP + return fmt.Errorf("unexpected return value: %d", bpfRet) + } + + return nil +} + +func testGtpExtHeader(bpfObjects *BpfObjects) error { + + teid := uint32(2) + + packet := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(packet, gopacket.SerializeOptions{}, + &layers.Ethernet{ + SrcMAC: net.HardwareAddr{1, 0, 0, 3, 0, 10}, + DstMAC: net.HardwareAddr{1, 0, 0, 3, 0, 20}, + EthernetType: layers.EthernetTypeIPv4, + }, + &layers.IPv4{ + Version: 4, + SrcIP: net.IP{1, 1, 1, 1}, + DstIP: net.IP{10, 60, 0, 1}, + Protocol: layers.IPProtocolICMPv4, + IHL: 5, + }, + &layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoReply, + Id: 0, + Seq: 0, + }, + ); err != nil { + return fmt.Errorf("serializing input packet failed: %v", err) + } + + pdr := PdrInfo{OuterHeaderRemoval: 0, FarId: 1, QerId: 1} + farForward := FarInfo{ + Action: 2, + OuterHeaderCreation: 1, + RemoteIP: binary.LittleEndian.Uint32(net.IP{10, 3, 0, 10}), + LocalIP: binary.LittleEndian.Uint32(net.IP{10, 3, 0, 20}), + Teid: teid, + TransportLevelMarking: 0} + qer := QerInfo{GateStatusUL: 0, GateStatusDL: 0, Qfi: 5, MaxBitrateUL: 1000000, MaxBitrateDL: 100000, StartUL: 0, StartDL: 0} + + if err := bpfObjects.FarMap.Put(uint32(1), unsafe.Pointer(&farForward)); err != nil { + return fmt.Errorf("can't set FAR: %v", err) + } + if err := bpfObjects.QerMap.Put(uint32(1), unsafe.Pointer(&qer)); err != nil { + return fmt.Errorf("can't set QER: %v", err) + } + + if err := bpfObjects.PutPdrDownlink(net.IP{10, 60, 0, 1}, pdr); err != nil { + return fmt.Errorf("can't set downlink PDR: %v", err) + } + + bpfRet, bufOut, err := bpfObjects.UpfIpEntrypointFunc.Test(packet.Bytes()) + if err != nil { + return fmt.Errorf("ebpf2 run failed: %v", err) + } + + if bpfRet != 4 { // XDP_REDIRECT + return fmt.Errorf("unexpected return value: %d", bpfRet) + } + + response := gopacket.NewPacket(bufOut, layers.LayerTypeEthernet, gopacket.Default) + if gtpLayer := response.Layer(layers.LayerTypeGTPv1U); gtpLayer != nil { + gtp, _ := gtpLayer.(*layers.GTPv1U) + + if gtp.MessageType != 255 { //GTPU_G_PDU + return fmt.Errorf("unexpected gtp response: %d", gtp.MessageType) + } + if gtp.ExtensionHeaderFlag != true { + return fmt.Errorf("unexpected gtp extention flag: %t", gtp.ExtensionHeaderFlag) + } + if gtp.TEID != teid { + return fmt.Errorf("unexpected gtp TEID: %d", gtp.TEID) + } + if len(gtp.GTPExtensionHeaders) != 1 { + return fmt.Errorf("unexpected gtp extention header count: %d", len(gtp.GTPExtensionHeaders)) + } + + extHeader := gtp.GTPExtensionHeaders[0] + if extHeader.Type != 0x85 { + return fmt.Errorf("unexpected gtp extention header: %d", gtp.GTPExtensionHeaders[0].Type) + } + if len(extHeader.Content) != 2 { + return fmt.Errorf("unexpected gtp extention header len: %d", len(extHeader.Content)) + } + + if extHeader.Content[1] != 5 { + return fmt.Errorf("unexpected gtp extention header QFI: %d %d", extHeader.Content[0], extHeader.Content[1]) + } + } else { + return fmt.Errorf("unexpected response: %v", response) + } + + return nil +} + +func TestEntrypoint(t *testing.T) { + + if err := IncreaseResourceLimits(); err != nil { + t.Fatalf("Can't increase resource limits: %s", err.Error()) + } + + bpfObjects := &BpfObjects{} + + if err := bpfObjects.Load(); err != nil { + t.Fatalf("Loading bpf objects failed: %s", err.Error()) + } + + defer bpfObjects.Close() + + t.Run("Arp test", func(t *testing.T) { + err := testArp(bpfObjects) + if err != nil { + t.Fatalf("test failed: %s", err) + } + }) + + t.Run("GTP-U Echo test", func(t *testing.T) { + err := testGtpEcho(bpfObjects) + if err != nil { + t.Fatalf("test failed: %s", err) + } + }) + + t.Run("SDF filter test", func(t *testing.T) { + err := testGtpWithSDFFilter(bpfObjects) + if err != nil { + t.Fatalf("test failed: %s", err) + } + }) + + t.Run("GTP Extention Header test", func(t *testing.T) { + err := testGtpExtHeader(bpfObjects) + if err != nil { + t.Fatalf("test failed: %s", err) + } + }) + +} + +func TestEntrypointBenchmark(t *testing.T) { + + if err := IncreaseResourceLimits(); err != nil { + t.Fatalf("Can't increase resource limits: %s", err.Error()) + } + + bpfObjects := &BpfObjects{} + + if err := bpfObjects.Load(); err != nil { + t.Fatalf("Loading bpf objects failed: %s", err.Error()) + } + + defer bpfObjects.Close() + + t.Run("Arp (x1)) benchmark", func(t *testing.T) { + duration, err := testArpBenchmark(bpfObjects, 1) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) + + t.Run("Arp (x1000000) benchmark", func(t *testing.T) { + duration, err := testArpBenchmark(bpfObjects, 1000000) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) + + t.Run("Gtp (x1)) benchmark", func(t *testing.T) { + duration, err := testGtpBenchmark(bpfObjects, 1) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) + + t.Run("Gtp (x1000000) benchmark", func(t *testing.T) { + duration, err := testGtpBenchmark(bpfObjects, 1000000) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) + + t.Run("Gtp with PDR (x1)) benchmark", func(t *testing.T) { + duration, err := testGtpWithPDRBenchmark(bpfObjects, 1) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) + + t.Run("Gtp with PDR (x1000000) benchmark", func(t *testing.T) { + duration, err := testGtpWithPDRBenchmark(bpfObjects, 1000000) + if err != nil { + t.Fatalf("test failed: %s", err) + } + + t.Logf("%s result: %d ns", t.Name(), duration) + }) +} diff --git a/lib/eupf/ebpf/pdr.go b/lib/eupf/ebpf/pdr.go new file mode 100644 index 00000000..ef2ebf3f --- /dev/null +++ b/lib/eupf/ebpf/pdr.go @@ -0,0 +1,303 @@ +package ebpf + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "net" + "unsafe" + + "github.com/cilium/ebpf" + + "github.com/rs/zerolog/log" +) + +// The BPF_ARRAY map type has no delete operation. The only way to delete an element is to replace it with a new one. + +type PdrInfo struct { + OuterHeaderRemoval uint8 + FarId uint32 + QerId uint32 + SdfFilter *SdfFilter +} + +type SdfFilter struct { + Protocol uint8 // 0: icmp, 1: ip, 2: tcp, 3: udp, 4: icmp6 + SrcAddress IpWMask + SrcPortRange PortRange + DstAddress IpWMask + DstPortRange PortRange +} + +type IpWMask struct { + Type uint8 // 0: any, 1: ip4, 2: ip6 + Ip net.IP + Mask net.IPMask +} + +type PortRange struct { + LowerBound uint16 + UpperBound uint16 +} + +func PreprocessPdrWithSdf(lookup func(interface{}, interface{}) error, key interface{}, pdrInfo PdrInfo) (IpEntrypointPdrInfo, error) { + var defaultPdr IpEntrypointPdrInfo + if err := lookup(key, &defaultPdr); err != nil { + return CombinePdrWithSdf(nil, pdrInfo), nil + } + + return CombinePdrWithSdf(&defaultPdr, pdrInfo), nil +} + +func (bpfObjects *BpfObjects) PutPdrUplink(teid uint32, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Put PDR Uplink: teid=%d, pdrInfo=%+v", teid, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapUplinkIp4.Lookup, teid, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapUplinkIp4.Put(teid, unsafe.Pointer(&pdrToStore)) +} + +func (bpfObjects *BpfObjects) PutPdrDownlink(ipv4 net.IP, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Put PDR Downlink: ipv4=%s, pdrInfo=%+v", ipv4, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapDownlinkIp4.Lookup, ipv4, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapDownlinkIp4.Put(ipv4, unsafe.Pointer(&pdrToStore)) +} + +func (bpfObjects *BpfObjects) UpdatePdrUplink(teid uint32, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Update PDR Uplink: teid=%d, pdrInfo=%+v", teid, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapUplinkIp4.Lookup, teid, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapUplinkIp4.Update(teid, unsafe.Pointer(&pdrToStore), ebpf.UpdateExist) +} + +func (bpfObjects *BpfObjects) UpdatePdrDownlink(ipv4 net.IP, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Update PDR Downlink: ipv4=%s, pdrInfo=%+v", ipv4, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapDownlinkIp4.Lookup, ipv4, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapDownlinkIp4.Update(ipv4, unsafe.Pointer(&pdrToStore), ebpf.UpdateExist) +} + +func (bpfObjects *BpfObjects) DeletePdrUplink(teid uint32) error { + log.Debug().Msgf("EBPF: Delete PDR Uplink: teid=%d", teid) + return bpfObjects.PdrMapUplinkIp4.Delete(teid) +} + +func (bpfObjects *BpfObjects) DeletePdrDownlink(ipv4 net.IP) error { + log.Debug().Msgf("EBPF: Delete PDR Downlink: ipv4=%s", ipv4) + return bpfObjects.PdrMapDownlinkIp4.Delete(ipv4) +} + +func (bpfObjects *BpfObjects) PutDownlinkPdrIp6(ipv6 net.IP, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Put PDR Ipv6 Downlink: ipv6=%s, pdrInfo=%+v", ipv6, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapDownlinkIp6.Lookup, ipv6, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapDownlinkIp6.Put(ipv6, unsafe.Pointer(&pdrToStore)) +} + +func (bpfObjects *BpfObjects) UpdateDownlinkPdrIp6(ipv6 net.IP, pdrInfo PdrInfo) error { + log.Debug().Msgf("EBPF: Update PDR Ipv6 Downlink: ipv6=%s, pdrInfo=%+v", ipv6, pdrInfo) + var pdrToStore IpEntrypointPdrInfo + var err error + if pdrInfo.SdfFilter != nil { + if pdrToStore, err = PreprocessPdrWithSdf(bpfObjects.PdrMapDownlinkIp6.Lookup, ipv6, pdrInfo); err != nil { + return err + } + } else { + pdrToStore = ToIpEntrypointPdrInfo(pdrInfo) + } + return bpfObjects.PdrMapDownlinkIp6.Update(ipv6, unsafe.Pointer(&pdrToStore), ebpf.UpdateExist) +} + +func (bpfObjects *BpfObjects) DeleteDownlinkPdrIp6(ipv6 net.IP) error { + log.Debug().Msgf("EBPF: Delete PDR Ipv6 Downlink: ipv6=%s", ipv6) + return bpfObjects.PdrMapDownlinkIp6.Delete(ipv6) +} + +type FarInfo struct { + Action uint8 + OuterHeaderCreation uint8 + Teid uint32 + RemoteIP uint32 + LocalIP uint32 + IfIndex uint32 + TransportLevelMarking uint16 +} + +func (f FarInfo) MarshalJSON() ([]byte, error) { + remoteIP := make(net.IP, 4) + localIP := make(net.IP, 4) + binary.LittleEndian.PutUint32(remoteIP, f.RemoteIP) + binary.LittleEndian.PutUint32(localIP, f.LocalIP) + data := map[string]interface{}{ + "action": f.Action, + "outer_header_creation": f.OuterHeaderCreation, + "teid": f.Teid, + "remote_ip": remoteIP.String(), + "local_ip": localIP.String(), + "transport_level_marking": f.TransportLevelMarking, + } + return json.Marshal(data) +} + +func (bpfObjects *BpfObjects) NewFar(farInfo FarInfo) (uint32, error) { + internalId, err := bpfObjects.FarIdTracker.GetNext() + if err != nil { + return 0, err + } + log.Debug().Msgf("EBPF: Put FAR: internalId=%d, qerInfo=%+v", internalId, farInfo) + return internalId, bpfObjects.FarMap.Put(internalId, unsafe.Pointer(&farInfo)) +} + +func (bpfObjects *BpfObjects) UpdateFar(internalId uint32, farInfo FarInfo) error { + log.Debug().Msgf("EBPF: Update FAR: internalId=%d, farInfo=%+v", internalId, farInfo) + return bpfObjects.FarMap.Update(internalId, unsafe.Pointer(&farInfo), ebpf.UpdateExist) +} + +func (bpfObjects *BpfObjects) DeleteFar(intenalId uint32) error { + log.Debug().Msgf("EBPF: Delete FAR: intenalId=%d", intenalId) + bpfObjects.FarIdTracker.Release(intenalId) + return bpfObjects.FarMap.Update(intenalId, unsafe.Pointer(&FarInfo{}), ebpf.UpdateExist) +} + +type QerInfo struct { + GateStatusUL uint8 + GateStatusDL uint8 + Qfi uint8 + MaxBitrateUL uint32 + MaxBitrateDL uint32 + StartUL uint64 + StartDL uint64 +} + +func (bpfObjects *BpfObjects) NewQer(qerInfo QerInfo) (uint32, error) { + internalId, err := bpfObjects.QerIdTracker.GetNext() + if err != nil { + return 0, err + } + log.Debug().Msgf("EBPF: Put QER: internalId=%d, qerInfo=%+v", internalId, qerInfo) + return internalId, bpfObjects.QerMap.Put(internalId, unsafe.Pointer(&qerInfo)) +} + +func (bpfObjects *BpfObjects) UpdateQer(internalId uint32, qerInfo QerInfo) error { + log.Debug().Msgf("EBPF: Update QER: internalId=%d, qerInfo=%+v", internalId, qerInfo) + return bpfObjects.QerMap.Update(internalId, unsafe.Pointer(&qerInfo), ebpf.UpdateExist) +} + +func (bpfObjects *BpfObjects) DeleteQer(internalId uint32) error { + log.Debug().Msgf("EBPF: Delete QER: internalId=%d", internalId) + bpfObjects.QerIdTracker.Release(internalId) + return bpfObjects.QerMap.Update(internalId, unsafe.Pointer(&QerInfo{}), ebpf.UpdateExist) +} + +type ForwardingPlaneController interface { + PutPdrUplink(teid uint32, pdrInfo PdrInfo) error + PutPdrDownlink(ipv4 net.IP, pdrInfo PdrInfo) error + UpdatePdrUplink(teid uint32, pdrInfo PdrInfo) error + UpdatePdrDownlink(ipv4 net.IP, pdrInfo PdrInfo) error + DeletePdrUplink(teid uint32) error + DeletePdrDownlink(ipv4 net.IP) error + PutDownlinkPdrIp6(ipv6 net.IP, pdrInfo PdrInfo) error + UpdateDownlinkPdrIp6(ipv6 net.IP, pdrInfo PdrInfo) error + DeleteDownlinkPdrIp6(ipv6 net.IP) error + NewFar(farInfo FarInfo) (uint32, error) + UpdateFar(internalId uint32, farInfo FarInfo) error + DeleteFar(internalId uint32) error + NewQer(qerInfo QerInfo) (uint32, error) + UpdateQer(internalId uint32, qerInfo QerInfo) error + DeleteQer(internalId uint32) error +} + +func CombinePdrWithSdf(defaultPdr *IpEntrypointPdrInfo, sdfPdr PdrInfo) IpEntrypointPdrInfo { + var pdrToStore IpEntrypointPdrInfo + // Default mapping options. + if defaultPdr != nil { + pdrToStore.OuterHeaderRemoval = defaultPdr.OuterHeaderRemoval + pdrToStore.FarId = defaultPdr.FarId + pdrToStore.QerId = defaultPdr.QerId + pdrToStore.SdfMode = 2 + } else { + pdrToStore.SdfMode = 1 + } + + // SDF mapping options. + pdrToStore.SdfRules.SdfFilter.Protocol = sdfPdr.SdfFilter.Protocol + pdrToStore.SdfRules.SdfFilter.SrcAddr.Type = sdfPdr.SdfFilter.SrcAddress.Type + pdrToStore.SdfRules.SdfFilter.SrcAddr.Ip = Copy16Ip(sdfPdr.SdfFilter.SrcAddress.Ip) + pdrToStore.SdfRules.SdfFilter.SrcAddr.Mask = Copy16Ip(sdfPdr.SdfFilter.SrcAddress.Mask) + pdrToStore.SdfRules.SdfFilter.SrcPort.LowerBound = sdfPdr.SdfFilter.SrcPortRange.LowerBound + pdrToStore.SdfRules.SdfFilter.SrcPort.UpperBound = sdfPdr.SdfFilter.SrcPortRange.UpperBound + pdrToStore.SdfRules.SdfFilter.DstAddr.Type = sdfPdr.SdfFilter.DstAddress.Type + pdrToStore.SdfRules.SdfFilter.DstAddr.Ip = Copy16Ip(sdfPdr.SdfFilter.DstAddress.Ip) + pdrToStore.SdfRules.SdfFilter.DstAddr.Mask = Copy16Ip(sdfPdr.SdfFilter.DstAddress.Mask) + pdrToStore.SdfRules.SdfFilter.DstPort.LowerBound = sdfPdr.SdfFilter.DstPortRange.LowerBound + pdrToStore.SdfRules.SdfFilter.DstPort.UpperBound = sdfPdr.SdfFilter.DstPortRange.UpperBound + pdrToStore.SdfRules.OuterHeaderRemoval = sdfPdr.OuterHeaderRemoval + pdrToStore.SdfRules.FarId = sdfPdr.FarId + pdrToStore.SdfRules.QerId = sdfPdr.QerId + return pdrToStore +} + +func ToIpEntrypointPdrInfo(defaultPdr PdrInfo) IpEntrypointPdrInfo { + var pdrToStore IpEntrypointPdrInfo + pdrToStore.OuterHeaderRemoval = defaultPdr.OuterHeaderRemoval + pdrToStore.FarId = defaultPdr.FarId + pdrToStore.QerId = defaultPdr.QerId + return pdrToStore +} + +func Copy16Ip[T ~[]byte](arr T) [16]byte { + const Ipv4len = 4 + const Ipv6len = 16 + var c [Ipv6len]byte + var arrLen int + if len(arr) == Ipv4len { + arrLen = Ipv4len + } else if len(arr) == Ipv6len { + arrLen = Ipv6len + } else if len(arr) == 0 || arr == nil { + return c + } + for i := 0; i < arrLen; i++ { + c[i] = (arr)[arrLen-1-i] + } + return c +} + +func (sdfFilter *SdfFilter) String() string { + return fmt.Sprintf("%+v", *sdfFilter) +} diff --git a/lib/eupf/ebpf/stats.go b/lib/eupf/ebpf/stats.go new file mode 100644 index 00000000..f963811d --- /dev/null +++ b/lib/eupf/ebpf/stats.go @@ -0,0 +1,101 @@ +package ebpf + +import ( + "github.com/rs/zerolog/log" +) + +type UpfXdpActionStatistic struct { + BpfObjects *BpfObjects +} + +type UpfCounters struct { + RxArp uint64 + RxIcmp uint64 + RxIcmp6 uint64 + RxIp4 uint64 + RxIp6 uint64 + RxTcp uint64 + RxUdp uint64 + RxOther uint64 + RxGtpEcho uint64 + RxGtpPdu uint64 + RxGtpOther uint64 + RxGtpUnexp uint64 +} + +type UpfStatistic struct { + Counters UpfCounters + XdpStats [5]uint64 +} + +func (current *UpfCounters) Add(new UpfCounters) { + current.RxArp += new.RxArp + current.RxIcmp += new.RxIcmp + current.RxIcmp6 += new.RxIcmp6 + current.RxIp4 += new.RxIp4 + current.RxIp6 += new.RxIp6 + current.RxTcp += new.RxTcp + current.RxUdp += new.RxUdp + current.RxOther += new.RxOther + current.RxGtpEcho += new.RxGtpEcho + current.RxGtpPdu += new.RxGtpPdu + current.RxGtpOther += new.RxGtpOther +} + +// Getters for the upf_xdp_statistic (xdp_action) + +func (stat *UpfXdpActionStatistic) getUpfXdpStatisticField(field uint32) uint64 { + + var statistics []IpEntrypointUpfStatistic + err := stat.BpfObjects.UpfExtStat.Lookup(uint32(0), &statistics) + if err != nil { + log.Info().Msg(err.Error()) + return 0 + } + + var totalValue uint64 = 0 + for _, statistic := range statistics { + totalValue += statistic.XdpActions[field] + } + + return totalValue +} + +func (stat *UpfXdpActionStatistic) GetAborted() uint64 { + return stat.getUpfXdpStatisticField(uint32(0)) +} + +func (stat *UpfXdpActionStatistic) GetDrop() uint64 { + return stat.getUpfXdpStatisticField(uint32(1)) +} + +func (stat *UpfXdpActionStatistic) GetPass() uint64 { + return stat.getUpfXdpStatisticField(uint32(2)) +} + +func (stat *UpfXdpActionStatistic) GetTx() uint64 { + return stat.getUpfXdpStatisticField(uint32(3)) +} + +func (stat *UpfXdpActionStatistic) GetRedirect() uint64 { + return stat.getUpfXdpStatisticField(uint32(4)) +} + +// Getters for the upf_ext_stat (upf_counters) +// #TODO: Do not retrieve the whole struct each time. +func (stat *UpfXdpActionStatistic) GetUpfExtStatField() UpfCounters { + + var statistics []IpEntrypointUpfStatistic + var counters UpfCounters + err := stat.BpfObjects.UpfExtStat.Lookup(uint32(0), &statistics) + if err != nil { + log.Info().Msg(err.Error()) + return counters + } + + for _, statistic := range statistics { + counters.Add(statistic.UpfCounters) + } + + return counters +} diff --git a/lib/eupf/ebpf/utils.go b/lib/eupf/ebpf/utils.go new file mode 100644 index 00000000..aca6c2f7 --- /dev/null +++ b/lib/eupf/ebpf/utils.go @@ -0,0 +1,99 @@ +package ebpf + +import ( + "fmt" + "github.com/cilium/ebpf" + "golang.org/x/sys/unix" + "unsafe" +) + +// IncreaseResourceLimits https://prototype-kernel.readthedocs.io/en/latest/bpf/troubleshooting.html#memory-ulimits +func IncreaseResourceLimits() error { + return unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ + Cur: unix.RLIM_INFINITY, + Max: unix.RLIM_INFINITY, + }) +} + +// https://man7.org/linux/man-pages/man2/bpf.2.html +// A program array map is a special kind of array map whose +// map values contain only file descriptors referring to +// other eBPF programs. Thus, both the key_size and +// value_size must be exactly four bytes. +type BpfMapProgArrayMember struct { + ProgramId uint32 `json:"id"` + ProgramRef uint32 `json:"fd"` + ProgramName string `json:"name"` + ProgramRunCount uint32 `json:"run_count"` + ProgramRunCountEnabled bool `json:"run_count_enabled"` + ProgramDuration uint32 `json:"duration"` + ProgramDurationEnabled bool `json:"duration_enabled"` +} + +func ListMapProgArrayContents(m *ebpf.Map) ([]BpfMapProgArrayMember, error) { + if m.Type() != ebpf.ProgramArray { + return nil, fmt.Errorf("map is not a program array") + } + var bpfMapProgArrayMember []BpfMapProgArrayMember + var ( + key uint32 + val *ebpf.Program + ) + + iter := m.Iterate() + for iter.Next(&key, &val) { + programInfo, _ := val.Info() + programID, _ := programInfo.ID() + runCount, runCountEnabled := programInfo.RunCount() + runDuration, runDurationEnabled := programInfo.Runtime() + bpfMapProgArrayMember = append(bpfMapProgArrayMember, + BpfMapProgArrayMember{ + ProgramId: key, + ProgramRef: uint32(programID), + ProgramName: programInfo.Name, + ProgramRunCount: uint32(runCount), + ProgramRunCountEnabled: runCountEnabled, + ProgramDuration: uint32(runDuration), + ProgramDurationEnabled: runDurationEnabled, + }) + } + return bpfMapProgArrayMember, iter.Err() +} + +type QerMapElement struct { + Id uint32 `json:"id"` + GateStatusUL uint8 `json:"gate_status_ul"` + GateStatusDL uint8 `json:"gate_status_dl"` + Qfi uint8 `json:"qfi"` + MaxBitrateUL uint32 `json:"max_bitrate_ul"` + MaxBitrateDL uint32 `json:"max_bitrate_dl"` +} + +func ListQerMapContents(m *ebpf.Map) ([]QerMapElement, error) { + if m.Type() != ebpf.Array { + return nil, fmt.Errorf("map %s is not a hash", m) + } + + contextMap := make([]QerMapElement, 0) + mapInfo, _ := m.Info() + + var value QerInfo + for i := uint32(0); i < mapInfo.MaxEntries; i++ { + err := m.Lookup(i, unsafe.Pointer(&value)) + if err != nil { + return nil, err + } + contextMap = append(contextMap, + QerMapElement{ + Id: i, + GateStatusUL: value.GateStatusUL, + GateStatusDL: value.GateStatusDL, + Qfi: value.Qfi, + MaxBitrateUL: value.MaxBitrateUL, + MaxBitrateDL: value.MaxBitrateDL, + }, + ) + } + + return contextMap, nil +} diff --git a/lib/eupf/ebpf/xdp/n3_entrypoint.c b/lib/eupf/ebpf/xdp/n3_entrypoint.c new file mode 100644 index 00000000..8c4b932c --- /dev/null +++ b/lib/eupf/ebpf/xdp/n3_entrypoint.c @@ -0,0 +1,29 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "xdp/program_array.h" + +/* N3 only entrypoint. Attach to N3 interfaces only */ +SEC("xdp/upf_n3_entrypoint") +int upf_n3_entrypoint_func(struct xdp_md *ctx) { + bpf_printk("upf n3 entrypoint start\n"); + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/n3n6_entrypoint.c b/lib/eupf/ebpf/xdp/n3n6_entrypoint.c new file mode 100644 index 00000000..f9b5b94e --- /dev/null +++ b/lib/eupf/ebpf/xdp/n3n6_entrypoint.c @@ -0,0 +1,487 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "xdp/program_array.h" +#include "xdp/statistics.h" +#include "xdp/qer.h" +#include "xdp/pdr.h" +#include "xdp/sdf_filter.h" + +#include "xdp/utils/common.h" +#include "xdp/utils/trace.h" +#include "xdp/utils/packet_context.h" +#include "xdp/utils/parsers.h" +#include "xdp/utils/csum.h" +#include "xdp/utils/gtp_utils.h" +#include "xdp/utils/routing.h" +#include "xdp/utils/icmp.h" + + +#define DEFAULT_XDP_ACTION XDP_PASS + + +static __always_inline enum xdp_action send_to_gtp_tunnel(struct packet_context *ctx, int srcip, int dstip, int ifindex, __u8 tos, __u8 qfi, int teid) { + if (-1 == add_gtp_over_ip4_headers(ctx, srcip, dstip, tos, qfi, teid)) + return XDP_ABORTED; + upf_printk("upf: send gtp pdu %pI4 -> %pI4", &ctx->ip4->saddr, &ctx->ip4->daddr); + increment_counter(ctx->n3_n6_counter, tx_n3); + return route_ipv4(ctx->xdp_ctx, ctx->eth, ctx->ip4, ifindex); +} + + + +static __always_inline __u16 handle_n6_packet_ipv4(struct packet_context *ctx) { + const struct iphdr *ip4 = ctx->ip4; + struct pdr_info *pdr = bpf_map_lookup_elem(&pdr_map_downlink_ip4, &ip4->saddr); + if (!pdr) { + pdr = bpf_map_lookup_elem(&pdr_map_downlink_ip4, &ip4->saddr); + if (!pdr) { + upf_printk("upf: no downlink session for ip:%pI4 %pI4", &ip4->saddr, &ip4->daddr); + return DEFAULT_XDP_ACTION; + } + } + + __u32 far_id = pdr->far_id; + __u32 qer_id = pdr->qer_id; + //__u8 outer_header_removal = pdr->outer_header_removal; + if (pdr->sdf_mode) { + struct sdf_filter *sdf = &pdr->sdf_rules.sdf_filter; + if(match_sdf_filter_ipv4(ctx, sdf)) { + upf_printk("Packet with source ip:%pI4 and destination ip:%pI4 matches SDF filter", &ip4->saddr, &ip4->daddr); + far_id = pdr->sdf_rules.far_id; + qer_id = pdr->sdf_rules.qer_id; + //outer_header_removal = pdr->sdf_rules.outer_header_removal; + } else if(pdr->sdf_mode & 1) { + return DEFAULT_XDP_ACTION; + } + } + + struct far_info *far = bpf_map_lookup_elem(&far_map, &far_id); + if (!far) { + upf_printk("upf: no downlink session far for ip:%pI4 far:%d", &ip4->daddr, far_id); + return XDP_DROP; + } + + upf_printk("upf: downlink session for ip:%pI4 far:%d action:%d", &ip4->daddr, far_id, far->outer_header_creation); + + // Only forwarding action is supported at the moment + if (!(far->action & FAR_FORW)) + return XDP_DROP; + + // Only outer header GTP/UDP/IPv4 is supported at the moment + if (!(far->outer_header_creation & OHC_GTP_U_UDP_IPv4)) + return XDP_DROP; + + struct qer_info *qer = bpf_map_lookup_elem(&qer_map, &qer_id); + if (!qer) { + upf_printk("upf: no downlink session qer for ip:%pI4 qer:%d", &ip4->daddr, qer_id); + return XDP_DROP; + } + + upf_printk("upf: qer:%d gate_status:%d mbr:%d", qer_id, qer->dl_gate_status, qer->dl_maximum_bitrate); + + if (qer->dl_gate_status != GATE_STATUS_OPEN) + return XDP_DROP; + + const __u64 packet_size = ctx->xdp_ctx->data_end - ctx->xdp_ctx->data; + if (XDP_DROP == limit_rate_sliding_window(packet_size, &qer->dl_start, qer->dl_maximum_bitrate)) + return XDP_DROP; + + __u8 tos = far->transport_level_marking >> 8; + + upf_printk("upf: use mapping %pI4 -> TEID:%d", &ip4->daddr, far->teid); + return send_to_gtp_tunnel(ctx, far->localip, far->remoteip, far->if_index, tos, qer->qfi, far->teid); +} + +static __always_inline enum xdp_action handle_n6_packet_ipv6(struct packet_context *ctx) { + const struct ipv6hdr *ip6 = ctx->ip6; + struct pdr_info *pdr = bpf_map_lookup_elem(&pdr_map_downlink_ip6, &ip6->daddr); + if (!pdr) { + upf_printk("upf: no downlink session for ip:%pI6c", &ip6->daddr); + return DEFAULT_XDP_ACTION; + } + + __u32 far_id = pdr->far_id; + __u32 qer_id = pdr->qer_id; + //__u8 outer_header_removal = pdr->outer_header_removal; + if (pdr->sdf_mode) { + struct sdf_filter *sdf = &pdr->sdf_rules.sdf_filter; + if(match_sdf_filter_ipv6(ctx, sdf)) { + upf_printk("Packet with source ip:%pI6c and destination ip:%pI6c matches SDF filter", &ip6->saddr, &ip6->daddr); + far_id = pdr->sdf_rules.far_id; + qer_id = pdr->sdf_rules.qer_id; + //outer_header_removal = pdr->sdf_rules.outer_header_removal; + } else if(pdr->sdf_mode & 1) { + return DEFAULT_XDP_ACTION; + } + } + + struct far_info *far = bpf_map_lookup_elem(&far_map, &far_id); + if (!far) { + upf_printk("upf: no downlink session far for ip:%pI6c far:%d", &ip6->daddr, far_id); + return XDP_DROP; + } + + upf_printk("upf: downlink session for ip:%pI6c far:%d action:%d", &ip6->daddr, far_id, far->action); + + // Only forwarding action supported at the moment + if (!(far->action & FAR_FORW)) + return XDP_DROP; + + // Only outer header GTP/UDP/IPv4 is supported at the moment + if (!(far->outer_header_creation & OHC_GTP_U_UDP_IPv4)) + return XDP_DROP; + + struct qer_info *qer = bpf_map_lookup_elem(&qer_map, &qer_id); + if (!qer) { + upf_printk("upf: no downlink session qer for ip:%pI6c qer:%d", &ip6->daddr, qer_id); + return XDP_DROP; + } + + upf_printk("upf: qer:%d gate_status:%d mbr:%d", qer_id, qer->dl_gate_status, qer->dl_maximum_bitrate); + + if (qer->dl_gate_status != GATE_STATUS_OPEN) + return XDP_DROP; + + const __u64 packet_size = ctx->xdp_ctx->data_end - ctx->xdp_ctx->data; + if (XDP_DROP == limit_rate_sliding_window(packet_size, &qer->dl_start, qer->dl_maximum_bitrate)) + return XDP_DROP; + + __u8 tos = far->transport_level_marking >> 8; + + upf_printk("upf: use mapping %pI6c -> TEID:%d", &ip6->daddr, far->teid); + return send_to_gtp_tunnel(ctx, far->localip, far->remoteip, far->if_index, tos, qer->qfi, far->teid); +} + +static __always_inline enum xdp_action handle_gtp_packet(struct packet_context *ctx) { + if (!ctx->gtp) { + upf_printk("upf: unexpected packet context. no gtp header"); + return DEFAULT_XDP_ACTION; + } + + /* + * Step 1: search for PDR and apply PDR instructions + */ + __u32 teid = bpf_htonl(ctx->gtp->teid); + struct pdr_info *pdr = bpf_map_lookup_elem(&pdr_map_uplink_ip4, &teid); + if (!pdr) { + upf_printk("upf: no session for teid:%d", teid); + return DEFAULT_XDP_ACTION; + } + + __u32 far_id = pdr->far_id; + __u32 qer_id = pdr->qer_id; + __u8 outer_header_removal = pdr->outer_header_removal; + + if (pdr->sdf_mode) { + struct packet_context inner_context = { + .data = (char *)(long)ctx->data, + .data_end = (const char *)(long)ctx->data_end, + }; + + if (inner_context.data + 1 > inner_context.data_end) + return DEFAULT_XDP_ACTION; + int eth_protocol = guess_eth_protocol(inner_context.data); + switch (eth_protocol) { + case ETH_P_IP_BE: + { + int ip_protocol = parse_ip4(&inner_context); + if (-1 == ip_protocol) { + upf_printk("upf: unable to parse IPv4 header"); + return DEFAULT_XDP_ACTION; + } + + if( -1 == parse_l4(ip_protocol, &inner_context)) { + upf_printk("upf: unable to parse L4 header"); + return DEFAULT_XDP_ACTION; + } + + const struct sdf_filter *sdf = &pdr->sdf_rules.sdf_filter; + if(match_sdf_filter_ipv4(&inner_context, sdf)) { + upf_printk("upf: sdf filter matches teid:%d", teid); + far_id = pdr->sdf_rules.far_id; + qer_id = pdr->sdf_rules.qer_id; + outer_header_removal = pdr->sdf_rules.outer_header_removal; + } else { + upf_printk("upf: sdf filter doesn't match teid:%d", teid); + if(pdr->sdf_mode & 1) + return DEFAULT_XDP_ACTION; + } + break; + } + case ETH_P_IPV6_BE: + { + int ip_protocol = parse_ip6(&inner_context); + if (ip_protocol == -1) { + upf_printk("upf: unable to parse IPv6 header"); + return DEFAULT_XDP_ACTION; + } + + if( -1 == parse_l4(ip_protocol, &inner_context)) { + upf_printk("upf: unable to parse L4 header"); + return DEFAULT_XDP_ACTION; + } + + const struct sdf_filter *sdf = &pdr->sdf_rules.sdf_filter; + if(match_sdf_filter_ipv6(&inner_context, sdf)) { + upf_printk("upf: sdf filter matches teid:%d", teid); + far_id = pdr->sdf_rules.far_id; + qer_id = pdr->sdf_rules.qer_id; + outer_header_removal = pdr->sdf_rules.outer_header_removal; + } else { + upf_printk("upf: sdf filter doesn't match teid:%d", teid); + if(pdr->sdf_mode & 1) + return DEFAULT_XDP_ACTION; + } + break; + } + default: + upf_printk("upf: unsupported inner ethernet protocol: %d", eth_protocol); + if(pdr->sdf_mode & 1) + return DEFAULT_XDP_ACTION; + break; + } + } + + /* + * Step 2: search for FAR and apply FAR instructions + */ + struct far_info *far = bpf_map_lookup_elem(&far_map, &far_id); + if (!far) { + upf_printk("upf: no session far for teid:%d far:%d", teid, far_id); + return XDP_DROP; + } + + upf_printk("upf: far:%d action:%d outer_header_creation:%d", far_id, far->action, far->outer_header_creation); + + // Only forwarding action supported at the moment + if (!(far->action & FAR_FORW)) + return XDP_DROP; + + /* + * Step 3: search for QER and apply QER instructions + */ + struct qer_info *qer = bpf_map_lookup_elem(&qer_map, &qer_id); + if (!qer) { + upf_printk("upf: no session qer for teid:%d qer:%d", teid, qer_id); + return XDP_DROP; + } + + upf_printk("upf: qer:%d gate_status:%d mbr:%d", qer_id, qer->ul_gate_status, qer->ul_maximum_bitrate); + + if (qer->ul_gate_status != GATE_STATUS_OPEN) + return XDP_DROP; + + const __u64 packet_size = ctx->xdp_ctx->data_end - ctx->xdp_ctx->data; + if (XDP_DROP == limit_rate_sliding_window(packet_size, &qer->ul_start, qer->ul_maximum_bitrate)) + return XDP_DROP; + + upf_printk("upf: session for teid:%d far:%d outer_header_removal:%d", teid, pdr->far_id, outer_header_removal); + + // N9: Only outer header GTP/UDP/IPv4 is supported at the moment + if (far->outer_header_creation & OHC_GTP_U_UDP_IPv4) + { + upf_printk("upf: session for teid:%d -> %d remote:%pI4", teid, far->teid, &far->remoteip); + update_gtp_tunnel(ctx, far->localip, far->remoteip, 0, far->teid); + } else if (outer_header_removal == OHR_GTP_U_UDP_IPv4) { + long result = remove_gtp_header(ctx); + if (result) { + upf_printk("upf: handle_gtp_packet: can't remove gtp header: %d", result); + return XDP_ABORTED; + } + } + + /* + * Decrement IP TTL and reply TTL exeeded message (debug purspose only) + */ + // if(ctx->ip4 && ctx->ip4->ttl < 2) + // { + // if (-1 == add_icmp_over_ip4_headers(ctx, far->localip, ctx->ip4->saddr)) + // return XDP_ABORTED; + + // upf_printk("upf: send icmp ttl exeeded %pI4 -> %pI4", &ctx->ip4->saddr, &ctx->ip4->daddr); + // return handle_n6_packet_ipv4(ctx); + // } + + /* + * Reply to ping requests (debug purspose only) + */ + if(ctx->ip4 && ctx->ip4->daddr == far->localip && ctx->ip4->protocol == IPPROTO_ICMP) + { + upf_printk("upf: prepare icmp ping reply to request %pI4 -> %pI4", &ctx->ip4->saddr, &ctx->ip4->daddr); + if (-1 == prepare_icmp_echo_reply(ctx, far->localip, ctx->ip4->saddr)) + return XDP_ABORTED; + + upf_printk("upf: send icmp ping reply %pI4 -> %pI4", &ctx->ip4->saddr, &ctx->ip4->daddr); + return handle_n6_packet_ipv4(ctx); + } + + /* + * Step 4: Route packet finally + */ + if (ctx->ip4) { + increment_counter(ctx->n3_n6_counter, tx_n6); + return route_ipv4(ctx->xdp_ctx, ctx->eth, ctx->ip4, far->if_index); + } else if (ctx->ip6) { + increment_counter(ctx->n3_n6_counter, tx_n6); + return route_ipv6(ctx->xdp_ctx, ctx->eth, ctx->ip6); + } else { + return XDP_ABORTED; + } + +} + +static __always_inline enum xdp_action handle_gtpu(struct packet_context *ctx) { + int pdu_type = parse_gtp(ctx); + switch (pdu_type) { + case GTPU_G_PDU: + increment_counter(ctx->counters, rx_gtp_pdu); + return handle_gtp_packet(ctx); + case GTPU_ECHO_REQUEST: + increment_counter(ctx->counters, rx_gtp_echo); + // upf_printk("upf: gtp header [ version=%d, pt=%d, e=%d]", gtp->version, gtp->pt, gtp->e); + // upf_printk("upf: gtp echo request [ type=%d ]", pdu_type); + upf_printk("upf: gtp echo request [ %pI4 -> %pI4 ]", &ctx->ip4->saddr, &ctx->ip4->daddr); + return handle_echo_request(ctx); + case GTPU_ECHO_RESPONSE: + return XDP_PASS; //Pass echo response to userspace program + case GTPU_ERROR_INDICATION: + case GTPU_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION: + case GTPU_END_MARKER: + increment_counter(ctx->counters, rx_gtp_other); + return DEFAULT_XDP_ACTION; + default: + increment_counter(ctx->counters, rx_gtp_unexp); + upf_printk("upf: unexpected gtp message: type=%d", pdu_type); + return DEFAULT_XDP_ACTION; + } +} + +static __always_inline enum xdp_action handle_ip4(struct packet_context *ctx) { + int l4_protocol = parse_ip4(ctx); + switch (l4_protocol) { + case IPPROTO_ICMP: { + increment_counter(ctx->counters, rx_icmp); + break; + } + case IPPROTO_UDP: + increment_counter(ctx->counters, rx_udp); + if (GTP_UDP_PORT == parse_udp(ctx)) { + upf_printk("upf: gtp-u received"); + increment_counter(ctx->n3_n6_counter, rx_n3); + return handle_gtpu(ctx); + } + break; + case IPPROTO_TCP: + increment_counter(ctx->counters, rx_tcp); + break; + default: + increment_counter(ctx->counters, rx_other); + return DEFAULT_XDP_ACTION; + } + + increment_counter(ctx->n3_n6_counter, rx_n6); + return handle_n6_packet_ipv4(ctx); +} + +static __always_inline enum xdp_action handle_ip6(struct packet_context *ctx) { + int l4_protocol = parse_ip6(ctx); + switch (l4_protocol) { + case IPPROTO_ICMPV6: // Let kernel stack take care + upf_printk("upf: icmp received. passing to kernel"); + increment_counter(ctx->counters, rx_icmp6); + return XDP_PASS; + case IPPROTO_UDP: + increment_counter(ctx->counters, rx_udp); + // Don't expect GTP over IPv6 at the moment + // if (GTP_UDP_PORT == parse_udp(ctx)) + // { + // upf_printk("upf: gtp-u received"); + // return handle_gtpu(ctx); + // } + break; + case IPPROTO_TCP: + increment_counter(ctx->counters, rx_tcp); + break; + default: + increment_counter(ctx->counters, rx_other); + return DEFAULT_XDP_ACTION; + } + increment_counter(ctx->n3_n6_counter, rx_n6); + return handle_n6_packet_ipv6(ctx); +} + +static __always_inline enum xdp_action process_packet(struct packet_context *ctx) { + __u16 l3_protocol = parse_ethernet(ctx); + switch (l3_protocol) { + case ETH_P_IPV6: + increment_counter(ctx->counters, rx_ip6); + return handle_ip6(ctx); + case ETH_P_IP: + increment_counter(ctx->counters, rx_ip4); + return handle_ip4(ctx); + case ETH_P_ARP: // Let kernel stack takes care + { + increment_counter(ctx->counters, rx_arp); + upf_printk("upf: arp received. passing to kernel"); + return XDP_PASS; + } + } + + return DEFAULT_XDP_ACTION; +} + +// Combined N3 & N6 entrypoint. Use for "on-a-stick" interfaces +SEC("xdp/upf_ip_entrypoint") +int upf_ip_entrypoint_func(struct xdp_md *ctx) { + // upf_printk("upf n3 & n6 combined entrypoint start"); + const __u32 key = 0; + struct upf_statistic *statistic = bpf_map_lookup_elem(&upf_ext_stat, &key); + if (!statistic) { + const struct upf_statistic initval = {}; + bpf_map_update_elem(&upf_ext_stat, &key, &initval, BPF_ANY); + statistic = bpf_map_lookup_elem(&upf_ext_stat, &key); + if(!statistic) + return XDP_ABORTED; + } + + /* These keep track of the packet pointers and statistic */ + struct packet_context context = { + .data = (char *)(long)ctx->data, + .data_end = (const char *)(long)ctx->data_end, + .xdp_ctx = ctx, + .counters = &statistic->upf_counters, + .n3_n6_counter = &statistic->upf_n3_n6_counter}; + + enum xdp_action action = process_packet(&context); + statistic->xdp_actions[action & EUPF_MAX_XDP_ACTION_MASK] += 1; + + return action; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/n6_entrypoint.c b/lib/eupf/ebpf/xdp/n6_entrypoint.c new file mode 100644 index 00000000..7ad0d9ca --- /dev/null +++ b/lib/eupf/ebpf/xdp/n6_entrypoint.c @@ -0,0 +1,29 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "xdp/program_array.h" + +/* N6 only entrypoint. Attach to N6 interfaces only */ +SEC("xdp/upf_n6_entrypoint") +int upf_n6_entrypoint_func(struct xdp_md *ctx) { + bpf_printk("upf n6 entrypoint start\n"); + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/pdr.h b/lib/eupf/ebpf/xdp/pdr.h new file mode 100644 index 00000000..6bea593c --- /dev/null +++ b/lib/eupf/ebpf/xdp/pdr.h @@ -0,0 +1,127 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "xdp/sdf_filter.h" + +#define PDR_MAP_UPLINK_SIZE 1024 +#define PDR_MAP_DOWNLINK_IPV4_SIZE 1024 +#define PDR_MAP_DOWNLINK_IPV6_SIZE 1024 +#define FAR_MAP_SIZE 1024 + + +enum outer_header_removal_values { + OHR_GTP_U_UDP_IPv4 = 0, + OHR_GTP_U_UDP_IPv6 = 1, + OHR_UDP_IPv4 = 2, + OHR_UDP_IPv6 = 3, + OHR_IPv4 = 4, + OHR_IPv6 = 5, + OHR_GTP_U_UDP_IP = 6, + OHR_VLAN_S_TAG = 7, + OHR_S_TAG_C_TAG = 8, +}; + +// Possible optimizations: +// 0. Store SDFs in a separate map. PDR will have only id of corresponding SDF. +// 1. Combine SrcAddress.Type and DstAddress.Type into one __u8 field. Then to retrieve and put data will be used operators & and | . +// 2. Put all fields into one big structure. Sort in specific order to reduce paddings inside structure. + +struct sdf_rules { + struct sdf_filter sdf_filter; + __u8 outer_header_removal; + __u32 far_id; + __u32 qer_id; +}; + +struct pdr_info { + __u32 far_id; + __u32 qer_id; + __u8 outer_header_removal; + __u8 sdf_mode; // 0 - no sdf, 1 - sdf only, 2 - sdf + default + struct sdf_rules sdf_rules; +}; + +/* ipv4 -> PDR */ +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, __u32); + __type(value, struct pdr_info); + __uint(max_entries, PDR_MAP_DOWNLINK_IPV4_SIZE); +} pdr_map_downlink_ip4 SEC(".maps"); + +/* ipv6 -> PDR */ +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, struct in6_addr); + __type(value, struct pdr_info); + __uint(max_entries, PDR_MAP_DOWNLINK_IPV6_SIZE); +} pdr_map_downlink_ip6 SEC(".maps"); + + +/* teid -> PDR */ +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, __u32); + __type(value, struct pdr_info); + __uint(max_entries, PDR_MAP_UPLINK_SIZE); +} pdr_map_uplink_ip4 SEC(".maps"); + +enum far_action_mask { + FAR_DROP = 0x01, + FAR_FORW = 0x02, + FAR_BUFF = 0x04, + FAR_NOCP = 0x08, + FAR_DUPL = 0x10, + FAR_IPMA = 0x20, + FAR_IPMD = 0x40, + FAR_DFRT = 0x80, +}; + +enum outer_header_creation_values { + OHC_GTP_U_UDP_IPv4 = 0x01, + OHC_GTP_U_UDP_IPv6 = 0x02, + OHC_UDP_IPv4 = 0x04, + OHC_UDP_IPv6 = 0x08, +}; + +struct far_info { + __u8 action; + __u8 outer_header_creation; + __u32 teid; + __u32 remoteip; + __u32 localip; + __u32 if_index; + /* first octet DSCP value in the Type-of-Service, second octet shall contain the ToS/Traffic Class mask field, which shall be set to "0xFC". */ + __u16 transport_level_marking; +}; + +/* FAR ID -> FAR */ +struct +{ + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, struct far_info); + __uint(max_entries, FAR_MAP_SIZE); +} far_map SEC(".maps"); diff --git a/lib/eupf/ebpf/xdp/program_array.h b/lib/eupf/ebpf/xdp/program_array.h new file mode 100644 index 00000000..e5cd74cd --- /dev/null +++ b/lib/eupf/ebpf/xdp/program_array.h @@ -0,0 +1,37 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +enum upf_program_type { + UPF_PROG_TYPE_MAIN = 0, + UPF_PROG_TYPE_FAR = 1, + UPF_PROG_TYPE_QER = 2, +}; + +struct +{ + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __type(key, uint32_t); + __type(value, uint32_t); + __uint(max_entries, 16); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} upf_pipeline SEC(".maps"); diff --git a/lib/eupf/ebpf/xdp/qer.h b/lib/eupf/ebpf/xdp/qer.h new file mode 100644 index 00000000..e04b7e46 --- /dev/null +++ b/lib/eupf/ebpf/xdp/qer.h @@ -0,0 +1,90 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +enum gate_status { + GATE_STATUS_OPEN = 0, + GATE_STATUS_CLOSED = 1, + GATE_STATUS_RESERVED1 = 2, + GATE_STATUS_RESERVED2 = 3, +}; + +struct qer_info { + __u8 ul_gate_status; + __u8 dl_gate_status; + __u8 qfi; + __u32 ul_maximum_bitrate; + __u32 dl_maximum_bitrate; + __u64 ul_start; + __u64 dl_start; +}; + +#define QER_MAP_SIZE 1024 + +/* QER ID -> QER */ +struct +{ + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, struct qer_info); + __uint(max_entries, QER_MAP_SIZE); +} qer_map SEC(".maps"); + +static __always_inline enum xdp_action limit_rate_simple(struct xdp_md *ctx, __u64 *end, const __u64 rate) { + static const __u64 NSEC_PER_SEC = 1000000000ULL; + + /* Currently 0 rate means that traffic rate is not limited */ + if (rate == 0) + return XDP_PASS; + + __u64 now = bpf_ktime_get_ns(); + if (now > *end) { + __u64 tx_time = (ctx->data_end - ctx->data) * 8 * NSEC_PER_SEC / rate; + *end = now + tx_time; + return XDP_PASS; + } + + return XDP_DROP; +} + +static __always_inline enum xdp_action limit_rate_sliding_window(const __u64 packet_size, __u64 *windows_start, const __u64 rate) { + static const __u64 NSEC_PER_SEC = 1000000000ULL; + static const __u64 window_size = 5000000ULL; + + /* Currently 0 rate means that traffic rate is not limited */ + if (rate == 0) + return XDP_PASS; + + __u64 tx_time = packet_size * 8 * NSEC_PER_SEC / rate; + __u64 now = bpf_ktime_get_ns(); + + __u64 start = *(volatile __u64 *)windows_start; + if (start + tx_time > now) + return XDP_DROP; + + if (start + window_size < now) { + *(volatile __u64 *)&windows_start = now - window_size + tx_time; + return XDP_PASS; + } + + *(volatile __u64 *)&windows_start = start + tx_time; + //__sync_fetch_and_add(&window->start, tx_time); + return XDP_PASS; +} diff --git a/lib/eupf/ebpf/xdp/sdf_filter.h b/lib/eupf/ebpf/xdp/sdf_filter.h new file mode 100644 index 00000000..270f3256 --- /dev/null +++ b/lib/eupf/ebpf/xdp/sdf_filter.h @@ -0,0 +1,152 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdp/utils/trace.h" +#include "xdp/utils/packet_context.h" +#include "xdp/utils/types.h" +#include "xdp/pdr.h" + +struct ip_subnet { + __u8 type; // 0: any, 1: ip4, 2: ip6 + // If type != any, ip field has meaningful value. + // If IPv4 -> lower 32 bits. If IPv6 -> all 128 bits. + __u128 ip; + // If type != any, mask field has meaningful value. + // If IPv4 mask -> lower 32 bits. If IPv6 mask -> all 128 bits. + // Should always be applied to matching ip (except type == any). + __u128 mask; +}; + +struct port_range { + __u16 lower_bound; // If not specified in SDF: 0 + __u16 upper_bound; // If not specified in SDF: 65535 +}; + +struct sdf_filter { + __u8 protocol; // Required by SDF. 0: icmp, 1: ip, 2: tcp, 3: udp + struct ip_subnet src_addr; + struct port_range src_port; + struct ip_subnet dst_addr; + struct port_range dst_port; +}; + + +static __always_inline __u8 get_sdf_protocol(__u8 ip_protocol) { + switch(ip_protocol) + { + case IPPROTO_ICMP: return 0; + case IPPROTO_TCP: return 2; + case IPPROTO_UDP: return 3; + default: return 1; + } +} + +static __always_inline __u8 match_sdf_filter_ipv4(const struct packet_context *ctx, const struct sdf_filter *sdf) { + if(!ctx || !ctx->ip4 || !sdf) + return 0; + + const struct iphdr *ip4 = ctx->ip4; + __u8 packet_protocol = get_sdf_protocol(ip4->protocol); //TODO: convert protocol in golang part + __u16 packet_src_port = 0; + __u16 packet_dst_port = 0; + if(ctx->udp) { + packet_src_port = bpf_ntohs(ctx->udp->source); //TODO: convert port in golang part + packet_dst_port = bpf_ntohs(ctx->udp->dest); //TODO: convert port in golang part + } else if (ctx->tcp) { + packet_src_port = bpf_ntohs(ctx->tcp->source); //TODO: convert port in golang part + packet_dst_port = bpf_ntohs(ctx->tcp->dest); //TODO: convert port in golang part + } + + __u32 sdf_src_ip = bpf_htonl(sdf->src_addr.ip); + __u32 sdf_dst_ip = bpf_htonl(sdf->dst_addr.ip); + __u32 sdf_src_mask = bpf_htonl(sdf->src_addr.mask); + __u32 sdf_dst_mask = bpf_htonl(sdf->dst_addr.mask); + upf_printk("SDF: filter protocol: %u", sdf->protocol); + upf_printk("SDF: filter source ip: %pI4, destination ip: %pI4", &sdf_src_ip, &sdf_dst_ip); + upf_printk("SDF: filter source ip mask: %pI4, destination ip mask: %pI4", &sdf_src_mask, &sdf_dst_mask); + upf_printk("SDF: filter source port lower bound: %u, source port upper bound: %u", sdf->src_port.lower_bound, sdf->src_port.upper_bound); + upf_printk("SDF: filter destination port lower bound: %u, destination port upper bound: %u", sdf->dst_port.lower_bound, sdf->dst_port.upper_bound); + + upf_printk("SDF: packet protocol: %u", packet_protocol); + upf_printk("SDF: packet source ip: %pI4, destination ip: %pI4", &ip4->saddr, &ip4->daddr); + upf_printk("SDF: packet source port: %u, destination port: %u", packet_src_port, packet_dst_port); + + if ((sdf->protocol != 1 && sdf->protocol != packet_protocol) + || ((ip4->saddr & sdf_src_mask) != sdf_src_ip) + || ((ip4->daddr & sdf_dst_mask) != sdf_dst_ip) + || (packet_src_port < sdf->src_port.lower_bound || packet_src_port > sdf->src_port.upper_bound) + || (packet_dst_port < sdf->dst_port.lower_bound || packet_dst_port > sdf->dst_port.upper_bound)) + { + return 0; + } + + upf_printk("Packet with source ip: %pI4, destination ip: %pI4 matches SDF filter", + &ip4->saddr, &ip4->daddr); + + return 1; +} + +static __always_inline __u8 match_sdf_filter_ipv6(const struct packet_context *ctx, const struct sdf_filter *sdf) { + const struct ipv6hdr *ipv6 = ctx->ip6; + __u8 packet_protocol = get_sdf_protocol(ipv6->nexthdr); + __u128 packet_src_ip_128 = *((__u128*)ipv6->saddr.s6_addr); + __u128 packet_dst_ip_128 = *((__u128*)ipv6->daddr.s6_addr); + __u16 packet_src_port = 0; + __u16 packet_dst_port = 0; + if(ctx->udp) { + packet_src_port = bpf_ntohs(ctx->udp->source); //TODO: convert port in golang part + packet_dst_port = bpf_ntohs(ctx->udp->dest); //TODO: convert port in golang part + } else if (ctx->tcp) { + packet_src_port = bpf_ntohs(ctx->tcp->source); //TODO: convert port in golang part + packet_dst_port = bpf_ntohs(ctx->tcp->dest); //TODO: convert port in golang part + } + + __u128 sdf_src_ip = bpf_htonl(sdf->src_addr.ip); + __u128 sdf_dst_ip = bpf_htonl(sdf->dst_addr.ip); + upf_printk("SDF: filter protocol: %u", sdf->protocol); + upf_printk("SDF: filter source ip: %pI6c, destination ip: %pI6c", &sdf_src_ip, &sdf_dst_ip); + upf_printk("SDF: filter source port lower bound: %u, source port upper bound: %u", sdf->src_port.lower_bound, sdf->src_port.upper_bound); + upf_printk("SDF: filter destination port lower bound: %u, destination port upper bound: %u", sdf->dst_port.lower_bound, sdf->dst_port.upper_bound); + upf_printk("SDF: filter source address mask: %pI4, destination address mask: %pI4", &sdf->dst_addr.mask, &sdf->dst_addr.mask); + + upf_printk("SDF: packet protocol: %u", packet_protocol); + upf_printk("SDF: packet source ip: %pI6c, destination ip: %pI6c", &packet_src_ip_128, &packet_dst_ip_128); + upf_printk("SDF: packet source port: %u, destination port: %u", packet_src_port, packet_dst_port); + + if ((sdf->protocol != 1 && sdf->protocol != packet_protocol) || + (packet_src_ip_128 & sdf->src_addr.mask) != sdf_src_ip || + (packet_dst_ip_128 & sdf->dst_addr.mask) != sdf_dst_ip || + packet_src_port < sdf->src_port.lower_bound || packet_src_port > sdf->src_port.upper_bound || + packet_dst_port < sdf->dst_port.lower_bound || packet_dst_port > sdf->dst_port.upper_bound) { + return 0; + } + + upf_printk("SDF: packet with source ip:%pI6c, destination ip:%pI6c matches SDF filter", + &packet_src_ip_128, &packet_dst_ip_128); + + return 1; +} \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/statistics.h b/lib/eupf/ebpf/xdp/statistics.h new file mode 100644 index 00000000..6215433e --- /dev/null +++ b/lib/eupf/ebpf/xdp/statistics.h @@ -0,0 +1,61 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +struct upf_counters { + __u64 rx_arp; + __u64 rx_icmp; + __u64 rx_icmp6; + __u64 rx_ip4; + __u64 rx_ip6; + __u64 rx_tcp; + __u64 rx_udp; + __u64 rx_other; + __u64 rx_gtp_echo; + __u64 rx_gtp_pdu; + __u64 rx_gtp_other; + __u64 rx_gtp_unexp; +}; + +struct n3_n6_counters { + __u64 rx_n3; + __u64 tx_n3; + __u64 rx_n6; + __u64 tx_n6; +}; + +#define EUPF_MAX_XDP_ACTION 8 +#define EUPF_MAX_XDP_ACTION_MASK 0x07 + +struct upf_statistic { + struct upf_counters upf_counters; + struct n3_n6_counters upf_n3_n6_counter; + __u64 xdp_actions[EUPF_MAX_XDP_ACTION]; +}; + +struct +{ + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, struct upf_statistic); + __uint(max_entries, 1); +} upf_ext_stat SEC(".maps"); + + diff --git a/lib/eupf/ebpf/xdp/upf_program.c b/lib/eupf/ebpf/xdp/upf_program.c new file mode 100644 index 00000000..aa5be97c --- /dev/null +++ b/lib/eupf/ebpf/xdp/upf_program.c @@ -0,0 +1,31 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "xdp/program_array.h" + +SEC("xdp/upf") +int upf_func(struct xdp_md *ctx) { + bpf_printk("upf_program start\n"); + + bpf_printk("tail call to UPF_PROG_TYPE_QER key\n"); + bpf_tail_call(ctx, &upf_pipeline, UPF_PROG_TYPE_QER); + bpf_printk("tail call to UPF_PROG_TYPE_QER key failed\n"); + return XDP_ABORTED; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/common.h b/lib/eupf/ebpf/xdp/utils/common.h new file mode 100644 index 00000000..8080c5fd --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/common.h @@ -0,0 +1,23 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#define increment_counter(OBJECT, COUNTER) \ + OBJECT->COUNTER++; + +#define increment_counter_sync(OBJECT, COUNTER) \ + __sync_fetch_and_add(&OBJECT->COUNTER, 1); diff --git a/lib/eupf/ebpf/xdp/utils/csum.h b/lib/eupf/ebpf/xdp/utils/csum.h new file mode 100644 index 00000000..3e2089be --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/csum.h @@ -0,0 +1,45 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +static __always_inline __u16 csum_fold_helper(__u64 csum) { +#pragma unroll + for (int i = 0; i < 4; i++) { + csum = (csum & 0xffff) + (csum >> 16); + } + + return ~csum; +} + +static __always_inline __u64 ipv4_csum(void *data_start, __u32 data_size) { + __u64 csum = bpf_csum_diff(0, 0, data_start, data_size, 0); + return csum_fold_helper(csum); +} + +static __always_inline void ipv4_csum_replace(__u16 *sum, __u16 old, __u16 new) +{ + __u16 csum = ~*sum; + csum += ~old; + csum += csum < (__u16)~old; + csum += new; + csum += csum < (__u16)new; + *sum = ~csum; +} \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/gtp_utils.h b/lib/eupf/ebpf/xdp/utils/gtp_utils.h new file mode 100644 index 00000000..e78d5baa --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/gtp_utils.h @@ -0,0 +1,256 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "xdp/utils/gtpu.h" +#include "xdp/utils/packet_context.h" +#include "xdp/utils/trace.h" + +static __always_inline __u32 parse_gtp(struct packet_context *ctx) { + struct gtpuhdr *gtp = (struct gtpuhdr *)ctx->data; + if ((const char *)(gtp + 1) > ctx->data_end) + return -1; + + ctx->data += sizeof(*gtp); + if (gtp->e || gtp->s || gtp->pn) + ctx->data += sizeof(struct gtp_hdr_ext) + 4; + ctx->gtp = gtp; + return gtp->message_type; +} + +static __always_inline __u32 handle_echo_request(struct packet_context *ctx) { + struct ethhdr *eth = ctx->eth; + struct iphdr *iph = ctx->ip4; + struct udphdr *udp = ctx->udp; + struct gtpuhdr *gtp = ctx->gtp; + + gtp->message_type = GTPU_ECHO_RESPONSE; + + /* TODO: add support GTP over IPv6 */ + swap_ip(iph); + swap_port(udp); + swap_mac(eth); + upf_printk("upf: send gtp echo response [ %pI4 -> %pI4 ]", &iph->saddr, &iph->daddr); + return XDP_TX; +} + +static __always_inline int guess_eth_protocol(const char *data) { + const __u8 ip_version = (*(const __u8 *)data) >> 4; + switch (ip_version) { + case 6: { + return ETH_P_IPV6_BE; + } + case 4: { + return ETH_P_IP_BE; + } + default: + /* do nothing with non-ip packets */ + upf_printk("upf: can't process non-IP packet: %d", ip_version); + return -1; + } +} + +static __always_inline long remove_gtp_header(struct packet_context *ctx) { + if (!ctx->gtp) { + upf_printk("upf: remove_gtp_header: not a gtp packet"); + return -1; + } + + size_t ext_gtp_header_size = 0; + struct gtpuhdr *gtp = ctx->gtp; + if (gtp->e || gtp->s || gtp->pn) + ext_gtp_header_size += sizeof(struct gtp_hdr_ext) + 4; + + const size_t gtp_encap_size = sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct gtpuhdr) + ext_gtp_header_size; + + char *data = (char *)(long)ctx->xdp_ctx->data; + const char *data_end = (const char *)(long)ctx->xdp_ctx->data_end; + struct ethhdr *eth = (struct ethhdr *)data; + if ((const char *)(eth + 1) > data_end) { + upf_printk("upf: remove_gtp_header: can't parse eth"); + return -1; + } + + data += gtp_encap_size; + struct ethhdr *new_eth = (struct ethhdr *)data; + if ((const char *)(new_eth + 1) > data_end) { + upf_printk("upf: remove_gtp_header: can't set new eth"); + return -1; + } + + data += sizeof(*new_eth); + if (data + 1 > data_end) + return -1; + + const int eth_proto = guess_eth_protocol(data); + + if (eth_proto == -1) + return -1; + + __builtin_memcpy(new_eth, eth, sizeof(*new_eth)); + + new_eth->h_proto = eth_proto; + + long result = bpf_xdp_adjust_head(ctx->xdp_ctx, gtp_encap_size); + if (result) + return result; + + /* Update packet pointers */ + data = (char *)(long)ctx->xdp_ctx->data; + data_end = (const char *)(long)ctx->xdp_ctx->data_end; + return context_reinit(ctx, data, data_end); +} + +static __always_inline void fill_ip_header(struct iphdr *ip, int saddr, int daddr, __u8 tos, int tot_len) { + ip->version = 4; + ip->ihl = 5; /* No options */ + ip->tos = tos; + ip->tot_len = bpf_htons(tot_len); + ip->id = 0; /* No fragmentation */ + ip->frag_off = 0x0040; /* Don't fragment; Fragment offset = 0 */ + ip->ttl = 64; + ip->protocol = IPPROTO_UDP; + ip->check = 0; + ip->saddr = saddr; + ip->daddr = daddr; +} + +static __always_inline void fill_udp_header(struct udphdr *udp, int port, int len) { + udp->source = bpf_htons(port); + udp->dest = udp->source; + udp->len = bpf_htons(len); + udp->check = 0; +} + +static __always_inline void fill_gtp_header(struct gtpuhdr *gtp, int teid, int len) { + *(__u8 *)gtp = GTP_FLAGS; + gtp->e = 1; + gtp->message_type = GTPU_G_PDU; + gtp->message_length = bpf_htons(len); + gtp->teid = bpf_htonl(teid); +} + +static __always_inline void fill_gtp_ext_header(struct gtp_hdr_ext *gtp_ext) { + gtp_ext->sqn = 0; + gtp_ext->npdu = 0; + gtp_ext->next_ext = GTPU_EXT_TYPE_PDU_SESSION_CONTAINER; +} + +static __always_inline void fill_gtp_ext_header_psc(struct gtp_hdr_ext_pdu_session_container *gtp_ext, int qfi, int pdu_type) { + gtp_ext->length = 1; + gtp_ext->pdu_type = pdu_type; + gtp_ext->spare1 = 0; + gtp_ext->spare2 = 0; + gtp_ext->rqi = 0; + gtp_ext->qfi = qfi; + gtp_ext->next_ext = 0; +} + +static __always_inline __u32 add_gtp_over_ip4_headers(struct packet_context *ctx, int saddr, int daddr, __u8 tos, __u8 qfi, int teid) { + static const size_t gtp_ext_hdr_size = sizeof(struct gtp_hdr_ext) + sizeof(struct gtp_hdr_ext_pdu_session_container); + static const size_t gtp_full_hdr_size = sizeof(struct gtpuhdr) + gtp_ext_hdr_size; + static const size_t gtp_encap_size = sizeof(struct iphdr) + sizeof(struct udphdr) + gtp_full_hdr_size; + + // int ip_packet_len = (ctx->xdp_ctx->data_end - ctx->xdp_ctx->data) - sizeof(*eth); + int ip_packet_len = 0; + if (ctx->ip4) + ip_packet_len = bpf_ntohs(ctx->ip4->tot_len); + else if (ctx->ip6) + ip_packet_len = bpf_ntohs(ctx->ip6->payload_len) + sizeof(struct ipv6hdr); + else + return -1; + + int result = bpf_xdp_adjust_head(ctx->xdp_ctx, (__s32)-gtp_encap_size); + if (result) + return -1; + + char *data = (char *)(long)ctx->xdp_ctx->data; + const char *data_end = (const char *)(long)ctx->xdp_ctx->data_end; + + struct ethhdr *orig_eth = (struct ethhdr *)(data + gtp_encap_size); + if ((const char *)(orig_eth + 1) > data_end) + return -1; + + struct ethhdr *eth = (struct ethhdr *)data; + __builtin_memcpy(eth, orig_eth, sizeof(*eth)); + eth->h_proto = bpf_htons(ETH_P_IP); + + struct iphdr *ip = (struct iphdr *)(eth + 1); + if ((const char *)(ip + 1) > data_end) + return -1; + + /* Add the outer IP header */ + fill_ip_header(ip, saddr, daddr, tos, ip_packet_len + gtp_encap_size); + + /* Add the UDP header */ + struct udphdr *udp = (struct udphdr *)(ip + 1); + if ((const char *)(udp + 1) > data_end) + return -1; + + fill_udp_header(udp, GTP_UDP_PORT, ip_packet_len + sizeof(*udp) + gtp_full_hdr_size); + + /* Add the GTP header */ + struct gtpuhdr *gtp = (struct gtpuhdr *)(udp + 1); + if ((const char *)(gtp + 1) > data_end) + return -1; + + fill_gtp_header(gtp, teid, gtp_ext_hdr_size + ip_packet_len); + + /* Add the GTP ext header */ + struct gtp_hdr_ext *gtp_ext = (struct gtp_hdr_ext *)(gtp + 1); + if ((const char *)(gtp_ext + 1) > data_end) + return -1; + + fill_gtp_ext_header(gtp_ext); + + /* Add the GTP PDU session container header */ + struct gtp_hdr_ext_pdu_session_container *gtp_psc = (struct gtp_hdr_ext_pdu_session_container *)(gtp_ext + 1); + if ((const char *)(gtp_psc + 1) > data_end) + return -1; + + fill_gtp_ext_header_psc(gtp_psc, qfi, PDU_SESSION_CONTAINER_PDU_TYPE_DL_PSU); + + ip->check = ipv4_csum(ip, sizeof(*ip)); + + /* TODO: implement UDP csum which pass ebpf verifier checks successfully */ + // cs = 0; + // const void* udp_start = (void*)udp; + // const __u16 udp_len = bpf_htons(udp->len); + // ipv4_l4_csum(udp, udp_len, &cs, ip); + // udp->check = cs; + + /* Update packet pointers */ + context_set_ip4(ctx, (char *)(long)ctx->xdp_ctx->data, (const char *)(long)ctx->xdp_ctx->data_end, eth, ip, udp, gtp); + return 0; +} + +static __always_inline void update_gtp_tunnel(struct packet_context *ctx, int srcip, int dstip, __u8 tos, int teid) { + + ctx->gtp->teid = bpf_htonl(teid); + ctx->ip4->saddr = srcip; + ctx->ip4->daddr = dstip; + ctx->ip4->check = 0; + ctx->ip4->check = ipv4_csum(ctx->ip4, sizeof(*ctx->ip4)); +} diff --git a/lib/eupf/ebpf/xdp/utils/gtpu.h b/lib/eupf/ebpf/xdp/utils/gtpu.h new file mode 100644 index 00000000..7cec801d --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/gtpu.h @@ -0,0 +1,76 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#define GTP_UDP_PORT 2152u + +/* Version: GTPv1, Protocol Type: GTP, Others: 0 */ +#define GTP_FLAGS 0x30 + +#define GTPU_ECHO_REQUEST (1) +#define GTPU_ECHO_RESPONSE (2) +#define GTPU_ERROR_INDICATION (26) +#define GTPU_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION (31) +#define GTPU_END_MARKER (254) +#define GTPU_G_PDU (255) + +struct gtpuhdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned int pn : 1; + unsigned int s : 1; + unsigned int e : 1; + unsigned int spare : 1; + unsigned int pt : 1; + unsigned int version : 3; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned int version : 3; + unsigned int pt : 1; + unsigned int spare : 1; + unsigned int e : 1; + unsigned int s : 1; + unsigned int pn : 1; +#else +#error "Please fix " +#endif + __u8 message_type; + __u16 message_length; + __u32 teid; +} __attribute__((packed)); + +/* Optional word of GTP header, present if any of E, S, PN is set. */ +struct gtp_hdr_ext { + __u16 sqn; + __u8 npdu; + __u8 next_ext; +} __attribute__((packed)); + +#define GTPU_EXT_TYPE_PDU_SESSION_CONTAINER (0x85) +#define PDU_SESSION_CONTAINER_PDU_TYPE_DL_PSU (0x00) +#define PDU_SESSION_CONTAINER_PDU_TYPE_UL_PSU (0x01) + +struct gtp_hdr_ext_pdu_session_container { + __u8 length; + __u8 spare1 : 4; + __u8 pdu_type : 4; + __u8 qfi : 6; + __u8 rqi : 1; + __u8 spare2 : 1; + __u8 next_ext; +} __attribute__((packed)); \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/icmp.h b/lib/eupf/ebpf/xdp/utils/icmp.h new file mode 100644 index 00000000..fb833586 --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/icmp.h @@ -0,0 +1,109 @@ +// Copyright 2023 Edgecom LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "xdp/utils/csum.h" +#include "xdp/utils/parsers.h" +#include "xdp/utils/packet_context.h" + +static __always_inline void fill_icmp_header(struct icmphdr *icmp) { + icmp->type = ICMP_TIME_EXCEEDED; + icmp->code = ICMP_EXC_TTL; + icmp->un.gateway = 0; + icmp->checksum = 0; +} + +// static __always_inline __u32 add_icmp_over_ip4_headers(struct packet_context *ctx, int saddr, int daddr) { +// static const size_t icmp_encap_size = sizeof(struct iphdr) + sizeof(struct icmphdr); + +// if (!ctx->ip4) +// return -1; + +// const __u32 ip_packet_len = bpf_ntohs(ctx->ip4->tot_len); + +// int result = bpf_xdp_adjust_head(ctx->xdp_ctx, (__s32)-icmp_encap_size); +// if (result) +// return -1; + +// char *data = (char *)(long)ctx->xdp_ctx->data; +// const char *data_end = (const char *)(long)ctx->xdp_ctx->data_end; + +// struct ethhdr *orig_eth = (struct ethhdr *)(data + icmp_encap_size); +// if ((const char *)(orig_eth + 1) > data_end) +// return -1; + +// struct ethhdr *eth = (struct ethhdr *)data; +// __builtin_memcpy(eth, orig_eth, sizeof(*eth)); +// eth->h_proto = bpf_htons(ETH_P_IP); + +// struct iphdr *ip = (struct iphdr *)(eth + 1); +// if ((const char *)(ip + 1) > data_end) +// return -1; + +// /* Add the outer IP header */ +// fill_ip_header(ip, saddr, daddr, 0, ip_packet_len + icmp_encap_size); +// ip->protocol = IPPROTO_ICMP; +// ip->check = ipv4_csum(ip, sizeof(*ip)); + +// /* Add the ICMP header */ +// struct icmphdr *icmp = (struct icmphdr *)(ip + 1); +// if ((const char *)(icmp + 1) > data_end) +// return -1; + +// fill_icmp_header(icmp); +// const __s8 icmp_payload_size = data_end - (const char *)icmp; +// icmp->checksum = ipv4_csum(icmp, icmp_payload_size); + +// /* Update packet pointers */ +// context_set_ip4(ctx, (char *)(long)ctx->xdp_ctx->data, (const char *)(long)ctx->xdp_ctx->data_end, eth, ip, 0, 0); +// return 0; +// } + +static __always_inline __u32 prepare_icmp_echo_reply(struct packet_context *ctx, int saddr, int daddr) { + if (!ctx->ip4) + return -1; + + struct ethhdr *eth = ctx->eth; + swap_mac(eth); + + const char *data_end = (const char *)(long)ctx->xdp_ctx->data_end; + struct iphdr *ip = ctx->ip4; + if ((const char *)(ip + 1) > data_end) + return -1; + + swap_ip(ip); + + struct icmphdr *icmp = (struct icmphdr *)(ip + 1); + if ((const char *)(icmp + 1) > data_end) + return -1; + + if(icmp->type != ICMP_ECHO) + return -1; + + __u16 old = *(__u16*)&icmp->type; + icmp->type = ICMP_ECHOREPLY; + icmp->code = 0; + + ipv4_csum_replace(&icmp->checksum, old, *(__u16*)&icmp->type); + + return 0; +} \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/packet_context.h b/lib/eupf/ebpf/xdp/utils/packet_context.h new file mode 100644 index 00000000..d9fe0b23 --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/packet_context.h @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "xdp/utils/gtpu.h" + +/* Header cursor to keep track of current parsing position */ +struct packet_context { + char *data; + const char *data_end; + struct upf_counters *counters; + struct n3_n6_counters *n3_n6_counter; + struct xdp_md *xdp_ctx; + struct ethhdr *eth; + struct iphdr *ip4; + struct ipv6hdr *ip6; + struct udphdr *udp; + struct tcphdr *tcp; + struct gtpuhdr *gtp; +}; diff --git a/lib/eupf/ebpf/xdp/utils/parsers.h b/lib/eupf/ebpf/xdp/utils/parsers.h new file mode 100644 index 00000000..937a1bc4 --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/parsers.h @@ -0,0 +1,181 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "xdp/utils/packet_context.h" +#include "xdp/utils/trace.h" + +#define ETH_P_IPV6_BE 0xDD86 +#define ETH_P_IP_BE 0x0008 + +static __always_inline int parse_ethernet(struct packet_context *ctx) { + struct ethhdr *eth = (struct ethhdr *)ctx->data; + if ((const char *)(eth + 1) > ctx->data_end) + return -1; + + /* TODO: Add vlan support */ + + ctx->data += sizeof(*eth); + ctx->eth = eth; + return bpf_ntohs(eth->h_proto); +} + +/* 0x3FFF mask to check for fragment offset field */ +#define IP_FRAGMENTED 65343 + +static __always_inline int parse_ip4(struct packet_context *ctx) { + struct iphdr *ip4 = (struct iphdr *)ctx->data; + if ((const char *)(ip4 + 1) > ctx->data_end) + return -1; + + /* do not support fragmented packets as L4 headers may be missing */ + // if (ip4->frag_off & IP_FRAGMENTED) + // return -1; + + ctx->data += ip4->ihl * 4; /* header + options */ + ctx->ip4 = ip4; + return ip4->protocol; +} + +static __always_inline int parse_ip6(struct packet_context *ctx) { + struct ipv6hdr *ip6 = (struct ipv6hdr *)ctx->data; + if ((const char *)(ip6 + 1) > ctx->data_end) + return -1; + + /* TODO: Add extention headers support */ + + ctx->data += sizeof(*ip6); + ctx->ip6 = ip6; + return ip6->nexthdr; +} + +static __always_inline int parse_udp(struct packet_context *ctx) { + struct udphdr *udp = (struct udphdr *)ctx->data; + if ((const char *)(udp + 1) > ctx->data_end) + return -1; + + ctx->data += sizeof(*udp); + ctx->udp = udp; + return bpf_ntohs(udp->dest); +} + +static __always_inline int parse_tcp(struct packet_context *ctx) { + struct tcphdr *tcp = (struct tcphdr *)ctx->data; + if ((const char *)(tcp + 1) > ctx->data_end) + return -1; + + //TODO: parse header lenght correctly (tcp options) + + ctx->data += sizeof(*tcp); + ctx->tcp = tcp; + return bpf_ntohs(tcp->dest); +} + +static __always_inline int parse_l4(int ip_protocol, struct packet_context *ctx) +{ + switch (ip_protocol) { + case IPPROTO_UDP: + return parse_udp(ctx); + case IPPROTO_TCP: + return parse_tcp(ctx); + default: + return 0; + } +} + +static __always_inline void swap_mac(struct ethhdr *eth) { + __u8 mac[6]; + __builtin_memcpy(mac, eth->h_source, sizeof(mac)); + __builtin_memcpy(eth->h_source, eth->h_dest, sizeof(eth->h_source)); + __builtin_memcpy(eth->h_dest, mac, sizeof(eth->h_dest)); +} + +static __always_inline void swap_port(struct udphdr *udp) { + __u16 tmp = udp->dest; + udp->dest = udp->source; + udp->source = tmp; + /* Update UDP checksum */ + udp->check = 0; + // cs = 0; + // ipv4_l4_csum(udp, sizeof(*udp), &cs, iph); + // udp->check = cs; +} + +static __always_inline void swap_ip(struct iphdr *iph) { + __u32 tmp_ip = iph->daddr; + iph->daddr = iph->saddr; + iph->saddr = tmp_ip; + + /* Don't need to recalc csum in case of ip swap */ + // ip->check = ipv4_csum(ip, sizeof(*ip)); +} + +static __always_inline void context_set_ip4(struct packet_context *ctx, char *data, const char *data_end, struct ethhdr *eth, struct iphdr *ip4, struct udphdr *udp, struct gtpuhdr *gtp) { + ctx->data = data; + ctx->data_end = data_end; + ctx->eth = eth; + ctx->ip4 = ip4; + ctx->ip6 = 0; + ctx->udp = udp; + ctx->gtp = gtp; +} + +static __always_inline void context_reset(struct packet_context *ctx, char *data, const char *data_end) { + ctx->data = data; + ctx->data_end = data_end; + ctx->eth = 0; + ctx->ip4 = 0; + ctx->ip6 = 0; + ctx->udp = 0; + ctx->gtp = 0; +} + + +static __always_inline long context_reinit(struct packet_context *ctx, char *data, const char *data_end) { + context_reset(ctx, data, data_end); + + int ethertype = parse_ethernet(ctx); + switch (ethertype) { + case ETH_P_IPV6: { + if (-1 == parse_ip6(ctx)) { + upf_printk("upf: can't parse ip6"); + return -1; + } + return 0; + } + case ETH_P_IP: { + if (-1 == parse_ip4(ctx)) { + upf_printk("upf: can't parse ip4"); + return -1; + } + return 0; + } + + default: + /* do nothing with non-ip packets */ + upf_printk("upf: can't process not an ip packet: %d", ethertype); + return -1; + } +} diff --git a/lib/eupf/ebpf/xdp/utils/routing.h b/lib/eupf/ebpf/xdp/utils/routing.h new file mode 100644 index 00000000..9aeaa11a --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/routing.h @@ -0,0 +1,204 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdp/utils/trace.h" + +struct route_stat { + __u64 fib_lookup_ip4_cache; + __u64 fib_lookup_ip4_ok; + __u64 fib_lookup_ip4_error_drop; + __u64 fib_lookup_ip4_error_pass; + __u64 fib_lookup_ip6_cache; + __u64 fib_lookup_ip6_ok; + __u64 fib_lookup_ip6_error_drop; + __u64 fib_lookup_ip6_error_pass; +}; + +struct +{ + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, struct route_stat); + __uint(max_entries, 1); +} upf_route_stat SEC(".maps"); + +#ifdef ENABLE_ROUTE_CACHE + +#warning "Routing cache enabled" + +#define ROUTE_CACHE_IPV4_SIZE 256 +#define ROUTE_CACHE_IPV6_SIZE 256 + +struct route_record { + int ifindex; + __u8 smac[6]; + __u8 dmac[6]; +}; + +/* ipv4 -> fib cached result */ +struct +{ + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __type(key, __u32); + __type(value, struct route_record); + __uint(max_entries, ROUTE_CACHE_IPV4_SIZE); +} upf_route_cache_ip4 SEC(".maps"); + +/* ipv6 -> fib cached result */ +struct +{ + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __type(key, struct in6_addr); + __type(value, struct route_record); + __uint(max_entries, ROUTE_CACHE_IPV6_SIZE); +} upf_route_cache_ip6 SEC(".maps"); + +static __always_inline void update_route_cache_ipv4(const struct bpf_fib_lookup *fib_params, __u32 daddr) { + struct route_record route = { + .ifindex = fib_params->ifindex, + }; + __builtin_memcpy(route.smac, fib_params->smac, ETH_ALEN); + __builtin_memcpy(route.dmac, fib_params->dmac, ETH_ALEN); + bpf_map_update_elem(&upf_route_cache_ip4, &daddr, &route, BPF_ANY); +} +#endif + +static __always_inline enum xdp_action do_route_ipv4(struct xdp_md *ctx, struct ethhdr *eth, __u32 ifindex, __u8 (*smac)[6], __u8 (*dmac)[6]) { + //_decr_ttl(ether_proto, l3hdr); + __builtin_memcpy(eth->h_source, smac, ETH_ALEN); + __builtin_memcpy(eth->h_dest, dmac, ETH_ALEN); + + if (ifindex == ctx->ingress_ifindex) + return XDP_TX; + return bpf_redirect(ifindex, 0); +} + +static __always_inline enum xdp_action route_ipv4(struct xdp_md *ctx, struct ethhdr *eth, const struct iphdr *ip4, __u32 ifindex) { + const __u32 key = 0; + struct route_stat *statistic = bpf_map_lookup_elem(&upf_route_stat, &key); + if (!statistic) { + return XDP_ABORTED; + } + +#ifdef ENABLE_ROUTE_CACHE + struct route_record *cache = bpf_map_lookup_elem(&upf_route_cache_ip4, &ip4->daddr); + if (cache) { + upf_printk("upf: bpf_fib_lookup %pI4 -> %pI4: cached ifindex: %d", &ip4->saddr, &ip4->daddr, cache->ifindex); + statistic->fib_lookup_ip4_cache += 1; + return do_route_ipv4(ctx, eth, cache->ifindex, &cache->smac, &cache->dmac); + } +#endif + + struct bpf_fib_lookup fib_params = {}; + fib_params.family = AF_INET; + fib_params.tos = ip4->tos; + fib_params.l4_protocol = ip4->protocol; + fib_params.sport = 0; + fib_params.dport = 0; + fib_params.tot_len = bpf_ntohs(ip4->tot_len); + //fib_params.ipv4_src = ip4->saddr; + fib_params.ipv4_dst = ip4->daddr; + fib_params.ifindex = ifindex; + + int rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0 /*BPF_FIB_LOOKUP_OUTPUT*/); + switch (rc) { + case BPF_FIB_LKUP_RET_SUCCESS: + upf_printk("upf: bpf_fib_lookup1 %pI4 -> %pI4: nexthop: %pI4", &ip4->saddr, &ip4->daddr, &fib_params.ipv4_dst); + statistic->fib_lookup_ip4_ok += 1; + +#ifdef ENABLE_ROUTE_CACHE + update_route_cache_ipv4(&fib_params, ip4->daddr); +#endif + return do_route_ipv4(ctx, eth, fib_params.ifindex, &fib_params.smac, &fib_params.dmac); + + case BPF_FIB_LKUP_RET_BLACKHOLE: + case BPF_FIB_LKUP_RET_UNREACHABLE: + case BPF_FIB_LKUP_RET_PROHIBIT: + upf_printk("upf: bpf_fib_lookup2 %pI4 -> %pI4: %d", &ip4->saddr, &ip4->daddr, rc); + statistic->fib_lookup_ip4_error_drop += 1; + return XDP_DROP; + case BPF_FIB_LKUP_RET_NOT_FWDED: + case BPF_FIB_LKUP_RET_FWD_DISABLED: + case BPF_FIB_LKUP_RET_UNSUPP_LWT: + case BPF_FIB_LKUP_RET_NO_NEIGH: + case BPF_FIB_LKUP_RET_FRAG_NEEDED: + default: + upf_printk("upf: bpf_fib_lookup3 %pI4 -> %pI4: %d", &ip4->saddr, &ip4->daddr, rc); + statistic->fib_lookup_ip4_error_pass += 1; + return XDP_PASS; /* Let's kernel takes care */ + } +} + +static __always_inline enum xdp_action route_ipv6(struct xdp_md *ctx, struct ethhdr *eth, const struct ipv6hdr *ip6) { + const __u32 key = 0; + struct route_stat *statistic = bpf_map_lookup_elem(&upf_route_stat, &key); + if (!statistic) { + return XDP_ABORTED; + } + + struct bpf_fib_lookup fib_params = {}; + fib_params.family = AF_INET; + // fib_params.tos = ip6->flow_lbl; + fib_params.l4_protocol = ip6->nexthdr; + fib_params.sport = 0; + fib_params.dport = 0; + fib_params.tot_len = bpf_ntohs(ip6->payload_len); + __builtin_memcpy(fib_params.ipv6_src, &ip6->saddr, sizeof(ip6->saddr)); + __builtin_memcpy(fib_params.ipv6_dst, &ip6->daddr, sizeof(ip6->daddr)); + fib_params.ifindex = ctx->ingress_ifindex; + + int rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0 /*BPF_FIB_LOOKUP_OUTPUT*/); + switch (rc) { + case BPF_FIB_LKUP_RET_SUCCESS: + upf_printk("upf: bpf_fib_lookup %pI6c -> %pI6c: nexthop: %pI4", &ip6->saddr, &ip6->daddr, &fib_params.ipv4_dst); + statistic->fib_lookup_ip6_ok += 1; + //_decr_ttl(ether_proto, l3hdr); + __builtin_memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN); + __builtin_memcpy(eth->h_source, fib_params.smac, ETH_ALEN); + upf_printk("upf: bpf_redirect: if=%d %lu -> %lu", fib_params.ifindex, fib_params.smac, fib_params.dmac); + + if (fib_params.ifindex == ctx->ingress_ifindex) + return XDP_TX; + + return bpf_redirect(fib_params.ifindex, 0); + case BPF_FIB_LKUP_RET_BLACKHOLE: + case BPF_FIB_LKUP_RET_UNREACHABLE: + case BPF_FIB_LKUP_RET_PROHIBIT: + upf_printk("upf: bpf_fib_lookup %pI6c -> %pI6c: %d", &ip6->saddr, &ip6->daddr, rc); + statistic->fib_lookup_ip6_error_drop += 1; + return XDP_DROP; + case BPF_FIB_LKUP_RET_NOT_FWDED: + case BPF_FIB_LKUP_RET_FWD_DISABLED: + case BPF_FIB_LKUP_RET_UNSUPP_LWT: + case BPF_FIB_LKUP_RET_NO_NEIGH: + case BPF_FIB_LKUP_RET_FRAG_NEEDED: + default: + upf_printk("upf: bpf_fib_lookup %pI6c -> %pI6c: %d", &ip6->saddr, &ip6->daddr, rc); + statistic->fib_lookup_ip6_error_pass += 1; + return XDP_PASS; /* Let's kernel takes care */ + } +} \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/trace.h b/lib/eupf/ebpf/xdp/utils/trace.h new file mode 100644 index 00000000..dc2a4f36 --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/trace.h @@ -0,0 +1,33 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +//#define ENABLE_LOG + +#ifdef ENABLE_LOG // trace_pipe logs disabled by default +#warning "Debug log enabled" +#define upf_printk(fmt, ...) \ + ({ \ + static const char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ + }) +#else +#define upf_printk(fmt, ...) +#endif \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/utils/types.h b/lib/eupf/ebpf/xdp/utils/types.h new file mode 100644 index 00000000..ac9af420 --- /dev/null +++ b/lib/eupf/ebpf/xdp/utils/types.h @@ -0,0 +1,20 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + + +typedef unsigned __int128 __u128; \ No newline at end of file diff --git a/lib/eupf/ebpf/xdp/zero_entrypoint.c b/lib/eupf/ebpf/xdp/zero_entrypoint.c new file mode 100644 index 00000000..8a9cdebe --- /dev/null +++ b/lib/eupf/ebpf/xdp/zero_entrypoint.c @@ -0,0 +1,26 @@ +/** + * Copyright 2023 Edgecom LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +/* Zero entrypoint purposed for enabling native attach mode for veth */ +SEC("xdp/upf_zero_entrypoint") +int upf_n3_entrypoint_func(struct xdp_md *ctx) { + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/lib/eupf/ebpf/zeroentrypoint_bpf.go b/lib/eupf/ebpf/zeroentrypoint_bpf.go new file mode 100644 index 00000000..6876b80d --- /dev/null +++ b/lib/eupf/ebpf/zeroentrypoint_bpf.go @@ -0,0 +1,114 @@ +// Code generated by bpf2go; DO NOT EDIT. + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// LoadZeroEntrypoint returns the embedded CollectionSpec for ZeroEntrypoint. +func LoadZeroEntrypoint() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_ZeroEntrypointBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load ZeroEntrypoint: %w", err) + } + + return spec, err +} + +// LoadZeroEntrypointObjects loads ZeroEntrypoint and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *ZeroEntrypointObjects +// *ZeroEntrypointPrograms +// *ZeroEntrypointMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func LoadZeroEntrypointObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := LoadZeroEntrypoint() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// ZeroEntrypointSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ZeroEntrypointSpecs struct { + ZeroEntrypointProgramSpecs + ZeroEntrypointMapSpecs +} + +// ZeroEntrypointSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ZeroEntrypointProgramSpecs struct { + UpfN3EntrypointFunc *ebpf.ProgramSpec `ebpf:"upf_n3_entrypoint_func"` +} + +// ZeroEntrypointMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ZeroEntrypointMapSpecs struct { +} + +// ZeroEntrypointObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to LoadZeroEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type ZeroEntrypointObjects struct { + ZeroEntrypointPrograms + ZeroEntrypointMaps +} + +func (o *ZeroEntrypointObjects) Close() error { + return _ZeroEntrypointClose( + &o.ZeroEntrypointPrograms, + &o.ZeroEntrypointMaps, + ) +} + +// ZeroEntrypointMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to LoadZeroEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type ZeroEntrypointMaps struct { +} + +func (m *ZeroEntrypointMaps) Close() error { + return _ZeroEntrypointClose() +} + +// ZeroEntrypointPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to LoadZeroEntrypointObjects or ebpf.CollectionSpec.LoadAndAssign. +type ZeroEntrypointPrograms struct { + UpfN3EntrypointFunc *ebpf.Program `ebpf:"upf_n3_entrypoint_func"` +} + +func (p *ZeroEntrypointPrograms) Close() error { + return _ZeroEntrypointClose( + p.UpfN3EntrypointFunc, + ) +} + +func _ZeroEntrypointClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed zeroentrypoint_bpf.o +var _ZeroEntrypointBytes []byte diff --git a/lib/eupf/ebpf/zeroentrypoint_bpf.o b/lib/eupf/ebpf/zeroentrypoint_bpf.o new file mode 100644 index 00000000..6e74f321 Binary files /dev/null and b/lib/eupf/ebpf/zeroentrypoint_bpf.o differ diff --git a/lib/eupf/generate.sh b/lib/eupf/generate.sh new file mode 100755 index 00000000..1cdb795c --- /dev/null +++ b/lib/eupf/generate.sh @@ -0,0 +1,2 @@ +#!/bin/bash +go generate -v ./ebpf/...