├── docs ├── pk.png ├── console.png ├── benchmarks.xlsx ├── environment │ ├── build_on_VMs │ │ ├── socket │ │ │ ├── tcp │ │ │ │ ├── client │ │ │ │ ├── server │ │ │ │ ├── server.go │ │ │ │ └── client.go │ │ │ ├── udp │ │ │ │ ├── client │ │ │ │ ├── server │ │ │ │ ├── server.go │ │ │ │ └── client.go │ │ │ └── readme.md │ │ ├── update_trafgen.sh │ │ ├── update_xdp-acl.sh │ │ ├── readme.md │ │ └── Vagrantfile │ ├── build_on_docker │ │ ├── Makefile │ │ ├── readme.md │ │ ├── Dockerfile.fedora │ │ └── Dockerfile.ubuntu │ ├── upgrade_kernel.md │ └── build_on_host │ │ └── readme.md └── restful-api.md ├── public ├── favicon.ico ├── fonts │ ├── element-icons.535877f5.woff │ └── element-icons.732389de.ttf ├── js │ ├── runtime.16f8e42f.js │ ├── npm.resize-observer-polyfill.1884e205.js │ ├── vendors~app.a19a9911.js │ ├── npm.async-validator.7418a59e.js │ ├── npm.axios.5314a0a4.js │ ├── npm.babel-runtime.87554630.js │ └── app.d0e3b634.js ├── css │ └── app.a33dffb1.css └── index.html ├── .gitignore ├── Makefile ├── script └── compile-bpf.sh ├── ebpf ├── xdp_acl.c └── headers │ ├── libxdp_map.h │ ├── bpf_endian.h │ ├── libxdp_acl.h │ ├── bpf_helpers.h │ └── bpf_core_read.h ├── rule_check.go ├── bitmap.go ├── log.go ├── rule_port.go ├── main.go ├── flag.go ├── readme.md ├── acl.json ├── go.mod ├── rule_addr.go ├── xdp_rule.go ├── .clang-format ├── web.go ├── xdp.go ├── rule.go └── LICENSE /docs/pk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/pk.png -------------------------------------------------------------------------------- /docs/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/console.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /docs/benchmarks.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/benchmarks.xlsx -------------------------------------------------------------------------------- /public/fonts/element-icons.535877f5.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/public/fonts/element-icons.535877f5.woff -------------------------------------------------------------------------------- /public/fonts/element-icons.732389de.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/public/fonts/element-icons.732389de.ttf -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/tcp/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/environment/build_on_VMs/socket/tcp/client -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/tcp/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/environment/build_on_VMs/socket/tcp/server -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/udp/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/environment/build_on_VMs/socket/udp/client -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/udp/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asphaltt/xdp_acl/HEAD/docs/environment/build_on_VMs/socket/udp/server -------------------------------------------------------------------------------- /docs/environment/build_on_docker/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: ubuntu fedora 2 | 3 | ubuntu: Dockerfile.ubuntu 4 | docker build -t xdp_acl_ubuntu:0.0.1 -f Dockerfile.ubuntu . 5 | 6 | fedora: Dockerfile.fedora 7 | docker build -t xdp_acl_fedora:0.0.1 -f Dockerfile.fedora . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xdp_acl 2 | xdpacl 3 | *_bpfeb.go 4 | *_bpfel.go 5 | 6 | log 7 | acl 8 | 9 | # Object files 10 | *.o 11 | *.ko 12 | *.obj 13 | *.elf 14 | *.s 15 | *.ll 16 | 17 | # editor 18 | .vscode/ 19 | 20 | # generated files 21 | /ebpf/headers/libxdp_generated.h 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PROJECT = xdp_acl 3 | 4 | .PHONY: clean $(PROJECT) pub 5 | 6 | 7 | all: main.go 8 | @go generate && go build 9 | 10 | clean: 11 | @rm -rf *.o *_bpfeb.go *_bpfel.go $(PROJECT) 12 | 13 | pub: xdp_acl public acl.json readme.md 14 | @if [ -d "acl" ]; then \ 15 | rm -rf acl; \ 16 | fi; \ 17 | mkdir acl; \ 18 | cp xdp_acl acl; \ 19 | cp -rf public acl; \ 20 | cp readme.md acl; \ 21 | cp acl.json acl; \ 22 | echo "Pub OK!"; 23 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/readme.md: -------------------------------------------------------------------------------- 1 | #### Test 2 | 3 | * tcp 4 | 5 | ``` 6 | # Start tcp server on VM xdp-acl 7 | $ /vagrant/socket/tcp/server 172.20.6.3:3333 8 | # Start tcp client on VM trafgen 9 | $ /vagrant/socket/tcp/client 172.20.6.3:3333 500 foo 10 | ``` 11 | 12 | * udp 13 | 14 | ``` 15 | # Start udp server on VM xdp-acl 16 | $ /vagrant/socket/udp/server 172.20.6.3:4444 17 | # Start udp client on VM trafgen 18 | $ /vagrant/socket/udp/client 172.20.6.3:4444 500 bar 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/update_trafgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # echo "will update 🍏" 5 | 6 | yes | cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | 8 | # sleep 1s 9 | 10 | # echo "dnf update begin 🍎 please wait a moment..." 11 | 12 | # sleep 1s 13 | 14 | # dnf install netsniff-ng -y -q 15 | 16 | # dnf module install nodejs:16/default -y -q 17 | # echo "🍊 node version:" && node -v 18 | 19 | # sleep 1s 20 | 21 | # dnf clean all -y 22 | 23 | # echo "dnf update complete 😃" 24 | 25 | -------------------------------------------------------------------------------- /docs/environment/build_on_docker/readme.md: -------------------------------------------------------------------------------- 1 | ### Ubuntu 2 | ``` 3 | # Create image (Under current path of this project) 4 | $ make ubuntu 5 | 6 | # Compile (Under the root path of this project) 7 | $ docker run --rm -v `pwd`:/workspace xdp_acl_ubuntu:0.0.1 bash -c "make clean && make" 8 | ``` 9 | 10 | 11 | ### Fedora 12 | ``` 13 | # Create image (Under current path of this project) 14 | $ make fedora 15 | 16 | # Compile (Under the root path of this project) 17 | $ docker run --rm -v `pwd`:/workspace xdp_acl_fedora:0.0.1 bash -c "make clean && make" 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /docs/environment/build_on_docker/Dockerfile.fedora: -------------------------------------------------------------------------------- 1 | FROM fedora:33 2 | 3 | LABEL author = "glenn" \ 4 | email = "promise_wg@qq.com" 5 | 6 | RUN dnf install clang llvm -y \ 7 | && dnf install elfutils-libelf-devel libpcap-devel perf -y \ 8 | && dnf install kernel-headers -y \ 9 | && dnf install bpftool -y \ 10 | && dnf install golang -y \ 11 | && dnf clean all -y 12 | 13 | ENV GOPROXY https://goproxy.cn 14 | ENV GOPATH /workspace/vendor_tmp 15 | ENV PATH $GOPATH/bin:$PATH 16 | ENV GO111MODULE on 17 | 18 | WORKDIR /workspace 19 | 20 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /docs/environment/build_on_docker/Dockerfile.ubuntu: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | LABEL author = "glenn" \ 4 | email = "promise_wg@qq.com" 5 | 6 | RUN apt update \ 7 | && apt install -y software-properties-common \ 8 | && add-apt-repository ppa:longsleep/golang-backports -y \ 9 | && apt update \ 10 | && apt install golang-go -y \ 11 | && apt install clang llvm libelf-dev libpcap-dev gcc-multilib build-essential -y \ 12 | && apt clean all 13 | 14 | ENV GOPROXY https://goproxy.cn 15 | ENV GOPATH /workspace/vendor_tmp 16 | ENV PATH $GOPATH/bin:$PATH 17 | ENV GO111MODULE on 18 | 19 | WORKDIR /workspace 20 | 21 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /docs/environment/upgrade_kernel.md: -------------------------------------------------------------------------------- 1 | #### Ubuntu 2 | ``` 3 | $ git clone https://github.com/mtompkins/linux-kernel-utilities.git 4 | $ cd linux-kernel-utilities/ 5 | $ chmod 750 *.sh 6 | $ git pull 7 | 8 | # Select specified version on the list (not lowlatency version) 9 | $ ./update_ubuntu_kernel.sh 10 | $ reboot 11 | ``` 12 | 13 | #### Fedora 14 | ``` 15 | $ curl -s https://repos.fedorapeople.org/repos/thl/kernel-vanilla.repo | sudo tee /etc/yum.repos.d/kernel-vanilla.repo 16 | $ dnf config-manager --set-enabled kernel-vanilla-stable 17 | 18 | # Automatically upgrade to the latest stable version without choice. 19 | $ dnf update -y 20 | $ reboot 21 | ``` -------------------------------------------------------------------------------- /script/compile-bpf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | CUR_DIR=$(cd $(dirname $0) || exit 1; pwd -P) 6 | BPFFILE="${CUR_DIR}/../ebpf/headers/libxdp_generated.h" 7 | 8 | build_bpf() { 9 | num="$1" 10 | cat >${BPFFILE} <data_end; 14 | void *data = (void *)(long)ctx->data; 15 | 16 | struct ethhdr *eth; 17 | eth = (typeof(eth))data; 18 | if ((void *)(eth + 1) > data_end) 19 | return XDP_PASS; 20 | 21 | __u16 l3_proto = eth->h_proto; 22 | 23 | if (bpf_htons(ETH_P_IP) == l3_proto) { 24 | bpf_tail_call_static(ctx, &progs, 0); 25 | } 26 | 27 | return XDP_PASS; 28 | } 29 | 30 | char _license[] SEC("license") = "GPL"; 31 | -------------------------------------------------------------------------------- /rule_check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func (r *Rule) checkProtos() error { 6 | if r.Protos>>3 > 0 || r.Protos&0b0111 == 0 { 7 | return fmt.Errorf("rule protos(%04b) is invalid", r.Protos) 8 | } 9 | 10 | return nil 11 | } 12 | 13 | func (r *Rule) checkStrategy() error { 14 | if strategy := r.Strategy; strategy != xdpDrop && strategy != xdpPass { 15 | return fmt.Errorf("rule strategy(%d) is invalid", strategy) 16 | } 17 | 18 | return nil 19 | } 20 | 21 | func (r *Rule) check() error { 22 | type checker func() error 23 | checkers := []checker{ 24 | r.checkProtos, 25 | r.checkStrategy, 26 | } 27 | 28 | for _, c := range checkers { 29 | if err := c(); err != nil { 30 | return err 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /bitmap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | bitmapSize = 64 12 | bitmapMask = bitmapSize - 1 13 | ) 14 | 15 | type bitmap []uint64 16 | 17 | func newBitmap(arrSize int) bitmap { 18 | return make(bitmap, arrSize) 19 | } 20 | 21 | func (b bitmap) Set(index uint32) { 22 | b[index>>6] |= 1 << (index & bitmapMask) // >>6 = /64, &63 = %64 23 | } 24 | 25 | func (b bitmap) MarshalBinary() ([]byte, error) { 26 | bm := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 27 | 28 | var bytes []byte 29 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) 30 | sh.Data = bm.Data 31 | sh.Len = len(b) << 3 // <<3 = *8 32 | sh.Cap = sh.Len 33 | return bytes, nil 34 | } 35 | 36 | func (b bitmap) String() string { 37 | var s []string 38 | for _, n := range b { 39 | s = append(s, fmt.Sprintf("%b", n)) 40 | } 41 | return fmt.Sprintf("{%s}", strings.Join(s, " ")) 42 | } 43 | -------------------------------------------------------------------------------- /docs/environment/build_on_host/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### Install dependencies 3 | 4 | ##### Download and install [Golang](https://golang.org/dl/) 5 | 6 | ##### Platform dependencies 7 | 8 | - Ubuntu20.04 or later 9 | 10 | ``` 11 | $ apt update 12 | $ apt install clang llvm libelf-dev libpcap-dev gcc-multilib build-essential -y 13 | $ apt install linux-tools-$(uname -r) -y 14 | $ apt install linux-tools-common linux-tools-generic -y 15 | ``` 16 | 17 | - Fedora31 or later 18 | 19 | ``` 20 | $ dnf install clang llvm -y 21 | $ dnf install elfutils-libelf-devel libpcap-devel perf -y 22 | $ dnf install kernel-headers -y 23 | $ dnf install bpftool -y 24 | ``` 25 | 26 | ### Compile 27 | After the environment is ok, you can execute the following cmds(root privileges) under project path. 28 | 29 | * Compile 30 | ``` 31 | $ make 32 | ``` 33 | 34 | * Clean the project 35 | ``` 36 | $ make clean 37 | ``` 38 | 39 | * Under the acl path, generate all necessary files for deploy. You can copy that path and run it on another machine 40 | ``` 41 | $ make pub 42 | ``` -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/natefinch/lumberjack" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | ) 8 | 9 | var zlog *zap.SugaredLogger 10 | 11 | func init() { 12 | writeSyncer := getLogWriter() 13 | encoder := getEncoder() 14 | 15 | core := zapcore.NewCore( 16 | encoder, 17 | // zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), writeSyncer), 18 | zapcore.NewMultiWriteSyncer(writeSyncer), 19 | zapcore.InfoLevel, 20 | // zapcore.DebugLevel, 21 | ) 22 | 23 | logger := zap.New(core, zap.AddCaller()) 24 | 25 | zlog = logger.Sugar() 26 | } 27 | 28 | func getEncoder() zapcore.Encoder { 29 | encoderConfig := zap.NewProductionEncoderConfig() 30 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 31 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 32 | // encoderConfig.CallerKey = "linenum" 33 | return zapcore.NewConsoleEncoder(encoderConfig) 34 | } 35 | 36 | func getLogWriter() zapcore.WriteSyncer { 37 | lumberJackLogger := &lumberjack.Logger{ 38 | Filename: "./log/acl.log", 39 | MaxSize: 10, // MByte 40 | MaxBackups: 5, 41 | MaxAge: 30, 42 | Compress: false, 43 | LocalTime: true, 44 | } 45 | return zapcore.AddSync(lumberJackLogger) 46 | } 47 | -------------------------------------------------------------------------------- /rule_port.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func (r *Rules) addSrcPortPriority(rule *Rule) { 4 | if rule.onlyICMP() { 5 | return 6 | } 7 | 8 | if len(rule.PortSrcArr) == 0 { // including all ports 9 | r.allSrcPortPriorities = append(r.allSrcPortPriorities, rule.Priority) 10 | return 11 | } 12 | 13 | for _, port := range rule.PortSrcArr { 14 | r.srcPortPriorities[port] = append(r.srcPortPriorities[port], rule.Priority) 15 | } 16 | } 17 | 18 | func (r *Rules) addDstPortPriority(rule *Rule) { 19 | if rule.onlyICMP() { 20 | return 21 | } 22 | 23 | if len(rule.PortDstArr) == 0 { // including all ports 24 | r.allDstPortPriorities = append(r.allDstPortPriorities, rule.Priority) 25 | return 26 | } 27 | 28 | for _, port := range rule.PortDstArr { 29 | r.dstPortPriorities[port] = append(r.dstPortPriorities[port], rule.Priority) 30 | } 31 | } 32 | 33 | func (r *Rules) fixPortPriority(all []uint32, arr [][]uint32) { 34 | if len(all) == 0 { 35 | return 36 | } 37 | 38 | for port := portBegin; port <= portEnd; port++ { 39 | arr[port] = append(arr[port], all...) 40 | } 41 | } 42 | 43 | func (r *Rules) fixSrcPortPriority() { 44 | r.fixPortPriority(r.allSrcPortPriorities, r.srcPortPriorities) 45 | } 46 | 47 | func (r *Rules) fixDstPortPriority() { 48 | r.fixPortPriority(r.allDstPortPriorities, r.dstPortPriorities) 49 | } 50 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/udp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | if len(os.Args) != 2 { 13 | fmt.Println("params error") 14 | return 15 | } 16 | 17 | hostPort := strings.Split(os.Args[1], ":") 18 | 19 | if len(hostPort) != 2 { 20 | fmt.Println("params error") 21 | return 22 | } 23 | 24 | ip := net.ParseIP(hostPort[0]) 25 | port, err := strconv.Atoi(hostPort[1]) 26 | if err != nil { 27 | fmt.Println("port is invalid") 28 | return 29 | } 30 | 31 | // 建立 udp 服务器 32 | listen, err := net.ListenUDP("udp", &net.UDPAddr{ 33 | IP: ip, 34 | Port: port, 35 | }) 36 | if err != nil { 37 | fmt.Printf("listen failed error:%v\n", err) 38 | return 39 | } else { 40 | fmt.Printf("udp server listen on: %s\n", listen.LocalAddr()) 41 | } 42 | defer listen.Close() // 使用完关闭服务 43 | 44 | num := 0 45 | for { 46 | // 接收数据 47 | var data [1024]byte 48 | n, addr, err := listen.ReadFromUDP(data[:]) 49 | if err != nil { 50 | fmt.Printf("read data error:%v\n", err) 51 | return 52 | } 53 | fmt.Printf("from: %s; %v\n", addr, string(data[:n])) 54 | 55 | // 发送数据 56 | num++ 57 | _, err = listen.WriteToUDP([]byte(fmt.Sprintf("%d", num)), addr) 58 | if err != nil { 59 | fmt.Printf("send data error:%v\n", err) 60 | return 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/cilium/ebpf/rlimit" 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | //go:generate bash ./script/compile-bpf.sh 15 | 16 | func main() { 17 | b := time.Now() 18 | 19 | flags := parseFlags() 20 | 21 | if err := rlimit.RemoveMemlock(); err != nil { 22 | zlog.Fatalf("Failed to remove memory lock: %v", err) 23 | } 24 | 25 | zlog.Info("dev: ", flags.Dev) 26 | if len(flags.Dev) == 0 { 27 | return 28 | } 29 | 30 | ts := time.Now() 31 | rules, err := LoadRules(flags.Conf, flags.LastRuleAccept, flags.LastRuleFixed) 32 | if err != nil { 33 | zlog.Errorf("Failed to load rules: %v", err) 34 | return 35 | } 36 | zlog.Infof("🍉 name: %s. Cost: %s.", "loading rules", time.Since(ts)) 37 | 38 | xdp, err := newXdp(flags, rules) 39 | if err != nil { 40 | zlog.Fatalf("Failed to new xdp: %v", err) 41 | } 42 | 43 | defer xdp.Close() 44 | 45 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 46 | defer stop() 47 | 48 | errg, ctx := errgroup.WithContext(ctx) 49 | errg.Go(func() error { 50 | return runWebApp(ctx, flags, rules, xdp) 51 | }) 52 | 53 | zlog.Infof("🍉🍉 name: %s. Cost: %s.", "app", time.Since(b)) 54 | 55 | if err := errg.Wait(); err != nil { 56 | zlog.Errorf("Failed to run web server: %v", err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/update_xdp-acl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # set -ex 4 | 5 | echo "will update 🍏" 6 | 7 | yes | cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 8 | 9 | systemctl stop firewalld && systemctl disable firewalld 10 | 11 | sleep 1s 12 | 13 | echo "dnf update begin 🍎 It will take some time here, please be patient..." 14 | 15 | mkdir -p /root/workspace/golang/src 16 | 17 | echo -e "\n" >> /root/.bashrc 18 | echo "export GOPROXY=https://goproxy.cn" >> /root/.bashrc 19 | echo -e "\n" >> /root/.bashrc 20 | echo "export GOPATH=~/workspace/golang" >> /root/.bashrc 21 | echo "export GOBIN=$GOPATH/bin/" >> /root/.bashrc 22 | echo "export PATH=$GOBIN:$PATH" >> /root/.bashrc 23 | echo -e "\n" >> /root/.bashrc 24 | 25 | source /root/.bashrc 26 | 27 | dnf install golang -y -q 28 | echo "🍊 golang version:" && go version 29 | 30 | # dnf module install nodejs:16/default -y -q 31 | # echo "🍊 node version:" && node -v 32 | 33 | sleep 1s 34 | 35 | dnf install clang -y -q 36 | dnf install llvm -y -q 37 | dnf install elfutils-libelf-devel -y -q 38 | dnf install libpcap-devel -y -q 39 | dnf install perf -y -q 40 | 41 | dnf install kernel-headers -y -q 42 | dnf install bpftool -y -q 43 | 44 | # dnf install netsniff-ng -y -q 45 | 46 | sleep 1s 47 | 48 | dnf clean all -y 49 | 50 | cd /root/workspace/golang/src && git clone https://github.com/glennWang/xdp_acl.git 51 | 52 | echo "dnf update complete 😃" 53 | -------------------------------------------------------------------------------- /public/js/runtime.16f8e42f.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,i=r[0],f=r[1],a=r[2],c=0,s=[];c.addr-ip-item label{text-align:left}.addr-ip-view>.addr-ip-item .addr-ip-item-textarea{width:199px}.addr-ip-view>.addr-ip-item .addr-ip-item-textarea>textarea{height:65px}.el-textarea__inner{padding:10px!important}.el-input__inner{padding:0 10px!important}.one-line-select{width:340px}.addButton.el-button{margin-bottom:24px;font-size:13px}.tablCellClassName.el-table td{padding:10px 0}.el-tag--plain.el-tag--info{margin-right:5px}.port-span{word-break:keep-all}.port-view{white-space:pre-wrap}.elx-cell.c--ellipsis{max-height:none!important}.elx-table.size--small{font-size:12px!important}.dst_text{line-height:10px}.el-message-box{padding-bottom:20px!important}.el-message-box__btns>button{padding:7px 15px}.el-dialog>.el-dialog__body{padding:10px 20px}.el-dialog>.el-dialog__body>p{margin:0}input::-webkit-inner-spin-button,input::-webkit-outer-spin-button{-webkit-appearance:none}input[type=number]{-moz-appearance:textfield}.elx-table--border-line{border:none!important}.el-notification__content{font-size:12px} -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/readme.md: -------------------------------------------------------------------------------- 1 | ## Environment ready 2 | * Download and install [vagrant](https://www.vagrantup.com/downloads) 3 | 4 | * Download and install [virtualbox](https://www.virtualbox.org/wiki/Downloads) 5 | 6 | 7 | ## Get started 8 | 9 | #### Create VM 10 | 11 | * Execute the following cmd under the path playground.(If it goes well, you will have two VMs called xdp-acl and trafgen.) 12 | ``` 13 | $ vagrant up 14 | ``` 15 | 16 | * Login VM xdp-acl as user vagrant 17 | ``` 18 | $ vagrant ssh xdp-acl 19 | ``` 20 | 21 | * Then switch account as root 22 | ``` 23 | $ sudo su - 24 | ``` 25 | 26 | #### Development 27 | 28 | The project will be git clone to the following path of VM xdp-acl. 29 | ``` 30 | /root/workspace/golang/src/xdp_acl 31 | ``` 32 | 33 | Under project path, you can execute the following cmds(root privileges). 34 | 35 | * Compile 36 | ``` 37 | $ make 38 | ``` 39 | 40 | * Clean the project 41 | ``` 42 | $ make clean 43 | ``` 44 | 45 | * Under the acl path, generate all necessary files for deploy. You can copy that path and run it on another machine 46 | ``` 47 | $ make pub 48 | ``` 49 | 50 | #### Get Started 51 | ``` 52 | # Get help 53 | ./xdp_acl -h 54 | 55 | # Start 56 | ./xdp_acl -D eth1 -S 57 | 58 | # Then you can view the configuration rules on the browser. 59 | http://172.21.6.6:9090/ 60 | ``` 61 | 62 | #### Test 63 | 64 | * tcp 65 | 66 | ``` 67 | # Start tcp server on VM xdp-acl 68 | $ /vagrant/socket/tcp/server 172.20.6.3:3333 69 | # Start tcp client on VM trafgen 70 | $ /vagrant/socket/tcp/client 172.20.6.3:3333 500 foo 71 | ``` 72 | 73 | * udp 74 | 75 | ``` 76 | # Start udp server on VM xdp-acl 77 | $ /vagrant/socket/udp/server 172.20.6.3:4444 78 | # Start udp client on VM trafgen 79 | $ /vagrant/socket/udp/client 172.20.6.3:4444 500 bar 80 | ``` 81 | 82 | * icmp 83 | 84 | ``` 85 | # ping from VM trafgen 86 | $ ping 172.20.6.6 87 | ``` 88 | 89 | ##### Enjoy yourself ! 😄 90 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/udp/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func keepReceive(wgPtr *sync.WaitGroup, udpConn *net.UDPConn) { 14 | defer wgPtr.Done() 15 | 16 | data := make([]byte, 4096) 17 | for { 18 | n, _, err := udpConn.ReadFromUDP(data) // 接收数据 19 | if err != nil { 20 | fmt.Println("接收数据失败,err:", err) 21 | continue 22 | } 23 | fmt.Printf("recv-len: %d; recv: %v\n", n, string(data[:n])) 24 | } 25 | } 26 | 27 | func keepSend(wgPtr *sync.WaitGroup, udpConn *net.UDPConn, clientName string, interval int) { 28 | defer wgPtr.Done() 29 | 30 | num := 0 31 | for range time.Tick(time.Duration(interval) * time.Millisecond) { 32 | num++ 33 | sendData := []byte(fmt.Sprintf("client: %s; num: %d", clientName, num)) 34 | _, err := udpConn.Write(sendData) // 发送数据 35 | if err != nil { 36 | fmt.Println("发送数据失败,err:", err) 37 | continue 38 | } 39 | } 40 | } 41 | 42 | func main() { 43 | if len(os.Args) != 4 { 44 | fmt.Println("params error") 45 | return 46 | } 47 | 48 | hostPort := strings.Split(os.Args[1], ":") 49 | 50 | if len(hostPort) != 2 { 51 | fmt.Println("params error") 52 | return 53 | } 54 | 55 | ip := net.ParseIP(hostPort[0]) 56 | port, err := strconv.Atoi(hostPort[1]) 57 | 58 | interval, err := strconv.Atoi(os.Args[2]) 59 | if err != nil { 60 | fmt.Println("interval is invalid") 61 | return 62 | } 63 | 64 | clientName := os.Args[3] 65 | 66 | // 建立服务 67 | udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{ 68 | IP: ip, 69 | Port: port, 70 | }) 71 | if err != nil { 72 | fmt.Printf("listen udp server error:%v\n", err) 73 | } else { 74 | fmt.Printf("connect to %s\n", udpConn.RemoteAddr().String()) 75 | } 76 | defer udpConn.Close() 77 | 78 | var wg sync.WaitGroup 79 | wg.Add(2) 80 | 81 | go keepSend(&wg, udpConn, clientName, interval) 82 | 83 | go keepReceive(&wg, udpConn) 84 | 85 | wg.Wait() 86 | } 87 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # XDP ACL 2 | 3 | Refactor the eBPF C code and Go code of 4 | [xdp_acl](https://github.com/glenn-wang/xdp_acl). 5 | 6 | ## Updated 7 | 8 | 1. Use CO-RE to enable XDP log with `--debug` dynamically. 9 | 2. Use specified BTF file with `--kernel-btf`. 10 | 3. Upgrade to dynamic bitmap size by rules number. 11 | 4. Upgrade bpf map of rule action to percpu-array map by replacing percpu-hash map. 12 | 5. Update ACL rules without detaching/attaching XDP. 13 | 14 | ## P.S. 15 | 16 | The original reference is [eBPF / XDP based firewall and packet 17 | filtering](http://vger.kernel.org/lpc_net2018_talks/ebpf-firewall-paper-LPC.pdf). 18 | 19 | From the reference, the advice is really important to deploy XDP ACL. 20 | 21 | ```txt 22 | We do not write or update these maps once the program is loaded to avoid any lock 23 | contentions. Instead, for any change in configuration, we create a new program with 24 | new maps and modify the XDP program in the program array. 25 | ``` 26 | 27 | --- 28 | 29 | #### Inspired by [this paper](https://blog.csdn.net/ByteDanceTech/article/details/106632252) 30 | 31 | 32 | #### Benchmarks 33 | 34 | Performance comparison test of dropping 64 byte syn packet 35 | ![alt benchmarks](./docs/pk.png "iptables vs xdp") 36 | 37 | #### Notice 38 | * Linux kernel required: v4.15 or later 39 | * [How to upgrade kernel quickly ?](./docs/environment/upgrade_kernel.md) 40 | 41 | #### Docs 42 | * [Inner web server RESTful API](./docs/restful-api.md) 43 | 44 | #### Environment 45 | * [Build development environment basied on VMs](./docs/environment/build_on_VMs) (Recommended) 46 | * [Build development environment basied on Docker image](./docs/environment/build_on_docker) 47 | * [Build development environment basied on Host](./docs/environment/build_on_host) 48 | 49 | #### Get Started 50 | 51 | Download directly from release tab or compile by yourself. 52 | 53 | ``` 54 | # Compile 55 | $ make 56 | 57 | # Get help 58 | $ ./xdp_acl -h 59 | 60 | # Start (Inner web server will default listen on 0.0.0.0:9090). 61 | $ ./xdp_acl -D eth1 -S 62 | ``` 63 | 64 | #### Web console 65 | 66 | ![alt web console](./docs/console.png "web console") 67 | 68 | -------------------------------------------------------------------------------- /acl.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "priority": 300, 4 | "strategy": 2, 5 | "protos": 4, 6 | "create_time": 1622033756947, 7 | "addr_src_arr": [ 8 | { 9 | "cidr_user": "172.20.6.7/32", 10 | "cidr_standard": "172.20.6.7/32" 11 | } 12 | ], 13 | "port_src_arr": [], 14 | "addr_dst_arr": [ 15 | { 16 | "cidr_user": "172.20.6.6/32", 17 | "cidr_standard": "172.20.6.6/32" 18 | } 19 | ], 20 | "port_dst_arr": [] 21 | }, 22 | { 23 | "priority": 200, 24 | "strategy": 2, 25 | "protos": 2, 26 | "create_time": 1622033631970, 27 | "addr_src_arr": [ 28 | { 29 | "cidr_user": "172.20.6.7/32", 30 | "cidr_standard": "172.20.6.7/32" 31 | } 32 | ], 33 | "port_src_arr": [], 34 | "addr_dst_arr": [ 35 | { 36 | "cidr_user": "172.20.6.3/32", 37 | "cidr_standard": "172.20.6.3/32" 38 | } 39 | ], 40 | "port_dst_arr": [] 41 | }, 42 | { 43 | "priority": 100, 44 | "strategy": 2, 45 | "protos": 1, 46 | "create_time": 1622033574894, 47 | "addr_src_arr": [ 48 | { 49 | "cidr_user": "172.20.6.7/32", 50 | "cidr_standard": "172.20.6.7/32" 51 | } 52 | ], 53 | "port_src_arr": [], 54 | "addr_dst_arr": [ 55 | { 56 | "cidr_user": "172.20.6.3/32", 57 | "cidr_standard": "172.20.6.3/32" 58 | } 59 | ], 60 | "port_dst_arr": [] 61 | }, 62 | { 63 | "priority": 10239, 64 | "strategy": 2, 65 | "protos": 7, 66 | "create_time": 1622032948261, 67 | "addr_src_arr": [ 68 | { 69 | "cidr_user": "0.0.0.0/0", 70 | "cidr_standard": "0.0.0.0/0" 71 | } 72 | ], 73 | "port_src_arr": [], 74 | "addr_dst_arr": [ 75 | { 76 | "cidr_user": "0.0.0.0/0", 77 | "cidr_standard": "0.0.0.0/0" 78 | } 79 | ], 80 | "port_dst_arr": [], 81 | "can_not_del": 1 82 | } 83 | ] -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/socket/tcp/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | func readDadaFromConsoleAndSend(connPtr *net.Conn) { 15 | // 使用 conn 连接进行数据的发送和接收 16 | input := bufio.NewReader(os.Stdin) 17 | for { 18 | s, _ := input.ReadString('\n') 19 | s = strings.TrimSpace(s) 20 | if strings.ToUpper(s) == "Q" { 21 | return 22 | } 23 | 24 | _, err := (*connPtr).Write([]byte(s)) 25 | if err != nil { 26 | fmt.Printf("send failed, err:%v\n", err) 27 | return 28 | } 29 | 30 | // 从服务端接收回复消息 31 | var buf [1024]byte 32 | n, err := (*connPtr).Read(buf[:]) 33 | if err != nil { 34 | fmt.Printf("read failed:%v\n", err) 35 | return 36 | } 37 | fmt.Printf("收到服务端回复:%v\n", string(buf[:n])) 38 | } 39 | } 40 | 41 | func keepSend(connPtr *net.Conn, wgPtr *sync.WaitGroup, clientName string, interval int) { 42 | defer wgPtr.Done() 43 | 44 | var num int 45 | 46 | for range time.Tick(time.Duration(interval) * time.Millisecond) { 47 | num++ 48 | 49 | str := fmt.Sprintf("client: %s; num: %d", clientName, num) 50 | 51 | _, err := (*connPtr).Write([]byte(str)) 52 | if err != nil { 53 | fmt.Printf("send failed, err:%v\n", err) 54 | return 55 | } 56 | } 57 | } 58 | 59 | func keepReceive(connPtr *net.Conn, wgPtr *sync.WaitGroup) { 60 | defer wgPtr.Done() 61 | 62 | for { 63 | var buf [1024]byte 64 | n, err := (*connPtr).Read(buf[:]) 65 | if err != nil { 66 | fmt.Printf("read failed:%v\n", err) 67 | return 68 | } 69 | fmt.Printf("rcv: %v\n", string(buf[:n])) 70 | } 71 | } 72 | 73 | func main() { 74 | 75 | if len(os.Args) != 4 { 76 | fmt.Println("params error") 77 | return 78 | } 79 | 80 | interval, err := strconv.Atoi(os.Args[2]) 81 | if err != nil { 82 | fmt.Println("interval is invalid") 83 | return 84 | } 85 | 86 | hostPort := os.Args[1] 87 | 88 | clientName := os.Args[3] 89 | 90 | fmt.Printf("will connect to: %s; clientName: %s; interval: %d\n", hostPort, clientName, interval) 91 | 92 | conn, err := net.Dial("tcp", hostPort) 93 | if err != nil { 94 | fmt.Printf("conn server failed, err:%v\n", err) 95 | return 96 | } 97 | 98 | var wg sync.WaitGroup 99 | wg.Add(2) 100 | 101 | go keepSend(&conn, &wg, clientName, interval) 102 | go keepReceive(&conn, &wg) 103 | 104 | wg.Wait() 105 | 106 | // readDadaFromConsoleAndSend(conn) 107 | } 108 | -------------------------------------------------------------------------------- /docs/environment/build_on_VMs/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "generic/fedora33" 3 | 4 | config.vm.box_check_update = false 5 | 6 | config.vm.define "xdp-acl" do |m| 7 | m.vm.provider "virtualbox" do |v| 8 | v.memory = 1024 9 | v.cpus = 2 10 | v.customize ["modifyvm", :id, "--name", "xdp-acl"] 11 | v.customize ["modifyvm", :id, "--nictype1", "82543GC"] 12 | v.customize ["modifyvm", :id, "--nictype2", "82543GC"] 13 | v.customize ["modifyvm", :id, "--nictype3", "82543GC"] 14 | v.customize ["modifyvm", :id, "--nictype4", "82543GC"] 15 | end 16 | m.vm.hostname = "xdp-acl" 17 | m.vm.synced_folder ".", "/vagrant", disabled: false 18 | 19 | m.vm.network "private_network", ip: "172.20.6.3", netmask: "255.255.255.0", :mac => "040404200603" 20 | m.vm.network "private_network", ip: "172.20.6.5", netmask: "255.255.255.0", :mac => "040404200605" 21 | 22 | m.vm.provision :shell, run: "always", :inline => "ip a add 172.20.6.4/24 dev eth1 && ip a add 172.20.6.6/24 dev eth2" 23 | 24 | m.vm.provision :shell, run: "always", :inline => "echo \"Hi, I'm xdp-acl 🍁 \n\" && uname -a" 25 | 26 | # manager 27 | m.vm.network "private_network", ip: "172.21.6.6", netmask: "255.255.255.0", :mac => "040404210606" 28 | 29 | # m.vm.provision :shell, run: "always", :inline => "echo \"nameserver 223.5.5.5\" > /etc/resolv.conf", privileged: true 30 | 31 | m.vm.provision :shell, path: "update_xdp-acl.sh", privileged: true 32 | end 33 | 34 | config.vm.define "trafgen" do |m| 35 | m.vm.provider "virtualbox" do |v| 36 | v.memory = 512 37 | v.cpus = 1 38 | v.customize ["modifyvm", :id, "--name", "trafgen"] 39 | v.customize ["modifyvm", :id, "--nictype1", "82543GC"] 40 | v.customize ["modifyvm", :id, "--nictype2", "82543GC"] 41 | v.customize ["modifyvm", :id, "--nictype3", "82543GC"] 42 | end 43 | m.vm.hostname = "trafgen" 44 | 45 | m.vm.synced_folder ".", "/vagrant", disabled: false 46 | 47 | m.vm.network "private_network", ip: "172.20.6.7", netmask: "255.255.255.0", :mac => "040404200607" 48 | 49 | m.vm.provision :shell, run: "always", :inline => "echo \"Hi, I'm trafgen 🍁 \n\" && uname -a" 50 | 51 | # manager 52 | m.vm.network "private_network", ip: "172.21.6.7", netmask: "255.255.255.0", :mac => "040404210606" 53 | 54 | # m.vm.provision :shell, run: "always", :inline => "echo \"nameserver 223.5.5.5\" > /etc/resolv.conf", privileged: true 55 | 56 | m.vm.provision :shell, path: "update_trafgen.sh", privileged: true 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | XDP ACL
-------------------------------------------------------------------------------- /ebpf/headers/libxdp_map.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBXDP_MAP_H_ 2 | #define __LIBXDP_MAP_H_ 3 | 4 | #include "vmlinux.h" 5 | 6 | #include "bpf_helpers.h" 7 | 8 | #include "libxdp_generated.h" 9 | 10 | // 支持的最多规则条数, 必须是 64 的整数倍; 比如: 64 * 16 == 1024 11 | #define RULE_NUM_MAX_ENTRIES_V4 64 * BITMAP_ARRAY_SIZE 12 | 13 | #define IP_MAX_ENTRIES_V4 RULE_NUM_MAX_ENTRIES_V4 14 | 15 | // 支持的最多端口个数 1~65535; 65536 == 2^16 16 | #define PORT_MAX_ENTRIES_V4 65536 17 | 18 | // 支持的最多协议类型数 tcp udp icmp 19 | #define PROTO_MAX_ENTRIES_V4 4 20 | 21 | // 支持的规则数 编号 22 | #define RULE_ACTION_MAX_ENTRIES_V4 RULE_NUM_MAX_ENTRIES_V4 23 | 24 | #define LPM_PREFIXLEN_IPv4 32 25 | 26 | struct lpm_key_ipv4 { 27 | __u32 prefixlen; /* up to 32 for AF_INET, 128 for AF_INET6 */ 28 | __u32 data; /* network order */ 29 | } __attribute__((aligned(4))); 30 | 31 | __u64 bitmap[BITMAP_ARRAY_SIZE]; 32 | 33 | // v4 v6 可共用此结构体 34 | struct rule_action { 35 | __u64 action; 36 | __u64 count; 37 | }; 38 | 39 | // v4 v6 可共用此结构体 40 | struct rule_action_key { 41 | __u64 bitmap_ffs; 42 | __u64 bitmap_array_index; 43 | }; 44 | 45 | struct { 46 | __uint(type, BPF_MAP_TYPE_LPM_TRIE); 47 | __type(key, struct lpm_key_ipv4); 48 | __type(value, bitmap); 49 | __uint(max_entries, IP_MAX_ENTRIES_V4); 50 | __uint(map_flags, BPF_F_NO_PREALLOC); 51 | } src_v4 SEC(".maps"); 52 | 53 | struct { 54 | __uint(type, BPF_MAP_TYPE_HASH); 55 | __type(key, __u16); 56 | __type(value, bitmap); 57 | __uint(max_entries, PORT_MAX_ENTRIES_V4); 58 | } sport_v4 SEC(".maps"); 59 | 60 | struct { 61 | __uint(type, BPF_MAP_TYPE_LPM_TRIE); 62 | __type(key, struct lpm_key_ipv4); 63 | __type(value, bitmap); 64 | __uint(max_entries, IP_MAX_ENTRIES_V4); 65 | __uint(map_flags, BPF_F_NO_PREALLOC); 66 | } dst_v4 SEC(".maps"); 67 | 68 | struct { 69 | __uint(type, BPF_MAP_TYPE_HASH); 70 | __type(key, __u16); 71 | __type(value, bitmap); 72 | __uint(max_entries, PORT_MAX_ENTRIES_V4); 73 | } dport_v4 SEC(".maps"); 74 | 75 | struct { 76 | __uint(type, BPF_MAP_TYPE_HASH); 77 | __type(key, __u32); 78 | __type(value, bitmap); 79 | __uint(max_entries, PROTO_MAX_ENTRIES_V4); 80 | } proto_v4 SEC(".maps"); 81 | 82 | struct { 83 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 84 | __type(key, __u32); 85 | __type(value, struct rule_action); 86 | __uint(max_entries, RULE_ACTION_MAX_ENTRIES_V4); 87 | } rule_action_v4 SEC(".maps"); 88 | 89 | struct { 90 | __uint(type, BPF_MAP_TYPE_PROG_ARRAY); 91 | __type(key, __u32); 92 | __type(value, __u32); 93 | __uint(max_entries, 1); 94 | } progs SEC(".maps"); 95 | 96 | #endif // __LIBXDP_MAP_H_ -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module xdp_acl 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/cilium/ebpf v0.9.3 7 | github.com/kataras/iris/v12 v12.1.8 8 | github.com/natefinch/lumberjack v2.0.0+incompatible 9 | github.com/samber/lo v1.30.0 10 | github.com/spf13/pflag v1.0.5 11 | github.com/vishvananda/netlink v1.1.0 12 | go.uber.org/zap v1.16.0 13 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 14 | ) 15 | 16 | require ( 17 | github.com/BurntSushi/toml v0.3.1 // indirect 18 | github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect 19 | github.com/CloudyKit/jet/v3 v3.0.0 // indirect 20 | github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 // indirect 21 | github.com/ajg/form v1.5.1 // indirect 22 | github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible // indirect 23 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect 24 | github.com/fatih/structs v1.1.0 // indirect 25 | github.com/google/go-querystring v1.1.0 // indirect 26 | github.com/gorilla/websocket v1.4.2 // indirect 27 | github.com/imkira/go-interpol v1.1.0 // indirect 28 | github.com/iris-contrib/blackfriday v2.0.0+incompatible // indirect 29 | github.com/iris-contrib/jade v1.1.3 // indirect 30 | github.com/iris-contrib/pongo2 v0.0.1 // indirect 31 | github.com/iris-contrib/schema v0.0.1 // indirect 32 | github.com/json-iterator/go v1.1.9 // indirect 33 | github.com/kataras/golog v0.0.10 // indirect 34 | github.com/kataras/pio v0.0.2 // indirect 35 | github.com/kataras/sitemap v0.0.5 // indirect 36 | github.com/klauspost/compress v1.11.8 // indirect 37 | github.com/microcosm-cc/bluemonday v1.0.2 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.1 // indirect 40 | github.com/moul/http2curl v1.0.0 // indirect 41 | github.com/ryanuber/columnize v2.1.0+incompatible // indirect 42 | github.com/schollz/closestmatch v2.1.0+incompatible // indirect 43 | github.com/sergi/go-diff v1.2.0 // indirect 44 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 45 | github.com/smartystreets/goconvey v1.6.4 // indirect 46 | github.com/valyala/fasthttp v1.23.0 // indirect 47 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect 48 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 49 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect 50 | github.com/yudai/gojsondiff v1.0.0 // indirect 51 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 52 | go.uber.org/atomic v1.7.0 // indirect 53 | go.uber.org/multierr v1.6.0 // indirect 54 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect 55 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 56 | golang.org/x/net v0.0.0-20210226101413-39120d07d75e // indirect 57 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect 58 | golang.org/x/text v0.3.5 // indirect 59 | gopkg.in/ini.v1 v1.51.1 // indirect 60 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 61 | gopkg.in/yaml.v2 v2.4.0 // indirect 62 | gopkg.in/yaml.v3 v3.0.1 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /rule_addr.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/netip" 6 | "sort" 7 | 8 | "github.com/samber/lo" 9 | ) 10 | 11 | func (r *Rule) checkCIDR(addrs []Addr) (map[netip.Prefix]struct{}, error) { 12 | m := make(map[netip.Prefix]struct{}, len(addrs)) 13 | 14 | for i := range addrs { 15 | addr := addrs[i] 16 | cidr, err := netip.ParsePrefix(addr.CidrUser) 17 | if err != nil { 18 | return nil, fmt.Errorf("%s is an invalid CIDR", addr.CidrUser) 19 | } 20 | 21 | cidr = cidr.Masked() 22 | addrs[i].CidrStandard = cidr.String() 23 | 24 | for k := range m { 25 | if cidr.Overlaps(k) { 26 | return nil, fmt.Errorf("%s overlaps with %s", cidr, k) 27 | } 28 | } 29 | 30 | m[cidr] = struct{}{} 31 | } 32 | 33 | return m, nil 34 | } 35 | 36 | func (r *Rule) checkSrcCIDR() (map[netip.Prefix]struct{}, error) { 37 | m, err := r.checkCIDR(r.AddrSrcArr) 38 | if err != nil { 39 | return nil, fmt.Errorf("failed to check src addrs: %w", err) 40 | } 41 | 42 | return m, nil 43 | } 44 | 45 | func (r *Rules) addSrcCIDRPriority(rule *Rule) error { 46 | m, err := rule.checkSrcCIDR() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | err = r.checkCIDRLimit(r.srcCIDRPriorities, m) 52 | if err != nil { 53 | return fmt.Errorf("source CIDR limit: %w", err) 54 | } 55 | 56 | for k := range m { 57 | r.srcCIDRPriorities[k] = append(r.srcCIDRPriorities[k], rule.Priority) 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func (r *Rule) checkDstCIDR() (map[netip.Prefix]struct{}, error) { 64 | m, err := r.checkCIDR(r.AddrDstArr) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to check dst addrs: %w", err) 67 | } 68 | 69 | return m, nil 70 | } 71 | 72 | func (r *Rules) addDstCIDRPriority(rule *Rule) error { 73 | m, err := rule.checkDstCIDR() 74 | if err != nil { 75 | return err 76 | } 77 | 78 | err = r.checkCIDRLimit(r.dstCIDRPriorities, m) 79 | if err != nil { 80 | return fmt.Errorf("destination CIDR limit: %w", err) 81 | } 82 | 83 | for k := range m { 84 | r.dstCIDRPriorities[k] = append(r.dstCIDRPriorities[k], rule.Priority) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (r *Rules) checkCIDRLimit(mExist map[netip.Prefix][]uint32, mAdding map[netip.Prefix]struct{}) error { 91 | nAdding := 0 92 | for k := range mAdding { 93 | if _, ok := mExist[k]; !ok { 94 | nAdding++ 95 | } 96 | } 97 | 98 | if total := len(mExist) + nAdding; total > int(ipMaxEntries) { 99 | return fmt.Errorf("the number of entry %d exceeds the limit %d", total, ipMaxEntries) 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func (r *Rules) fixCIDRPriority(m map[netip.Prefix][]uint32) { 106 | prefixes := lo.Keys(m) 107 | sort.Slice(prefixes, func(i, j int) bool { 108 | a, b := prefixes[i], prefixes[j] 109 | if a.Bits() < b.Bits() { 110 | return true 111 | } 112 | if a.Bits() > b.Bits() { 113 | return false 114 | } 115 | 116 | ipA, ipB := a.Addr(), b.Addr() 117 | return ipA.Less(ipB) 118 | }) 119 | 120 | length := len(prefixes) 121 | for i := range prefixes { 122 | prev := prefixes[i] 123 | for j := i + 1; j < length; j++ { 124 | curr := prefixes[j] 125 | 126 | if prev.Overlaps(curr) { 127 | // prev CIDR contains curr CIDR 128 | // then append all priority of prev to curr 129 | // because curr is part of prev 130 | m[curr] = append(m[curr], m[prev]...) 131 | 132 | // remove duplicated priority 133 | // not required, but for performance 134 | m[curr] = lo.Uniq(m[curr]) 135 | } 136 | } 137 | } 138 | } 139 | 140 | func (r *Rules) fixSrcCIDRPriority() { 141 | r.fixCIDRPriority(r.srcCIDRPriorities) 142 | } 143 | 144 | func (r *Rules) fixDstCIDRPriority() { 145 | r.fixCIDRPriority(r.dstCIDRPriorities) 146 | } 147 | -------------------------------------------------------------------------------- /docs/restful-api.md: -------------------------------------------------------------------------------- 1 | ## XDP-ACL RESTful API 2 | 3 | [获取规则列表](#获取规则列表) 4 | 5 | [获取规则命中次数](#获取规则命中次数) 6 | 7 | [添加规则](#添加规则) 8 | 9 | [删除规则](#删除规则) 10 | 11 | [获取eBPF map详情](#获取eBPF-Map详情) 12 | 13 | ---------------------------------- 14 | 15 | #### 获取规则列表 16 | 17 | | 参数名称 | 参数说明 | 参数格式 | 18 | | :-----: | :----: | :---- | 19 | | strategy | 策略 | 1: 拒绝; 2: 允许 | 20 | | protos | 协议类型 | 从右到左 二进制位分别表示 tcp, udp, icmp; 即 tcp: 0x01; tcp,udp: 0x03; all:0x07 | 21 | 22 | * 请求方式 23 | 24 | ``` 25 | GET http://xdp-acl.com/xdp-acl/IPv4/rules 26 | ``` 27 | 28 | * 应答数据 29 | 30 | ``` 31 | status: 200 32 | body: 33 | [ 34 | { 35 | "priority": 33, 36 | "strategy": 1, // 1: 拒绝; 2: 允许 37 | "protos": 1, // bitmap 解析 38 | "addr_src_arr": [{ 39 | "cidr_user": "4.3.2.1/28", 40 | "cidr_standard": "4.3.2.1/28" 41 | }], 42 | "port_src_arr": [ 43 | 80, 44 | 8989 45 | ], 46 | "addr_dst_arr": [{ 47 | "cidr_user": "4.3.2.1/28", 48 | "cidr_standard": "4.3.2.1/28" 49 | }], 50 | "port_dst_arr": [ 51 | 80, 52 | 8989 53 | ], 54 | "hit_counts": "1000", 55 | "create_time": 1617020255000 56 | } 57 | ] 58 | ``` 59 | 60 | #### 获取规则命中次数 61 | 62 | * 请求方式 63 | 64 | ``` 65 | GET http://xdp-acl.com/xdp-acl/IPv4/rules/hitcount 66 | ``` 67 | 68 | * 应答数据 69 | 70 | ``` 71 | status: 200 72 | body: 73 | [ 74 | { 75 | "priority": 33, 76 | "hit_count": "1000" 77 | }, 78 | { 79 | "priority": 34, 80 | "hit_count": "101" 81 | } 82 | ] 83 | ``` 84 | 85 | #### 添加规则 86 | 87 | * 请求方式: 88 | 89 | ``` 90 | POST http://xdp-acl.com/xdp-acl/IPv4/rule 91 | body: 92 | { 93 | "priority": 34, 94 | "strategy": 1, 95 | "protos": 1, 96 | "addr_src_arr": [{ 97 | "cidr_user": "4.3.2.1/28", 98 | }], 99 | "port_src": [], 100 | "addr_dst_arr": [{ 101 | "cidr_user": "4.3.2.1/28", 102 | }], 103 | "port_dst": [ 104 | 80 105 | ] 106 | } 107 | ``` 108 | * 应答数据: 109 | 110 | ``` 111 | status: 201 112 | body: 113 | { 114 | "priority": 34, 115 | "create_time": 1617020255000, 116 | "addr_src_arr": [{ 117 | "cidr_user": "4.3.2.1/28", 118 | "cidr_standard": "4.3.2.1/28" 119 | }], 120 | "addr_dst_arr": [{ 121 | "cidr_user": "4.3.2.1/28", 122 | "cidr_standard": "4.3.2.1/28" 123 | }] 124 | } 125 | ``` 126 | 127 | #### 删除规则 128 | 129 | * 请求方式 130 | 131 | ``` 132 | DELETE http://xdp-acl.com/xdp-acl/IPv4/rule?priority=45 133 | ``` 134 | 135 | * 应答数据: 136 | 137 | ``` 138 | status: 200 139 | body: 140 | { 141 | "priority": 34 142 | } 143 | ``` 144 | 145 | #### 获取eBPF map详情 146 | 147 | | 参数名称 | 参数说明 | 参数类型 | 参数格式 148 | | :-----: | :----: | :---- | :---- | 149 | | name | eBPF map name | 路由参数| proto、rule_action、port_src、port_dst、ip_src、ip_dst| 150 | | key | eBPF map key | 查询参数 | tcp/udp/icmp、1/2/3/4,5、 80/90、1.2.3.5/24 | 151 | | filter | 是否被置位 | 查询参数 | unset、set、all | 152 | 153 | * 示例一 请求方式 154 | 155 | ``` 156 | GET http://xdp-acl.com/xdp-acl/IPv4/bpfmap/proto?key=tcp&filter=set 157 | ``` 158 | 159 | * 应答数据 160 | 161 | ``` 162 | status: 200 163 | body: 164 | { 165 | "time": "2021-05-27 00:46:51", 166 | "set": { 167 | "size": 2, 168 | "arr": [100, 10239] 169 | }, 170 | "unset": {} 171 | } 172 | ``` 173 | 174 | * 示例二 请求方式 175 | 176 | ``` 177 | GET http://xdp-acl.com/xdp-acl/IPv4/bpfmap/rule_action?key=100,300 178 | ``` 179 | 180 | * 应答数据 181 | 182 | ``` 183 | status: 200 184 | body: 185 | { 186 | "time": "2021-05-27 01:27:48", 187 | "size": 2, 188 | "rule_action_arr": [{ 189 | "priority": 100, 190 | "action": 2, 191 | "hit_count": "0" 192 | }, { 193 | "priority": 300, 194 | "action": 2, 195 | "hit_count": "0" 196 | }] 197 | } 198 | ``` -------------------------------------------------------------------------------- /ebpf/headers/bpf_endian.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | #ifndef __BPF_ENDIAN__ 3 | #define __BPF_ENDIAN__ 4 | 5 | /* 6 | * Isolate byte #n and put it into byte #m, for __u##b type. 7 | * E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64: 8 | * 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 9 | * 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000 10 | * 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 11 | * 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000 12 | */ 13 | #define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8)) 14 | 15 | #define ___bpf_swab16(x) ((__u16)( \ 16 | ___bpf_mvb(x, 16, 0, 1) | \ 17 | ___bpf_mvb(x, 16, 1, 0))) 18 | 19 | #define ___bpf_swab32(x) ((__u32)( \ 20 | ___bpf_mvb(x, 32, 0, 3) | \ 21 | ___bpf_mvb(x, 32, 1, 2) | \ 22 | ___bpf_mvb(x, 32, 2, 1) | \ 23 | ___bpf_mvb(x, 32, 3, 0))) 24 | 25 | #define ___bpf_swab64(x) ((__u64)( \ 26 | ___bpf_mvb(x, 64, 0, 7) | \ 27 | ___bpf_mvb(x, 64, 1, 6) | \ 28 | ___bpf_mvb(x, 64, 2, 5) | \ 29 | ___bpf_mvb(x, 64, 3, 4) | \ 30 | ___bpf_mvb(x, 64, 4, 3) | \ 31 | ___bpf_mvb(x, 64, 5, 2) | \ 32 | ___bpf_mvb(x, 64, 6, 1) | \ 33 | ___bpf_mvb(x, 64, 7, 0))) 34 | 35 | /* LLVM's BPF target selects the endianness of the CPU 36 | * it compiles on, or the user specifies (bpfel/bpfeb), 37 | * respectively. The used __BYTE_ORDER__ is defined by 38 | * the compiler, we cannot rely on __BYTE_ORDER from 39 | * libc headers, since it doesn't reflect the actual 40 | * requested byte order. 41 | * 42 | * Note, LLVM's BPF target has different __builtin_bswapX() 43 | * semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE 44 | * in bpfel and bpfeb case, which means below, that we map 45 | * to cpu_to_be16(). We could use it unconditionally in BPF 46 | * case, but better not rely on it, so that this header here 47 | * can be used from application and BPF program side, which 48 | * use different targets. 49 | */ 50 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 51 | # define __bpf_ntohs(x) __builtin_bswap16(x) 52 | # define __bpf_htons(x) __builtin_bswap16(x) 53 | # define __bpf_constant_ntohs(x) ___bpf_swab16(x) 54 | # define __bpf_constant_htons(x) ___bpf_swab16(x) 55 | # define __bpf_ntohl(x) __builtin_bswap32(x) 56 | # define __bpf_htonl(x) __builtin_bswap32(x) 57 | # define __bpf_constant_ntohl(x) ___bpf_swab32(x) 58 | # define __bpf_constant_htonl(x) ___bpf_swab32(x) 59 | # define __bpf_be64_to_cpu(x) __builtin_bswap64(x) 60 | # define __bpf_cpu_to_be64(x) __builtin_bswap64(x) 61 | # define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x) 62 | # define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x) 63 | #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 64 | # define __bpf_ntohs(x) (x) 65 | # define __bpf_htons(x) (x) 66 | # define __bpf_constant_ntohs(x) (x) 67 | # define __bpf_constant_htons(x) (x) 68 | # define __bpf_ntohl(x) (x) 69 | # define __bpf_htonl(x) (x) 70 | # define __bpf_constant_ntohl(x) (x) 71 | # define __bpf_constant_htonl(x) (x) 72 | # define __bpf_be64_to_cpu(x) (x) 73 | # define __bpf_cpu_to_be64(x) (x) 74 | # define __bpf_constant_be64_to_cpu(x) (x) 75 | # define __bpf_constant_cpu_to_be64(x) (x) 76 | #else 77 | # error "Fix your compiler's __BYTE_ORDER__?!" 78 | #endif 79 | 80 | #define bpf_htons(x) \ 81 | (__builtin_constant_p(x) ? \ 82 | __bpf_constant_htons(x) : __bpf_htons(x)) 83 | #define bpf_ntohs(x) \ 84 | (__builtin_constant_p(x) ? \ 85 | __bpf_constant_ntohs(x) : __bpf_ntohs(x)) 86 | #define bpf_htonl(x) \ 87 | (__builtin_constant_p(x) ? \ 88 | __bpf_constant_htonl(x) : __bpf_htonl(x)) 89 | #define bpf_ntohl(x) \ 90 | (__builtin_constant_p(x) ? \ 91 | __bpf_constant_ntohl(x) : __bpf_ntohl(x)) 92 | #define bpf_cpu_to_be64(x) \ 93 | (__builtin_constant_p(x) ? \ 94 | __bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x)) 95 | #define bpf_be64_to_cpu(x) \ 96 | (__builtin_constant_p(x) ? \ 97 | __bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x)) 98 | 99 | #endif /* __BPF_ENDIAN__ */ 100 | -------------------------------------------------------------------------------- /xdp_rule.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net/netip" 7 | "runtime" 8 | "unsafe" 9 | 10 | "github.com/cilium/ebpf" 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | func (x *xdp) doStoreRules(rules *Rules, prevHitcount map[uint32]uint64) error { 15 | var errg errgroup.Group 16 | 17 | errg.Go(func() error { 18 | return storePortRules(rules.srcPortPriorities, x.updatableObjs.SportV4, rules.BitmapArraySize) 19 | }) 20 | errg.Go(func() error { 21 | return storePortRules(rules.dstPortPriorities, x.updatableObjs.DportV4, rules.BitmapArraySize) 22 | }) 23 | errg.Go(func() error { 24 | return storeAddrRules(rules.srcCIDRPriorities, x.updatableObjs.SrcV4, rules.BitmapArraySize) 25 | }) 26 | errg.Go(func() error { 27 | return storeAddrRules(rules.dstCIDRPriorities, x.updatableObjs.DstV4, rules.BitmapArraySize) 28 | }) 29 | errg.Go(func() error { 30 | return storeProtocolRules(rules.protocolPriorities, x.updatableObjs.ProtoV4, rules.BitmapArraySize) 31 | }) 32 | errg.Go(func() error { 33 | return storeActions(rules.priorityActions, rules, x.updatableObjs.RuleActionV4, prevHitcount) 34 | }) 35 | 36 | err := errg.Wait() 37 | if err != nil { 38 | return fmt.Errorf("failed to store rules: %w", err) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func storePortRules(ports [][]uint32, m *ebpf.Map, bitmapArraySize int) error { 45 | for port, priorities := range ports { 46 | // ignore the missing-priorities ports 47 | if len(priorities) == 0 { 48 | continue 49 | } 50 | 51 | b := newBitmap(bitmapArraySize) 52 | for _, prio := range priorities { 53 | b.Set(prio) 54 | } 55 | 56 | k := htons(uint16(port)) 57 | v, _ := b.MarshalBinary() 58 | if err := m.Put(k, v); err != nil { 59 | return fmt.Errorf("failed to update rules of port(%d) to bpf map: %w", port, err) 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func htons(n uint16) uint16 { 67 | b := *(*[2]uint8)(unsafe.Pointer(&n)) 68 | return binary.BigEndian.Uint16(b[:]) 69 | } 70 | 71 | func storeAddrRules(addrs map[netip.Prefix][]uint32, m *ebpf.Map, bitmapArraySize int) error { 72 | type lpmKey struct { 73 | Prefix uint32 74 | Addr [4]uint8 75 | } 76 | 77 | for cidr, priorities := range addrs { 78 | b := newBitmap(bitmapArraySize) 79 | for _, prio := range priorities { 80 | b.Set(prio) 81 | } 82 | 83 | var k lpmKey 84 | k.Prefix = uint32(cidr.Bits()) 85 | k.Addr = cidr.Addr().As4() // network order 86 | 87 | v, _ := b.MarshalBinary() 88 | if err := m.Put(k, v); err != nil { 89 | return fmt.Errorf("failed to update rules of CIDR(%s) to bpf map: %w", cidr, err) 90 | } 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func storeProtocolRules(protos map[uint32][]uint32, m *ebpf.Map, bitmapArraySize int) error { 97 | for proto, priorities := range protos { 98 | b := newBitmap(bitmapArraySize) 99 | for _, prio := range priorities { 100 | b.Set(prio) 101 | } 102 | 103 | v, _ := b.MarshalBinary() 104 | if err := m.Put(proto, v); err != nil { 105 | return fmt.Errorf("failed to update rules of protocol(%d) to bpf map: %w", proto, err) 106 | } 107 | } 108 | 109 | return nil 110 | } 111 | 112 | type actionValue struct { 113 | Action uint64 114 | Count uint64 115 | } 116 | 117 | var numCPU = runtime.NumCPU() 118 | 119 | func getActionValue(action uint8, count uint64) []actionValue { 120 | val := make([]actionValue, numCPU) 121 | for i := range val { 122 | val[i].Action = uint64(action) 123 | val[i].Count = count 124 | } 125 | return val 126 | } 127 | 128 | // storeActions retrieves the old-hit-count-data from oldMap, and updates it to m. 129 | func storeActions(actions []uint8, r *Rules, m *ebpf.Map, prevHitCount map[uint32]uint64) error { 130 | for priority, action := range actions { 131 | key, val := uint32(priority), getActionValue(action, prevHitCount[r.GetRealPriority(uint32(priority))]) 132 | if err := m.Put(key, val); err != nil { 133 | return fmt.Errorf("failed to update action of priority(%d) to bpf map: %w", priority, err) 134 | } 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func getHitCount(m *ebpf.Map, r *Rules) (map[uint32]uint64, error) { 141 | counts, err := retrieveHitCount(m, len(r.rules)) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | hits := make(map[uint32]uint64, len(counts)) 147 | for i, c := range counts { 148 | hits[r.GetRealPriority(uint32(i))] = c 149 | } 150 | return hits, nil 151 | } 152 | 153 | func retrieveHitCount(m *ebpf.Map, ruleNum int) ([]uint64, error) { 154 | counts := make([]uint64, ruleNum) 155 | 156 | sumOfVal := func(val []actionValue) uint64 { 157 | var sum uint64 158 | for _, val := range val { 159 | sum += val.Count 160 | } 161 | return sum 162 | } 163 | 164 | var err error 165 | value := make([]actionValue, numCPU) 166 | for key := uint32(0); key < uint32(ruleNum); key++ { 167 | err = m.Lookup(key, &value) 168 | if err != nil { 169 | return counts, fmt.Errorf("failed to retrieve hit count: %w", err) 170 | } 171 | 172 | counts[key] = sumOfVal(value) 173 | } 174 | 175 | return counts, nil 176 | } 177 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | 5 | # 访问说明符(public、private等)的偏移 6 | AccessModifierOffset: -4 7 | 8 | # 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) 9 | AlignAfterOpenBracket: Align 10 | 11 | # 连续赋值时,对齐所有等号 12 | AlignConsecutiveAssignments: false 13 | 14 | # 连续声明时,对齐所有声明的变量名 15 | AlignConsecutiveDeclarations: false 16 | 17 | # 右对齐逃脱换行(使用反斜杠换行)的反斜杠 18 | AlignEscapedNewlines: Right 19 | 20 | # 水平对齐二元和三元表达式的操作数 21 | AlignOperands: true 22 | 23 | # 对齐连续的尾随的注释 24 | AlignTrailingComments: true 25 | 26 | # 不允许函数声明的所有参数在放在下一行 27 | AllowAllParametersOfDeclarationOnNextLine: false 28 | 29 | # 不允许短的块放在同一行 30 | AllowShortBlocksOnASingleLine: true 31 | 32 | # 允许短的case标签放在同一行 33 | AllowShortCaseLabelsOnASingleLine: true 34 | 35 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 36 | AllowShortFunctionsOnASingleLine: None 37 | 38 | # 允许短的if语句保持在同一行 39 | AllowShortIfStatementsOnASingleLine: true 40 | 41 | # 允许短的循环保持在同一行 42 | AllowShortLoopsOnASingleLine: true 43 | 44 | # 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), 45 | # AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) 46 | AlwaysBreakAfterReturnType: None 47 | 48 | # 总是在多行string字面量前换行 49 | AlwaysBreakBeforeMultilineStrings: false 50 | 51 | # 总是在template声明后换行 52 | AlwaysBreakTemplateDeclarations: true 53 | 54 | # false表示函数实参要么都在同一行,要么都各自一行 55 | BinPackArguments: true 56 | 57 | # false表示所有形参要么都在同一行,要么都各自一行 58 | BinPackParameters: true 59 | 60 | # 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 61 | BraceWrapping: 62 | # class定义后面 63 | AfterClass: false 64 | # 控制语句后面 65 | AfterControlStatement: false 66 | # enum定义后面 67 | AfterEnum: false 68 | # 函数定义后面 69 | AfterFunction: false 70 | # 命名空间定义后面 71 | AfterNamespace: false 72 | # struct定义后面 73 | AfterStruct: false 74 | # union定义后面 75 | AfterUnion: false 76 | # extern之后 77 | AfterExternBlock: false 78 | # catch之前 79 | BeforeCatch: false 80 | # else之前 81 | BeforeElse: false 82 | # 缩进大括号 83 | IndentBraces: false 84 | # 分离空函数 85 | SplitEmptyFunction: false 86 | # 分离空语句 87 | SplitEmptyRecord: false 88 | # 分离空命名空间 89 | SplitEmptyNamespace: false 90 | 91 | # 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) 92 | BreakBeforeBinaryOperators: NonAssignment 93 | 94 | # 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 95 | # Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 96 | # Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom 97 | # 注:这里认为语句块也属于函数 98 | BreakBeforeBraces: Custom 99 | 100 | # 在三元运算符前换行 101 | BreakBeforeTernaryOperators: false 102 | 103 | # 在构造函数的初始化列表的冒号后换行 104 | BreakConstructorInitializers: AfterColon 105 | 106 | #BreakInheritanceList: AfterColon 107 | 108 | BreakStringLiterals: false 109 | 110 | # 每行字符的限制,0表示没有限制 111 | ColumnLimit: 0 112 | 113 | CompactNamespaces: true 114 | 115 | # 构造函数的初始化列表要么都在同一行,要么都各自一行 116 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 117 | 118 | # 构造函数的初始化列表的缩进宽度 119 | ConstructorInitializerIndentWidth: 4 120 | 121 | # 延续的行的缩进宽度 122 | ContinuationIndentWidth: 4 123 | 124 | # 去除C++11的列表初始化的大括号{后和}前的空格 125 | Cpp11BracedListStyle: true 126 | 127 | # 继承最常用的指针和引用的对齐方式 128 | DerivePointerAlignment: false 129 | 130 | # 固定命名空间注释 131 | FixNamespaceComments: true 132 | 133 | # 缩进case标签 134 | IndentCaseLabels: false 135 | 136 | IndentPPDirectives: None 137 | 138 | # 缩进宽度 139 | IndentWidth: 4 140 | 141 | # 函数返回类型换行时,缩进函数声明或函数定义的函数名 142 | IndentWrappedFunctionNames: false 143 | 144 | # 保留在块开始处的空行 145 | KeepEmptyLinesAtTheStartOfBlocks: false 146 | 147 | # 连续空行的最大数量 148 | MaxEmptyLinesToKeep: 1 149 | 150 | # 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All 151 | NamespaceIndentation: None 152 | 153 | # 指针和引用的对齐: Left, Right, Middle 154 | PointerAlignment: Right 155 | 156 | # 允许重新排版注释 157 | ReflowComments: true 158 | 159 | # 允许排序#include 160 | SortIncludes: false 161 | 162 | # 允许排序 using 声明 163 | SortUsingDeclarations: false 164 | 165 | # 在C风格类型转换后添加空格 166 | SpaceAfterCStyleCast: false 167 | 168 | # 在Template 关键字后面添加空格 169 | SpaceAfterTemplateKeyword: true 170 | 171 | # 在赋值运算符之前添加空格 172 | SpaceBeforeAssignmentOperators: true 173 | 174 | # SpaceBeforeCpp11BracedList: true 175 | 176 | # SpaceBeforeCtorInitializerColon: true 177 | 178 | # SpaceBeforeInheritanceColon: true 179 | 180 | # 开圆括号之前添加一个空格: Never, ControlStatements, Always 181 | SpaceBeforeParens: ControlStatements 182 | 183 | # SpaceBeforeRangeBasedForLoopColon: true 184 | 185 | # 在空的圆括号中添加空格 186 | SpaceInEmptyParentheses: false 187 | 188 | # 在尾随的评论前添加的空格数(只适用于//) 189 | SpacesBeforeTrailingComments: 1 190 | 191 | # 在尖括号的<后和>前添加空格 192 | SpacesInAngles: false 193 | 194 | # 在C风格类型转换的括号中添加空格 195 | SpacesInCStyleCastParentheses: false 196 | 197 | # 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 198 | SpacesInContainerLiterals: true 199 | 200 | # 在圆括号的(后和)前添加空格 201 | SpacesInParentheses: false 202 | 203 | # 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 204 | SpacesInSquareBrackets: false 205 | 206 | # 标准: Cpp03, Cpp11, Auto 207 | Standard: Cpp11 208 | 209 | # tab宽度 210 | TabWidth: 4 211 | 212 | # 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always 213 | UseTab: Never -------------------------------------------------------------------------------- /web.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "sync" 9 | "time" 10 | 11 | "github.com/kataras/iris/v12" 12 | ) 13 | 14 | type webApp struct { 15 | mu sync.Mutex 16 | rules *Rules 17 | xdp *xdp 18 | 19 | lastRuleFixed bool 20 | lastRuleDisplay bool 21 | } 22 | 23 | func runWebApp(ctx context.Context, flags *Flags, rules *Rules, xdp *xdp) error { 24 | var w webApp 25 | w.rules = rules 26 | w.xdp = xdp 27 | 28 | app := iris.New() 29 | 30 | go func() { 31 | <-ctx.Done() 32 | 33 | // shutdown the running web server 34 | app.Shutdown(context.TODO()) 35 | }() 36 | 37 | app.HandleDir("/", "./public", iris.DirOptions{ 38 | Gzip: true, 39 | IndexName: "index.html", 40 | }) 41 | 42 | v1 := app.Party("/xdp-acl") 43 | 44 | v1.Use(iris.Gzip) 45 | 46 | v1.Get("/IPv4/rules", w.getRules) 47 | 48 | v1.Get("/IPv4/rules/hitcount", w.getHitCount) 49 | 50 | v1.Post("/IPv4/rule", w.addRule) 51 | 52 | v1.Delete("/IPv4/rule", w.delRule) 53 | 54 | // debug assist 55 | v1.Get("/IPv4/bpfmap/{name:string}", w.notImplement) 56 | 57 | addr := fmt.Sprintf("%s:%d", flags.Server, flags.Port) 58 | zlog.Infof("Start web server listening on http://%s", addr) 59 | 60 | if err := app.Run(iris.Addr(addr), iris.WithConfiguration(iris.Configuration{ 61 | DisableAutoFireStatusCode: true, 62 | })); err != nil { 63 | if errors.Is(err, iris.ErrServerClosed) { 64 | zlog.Info("Web server is closed") 65 | return nil 66 | } else { 67 | return fmt.Errorf("failed to run web server: %w", err) 68 | } 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (w *webApp) getRules(ctx iris.Context) { 75 | w.mu.Lock() 76 | defer w.mu.Unlock() 77 | 78 | rules := w.rules.GetRulesWithRealPriority() 79 | 80 | if w.lastRuleFixed && !w.lastRuleDisplay { 81 | ctx.JSON(rules[:len(rules)-1]) 82 | return 83 | } 84 | 85 | ctx.JSON(rules) 86 | } 87 | 88 | func (w *webApp) getHitCount(ctx iris.Context) { 89 | type ruleAction struct { 90 | Priority uint32 `json:"priority"` 91 | Action uint32 `json:"action,omitempty"` 92 | HitCount string `json:"hit_count"` 93 | } 94 | 95 | w.mu.Lock() 96 | defer w.mu.Unlock() 97 | 98 | hits, err := retrieveHitCount(w.xdp.updatableObjs.RuleActionV4, len(w.rules.rules)) 99 | if err != nil { 100 | zlog.Errorf("Failed to retrieve hit count: %v", err) 101 | ctx.StatusCode(iris.StatusInternalServerError) 102 | w.outputError(ctx, 1002, err.Error()) 103 | return 104 | } 105 | 106 | counts := make([]ruleAction, 0, len(hits)) 107 | for priority := uint32(0); priority < uint32(len(hits)); priority++ { 108 | counts = append(counts, ruleAction{ 109 | Priority: w.rules.GetRealPriority(uint32(priority)), 110 | HitCount: fmt.Sprintf("%d", hits[priority]), 111 | }) 112 | } 113 | 114 | ctx.JSON(counts) 115 | } 116 | 117 | func (w *webApp) addRule(ctx iris.Context) { 118 | var rule Rule 119 | if err := ctx.ReadJSON(&rule); err != nil { 120 | zlog.Errorf("Failed to add rule: %v", err) 121 | 122 | ctx.StatusCode(iris.StatusBadRequest) 123 | w.outputError(ctx, 1002, err.Error()) 124 | return 125 | } 126 | 127 | if rule.IsFixed(w.lastRuleFixed) { 128 | zlog.Errorf("Last rule is fixed to add") 129 | 130 | ctx.StatusCode(iris.StatusBadRequest) 131 | w.outputError(ctx, 1002, "Last rule is fixed to add") 132 | } 133 | 134 | rule.CreateTime = time.Now().UnixNano() / int64(time.Millisecond) 135 | 136 | w.mu.Lock() 137 | defer w.mu.Unlock() 138 | 139 | hitcount, err := getHitCount(w.xdp.updatableObjs.RuleActionV4, w.rules) 140 | if err != nil { 141 | zlog.Errorf("Failed to delete rule: %v", err) 142 | 143 | ctx.StatusCode(iris.StatusBadRequest) 144 | w.outputError(ctx, 1002, err.Error()) 145 | return 146 | } 147 | 148 | w.rules.AddRule(&rule) 149 | 150 | if w.reloadXDP(ctx, hitcount) { 151 | retRule := rule 152 | retRule.Priority = w.rules.GetRealPriority(rule.Priority) 153 | ctx.StatusCode(iris.StatusCreated) 154 | ctx.JSON(&retRule) 155 | } 156 | 157 | if err := w.rules.Save(); err != nil { 158 | zlog.Errorf("Failed to save rules: %v", err) 159 | } 160 | } 161 | 162 | func (w *webApp) delRule(ctx iris.Context) { 163 | priority, err := strconv.Atoi(ctx.Request().URL.Query().Get("priority")) 164 | if err != nil { 165 | err := fmt.Errorf("failed to get priority from URL: %w", err) 166 | zlog.Error(err) 167 | 168 | ctx.StatusCode(iris.StatusBadRequest) 169 | w.outputError(ctx, 1002, err.Error()) 170 | return 171 | } 172 | 173 | var rule Rule 174 | rule.Priority = uint32(priority) 175 | 176 | if rule.IsFixed(w.lastRuleFixed) { 177 | err := fmt.Errorf("last rule priority is fixed to delete") 178 | zlog.Error(err) 179 | 180 | ctx.StatusCode(iris.StatusBadRequest) 181 | w.outputError(ctx, 1002, err.Error()) 182 | return 183 | } 184 | 185 | w.mu.Lock() 186 | defer w.mu.Unlock() 187 | 188 | hitcount, err := getHitCount(w.xdp.updatableObjs.RuleActionV4, w.rules) 189 | if err != nil { 190 | zlog.Errorf("Failed to delete rule: %v", err) 191 | 192 | ctx.StatusCode(iris.StatusBadRequest) 193 | w.outputError(ctx, 1002, err.Error()) 194 | return 195 | } 196 | 197 | w.rules.DeleteRule(&rule) 198 | 199 | if w.reloadXDP(ctx, hitcount) { 200 | ctx.JSON(iris.Map{ 201 | "priority": rule.Priority, 202 | }) 203 | } 204 | 205 | if err := w.rules.Save(); err != nil { 206 | zlog.Errorf("Failed to save rules: %v", err) 207 | } 208 | } 209 | 210 | func (w *webApp) reloadXDP(ctx iris.Context, prevHitcount map[uint32]uint64) bool { 211 | if err := w.xdp.reload(w.rules, prevHitcount); err != nil { 212 | err = fmt.Errorf("failed to reload XDP: %v", err) 213 | zlog.Error(err) 214 | 215 | ctx.StatusCode(iris.StatusBadRequest) 216 | w.outputError(ctx, 1001, err.Error()) 217 | return false 218 | } 219 | 220 | return true 221 | } 222 | 223 | func (w *webApp) notImplement(ctx iris.Context) { 224 | w.outputError(ctx, 1002, "Not implemented") 225 | } 226 | 227 | func (w *webApp) outputError(ctx iris.Context, code int, msg string) { 228 | ctx.JSON(iris.Map{ 229 | "errCode": code, 230 | "msg": msg, 231 | }) 232 | } 233 | -------------------------------------------------------------------------------- /public/js/npm.resize-observer-polyfill.1884e205.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([["npm.resize-observer-polyfill"],{"6dd8":function(t,e,n){"use strict";n.r(e),function(t){var n=function(){if("undefined"!=typeof Map)return Map;function t(t,e){var n=-1;return t.some((function(t,r){return t[0]===e&&(n=r,!0)})),n}return function(){function e(){this.__entries__=[]}return Object.defineProperty(e.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),e.prototype.get=function(e){var n=t(this.__entries__,e),r=this.__entries__[n];return r&&r[1]},e.prototype.set=function(e,n){var r=t(this.__entries__,e);~r?this.__entries__[r][1]=n:this.__entries__.push([e,n])},e.prototype.delete=function(e){var n=this.__entries__,r=t(n,e);~r&&n.splice(r,1)},e.prototype.has=function(e){return!!~t(this.__entries__,e)},e.prototype.clear=function(){this.__entries__.splice(0)},e.prototype.forEach=function(t,e){void 0===e&&(e=null);for(var n=0,r=this.__entries__;n0},t.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),c?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},t.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},t.prototype.onTransitionEnd_=function(t){var e=t.propertyName,n=void 0===e?"":e;s.some((function(t){return!!~n.indexOf(t)}))&&this.refresh()},t.getInstance=function(){return this.instance_||(this.instance_=new t),this.instance_},t.instance_=null,t}(),h=function(t,e){for(var n=0,r=Object.keys(e);n0},t}(),g="undefined"!=typeof WeakMap?new WeakMap:new n,E=function t(e){if(!(this instanceof t))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=a.getInstance(),r=new w(e,n,this);g.set(this,r)};["observe","unobserve","disconnect"].forEach((function(t){E.prototype[t]=function(){var e;return(e=g.get(this))[t].apply(e,arguments)}}));var O=void 0!==i.ResizeObserver?i.ResizeObserver:E;e.default=O}.call(this,n("c8ba"))}}]); -------------------------------------------------------------------------------- /ebpf/headers/libxdp_acl.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBXDP_ACL_H_ 2 | #define __LIBXDP_ACL_H_ 3 | 4 | #include "vmlinux.h" 5 | 6 | #include "bpf_helpers.h" 7 | #include "bpf_endian.h" 8 | 9 | #include "libxdp_map.h" 10 | 11 | static volatile const __u32 XDPACL_BITMAP_ARRAY_SIZE_LIMIT = BITMAP_ARRAY_SIZE; 12 | 13 | static volatile const __u32 XDPACL_DEBUG = 0; 14 | 15 | #define bpf_debug_printk(fmt, ...) \ 16 | do { \ 17 | if (XDPACL_DEBUG) \ 18 | bpf_printk(fmt, ##__VA_ARGS__); \ 19 | } while (0) 20 | 21 | #define ETH_P_IP 0x0800 /* Internet Protocol packet */ 22 | 23 | #ifndef IPPROTO_OSPF 24 | #define IPPROTO_OSPF 89 25 | #endif 26 | 27 | // cacheline alignment 28 | #ifndef L1_CACHE_BYTES 29 | #define L1_CACHE_BYTES 64 30 | #endif 31 | 32 | #ifndef SMP_CACHE_BYTES 33 | #define SMP_CACHE_BYTES L1_CACHE_BYTES 34 | #endif 35 | 36 | #ifndef ____cacheline_aligned 37 | #define ____cacheline_aligned __attribute__((__aligned__(SMP_CACHE_BYTES))) 38 | #endif 39 | 40 | // likely optimization 41 | #define likely(x) __builtin_expect(!!(x), 1) 42 | #define unlikely(x) __builtin_expect(!!(x), 0) 43 | 44 | static __always_inline void get_hit_rules_optimize(__u64 *rule_array[], __u32 rules_num, __u64 *arr_index, __u64 *hit_rules) { 45 | #define index_rule(idx) (rule_array[idx][(*arr_index)]) 46 | #define bit_and_5() (index_rule(0) & index_rule(1) & index_rule(2) & index_rule(3) & index_rule(4)) 47 | #define bit_and_3() (index_rule(0) & index_rule(1) & index_rule(2)) 48 | #define hit_rule_5() \ 49 | do { \ 50 | *hit_rules = bit_and_5(); \ 51 | if (*hit_rules > 0) \ 52 | return; \ 53 | } while (0) 54 | #define hit_rule_3() \ 55 | do { \ 56 | *hit_rules = bit_and_3(); \ 57 | if (*hit_rules > 0) \ 58 | return; \ 59 | } while (0) 60 | #define inc_then_hit_rule_5() \ 61 | (*arr_index)++; \ 62 | hit_rule_5() 63 | #define inc_then_hit_rule_3() \ 64 | (*arr_index)++; \ 65 | hit_rule_3() 66 | 67 | if (5 == rules_num) { 68 | // 5 69 | 70 | hit_rule_5(); 71 | inc_then_hit_rule_5(); 72 | inc_then_hit_rule_5(); 73 | inc_then_hit_rule_5(); 74 | inc_then_hit_rule_5(); 75 | inc_then_hit_rule_5(); 76 | inc_then_hit_rule_5(); 77 | inc_then_hit_rule_5(); 78 | 79 | } else { 80 | // 3 81 | 82 | hit_rule_3(); 83 | inc_then_hit_rule_3(); 84 | inc_then_hit_rule_3(); 85 | inc_then_hit_rule_3(); 86 | inc_then_hit_rule_3(); 87 | inc_then_hit_rule_3(); 88 | inc_then_hit_rule_3(); 89 | inc_then_hit_rule_3(); 90 | } 91 | 92 | #undef inc_then_hit_rule 93 | #undef hit_rule 94 | #undef bit_and_5 95 | #undef bit_and_3 96 | #undef index_rule 97 | 98 | return; 99 | } 100 | 101 | static __always_inline __u32 102 | get_priority(__u64 bit, __u64 index) { 103 | __u64 shift = 0; 104 | if (bit >= ((__u64)1 << 32)) { 105 | bit >>= 32; 106 | shift += 32; 107 | } 108 | if (bit >= (1 << 16)) { 109 | bit >>= 16; 110 | shift += 16; 111 | } 112 | if (bit >= (1 << 8)) { 113 | bit >>= 8; 114 | shift += 8; 115 | } 116 | if (bit >= (1 << 4)) { 117 | bit >>= 4; 118 | shift += 4; 119 | } 120 | if (bit >= (1 << 2)) { 121 | bit >>= 2; 122 | shift += 2; 123 | } 124 | if (bit == 1) { 125 | shift++; 126 | } else if (bit == 2) { 127 | shift += 2; 128 | } 129 | 130 | return (__u32)((index << 6) + shift) - 1; // get the index of the '1' bit in bitmap 131 | } 132 | 133 | static __always_inline int get_rule_action_v4(__u64 *rule_array[], __u32 rules_num) { 134 | /* 135 | 三种特殊情况: 136 | 未匹配到规则: 137 | 1 *rule_array_len == 0 => (bitmap[0] == 0); 或者 *rule_array_len == 1 2 4 返回 XDP_PASS; 138 | 2 *rule_array_len != 0; 但 bitmap 按位与时,都为 0 => (bitmap[BITMAP_ARRAY_SIZE - 1] == 0); 返回 XDP_PASS 139 | 140 | 匹配到规则: 141 | 3 未找到 action 142 | */ 143 | 144 | if (unlikely(3 != rules_num && 5 != rules_num)) { 145 | // 特殊情况 1 146 | return XDP_PASS; 147 | } 148 | 149 | __u64 rule_array_index ____cacheline_aligned = 0; 150 | __u64 hit_rules ____cacheline_aligned = 0; 151 | 152 | __u64 rule_array_outer_index ____cacheline_aligned = 0; 153 | #pragma unroll 154 | for (; rule_array_outer_index < BITMAP_ARRAY_SIZE; rule_array_outer_index += 8) { 155 | get_hit_rules_optimize(rule_array, rules_num, &rule_array_index, &hit_rules); 156 | if (hit_rules > 0) { 157 | break; 158 | } 159 | rule_array_index++; 160 | } 161 | 162 | if (rule_array_index >= XDPACL_BITMAP_ARRAY_SIZE_LIMIT) { 163 | // 特殊情况 2 164 | return XDP_PASS; 165 | } 166 | 167 | hit_rules = hit_rules & (-hit_rules); // get the very first '1' bit 168 | __u32 key = get_priority(hit_rules, rule_array_index); 169 | 170 | bpf_debug_printk("xdpacl, get rule, hit_rules: 0x%x, array index: %d, key: %d\n", hit_rules, rule_array_index, key); 171 | 172 | struct rule_action *value; 173 | value = (typeof(value))bpf_map_lookup_elem(&rule_action_v4, &key); 174 | if (NULL != value) { 175 | __sync_fetch_and_add(&value->count, 1); 176 | return value->action; 177 | } 178 | 179 | // 特殊情况 3,现在默认为 XDP_PASS 180 | 181 | return XDP_PASS; 182 | } 183 | 184 | static __always_inline int 185 | xdp_acl_ipv4(struct xdp_md *ctx) { 186 | void *data = (void *)(long)ctx->data; 187 | void *data_end = (void *)(long)ctx->data_end; 188 | 189 | __u64 *rule_arr[5]; 190 | __u32 rules_num = 0; 191 | 192 | struct lpm_key_ipv4 key = {}; 193 | key.prefixlen = LPM_PREFIXLEN_IPv4; 194 | 195 | __u64 *bitmap; 196 | 197 | #define lookup_map(map, key) \ 198 | do { \ 199 | bitmap = (typeof(bitmap))bpf_map_lookup_elem(&map, &key); \ 200 | if (NULL != bitmap) \ 201 | rule_arr[rules_num++] = bitmap; \ 202 | } while (0) 203 | 204 | struct iphdr *iph; 205 | iph = (typeof(iph))(data + sizeof(struct ethhdr)); 206 | 207 | if ((void *)(iph + 1) > data_end) 208 | return XDP_PASS; 209 | 210 | __u32 l4_proto = iph->protocol; 211 | bool is_tcp_udp = (IPPROTO_TCP == l4_proto || IPPROTO_UDP == l4_proto); 212 | bool is_icmp = (IPPROTO_ICMP == l4_proto); 213 | 214 | if (is_tcp_udp) { 215 | struct udphdr *udph; 216 | udph = (typeof(udph))((void *)iph + iph->ihl * 4); 217 | if ((void *)(udph + 1) > data_end) 218 | return XDP_PASS; 219 | 220 | bpf_debug_printk("TCP or UDP packet, dport: %d\n", bpf_ntohs(udph->dest)); 221 | 222 | key.data = iph->saddr; 223 | lookup_map(src_v4, key); 224 | 225 | key.data = iph->daddr; 226 | lookup_map(dst_v4, key); 227 | 228 | __u16 port = udph->source; 229 | lookup_map(sport_v4, port); 230 | 231 | port = udph->dest; 232 | lookup_map(dport_v4, port); 233 | 234 | } else if (is_icmp) { 235 | bpf_debug_printk("ICMP packet\n"); 236 | 237 | key.data = iph->saddr; 238 | lookup_map(src_v4, key); 239 | 240 | key.data = iph->daddr; 241 | lookup_map(dst_v4, key); 242 | 243 | } else { 244 | return XDP_PASS; 245 | } 246 | 247 | lookup_map(proto_v4, l4_proto); 248 | 249 | #undef lookup_map 250 | 251 | return get_rule_action_v4(rule_arr, rules_num); 252 | } 253 | 254 | #endif // __LIBXDP_ACL_H_ -------------------------------------------------------------------------------- /xdp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/cilium/ebpf" 8 | "github.com/cilium/ebpf/btf" 9 | "github.com/cilium/ebpf/link" 10 | "github.com/vishvananda/netlink" 11 | ) 12 | 13 | type XDPACLObjects = XDPACL8Objects 14 | 15 | type xdp struct { 16 | devices []netlink.Link 17 | xdps []link.Link 18 | 19 | bpfSpec *ebpf.CollectionSpec 20 | btfSpec *btf.Spec 21 | 22 | updatableObjs *XDPACLObjects 23 | persistentObjs *XDPACLObjects 24 | 25 | attachFlags link.XDPAttachFlags 26 | 27 | debugMode uint32 28 | bitmapArraySize uint32 29 | } 30 | 31 | func newXdp(flags *Flags, r *Rules) (*xdp, error) { 32 | var x xdp 33 | 34 | x.devices = make([]netlink.Link, 0, len(flags.Dev)) 35 | for _, dev := range flags.Dev { 36 | l, err := netlink.LinkByName(dev) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to get device from %s: %w", dev, err) 39 | } 40 | 41 | x.devices = append(x.devices, l) 42 | } 43 | 44 | x.debugMode = b2u32(flags.Debug) 45 | x.bitmapArraySize = uint32(r.BitmapArraySize) 46 | 47 | err := x.loadSpec() 48 | if err != nil { 49 | return nil, fmt.Errorf("failed to load bpf spec: %w", err) 50 | } 51 | 52 | if flags.KernelBTF != "" { 53 | x.btfSpec, err = btf.LoadSpec(flags.KernelBTF) 54 | } else { 55 | x.btfSpec, err = btf.LoadKernelSpec() 56 | } 57 | if err != nil { 58 | return nil, fmt.Errorf("failed to load BTF spec: %w", err) 59 | } 60 | 61 | x.attachFlags = getXDPAttachFlags(flags.Skb, flags.Native) 62 | 63 | x.updatableObjs = new(XDPACLObjects) 64 | x.persistentObjs = new(XDPACLObjects) 65 | 66 | if err := x.loadObj(x.persistentObjs, len(r.rules)); err != nil { 67 | return nil, fmt.Errorf("failed to load and assign objs: %w", err) 68 | } 69 | 70 | if err := x.persistentObjs.Progs.Put(uint32(0), x.persistentObjs.XdpAclFuncImm); err != nil { 71 | return nil, fmt.Errorf("failed to update progs bpf map: %w", err) 72 | } 73 | 74 | x.updatableObjs.SrcV4 = x.persistentObjs.SrcV4 75 | x.updatableObjs.DstV4 = x.persistentObjs.DstV4 76 | x.updatableObjs.SportV4 = x.persistentObjs.SportV4 77 | x.updatableObjs.DportV4 = x.persistentObjs.DportV4 78 | x.updatableObjs.ProtoV4 = x.persistentObjs.ProtoV4 79 | x.updatableObjs.RuleActionV4 = x.persistentObjs.RuleActionV4 80 | x.updatableObjs.XdpAclFuncImm = x.persistentObjs.XdpAclFuncImm 81 | x.persistentObjs.SrcV4 = nil 82 | x.persistentObjs.DstV4 = nil 83 | x.persistentObjs.SportV4 = nil 84 | x.persistentObjs.DportV4 = nil 85 | x.persistentObjs.ProtoV4 = nil 86 | x.persistentObjs.RuleActionV4 = nil 87 | x.persistentObjs.XdpAclFuncImm = nil 88 | 89 | if err := x.storeRules(r, map[uint32]uint64{}); err != nil { 90 | _ = x.Close() 91 | return nil, err 92 | } 93 | 94 | if err := x.attach(); err != nil { 95 | _ = x.Close() 96 | return nil, fmt.Errorf("failed to attach XDP: %w", err) 97 | } 98 | 99 | return &x, nil 100 | } 101 | 102 | func b2u32(b bool) uint32 { 103 | if b { 104 | return 1 105 | } 106 | return 0 107 | } 108 | 109 | func (x *xdp) loadSpec() error { 110 | var err error 111 | switch n := x.bitmapArraySize; n { 112 | case 8: 113 | x.bpfSpec, err = LoadXDPACL8() 114 | case 16: 115 | x.bpfSpec, err = LoadXDPACL16() 116 | case 32: 117 | x.bpfSpec, err = LoadXDPACL32() 118 | case 64: 119 | x.bpfSpec, err = LoadXDPACL64() 120 | case 128: 121 | x.bpfSpec, err = LoadXDPACL128() 122 | case 160: 123 | x.bpfSpec, err = LoadXDPACL160() 124 | case 256: 125 | x.bpfSpec, err = LoadXDPACL256() 126 | default: 127 | err = fmt.Errorf("no bpf spec for %d", n) 128 | } 129 | return err 130 | } 131 | 132 | func getBitmapArraySizeLimit(n int) int { 133 | return n>>6 + 1 // n/64 + 1 134 | } 135 | 136 | func (x *xdp) loadObj(objs interface{}, ruleNum int) error { 137 | rc := map[string]interface{}{ 138 | "XDPACL_DEBUG": x.debugMode, 139 | "XDPACL_BITMAP_ARRAY_SIZE_LIMIT": uint32(getBitmapArraySizeLimit(ruleNum)), 140 | } 141 | if err := x.bpfSpec.RewriteConstants(rc); err != nil { 142 | return fmt.Errorf("failed to rewrite constants: %v: %w", rc, err) 143 | } 144 | zlog.Infof("XDP rewrote constants: %v", rc) 145 | 146 | var opts ebpf.CollectionOptions 147 | opts.Programs.KernelTypes = x.btfSpec 148 | // opts.Programs.LogLevel = ebpf.LogLevelBranch | ebpf.LogLevelInstruction 149 | // opts.Programs.LogSize = ebpf.DefaultVerifierLogSize * 10000 150 | 151 | if err := x.bpfSpec.LoadAndAssign(objs, &opts); err != nil { 152 | return fmt.Errorf("failed to load and assign objs: %w", err) 153 | } 154 | 155 | return nil 156 | } 157 | 158 | func (x *xdp) storeRules(r *Rules, prevHitcount map[uint32]uint64) error { 159 | ts := time.Now() 160 | 161 | err := x.doStoreRules(r, prevHitcount) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | // the rules' cache is useless 167 | r.ClearCache() 168 | 169 | zlog.Infof("🍉 name: storing %d rules. Cost: %s.", len(r.rules), time.Since(ts)) 170 | 171 | return nil 172 | } 173 | 174 | func (x *xdp) attach() error { 175 | x.xdps = make([]link.Link, 0, len(x.devices)) 176 | 177 | for _, l := range x.devices { 178 | xdp, err := link.AttachXDP(link.XDPOptions{ 179 | Program: x.persistentObjs.XdpAclFunc, 180 | Interface: l.Attrs().Index, 181 | Flags: x.attachFlags, 182 | }) 183 | if err != nil { 184 | return fmt.Errorf("failed to attach XDP to %s: %w", l.Attrs().Name, err) 185 | } 186 | 187 | x.xdps = append(x.xdps, xdp) 188 | } 189 | 190 | return nil 191 | } 192 | 193 | func (x *xdp) detach() { 194 | for _, l := range x.xdps { 195 | _ = l.Close() 196 | } 197 | x.xdps = nil 198 | } 199 | 200 | func (x *xdp) closeUpdatableObjs() { 201 | _ = x.updatableObjs.SrcV4.Close() 202 | _ = x.updatableObjs.DstV4.Close() 203 | _ = x.updatableObjs.SportV4.Close() 204 | _ = x.updatableObjs.DportV4.Close() 205 | _ = x.updatableObjs.ProtoV4.Close() 206 | _ = x.updatableObjs.RuleActionV4.Close() 207 | _ = x.updatableObjs.XdpAclFuncImm.Close() 208 | x.updatableObjs = nil 209 | } 210 | 211 | func (x *xdp) Close() error { 212 | x.detach() 213 | 214 | x.closeUpdatableObjs() 215 | 216 | _ = x.persistentObjs.Progs.Close() 217 | _ = x.persistentObjs.XdpAclFunc.Close() 218 | 219 | return nil 220 | } 221 | 222 | func (x *xdp) reload(r *Rules, prevHitcount map[uint32]uint64) error { 223 | ts := time.Now() 224 | 225 | r.FixRules() 226 | 227 | x.bitmapArraySize = uint32(r.BitmapArraySize) 228 | 229 | if err := x.loadSpec(); err != nil { 230 | return fmt.Errorf("failed to load bpf spec while reloading: %w", err) 231 | } 232 | 233 | o := x.updatableObjs 234 | x.updatableObjs = new(XDPACLObjects) 235 | 236 | if err := x.loadObj(x.updatableObjs, len(r.rules)); err != nil { 237 | return fmt.Errorf("failed to load xdp obj while reloading: %w", err) 238 | } 239 | 240 | _ = x.updatableObjs.Progs.Close() 241 | _ = x.updatableObjs.XdpAclFunc.Close() 242 | x.updatableObjs.Progs = nil 243 | x.updatableObjs.XdpAclFunc = nil 244 | 245 | if err := x.storeRules(r, prevHitcount); err != nil { 246 | x.closeUpdatableObjs() 247 | x.updatableObjs = o 248 | return fmt.Errorf("failed to store rules while reloading: %w", err) 249 | } 250 | 251 | if err := x.persistentObjs.Progs.Put(uint32(0), x.updatableObjs.XdpAclFuncImm); err != nil { 252 | x.closeUpdatableObjs() 253 | x.updatableObjs = o 254 | return fmt.Errorf("failed to update progs bpf map while reloading: %w", err) 255 | } 256 | 257 | _ = o.SrcV4.Close() 258 | _ = o.DstV4.Close() 259 | _ = o.SportV4.Close() 260 | _ = o.DportV4.Close() 261 | _ = o.ProtoV4.Close() 262 | _ = o.RuleActionV4.Close() 263 | _ = o.XdpAclFuncImm.Close() 264 | 265 | zlog.Infof("🍉 name: %s. Cost: %s.", "reloading rules", time.Since(ts)) 266 | return nil 267 | } 268 | 269 | func getXDPAttachFlags(skbMode, nativeMode bool) link.XDPAttachFlags { 270 | var flag link.XDPAttachFlags 271 | if skbMode { 272 | flag = link.XDPGenericMode 273 | } else if nativeMode { 274 | flag = link.XDPDriverMode 275 | } 276 | return flag 277 | } 278 | -------------------------------------------------------------------------------- /ebpf/headers/bpf_helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | #ifndef __BPF_HELPERS__ 3 | #define __BPF_HELPERS__ 4 | 5 | /* 6 | * Note that bpf programs need to include either 7 | * vmlinux.h (auto-generated from BTF) or linux/types.h 8 | * in advance since bpf_helper_defs.h uses such types 9 | * as __u64. 10 | */ 11 | #include "bpf_helper_defs.h" 12 | 13 | #define __uint(name, val) int (*name)[val] 14 | #define __type(name, val) typeof(val) *name 15 | #define __array(name, val) typeof(val) *name[] 16 | 17 | /* Helper macro to print out debug messages */ 18 | #define bpf_printk(fmt, ...) \ 19 | ({ \ 20 | char ____fmt[] = fmt; \ 21 | bpf_trace_printk(____fmt, sizeof(____fmt), \ 22 | ##__VA_ARGS__); \ 23 | }) 24 | 25 | /* 26 | * Helper macro to place programs, maps, license in 27 | * different sections in elf_bpf file. Section names 28 | * are interpreted by libbpf depending on the context (BPF programs, BPF maps, 29 | * extern variables, etc). 30 | * To allow use of SEC() with externs (e.g., for extern .maps declarations), 31 | * make sure __attribute__((unused)) doesn't trigger compilation warning. 32 | */ 33 | #define SEC(name) \ 34 | _Pragma("GCC diagnostic push") \ 35 | _Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \ 36 | __attribute__((section(name), used)) \ 37 | _Pragma("GCC diagnostic pop") \ 38 | 39 | /* Avoid 'linux/stddef.h' definition of '__always_inline'. */ 40 | #undef __always_inline 41 | #define __always_inline inline __attribute__((always_inline)) 42 | 43 | #ifndef __noinline 44 | #define __noinline __attribute__((noinline)) 45 | #endif 46 | #ifndef __weak 47 | #define __weak __attribute__((weak)) 48 | #endif 49 | 50 | /* 51 | * Use __hidden attribute to mark a non-static BPF subprogram effectively 52 | * static for BPF verifier's verification algorithm purposes, allowing more 53 | * extensive and permissive BPF verification process, taking into account 54 | * subprogram's caller context. 55 | */ 56 | #define __hidden __attribute__((visibility("hidden"))) 57 | 58 | /* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include 59 | * any system-level headers (such as stddef.h, linux/version.h, etc), and 60 | * commonly-used macros like NULL and KERNEL_VERSION aren't available through 61 | * vmlinux.h. This just adds unnecessary hurdles and forces users to re-define 62 | * them on their own. So as a convenience, provide such definitions here. 63 | */ 64 | #ifndef NULL 65 | #define NULL ((void *)0) 66 | #endif 67 | 68 | #ifndef KERNEL_VERSION 69 | #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c))) 70 | #endif 71 | 72 | /* 73 | * Helper macros to manipulate data structures 74 | */ 75 | #ifndef offsetof 76 | #define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER) 77 | #endif 78 | #ifndef container_of 79 | #define container_of(ptr, type, member) \ 80 | ({ \ 81 | void *__mptr = (void *)(ptr); \ 82 | ((type *)(__mptr - offsetof(type, member))); \ 83 | }) 84 | #endif 85 | 86 | /* 87 | * Helper macro to throw a compilation error if __bpf_unreachable() gets 88 | * built into the resulting code. This works given BPF back end does not 89 | * implement __builtin_trap(). This is useful to assert that certain paths 90 | * of the program code are never used and hence eliminated by the compiler. 91 | * 92 | * For example, consider a switch statement that covers known cases used by 93 | * the program. __bpf_unreachable() can then reside in the default case. If 94 | * the program gets extended such that a case is not covered in the switch 95 | * statement, then it will throw a build error due to the default case not 96 | * being compiled out. 97 | */ 98 | #ifndef __bpf_unreachable 99 | # define __bpf_unreachable() __builtin_trap() 100 | #endif 101 | 102 | /* 103 | * Helper function to perform a tail call with a constant/immediate map slot. 104 | */ 105 | #if __clang_major__ >= 8 && defined(__bpf__) 106 | static __always_inline void 107 | bpf_tail_call_static(void *ctx, const void *map, const __u32 slot) 108 | { 109 | if (!__builtin_constant_p(slot)) 110 | __bpf_unreachable(); 111 | 112 | /* 113 | * Provide a hard guarantee that LLVM won't optimize setting r2 (map 114 | * pointer) and r3 (constant map index) from _different paths_ ending 115 | * up at the _same_ call insn as otherwise we won't be able to use the 116 | * jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel 117 | * given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key 118 | * tracking for prog array pokes") for details on verifier tracking. 119 | * 120 | * Note on clobber list: we need to stay in-line with BPF calling 121 | * convention, so even if we don't end up using r0, r4, r5, we need 122 | * to mark them as clobber so that LLVM doesn't end up using them 123 | * before / after the call. 124 | */ 125 | asm volatile("r1 = %[ctx]\n\t" 126 | "r2 = %[map]\n\t" 127 | "r3 = %[slot]\n\t" 128 | "call 12" 129 | :: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot) 130 | : "r0", "r1", "r2", "r3", "r4", "r5"); 131 | } 132 | #endif 133 | 134 | /* 135 | * Helper structure used by eBPF C program 136 | * to describe BPF map attributes to libbpf loader 137 | */ 138 | struct bpf_map_def { 139 | unsigned int type; 140 | unsigned int key_size; 141 | unsigned int value_size; 142 | unsigned int max_entries; 143 | unsigned int map_flags; 144 | }; 145 | 146 | enum libbpf_pin_type { 147 | LIBBPF_PIN_NONE, 148 | /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */ 149 | LIBBPF_PIN_BY_NAME, 150 | }; 151 | 152 | enum libbpf_tristate { 153 | TRI_NO = 0, 154 | TRI_YES = 1, 155 | TRI_MODULE = 2, 156 | }; 157 | 158 | #define __kconfig __attribute__((section(".kconfig"))) 159 | #define __ksym __attribute__((section(".ksyms"))) 160 | 161 | #ifndef ___bpf_concat 162 | #define ___bpf_concat(a, b) a ## b 163 | #endif 164 | #ifndef ___bpf_apply 165 | #define ___bpf_apply(fn, n) ___bpf_concat(fn, n) 166 | #endif 167 | #ifndef ___bpf_nth 168 | #define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N 169 | #endif 170 | #ifndef ___bpf_narg 171 | #define ___bpf_narg(...) \ 172 | ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 173 | #endif 174 | 175 | #define ___bpf_fill0(arr, p, x) do {} while (0) 176 | #define ___bpf_fill1(arr, p, x) arr[p] = x 177 | #define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args) 178 | #define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args) 179 | #define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args) 180 | #define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args) 181 | #define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args) 182 | #define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args) 183 | #define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args) 184 | #define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args) 185 | #define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args) 186 | #define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args) 187 | #define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args) 188 | #define ___bpf_fill(arr, args...) \ 189 | ___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args) 190 | 191 | /* 192 | * BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values 193 | * in a structure. 194 | */ 195 | #define BPF_SEQ_PRINTF(seq, fmt, args...) \ 196 | ({ \ 197 | static const char ___fmt[] = fmt; \ 198 | unsigned long long ___param[___bpf_narg(args)]; \ 199 | \ 200 | _Pragma("GCC diagnostic push") \ 201 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ 202 | ___bpf_fill(___param, args); \ 203 | _Pragma("GCC diagnostic pop") \ 204 | \ 205 | bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \ 206 | ___param, sizeof(___param)); \ 207 | }) 208 | 209 | /* 210 | * BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of 211 | * an array of u64. 212 | */ 213 | #define BPF_SNPRINTF(out, out_size, fmt, args...) \ 214 | ({ \ 215 | static const char ___fmt[] = fmt; \ 216 | unsigned long long ___param[___bpf_narg(args)]; \ 217 | \ 218 | _Pragma("GCC diagnostic push") \ 219 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ 220 | ___bpf_fill(___param, args); \ 221 | _Pragma("GCC diagnostic pop") \ 222 | \ 223 | bpf_snprintf(out, out_size, ___fmt, \ 224 | ___param, sizeof(___param)); \ 225 | }) 226 | 227 | #endif 228 | -------------------------------------------------------------------------------- /public/js/vendors~app.a19a9911.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([["vendors~app"],{"0e15":function(e,n,t){var r=t("597f");e.exports=function(e,n,t){return void 0===t?r(e,n,!1):r(e,t,!1!==n)}},2877:function(e,n,t){"use strict";function r(e,n,t,r,o,i,a,u){var c,s="function"==typeof e?e.options:e;if(n&&(s.render=n,s.staticRenderFns=t,s._compiled=!0),r&&(s.functional=!0),i&&(s._scopeId="data-v-"+i),a?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},s._ssrRegister=c):o&&(c=u?function(){o.call(this,(s.functional?this.parent:this).$root.$options.shadowRoot)}:o),c)if(s.functional){s._injectStyles=c;var f=s.render;s.render=function(e,n){return c.call(n),f(e,n)}}else{var l=s.beforeCreate;s.beforeCreate=l?[].concat(l,c):[c]}return{exports:e,options:s}}t.d(n,"a",(function(){return r}))},"3c4e":function(e,n,t){"use strict";var r=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var n=Object.prototype.toString.call(e);return"[object RegExp]"===n||"[object Date]"===n||function(e){return e.$$typeof===o}(e)}(e)};var o="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function i(e,n){var t;return n&&!0===n.clone&&r(e)?u((t=e,Array.isArray(t)?[]:{}),e,n):e}function a(e,n,t){var o=e.slice();return n.forEach((function(n,a){void 0===o[a]?o[a]=i(n,t):r(n)?o[a]=u(e[a],n,t):-1===e.indexOf(n)&&o.push(i(n,t))})),o}function u(e,n,t){var o=Array.isArray(n);return o===Array.isArray(e)?o?((t||{arrayMerge:a}).arrayMerge||a)(e,n,t):function(e,n,t){var o={};return r(e)&&Object.keys(e).forEach((function(n){o[n]=i(e[n],t)})),Object.keys(n).forEach((function(a){r(n[a])&&e[a]?o[a]=u(e[a],n[a],t):o[a]=i(n[a],t)})),o}(e,n,t):i(n,t)}u.all=function(e,n){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce((function(e,t){return u(e,t,n)}))};var c=u;e.exports=c},4362:function(e,n,t){var r,o;n.nextTick=function(e){var n=Array.prototype.slice.call(arguments);n.shift(),setTimeout((function(){e.apply(null,n)}),0)},n.platform=n.arch=n.execPath=n.title="browser",n.pid=1,n.browser=!0,n.env={},n.argv=[],n.binding=function(e){throw new Error("No such module. (Possibly not yet loaded)")},o="/",n.cwd=function(){return o},n.chdir=function(e){r||(r=t("df7c")),o=r.resolve(e,o)},n.exit=n.kill=n.umask=n.dlopen=n.uptime=n.memoryUsage=n.uvCounters=function(){},n.features={}},"597f":function(e,n){e.exports=function(e,n,t,r){var o,i=0;return"boolean"!=typeof n&&(r=t,t=n,n=void 0),function(){var a=this,u=Number(new Date)-i,c=arguments;function s(){i=Number(new Date),t.apply(a,c)}function f(){o=void 0}r&&!o&&s(),o&&clearTimeout(o),void 0===r&&u>e?s():!0!==n&&(o=setTimeout(r?f:s,void 0===r?e-u:e))}}},"7b3e":function(e,n,t){"use strict";var r,o=t("a3de");o.canUseDOM&&(r=document.implementation&&document.implementation.hasFeature&&!0!==document.implementation.hasFeature("","")) 2 | /** 3 | * Checks if an event is supported in the current execution environment. 4 | * 5 | * NOTE: This will not work correctly for non-generic events such as `change`, 6 | * `reset`, `load`, `error`, and `select`. 7 | * 8 | * Borrows from Modernizr. 9 | * 10 | * @param {string} eventNameSuffix Event name, e.g. "click". 11 | * @param {?boolean} capture Check if the capture phase is supported. 12 | * @return {boolean} True if the event is supported. 13 | * @internal 14 | * @license Modernizr 3.0.0pre (Custom Build) | MIT 15 | */,e.exports=function(e,n){if(!o.canUseDOM||n&&!("addEventListener"in document))return!1;var t="on"+e,i=t in document;if(!i){var a=document.createElement("div");a.setAttribute(t,"return;"),i="function"==typeof a[t]}return!i&&r&&"wheel"===e&&(i=document.implementation.hasFeature("Events.wheel","3.0")),i}},"8eb7":function(e,n){var t,r,o,i,a,u,c,s,f,l,d,p,h,v,m,w=!1;function y(){if(!w){w=!0;var e=navigator.userAgent,n=/(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(e),y=/(Mac OS X)|(Windows)|(Linux)/.exec(e);if(p=/\b(iPhone|iP[ao]d)/.exec(e),h=/\b(iP[ao]d)/.exec(e),l=/Android/i.exec(e),v=/FBAN\/\w+;/i.exec(e),m=/Mobile/i.exec(e),d=!!/Win64/.exec(e),n){(t=n[1]?parseFloat(n[1]):n[5]?parseFloat(n[5]):NaN)&&document&&document.documentMode&&(t=document.documentMode);var b=/(?:Trident\/(\d+.\d+))/.exec(e);u=b?parseFloat(b[1])+4:t,r=n[2]?parseFloat(n[2]):NaN,o=n[3]?parseFloat(n[3]):NaN,(i=n[4]?parseFloat(n[4]):NaN)?(n=/(?:Chrome\/(\d+\.\d+))/.exec(e),a=n&&n[1]?parseFloat(n[1]):NaN):a=NaN}else t=r=o=a=i=NaN;if(y){if(y[1]){var g=/(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(e);c=!g||parseFloat(g[1].replace("_","."))}else c=!1;s=!!y[2],f=!!y[3]}else c=s=f=!1}}var b={ie:function(){return y()||t},ieCompatibilityMode:function(){return y()||u>t},ie64:function(){return b.ie()&&d},firefox:function(){return y()||r},opera:function(){return y()||o},webkit:function(){return y()||i},safari:function(){return b.webkit()},chrome:function(){return y()||a},windows:function(){return y()||s},osx:function(){return y()||c},linux:function(){return y()||f},iphone:function(){return y()||p},mobile:function(){return y()||p||h||l||m},nativeApp:function(){return y()||v},android:function(){return y()||l},ipad:function(){return y()||h}};e.exports=b},"92fa":function(e,n){var t=/^(attrs|props|on|nativeOn|class|style|hook)$/;function r(e,n){return function(){e&&e.apply(this,arguments),n&&n.apply(this,arguments)}}e.exports=function(e){return e.reduce((function(e,n){var o,i,a,u,c;for(a in n)if(o=e[a],i=n[a],o&&t.test(a))if("class"===a&&("string"==typeof o&&(c=o,e[a]=o={},o[c]=!0),"string"==typeof i&&(c=i,n[a]=i={},i[c]=!0)),"on"===a||"nativeOn"===a||"hook"===a)for(u in i)o[u]=r(o[u],i[u]);else if(Array.isArray(o))e[a]=o.concat(i);else if(Array.isArray(i))e[a]=[o].concat(i);else for(u in i)o[u]=i[u];else e[a]=n[a];return e}),{})}},9619:function(e,n,t){var r=t("597f"),o=t("0e15");e.exports={throttle:r,debounce:o}},a3de:function(e,n,t){"use strict";var r=!("undefined"==typeof window||!window.document||!window.document.createElement),o={canUseDOM:r,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:r&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:r&&!!window.screen,isInWorker:!r};e.exports=o},c098:function(e,n,t){e.exports=t("d4af")},c8ba:function(e,n){var t;t=function(){return this}();try{t=t||new Function("return this")()}catch(e){"object"==typeof window&&(t=window)}e.exports=t},d4af:function(e,n,t){"use strict";var r=t("8eb7"),o=t("7b3e");function i(e){var n=0,t=0,r=0,o=0;return"detail"in e&&(t=e.detail),"wheelDelta"in e&&(t=-e.wheelDelta/120),"wheelDeltaY"in e&&(t=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(n=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(n=t,t=0),r=10*n,o=10*t,"deltaY"in e&&(o=e.deltaY),"deltaX"in e&&(r=e.deltaX),(r||o)&&e.deltaMode&&(1==e.deltaMode?(r*=40,o*=40):(r*=800,o*=800)),r&&!n&&(n=r<1?-1:1),o&&!t&&(t=o<1?-1:1),{spinX:n,spinY:t,pixelX:r,pixelY:o}}i.getEventType=function(){return r.firefox()?"DOMMouseScroll":o("wheel")?"wheel":"mousewheel"},e.exports=i},df7c:function(e,n,t){(function(e){function t(e,n){for(var t=0,r=e.length-1;r>=0;r--){var o=e[r];"."===o?e.splice(r,1):".."===o?(e.splice(r,1),t++):t&&(e.splice(r,1),t--)}if(n)for(;t--;t)e.unshift("..");return e}function r(e,n){if(e.filter)return e.filter(n);for(var t=[],r=0;r=-1&&!o;i--){var a=i>=0?arguments[i]:e.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(n=a+"/"+n,o="/"===a.charAt(0))}return(o?"/":"")+(n=t(r(n.split("/"),(function(e){return!!e})),!o).join("/"))||"."},n.normalize=function(e){var i=n.isAbsolute(e),a="/"===o(e,-1);return(e=t(r(e.split("/"),(function(e){return!!e})),!i).join("/"))||i||(e="."),e&&a&&(e+="/"),(i?"/":"")+e},n.isAbsolute=function(e){return"/"===e.charAt(0)},n.join=function(){var e=Array.prototype.slice.call(arguments,0);return n.normalize(r(e,(function(e,n){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e})).join("/"))},n.relative=function(e,t){function r(e){for(var n=0;n=0&&""===e[t];t--);return n>t?[]:e.slice(n,t-n+1)}e=n.resolve(e).substr(1),t=n.resolve(t).substr(1);for(var o=r(e.split("/")),i=r(t.split("/")),a=Math.min(o.length,i.length),u=a,c=0;c=1;--i)if(47===(n=e.charCodeAt(i))){if(!o){r=i;break}}else o=!1;return-1===r?t?"/":".":t&&1===r?"/":e.slice(0,r)},n.basename=function(e,n){var t=function(e){"string"!=typeof e&&(e+="");var n,t=0,r=-1,o=!0;for(n=e.length-1;n>=0;--n)if(47===e.charCodeAt(n)){if(!o){t=n+1;break}}else-1===r&&(o=!1,r=n+1);return-1===r?"":e.slice(t,r)}(e);return n&&t.substr(-1*n.length)===n&&(t=t.substr(0,t.length-n.length)),t},n.extname=function(e){"string"!=typeof e&&(e+="");for(var n=-1,t=0,r=-1,o=!0,i=0,a=e.length-1;a>=0;--a){var u=e.charCodeAt(a);if(47!==u)-1===r&&(o=!1,r=a+1),46===u?-1===n?n=a:1!==i&&(i=1):-1!==n&&(i=-1);else if(!o){t=a+1;break}}return-1===n||-1===r||0===i||1===i&&n===r-1&&n===t+1?"":e.slice(n,r)};var o="b"==="ab".substr(-1)?function(e,n,t){return e.substr(n,t)}:function(e,n,t){return n<0&&(n=e.length+n),e.substr(n,t)}}).call(this,t("4362"))}}]); -------------------------------------------------------------------------------- /rule.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/netip" 7 | "os" 8 | "sort" 9 | "time" 10 | ) 11 | 12 | const ( 13 | xdpDrop = 1 14 | xdpPass = 2 15 | 16 | tcpBit uint8 = 0b0001 17 | udpBit uint8 = 0b0010 18 | icmpBit uint8 = 0b0100 19 | 20 | protoICMP uint32 = 1 21 | protoTCP uint32 = 6 22 | protoUDP uint32 = 17 23 | 24 | portBegin = 0 25 | portEnd = 65535 26 | ) 27 | 28 | var ( 29 | ipMaxEntries uint32 30 | maxPriority uint32 31 | ) 32 | 33 | type Addr struct { 34 | CidrUser string `json:"cidr_user"` 35 | CidrStandard string `json:"cidr_standard"` 36 | CidrSpecial netip.Prefix `json:"-"` 37 | } 38 | 39 | type Rule struct { 40 | Priority uint32 `json:"priority"` 41 | Strategy uint8 `json:"strategy"` 42 | Protos uint8 `json:"protos"` 43 | CreateTime int64 `json:"create_time"` 44 | AddrSrcArr []Addr `json:"addr_src_arr"` 45 | PortSrcArr []uint16 `json:"port_src_arr"` 46 | AddrDstArr []Addr `json:"addr_dst_arr"` 47 | PortDstArr []uint16 `json:"port_dst_arr"` 48 | HitCounts string `json:"-"` 49 | CanNotDel uint8 `json:"can_not_del,omitempty"` 50 | } 51 | 52 | func (r *Rule) onlyICMP() bool { 53 | return (r.Protos&icmpBit != 0) && (r.Protos&(tcpBit|udpBit) == 0) 54 | } 55 | 56 | func (r *Rule) IsFixed(lastRuleFixed bool) bool { 57 | return lastRuleFixed && r.Priority == maxPriority 58 | } 59 | 60 | func (r *Rule) isLastRule() bool { 61 | zeroCidr := "0.0.0.0/0" 62 | return len(r.AddrSrcArr) == 1 && r.AddrSrcArr[0].CidrUser == zeroCidr && 63 | len(r.AddrDstArr) == 1 && r.AddrDstArr[0].CidrUser == zeroCidr && 64 | len(r.PortSrcArr) == 0 && len(r.PortDstArr) == 0 && 65 | r.Protos&0b111 == 0b111 66 | } 67 | 68 | type Rules struct { 69 | file string 70 | 71 | BitmapArraySize int 72 | 73 | lastRuleAccept bool 74 | lastRuleFixed bool 75 | 76 | rules []*Rule 77 | 78 | realPriorities []uint32 79 | 80 | allSrcPortPriorities []uint32 81 | allDstPortPriorities []uint32 82 | 83 | srcCIDRPriorities map[netip.Prefix][]uint32 84 | dstCIDRPriorities map[netip.Prefix][]uint32 85 | srcPortPriorities [][]uint32 86 | dstPortPriorities [][]uint32 87 | protocolPriorities map[uint32][]uint32 88 | priorityActions []uint8 89 | } 90 | 91 | func (r *Rules) init() { 92 | r.allSrcPortPriorities = nil 93 | r.allDstPortPriorities = nil 94 | r.srcCIDRPriorities = make(map[netip.Prefix][]uint32) 95 | r.dstCIDRPriorities = make(map[netip.Prefix][]uint32) 96 | r.srcPortPriorities = make([][]uint32, portEnd+1) 97 | r.dstPortPriorities = make([][]uint32, portEnd+1) 98 | r.protocolPriorities = make(map[uint32][]uint32, 3) // ICMP, TCP, UDP 99 | r.priorityActions = make([]uint8, len(r.rules)) 100 | } 101 | 102 | func (r *Rules) ClearCache() { 103 | r.allSrcPortPriorities = nil 104 | r.allDstPortPriorities = nil 105 | r.srcCIDRPriorities = nil 106 | r.dstCIDRPriorities = nil 107 | r.srcPortPriorities = nil 108 | r.dstPortPriorities = nil 109 | r.protocolPriorities = nil 110 | r.priorityActions = nil 111 | } 112 | 113 | func loadRules(fpath string) ([]*Rule, error) { 114 | fd, err := os.Open(fpath) 115 | if err != nil { 116 | return nil, fmt.Errorf("failed to open %s: %w", fpath, err) 117 | } 118 | defer fd.Close() 119 | 120 | var rules []*Rule 121 | err = json.NewDecoder(fd).Decode(&rules) 122 | if err != nil { 123 | return nil, fmt.Errorf("failed to decode rules from %s: %w", fpath, err) 124 | } 125 | 126 | return rules, nil 127 | } 128 | 129 | func LoadRules(conf string, lastRuleAccept, lastRuleFixed bool) (*Rules, error) { 130 | rules, err := loadRules(conf) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | var r Rules 136 | r.file = conf 137 | r.rules = rules 138 | r.lastRuleAccept = lastRuleAccept 139 | r.lastRuleFixed = lastRuleFixed 140 | 141 | return &r, r.FixRules() 142 | } 143 | 144 | func (r *Rules) fixBitmap() { 145 | r.BitmapArraySize = roundUp(len(r.rules)) 146 | ipMaxEntries = uint32(r.BitmapArraySize * bitmapSize) 147 | maxPriority = ipMaxEntries - 1 148 | } 149 | 150 | func roundUp(n int) int { 151 | n >>= 6 // n /= 64 152 | if n&63 != 0 { // n%64 != 0 153 | n++ 154 | } 155 | 156 | rounds := []int{8, 16, 32, 64, 128, 160, 256} 157 | for _, r := range rounds { 158 | if n < r { 159 | return r 160 | } 161 | } 162 | 163 | panic("bitmap array size cannot be larger than 256") 164 | } 165 | 166 | func (r *Rules) FixRules() error { 167 | r.init() 168 | 169 | r.fixBitmap() 170 | 171 | if err := r.sortRules(); err != nil { 172 | return err 173 | } 174 | 175 | if r.lastRuleFixed { 176 | r.fixLastRule(r.lastRuleAccept) 177 | } 178 | 179 | r.fixDeletion(r.lastRuleFixed) 180 | 181 | if err := r.check(); err != nil { 182 | return err 183 | } 184 | 185 | if err := r.addPriorities(); err != nil { 186 | return err 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (r *Rules) GetRealPriority(p uint32) uint32 { 193 | if int(p) < len(r.realPriorities) { 194 | return r.realPriorities[p] 195 | } 196 | return p 197 | } 198 | 199 | func (r *Rules) sortRules() error { 200 | sort.Slice(r.rules, func(i, j int) bool { 201 | return r.rules[i].Priority < r.rules[j].Priority 202 | }) 203 | 204 | length := len(r.rules) 205 | for i := 1; i < length; i++ { 206 | if r.rules[i].Priority == r.rules[i-1].Priority { 207 | return fmt.Errorf("duplicated rule's priority %d", r.rules[i].Priority) 208 | } 209 | } 210 | 211 | r.realPriorities = make([]uint32, length) 212 | for i := range r.rules { 213 | priority := uint32(i) 214 | r.realPriorities[priority] = r.rules[i].Priority 215 | r.rules[i].Priority = priority 216 | } 217 | 218 | return nil 219 | } 220 | 221 | func (r *Rules) fixLastRule(lastRuleAccept bool) { 222 | if len(r.rules) != 0 { 223 | if rule := r.rules[len(r.rules)-1]; rule.isLastRule() { 224 | if lastRuleAccept { 225 | rule.Strategy = xdpPass 226 | } else { 227 | rule.Strategy = xdpDrop 228 | } 229 | return 230 | } 231 | } 232 | 233 | zeroCidr := "0.0.0.0/0" 234 | 235 | lastRule := Rule{ 236 | Priority: maxPriority, 237 | Protos: 0b0111, 238 | CreateTime: time.Now().UnixNano() / int64(time.Millisecond), 239 | AddrSrcArr: []Addr{{CidrUser: zeroCidr, CidrStandard: zeroCidr}}, 240 | PortSrcArr: []uint16{}, 241 | AddrDstArr: []Addr{{CidrUser: zeroCidr, CidrStandard: zeroCidr}}, 242 | PortDstArr: []uint16{}, 243 | CanNotDel: 1, 244 | } 245 | 246 | if lastRuleAccept { 247 | lastRule.Strategy = xdpPass 248 | } else { 249 | lastRule.Strategy = xdpDrop 250 | } 251 | 252 | r.rules = append(r.rules, &lastRule) 253 | } 254 | 255 | func (r *Rules) fixDeletion(lastRuleFixed bool) { 256 | for i := range r.rules { 257 | if r.rules[i].Priority == maxPriority { 258 | if lastRuleFixed { 259 | r.rules[i].CanNotDel = 1 260 | } else { 261 | r.rules[i].CanNotDel = 0 262 | } 263 | } 264 | } 265 | } 266 | 267 | func (r *Rules) check() error { 268 | for i := range r.rules { 269 | rule := r.rules[i] 270 | if err := r.checkRule(rule); err != nil { 271 | return err 272 | } 273 | } 274 | 275 | return nil 276 | } 277 | 278 | func (r *Rules) checkRule(rule *Rule) error { 279 | return rule.check() 280 | } 281 | 282 | func (r *Rules) addPriorities() error { 283 | for _, rule := range r.rules { 284 | if err := r.addPriority(rule); err != nil { 285 | return err 286 | } 287 | } 288 | 289 | r.fixSrcCIDRPriority() 290 | r.fixDstCIDRPriority() 291 | 292 | r.fixSrcPortPriority() 293 | r.fixDstPortPriority() 294 | 295 | return nil 296 | } 297 | 298 | func (r *Rules) addPriority(rule *Rule) error { 299 | if err := r.addSrcCIDRPriority(rule); err != nil { 300 | return err 301 | } 302 | 303 | if err := r.addDstCIDRPriority(rule); err != nil { 304 | return err 305 | } 306 | 307 | r.addSrcPortPriority(rule) 308 | r.addDstPortPriority(rule) 309 | r.addProtocolPriority(rule) 310 | r.addPriorityAction(rule) 311 | 312 | return nil 313 | } 314 | 315 | func (r *Rules) addProtocolPriority(rule *Rule) { 316 | proto := rule.Protos 317 | if proto&tcpBit != 0 { 318 | r.protocolPriorities[protoTCP] = append(r.protocolPriorities[protoTCP], rule.Priority) 319 | } 320 | 321 | if proto&udpBit != 0 { 322 | r.protocolPriorities[protoUDP] = append(r.protocolPriorities[protoUDP], rule.Priority) 323 | } 324 | 325 | if proto&icmpBit != 0 { 326 | r.protocolPriorities[protoICMP] = append(r.protocolPriorities[protoICMP], rule.Priority) 327 | } 328 | } 329 | 330 | func (r *Rules) addPriorityAction(rule *Rule) { 331 | r.priorityActions[rule.Priority] = rule.Strategy 332 | } 333 | 334 | func (r *Rules) AddRule(rule *Rule) { 335 | rules := r.GetRulesWithRealPriority() 336 | rules = append(rules, rule) 337 | r.rules = rules 338 | } 339 | 340 | func (r *Rules) DeleteRule(rule *Rule) { 341 | rules := r.GetRulesWithRealPriority() 342 | r.rules = r.rules[:0] 343 | for i := range rules { 344 | if rules[i].Priority != rule.Priority { 345 | r.rules = append(r.rules, rules[i]) 346 | } 347 | } 348 | } 349 | 350 | func (r *Rules) GetRulesWithRealPriority() []*Rule { 351 | copied := make([]*Rule, 0, len(r.rules)) 352 | // Note: do deep copy instead of copying pointers 353 | for i := range r.rules { 354 | rule := *r.rules[i] 355 | rule.Priority = r.GetRealPriority(r.rules[i].Priority) 356 | copied = append(copied, &rule) 357 | } 358 | return copied 359 | } 360 | 361 | func (r *Rules) Save() error { 362 | data, err := json.MarshalIndent(r.GetRulesWithRealPriority(), "", " ") 363 | if err != nil { 364 | return fmt.Errorf("failed to marshal rules: %w", err) 365 | } 366 | 367 | fd, err := os.OpenFile(r.file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) 368 | if err != nil { 369 | return fmt.Errorf("failed to open file %s to write rules: %w", r.file, err) 370 | } 371 | defer fd.Close() 372 | 373 | _, err = fd.Write(data) 374 | if err != nil { 375 | return fmt.Errorf("failed to write rules to %s: %w", r.file, err) 376 | } 377 | 378 | return nil 379 | } 380 | -------------------------------------------------------------------------------- /public/js/npm.async-validator.7418a59e.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([["npm.async-validator"],{a15e:function(e,r,t){"use strict";t.r(r);var n=t("41b2"),i=t.n(n),a=t("1098"),s=t.n(a),u=/%[sdj%]/g;function o(){for(var e=arguments.length,r=Array(e),t=0;t=a)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}break;default:return e}})),o=r[n];n()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,url:new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$","i"),hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},m={integer:function(e){return m.number(e)&&parseInt(e,10)===e},float:function(e){return m.number(e)&&!m.integer(e)},array:function(e){return Array.isArray(e)},regexp:function(e){if(e instanceof RegExp)return!0;try{return!!new RegExp(e)}catch(e){return!1}},date:function(e){return"function"==typeof e.getTime&&"function"==typeof e.getMonth&&"function"==typeof e.getYear},number:function(e){return!isNaN(e)&&"number"==typeof e},object:function(e){return"object"===(void 0===e?"undefined":s()(e))&&!m.array(e)},method:function(e){return"function"==typeof e},email:function(e){return"string"==typeof e&&!!e.match(g.email)&&e.length<255},url:function(e){return"string"==typeof e&&!!e.match(g.url)},hex:function(e){return"string"==typeof e&&!!e.match(g.hex)}};var v=function(e,r,t,n,i){if(e.required&&void 0===r)y(e,r,t,n,i);else{var a=e.type;["integer","float","array","regexp","object","method","email","number","date","url","hex"].indexOf(a)>-1?m[a](r)||n.push(o(i.messages.types[a],e.fullField,e.type)):a&&(void 0===r?"undefined":s()(r))!==e.type&&n.push(o(i.messages.types[a],e.fullField,e.type))}};var q={required:y,whitespace:h,type:v,range:function(e,r,t,n,i){var a="number"==typeof e.len,s="number"==typeof e.min,u="number"==typeof e.max,f=r,l=null,d="number"==typeof r,c="string"==typeof r,p=Array.isArray(r);if(d?l="number":c?l="string":p&&(l="array"),!l)return!1;p&&(f=r.length),c&&(f=r.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"_").length),a?f!==e.len&&n.push(o(i.messages[l].len,e.fullField,e.len)):s&&!u&&fe.max?n.push(o(i.messages[l].max,e.fullField,e.max)):s&&u&&(fe.max)&&n.push(o(i.messages[l].range,e.fullField,e.min,e.max))},enum:function(e,r,t,n,i){e.enum=Array.isArray(e.enum)?e.enum:[],-1===e.enum.indexOf(r)&&n.push(o(i.messages.enum,e.fullField,e.enum.join(", ")))},pattern:function(e,r,t,n,i){if(e.pattern)if(e.pattern instanceof RegExp)e.pattern.lastIndex=0,e.pattern.test(r)||n.push(o(i.messages.pattern.mismatch,e.fullField,r,e.pattern));else if("string"==typeof e.pattern){new RegExp(e.pattern).test(r)||n.push(o(i.messages.pattern.mismatch,e.fullField,r,e.pattern))}}};var b=function(e,r,t,n,i){var a=e.type,s=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r,a)&&!e.required)return t();q.required(e,r,n,s,i,a),f(r,a)||q.type(e,r,n,s,i)}t(s)},w={string:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r,"string")&&!e.required)return t();q.required(e,r,n,a,i,"string"),f(r,"string")||(q.type(e,r,n,a,i),q.range(e,r,n,a,i),q.pattern(e,r,n,a,i),!0===e.whitespace&&q.whitespace(e,r,n,a,i))}t(a)},method:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&q.type(e,r,n,a,i)}t(a)},number:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&(q.type(e,r,n,a,i),q.range(e,r,n,a,i))}t(a)},boolean:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&q.type(e,r,n,a,i)}t(a)},regexp:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),f(r)||q.type(e,r,n,a,i)}t(a)},integer:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&(q.type(e,r,n,a,i),q.range(e,r,n,a,i))}t(a)},float:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&(q.type(e,r,n,a,i),q.range(e,r,n,a,i))}t(a)},array:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r,"array")&&!e.required)return t();q.required(e,r,n,a,i,"array"),f(r,"array")||(q.type(e,r,n,a,i),q.range(e,r,n,a,i))}t(a)},object:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),void 0!==r&&q.type(e,r,n,a,i)}t(a)},enum:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();q.required(e,r,n,a,i),r&&q.enum(e,r,n,a,i)}t(a)},pattern:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r,"string")&&!e.required)return t();q.required(e,r,n,a,i),f(r,"string")||q.pattern(e,r,n,a,i)}t(a)},date:function(e,r,t,n,i){var a=[];if(e.required||!e.required&&n.hasOwnProperty(e.field)){if(f(r)&&!e.required)return t();if(q.required(e,r,n,a,i),!f(r)){var s=void 0;s="number"==typeof r?new Date(r):r,q.type(e,s,n,a,i),s&&q.range(e,s.getTime(),n,a,i)}}t(a)},url:b,hex:b,email:b,required:function(e,r,t,n,i){var a=[],u=Array.isArray(r)?"array":void 0===r?"undefined":s()(r);q.required(e,r,n,a,i,u),t(a)}};function x(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var e=JSON.parse(JSON.stringify(this));return e.clone=this.clone,e}}}var O=x();function F(e){this.rules=null,this._messages=O,this.define(e)}F.prototype={messages:function(e){return e&&(this._messages=p(x(),e)),this._messages},define:function(e){if(!e)throw new Error("Cannot configure a schema with no rules");if("object"!==(void 0===e?"undefined":s()(e))||Array.isArray(e))throw new Error("Rules must be an object");this.rules={};var r=void 0,t=void 0;for(r in e)e.hasOwnProperty(r)&&(t=e[r],this.rules[r]=Array.isArray(t)?t:[t])},validate:function(e){var r=this,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],a=e,u=t,f=n;if("function"==typeof u&&(f=u,u={}),this.rules&&0!==Object.keys(this.rules).length){if(u.messages){var l=this.messages();l===O&&(l=x()),p(l,u.messages),u.messages=l}else u.messages=this.messages();var y=void 0,h=void 0,g={},m=u.keys||Object.keys(this.rules);m.forEach((function(t){y=r.rules[t],h=a[t],y.forEach((function(n){var s=n;"function"==typeof s.transform&&(a===e&&(a=i()({},a)),h=a[t]=s.transform(h)),(s="function"==typeof s?{validator:s}:i()({},s)).validator=r.getValidationMethod(s),s.field=t,s.fullField=s.fullField||t,s.type=r.getType(s),s.validator&&(g[t]=g[t]||[],g[t].push({rule:s,value:h,source:a,field:t}))}))}));var v={};d(g,u,(function(e,r){var t=e.rule,n=!("object"!==t.type&&"array"!==t.type||"object"!==s()(t.fields)&&"object"!==s()(t.defaultField));function a(e,r){return i()({},r,{fullField:t.fullField+"."+e})}function f(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],f=s;if(Array.isArray(f)||(f=[f]),f.length,f.length&&t.message&&(f=[].concat(t.message)),f=f.map(c(t)),u.first&&f.length)return v[t.field]=1,r(f);if(n){if(t.required&&!e.value)return f=t.message?[].concat(t.message).map(c(t)):u.error?[u.error(t,o(u.messages.required,t.field))]:[],r(f);var l={};if(t.defaultField)for(var d in e.value)e.value.hasOwnProperty(d)&&(l[d]=t.defaultField);for(var p in l=i()({},l,e.rule.fields))if(l.hasOwnProperty(p)){var y=Array.isArray(l[p])?l[p]:[l[p]];l[p]=y.map(a.bind(null,p))}var h=new F(l);h.messages(u.messages),e.rule.options&&(e.rule.options.messages=u.messages,e.rule.options.error=u.error),h.validate(e.value,e.rule.options||u,(function(e){r(e&&e.length?f.concat(e):e)}))}else r(f)}n=n&&(t.required||!t.required&&e.value),t.field=e.field;var l=t.validator(t,e.value,f,e.source,u);l&&l.then&&l.then((function(){return f()}),(function(e){return f(e)}))}),(function(e){q(e)}))}else f&&f();function q(e){var r,t=void 0,n=void 0,i=[],a={};for(t=0;t=200&&e<300}};c.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){c.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){c.headers[e]=r.merge(s)})),e.exports=c}).call(this,n("4362"))},"2d83":function(e,t,n){"use strict";var r=n("387f");e.exports=function(e,t,n,o,s){var i=new Error(e);return r(i,t,n,o,s)}},"2e67":function(e,t,n){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},"30b5":function(e,t,n){"use strict";var r=n("c532");function o(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(r.isURLSearchParams(t))s=t.toString();else{var i=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),i.push(o(t)+"="+o(e))})))})),s=i.join("&")}if(s){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+s}return e}},"387f":function(e,t,n){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},3934:function(e,t,n){"use strict";var r=n("c532");e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},"467f":function(e,t,n){"use strict";var r=n("2d83");e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},"4a7b":function(e,t,n){"use strict";var r=n("c532");e.exports=function(e,t){t=t||{};var n={},o=["url","method","data"],s=["headers","auth","proxy","params"],i=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],a=["validateStatus"];function c(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function u(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(n[o]=c(void 0,e[o])):n[o]=c(e[o],t[o])}r.forEach(o,(function(e){r.isUndefined(t[e])||(n[e]=c(void 0,t[e]))})),r.forEach(s,u),r.forEach(i,(function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(n[o]=c(void 0,e[o])):n[o]=c(void 0,t[o])})),r.forEach(a,(function(r){r in t?n[r]=c(e[r],t[r]):r in e&&(n[r]=c(void 0,e[r]))}));var f=o.concat(s).concat(i).concat(a),p=Object.keys(e).concat(Object.keys(t)).filter((function(e){return-1===f.indexOf(e)}));return r.forEach(p,u),n}},5270:function(e,t,n){"use strict";var r=n("c532"),o=n("c401"),s=n("2e67"),i=n("2444");function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){return a(e),e.headers=e.headers||{},e.data=o(e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||i.adapter)(e).then((function(t){return a(e),t.data=o(t.data,t.headers,e.transformResponse),t}),(function(t){return s(t)||(a(e),t&&t.response&&(t.response.data=o(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},"5f02":function(e,t,n){"use strict";e.exports=function(e){return"object"==typeof e&&!0===e.isAxiosError}},"7a77":function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},"7aac":function(e,t,n){"use strict";var r=n("c532");e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,o,s,i){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(s)&&a.push("domain="+s),!0===i&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},"83b9":function(e,t,n){"use strict";var r=n("d925"),o=n("e683");e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},"8df4":function(e,t,n){"use strict";var r=n("7a77");function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;e((function(e){n.reason||(n.reason=new r(e),t(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var e;return{token:new o((function(t){e=t})),cancel:e}},e.exports=o},b50d:function(e,t,n){"use strict";var r=n("c532"),o=n("467f"),s=n("7aac"),i=n("30b5"),a=n("83b9"),c=n("c345"),u=n("3934"),f=n("2d83");e.exports=function(e){return new Promise((function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";d.Authorization="Basic "+btoa(h+":"+m)}var g=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),i(g,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?c(l.getAllResponseHeaders()):null,s={data:e.responseType&&"text"!==e.responseType?l.response:l.responseText,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,s),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var y=(e.withCredentials||u(g))&&e.xsrfCookieName?s.read(e.xsrfCookieName):void 0;y&&(d[e.xsrfHeaderName]=y)}if("setRequestHeader"in l&&r.forEach(d,(function(e,t){void 0===p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){l&&(l.abort(),n(e),l=null)})),p||(p=null),l.send(p)}))}},bc3a:function(e,t,n){e.exports=n("cee4")},c345:function(e,t,n){"use strict";var r=n("c532"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,s,i={};return e?(r.forEach(e.split("\n"),(function(e){if(s=e.indexOf(":"),t=r.trim(e.substr(0,s)).toLowerCase(),n=r.trim(e.substr(s+1)),t){if(i[t]&&o.indexOf(t)>=0)return;i[t]="set-cookie"===t?(i[t]?i[t]:[]).concat([n]):i[t]?i[t]+", "+n:n}})),i):i}},c401:function(e,t,n){"use strict";var r=n("c532");e.exports=function(e,t,n){return r.forEach(n,(function(n){e=n(e,t)})),e}},c532:function(e,t,n){"use strict";var r=n("1d2b"),o=Object.prototype.toString;function s(e){return"[object Array]"===o.call(e)}function i(e){return void 0===e}function a(e){return null!==e&&"object"==typeof e}function c(e){if("[object Object]"!==o.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function u(e){return"[object Function]"===o.call(e)}function f(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),s(e))for(var n=0,r=e.length;nc;)r(f,e=n[c++])&&(~i(a,e)||a.push(e));return a}},"051b":function(t,n,e){var r=e("1a14"),o=e("10db");t.exports=e("0bad")?function(t,n,e){return r.f(t,n,o(1,e))}:function(t,n,e){return t[n]=e,t}},"05f5":function(t,n,e){var r=e("7a41"),o=e("ef08").document,i=r(o)&&r(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},"072d":function(t,n,e){"use strict";var r=e("0bad"),o=e("9876"),i=e("fed5"),u=e("1917"),f=e("0983"),c=e("9fbb"),a=Object.assign;t.exports=!a||e("4b8b")((function(){var t={},n={},e=Symbol(),r="abcdefghijklmnopqrst";return t[e]=7,r.split("").forEach((function(t){n[t]=t})),7!=a({},t)[e]||Object.keys(a({},n)).join("")!=r}))?function(t,n){for(var e=f(t),a=arguments.length,s=1,p=i.f,l=u.f;a>s;)for(var y,b=c(arguments[s++]),d=p?o(b).concat(p(b)):o(b),v=d.length,h=0;v>h;)y=d[h++],r&&!l.call(b,y)||(e[y]=b[y]);return e}:a},"0983":function(t,n,e){var r=e("c901");t.exports=function(t){return Object(r(t))}},"0ae2":function(t,n,e){var r=e("9876"),o=e("fed5"),i=e("1917");t.exports=function(t){var n=r(t),e=o.f;if(e)for(var u,f=e(t),c=i.f,a=0;f.length>a;)c.call(t,u=f[a++])&&n.push(u);return n}},"0b99":function(t,n,e){"use strict";var r=e("19fa")(!0);e("393a")(String,"String",(function(t){this._t=String(t),this._i=0}),(function(){var t,n=this._t,e=this._i;return e>=n.length?{value:void 0,done:!0}:(t=r(n,e),this._i+=t.length,{value:t,done:!1})}))},"0bad":function(t,n,e){t.exports=!e("4b8b")((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},1098:function(t,n,e){"use strict";n.__esModule=!0;var r=u(e("17ed")),o=u(e("f893")),i="function"==typeof o.default&&"symbol"==typeof r.default?function(t){return typeof t}:function(t){return t&&"function"==typeof o.default&&t.constructor===o.default&&t!==o.default.prototype?"symbol":typeof t};function u(t){return t&&t.__esModule?t:{default:t}}n.default="function"==typeof o.default&&"symbol"===i(r.default)?function(t){return void 0===t?"undefined":i(t)}:function(t){return t&&"function"==typeof o.default&&t.constructor===o.default&&t!==o.default.prototype?"symbol":void 0===t?"undefined":i(t)}},"10db":function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},1609:function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},"17ed":function(t,n,e){t.exports={default:e("511f"),__esModule:!0}},1836:function(t,n,e){var r=e("6ca1"),o=e("6438").f,i={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==i.call(t)?function(t){try{return o(t)}catch(t){return u.slice()}}(t):o(r(t))}},1917:function(t,n){n.f={}.propertyIsEnumerable},"19fa":function(t,n,e){var r=e("fc5e"),o=e("c901");t.exports=function(t){return function(n,e){var i,u,f=String(o(n)),c=r(e),a=f.length;return c<0||c>=a?t?"":void 0:(i=f.charCodeAt(c))<55296||i>56319||c+1===a||(u=f.charCodeAt(c+1))<56320||u>57343?t?f.charAt(c):i:t?f.slice(c,c+2):u-56320+(i-55296<<10)+65536}}},"1a14":function(t,n,e){var r=e("77e9"),o=e("faf5"),i=e("3397"),u=Object.defineProperty;n.f=e("0bad")?Object.defineProperty:function(t,n,e){if(r(t),n=i(n,!0),r(e),o)try{return u(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported!");return"value"in e&&(t[n]=e.value),t}},"26dd":function(t,n,e){"use strict";var r=e("6f4f"),o=e("10db"),i=e("92f0"),u={};e("051b")(u,e("cc15")("iterator"),(function(){return this})),t.exports=function(t,n,e){t.prototype=r(u,{next:o(1,e)}),i(t,n+" Iterator")}},"2f9a":function(t,n){t.exports=function(){}},"301c":function(t,n,e){e("e198")("asyncIterator")},3397:function(t,n,e){var r=e("7a41");t.exports=function(t,n){if(!r(t))return t;var e,o;if(n&&"function"==typeof(e=t.toString)&&!r(o=e.call(t)))return o;if("function"==typeof(e=t.valueOf)&&!r(o=e.call(t)))return o;if(!n&&"function"==typeof(e=t.toString)&&!r(o=e.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},"393a":function(t,n,e){"use strict";var r=e("e444"),o=e("512c"),i=e("ba01"),u=e("051b"),f=e("8a0d"),c=e("26dd"),a=e("92f0"),s=e("ce7a"),p=e("cc15")("iterator"),l=!([].keys&&"next"in[].keys()),y=function(){return this};t.exports=function(t,n,e,b,d,v,h){c(e,n,b);var g,m,S,x=function(t){if(!l&&t in j)return j[t];switch(t){case"keys":case"values":return function(){return new e(this,t)}}return function(){return new e(this,t)}},O=n+" Iterator",w="values"==d,_=!1,j=t.prototype,P=j[p]||j["@@iterator"]||d&&j[d],E=P||x(d),M=d?w?x("entries"):E:void 0,L="Array"==n&&j.entries||P;if(L&&(S=s(L.call(new t)))!==Object.prototype&&S.next&&(a(S,O,!0),r||"function"==typeof S[p]||u(S,p,y)),w&&P&&"values"!==P.name&&(_=!0,E=function(){return P.call(this)}),r&&!h||!l&&!_&&j[p]||u(j,p,E),f[n]=E,f[O]=y,d)if(g={values:w?E:x("values"),keys:v?E:x("keys"),entries:M},h)for(m in g)m in j||i(j,m,g[m]);else o(o.P+o.F*(l||_),n,g);return g}},"39ad":function(t,n,e){var r=e("6ca1"),o=e("d16a"),i=e("9d11");t.exports=function(t){return function(n,e,u){var f,c=r(n),a=o(c.length),s=i(u,a);if(t&&e!=e){for(;a>s;)if((f=c[s++])!=f)return!0}else for(;a>s;s++)if((t||s in c)&&c[s]===e)return t||s||0;return!t&&-1}}},"3f6b":function(t,n,e){t.exports={default:e("b9c7"),__esModule:!0}},"41b2":function(t,n,e){"use strict";n.__esModule=!0;var r,o=e("3f6b"),i=(r=o)&&r.__esModule?r:{default:r};n.default=i.default||function(t){for(var n=1;n=t.length?(this._t=void 0,o(1)):o(0,"keys"==n?e:"values"==n?t[e]:[e,t[e]])}),"values"),i.Arguments=i.Array,r("keys"),r("values"),r("entries")},"693d":function(t,n,e){"use strict";var r=e("ef08"),o=e("9c0e"),i=e("0bad"),u=e("512c"),f=e("ba01"),c=e("e34a").KEY,a=e("4b8b"),s=e("b367"),p=e("92f0"),l=e("8b1a"),y=e("cc15"),b=e("fcd4"),d=e("e198"),v=e("0ae2"),h=e("4ebc"),g=e("77e9"),m=e("7a41"),S=e("0983"),x=e("6ca1"),O=e("3397"),w=e("10db"),_=e("6f4f"),j=e("1836"),P=e("4d20"),E=e("fed5"),M=e("1a14"),L=e("9876"),T=P.f,k=M.f,F=j.f,A=r.Symbol,N=r.JSON,C=N&&N.stringify,I=y("_hidden"),D=y("toPrimitive"),G={}.propertyIsEnumerable,R=s("symbol-registry"),V=s("symbols"),J=s("op-symbols"),W=Object.prototype,H="function"==typeof A&&!!E.f,z=r.QObject,B=!z||!z.prototype||!z.prototype.findChild,K=i&&a((function(){return 7!=_(k({},"a",{get:function(){return k(this,"a",{value:7}).a}})).a}))?function(t,n,e){var r=T(W,n);r&&delete W[n],k(t,n,e),r&&t!==W&&k(W,n,r)}:k,q=function(t){var n=V[t]=_(A.prototype);return n._k=t,n},Y=H&&"symbol"==typeof A.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof A},Q=function(t,n,e){return t===W&&Q(J,n,e),g(t),n=O(n,!0),g(e),o(V,n)?(e.enumerable?(o(t,I)&&t[I][n]&&(t[I][n]=!1),e=_(e,{enumerable:w(0,!1)})):(o(t,I)||k(t,I,w(1,{})),t[I][n]=!0),K(t,n,e)):k(t,n,e)},U=function(t,n){g(t);for(var e,r=v(n=x(n)),o=0,i=r.length;i>o;)Q(t,e=r[o++],n[e]);return t},X=function(t){var n=G.call(this,t=O(t,!0));return!(this===W&&o(V,t)&&!o(J,t))&&(!(n||!o(this,t)||!o(V,t)||o(this,I)&&this[I][t])||n)},Z=function(t,n){if(t=x(t),n=O(n,!0),t!==W||!o(V,n)||o(J,n)){var e=T(t,n);return!e||!o(V,n)||o(t,I)&&t[I][n]||(e.enumerable=!0),e}},$=function(t){for(var n,e=F(x(t)),r=[],i=0;e.length>i;)o(V,n=e[i++])||n==I||n==c||r.push(n);return r},tt=function(t){for(var n,e=t===W,r=F(e?J:x(t)),i=[],u=0;r.length>u;)!o(V,n=r[u++])||e&&!o(W,n)||i.push(V[n]);return i};H||(f((A=function(){if(this instanceof A)throw TypeError("Symbol is not a constructor!");var t=l(arguments.length>0?arguments[0]:void 0),n=function(e){this===W&&n.call(J,e),o(this,I)&&o(this[I],t)&&(this[I][t]=!1),K(this,t,w(1,e))};return i&&B&&K(W,t,{configurable:!0,set:n}),q(t)}).prototype,"toString",(function(){return this._k})),P.f=Z,M.f=Q,e("6438").f=j.f=$,e("1917").f=X,E.f=tt,i&&!e("e444")&&f(W,"propertyIsEnumerable",X,!0),b.f=function(t){return q(y(t))}),u(u.G+u.W+u.F*!H,{Symbol:A});for(var nt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),et=0;nt.length>et;)y(nt[et++]);for(var rt=L(y.store),ot=0;rt.length>ot;)d(rt[ot++]);u(u.S+u.F*!H,"Symbol",{for:function(t){return o(R,t+="")?R[t]:R[t]=A(t)},keyFor:function(t){if(!Y(t))throw TypeError(t+" is not a symbol!");for(var n in R)if(R[n]===t)return n},useSetter:function(){B=!0},useSimple:function(){B=!1}}),u(u.S+u.F*!H,"Object",{create:function(t,n){return void 0===n?_(t):U(_(t),n)},defineProperty:Q,defineProperties:U,getOwnPropertyDescriptor:Z,getOwnPropertyNames:$,getOwnPropertySymbols:tt});var it=a((function(){E.f(1)}));u(u.S+u.F*it,"Object",{getOwnPropertySymbols:function(t){return E.f(S(t))}}),N&&u(u.S+u.F*(!H||a((function(){var t=A();return"[null]"!=C([t])||"{}"!=C({a:t})||"{}"!=C(Object(t))}))),"JSON",{stringify:function(t){for(var n,e,r=[t],o=1;arguments.length>o;)r.push(arguments[o++]);if(e=n=r[1],(m(n)||void 0!==t)&&!Y(t))return h(n)||(n=function(t,n){if("function"==typeof e&&(n=e.call(this,t,n)),!Y(n))return n}),r[1]=n,C.apply(N,r)}}),A.prototype[D]||e("051b")(A.prototype,D,A.prototype.valueOf),p(A,"Symbol"),p(Math,"Math",!0),p(r.JSON,"JSON",!0)},"6ca1":function(t,n,e){var r=e("9fbb"),o=e("c901");t.exports=function(t){return r(o(t))}},"6f4f":function(t,n,e){var r=e("77e9"),o=e("85e7"),i=e("9742"),u=e("5a94")("IE_PROTO"),f=function(){},c=function(){var t,n=e("05f5")("iframe"),r=i.length;for(n.style.display="none",e("9141").appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("