├── docs ├── design.jpg ├── design-v2.png ├── go-xtables.png └── users │ ├── ZTA-LOGO.png │ └── Moresec-LOGO.png ├── .gitattributes ├── pkg ├── network │ ├── README.md │ ├── service_test.go │ ├── pkttype.go │ ├── ip4.go │ ├── address_test.go │ ├── tcp.go │ ├── ethernet_test.go │ ├── arp.go │ ├── ip6.go │ ├── hardware.go │ ├── icmp4.go │ ├── icmp6.go │ ├── address.go │ └── ethernet.go ├── iproute2 │ └── group.go ├── cmd │ ├── cmd_test.go │ └── cmd.go ├── constraint │ └── constraint.go └── log │ └── log.go ├── ROADMAP.md ├── AUTHORS ├── iptables ├── test │ ├── flush_all.go │ ├── allow_sip_accept.go │ ├── find_sip.go │ ├── anti_ping.go │ ├── allow_dport_accept.go │ ├── mirror_to_gw.go │ ├── allow_dport_10cs.go │ ├── anti_ddos.go │ ├── allow_output.go │ └── env.go ├── table.go ├── parser_test.go ├── rule.go ├── chain.go ├── iptables.go ├── statement.go ├── option.go ├── end_test.go └── parser.go ├── direction.go ├── internal ├── xutil │ ├── utils_test.go │ └── utils.go └── sandbox │ └── sandbox.go ├── ebtables ├── dump.go ├── test │ ├── reject_smac.go │ └── env.go ├── table.go ├── parser_test.go ├── chain.go ├── ebtables.go ├── rule.go ├── statement.go ├── parser.go ├── option.go └── end_test.go ├── .github └── workflows │ └── go.yml ├── loglevel.go ├── go.mod ├── examples ├── allow_multiports_accept │ └── main.go └── allow_dports_accept │ └── main.go ├── rate.go ├── operator.go ├── test └── stdout │ ├── list_ebtables_filter │ ├── list_arptables_filter │ └── list_iptables_filter ├── time_test.go ├── .gitignore ├── error.go ├── gen ├── gen.go ├── protocols_gen.go ├── protocols └── services_gen.go ├── time.go ├── go.sum └── README_cn.md /docs/design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singchia/go-xtables/HEAD/docs/design.jpg -------------------------------------------------------------------------------- /docs/design-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singchia/go-xtables/HEAD/docs/design-v2.png -------------------------------------------------------------------------------- /docs/go-xtables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singchia/go-xtables/HEAD/docs/go-xtables.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | go.sum linguist-generated 2 | * text=auto eol=lf 3 | *.ps1 text eol=crlf -------------------------------------------------------------------------------- /pkg/network/README.md: -------------------------------------------------------------------------------- 1 | ### references: 2 | 3 | * gopacket 4 | * netfilter 5 | * linux kernel 6 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ## Roadmap 2 | 3 | * arptables 4 | * 100% unit test coverage 5 | * utils based on the lib -------------------------------------------------------------------------------- /docs/users/ZTA-LOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singchia/go-xtables/HEAD/docs/users/ZTA-LOGO.png -------------------------------------------------------------------------------- /docs/users/Moresec-LOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singchia/go-xtables/HEAD/docs/users/Moresec-LOGO.png -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Names should be added to this file like so: 2 | # Name or Organization 3 | 4 | # Initial version authors: 5 | Austin Zhai -------------------------------------------------------------------------------- /pkg/iproute2/group.go: -------------------------------------------------------------------------------- 1 | package iproute2 2 | 3 | const ( 4 | group = "/etc/iproute2/group" 5 | ) 6 | 7 | type Group struct { 8 | names map[int]string 9 | } 10 | -------------------------------------------------------------------------------- /iptables/test/flush_all.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | ) 8 | 9 | func FlushALL() { 10 | set() 11 | defer unset() 12 | 13 | err := iptables.NewIPTables().Flush() 14 | fmt.Println(err) 15 | } 16 | -------------------------------------------------------------------------------- /direction.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | type Direction int 4 | 5 | func (dir Direction) String() string { 6 | switch dir { 7 | case In: 8 | return "in" 9 | case Out: 10 | return "out" 11 | default: 12 | return "" 13 | } 14 | } 15 | 16 | const ( 17 | In Direction = 1 << iota 18 | Out 19 | ) 20 | -------------------------------------------------------------------------------- /internal/xutil/utils_test.go: -------------------------------------------------------------------------------- 1 | package xutil 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNFields(t *testing.T) { 9 | line := "ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED" 10 | fields, index := NFields([]byte(line), 5) 11 | fmt.Printf("%v, %v", fields, line[index:]) 12 | } 13 | -------------------------------------------------------------------------------- /ebtables/dump.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | ) 7 | 8 | func dump(data []byte) ([]string, error) { 9 | rules := []string{} 10 | 11 | buf := bytes.NewBuffer(data) 12 | scanner := bufio.NewScanner(buf) 13 | 14 | for scanner.Scan() { 15 | line := scanner.Bytes() 16 | rules = append(rules, string(line)) 17 | } 18 | return rules, nil 19 | } 20 | -------------------------------------------------------------------------------- /ebtables/test/reject_smac.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables/ebtables" 7 | ) 8 | 9 | func main() { 10 | set() 11 | defer unset() 12 | 13 | err := ebtables.NewEBTables(). 14 | Table(ebtables.TableTypeFilter). 15 | Chain(ebtables.ChainTypeINPUT). 16 | MatchSource(false, "00:11:22:33:44:55"). 17 | TargetDrop(). 18 | Append() 19 | fmt.Println(err) 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.22 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | -------------------------------------------------------------------------------- /iptables/test/allow_sip_accept.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | ) 8 | 9 | func AllowSIPAccept() { 10 | set() 11 | defer unset() 12 | 13 | err := iptables.NewIPTables(). 14 | Table(iptables.TableTypeFilter). 15 | Chain(iptables.ChainTypeINPUT). 16 | MatchSource(false, "192.168.1.100"). 17 | TargetAccept(). 18 | Append() 19 | fmt.Println(err) 20 | } 21 | -------------------------------------------------------------------------------- /iptables/test/find_sip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | ) 8 | 9 | func FindSIP() { 10 | set() 11 | defer unset() 12 | 13 | rules, err := iptables.NewIPTables(). 14 | Table(iptables.TableTypeFilter). 15 | Chain(iptables.ChainTypeINPUT). 16 | MatchSource(false, "192.168.1.100"). 17 | TargetAccept(). 18 | FindRules() 19 | fmt.Println(len(rules), err) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/network/service_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import "testing" 4 | 5 | func TestServiceName(t *testing.T) { 6 | for key := range ServicePortProtoMaps { 7 | name := key.Value() 8 | for _, char := range name { 9 | if (char < '0' || char > 'z') && 10 | char != '+' && char != '-' && 11 | char != '*' && char != '.' && 12 | char != '_' && char != '/' { 13 | t.Error("illegal char", char, 'A', '9') 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /iptables/test/anti_ping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/singchia/go-xtables/iptables" 5 | "github.com/singchia/go-xtables/pkg/network" 6 | ) 7 | 8 | func AntiPING() { 9 | set() 10 | defer unset() 11 | 12 | iptables.NewIPTables(). 13 | Table(iptables.TableTypeFilter). 14 | Chain(iptables.ChainTypeINPUT). 15 | MatchProtocol(false, network.ProtocolICMP). 16 | MatchICMP(false, network.ICMPType(network.EchoRequest)). 17 | TargetDrop(). 18 | Append() 19 | } 20 | -------------------------------------------------------------------------------- /iptables/test/allow_dport_accept.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | "github.com/singchia/go-xtables/pkg/network" 8 | ) 9 | 10 | func AllowDPortAccept() { 11 | set() 12 | defer unset() 13 | 14 | err := iptables.NewIPTables(). 15 | Table(iptables.TableTypeFilter). 16 | Chain(iptables.ChainTypeINPUT). 17 | MatchProtocol(false, network.ProtocolTCP). 18 | MatchTCP(iptables.WithMatchTCPDstPort(false, 2432)). 19 | TargetAccept(). 20 | Append() 21 | fmt.Println(err) 22 | } 23 | -------------------------------------------------------------------------------- /loglevel.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | type LogLevel int8 4 | 5 | const ( 6 | LogLevelEMERG LogLevel = 0 /* system is unusable */ 7 | LogLevelALERT LogLevel = 1 /* action must be taken immediately */ 8 | LogLevelCRIT LogLevel = 2 /* critical conditions */ 9 | LogLevelERR LogLevel = 3 /* error conditions */ 10 | LogLevelWARNING LogLevel = 4 /* warning conditions */ 11 | LogLevelNOTICE LogLevel = 5 /* normal but significant condition */ 12 | LogLevelINFO LogLevel = 6 /* informational */ 13 | LogLevelDEBUG LogLevel = 7 /* debug-level messages */ 14 | ) 15 | -------------------------------------------------------------------------------- /iptables/test/mirror_to_gw.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/singchia/go-xtables/iptables" 8 | "github.com/singchia/go-xtables/pkg/network" 9 | ) 10 | 11 | func MirrorToGW() { 12 | set() 13 | defer unset() 14 | 15 | err := iptables.NewIPTables(). 16 | Table(iptables.TableTypeMangle). 17 | Chain(iptables.ChainTypePREROUTING). 18 | MatchProtocol(false, network.ProtocolTCP). 19 | MatchTCP(iptables.WithMatchTCPDstPort(false, 2432)). 20 | TargetTEE(net.ParseIP("192.168.1.1")). 21 | Insert() 22 | fmt.Println(err) 23 | } 24 | -------------------------------------------------------------------------------- /iptables/test/allow_dport_10cs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/singchia/go-xtables" 7 | "github.com/singchia/go-xtables/iptables" 8 | "github.com/singchia/go-xtables/pkg/network" 9 | ) 10 | 11 | func AllowDPort10CS() { 12 | set() 13 | defer unset() 14 | 15 | err := iptables.NewIPTables(). 16 | Table(iptables.TableTypeFilter). 17 | Chain(iptables.ChainTypeINPUT). 18 | MatchProtocol(false, network.ProtocolTCP). 19 | MatchTCP(iptables.WithMatchTCPDstPort(false, 80)). 20 | MatchLimit(iptables.WithMatchLimit(xtables.Rate{Rate: 10, Unit: xtables.Minute})). 21 | TargetAccept(). 22 | Append() 23 | fmt.Println(err) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/cmd_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/singchia/go-xtables/internal/sandbox" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | const ( 12 | user = "root" 13 | password = "password" 14 | addr = "localhost:2222" 15 | ) 16 | 17 | func TestSSHCmdPassword(t *testing.T) { 18 | sb := sandbox.NewSandbox(addr, user, password) 19 | sb.SetReturnString("hi") 20 | go func() { 21 | sb.ListenAndServe() 22 | }() 23 | // wait for the sandbox ready 24 | time.Sleep(100 * time.Millisecond) 25 | t.Cleanup(func() { 26 | sb.Close() 27 | }) 28 | stdout, _, _ := SSHCmdPassword(addr, user, password, "hello") 29 | assert.Equal(t, "hi", string(stdout)) 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/singchia/go-xtables 2 | 3 | go 1.18 4 | 5 | require ( 6 | bou.ke/monkey v1.0.2 7 | github.com/gliderlabs/ssh v0.3.5 8 | github.com/singchia/go-hammer v0.0.2-0.20220516141917-9d83fc02d653 9 | github.com/stretchr/testify v1.8.1 10 | github.com/vishvananda/netns v0.0.4 11 | golang.org/x/crypto v0.31.0 12 | ) 13 | 14 | require ( 15 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/kr/pretty v0.3.0 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/rogpeppe/go-internal v1.9.0 // indirect 20 | golang.org/x/sys v0.28.0 // indirect 21 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 22 | gopkg.in/yaml.v3 v3.0.1 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /examples/allow_multiports_accept/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | "github.com/singchia/go-xtables/pkg/network" 8 | ) 9 | 10 | func main() { 11 | ipt := iptables.NewIPTables(). 12 | Table(iptables.TableTypeFilter). 13 | Chain(iptables.ChainTypeINPUT). 14 | MatchProtocol(false, network.ProtocolTCP) 15 | 16 | // allow ssh, http and https 17 | err := ipt.MatchMultiPort( 18 | iptables.WithMatchMultiPortDstPorts(false, 22, 80, 443)). 19 | TargetAccept().Insert() 20 | if err != nil { 21 | log.Fatal(err) 22 | return 23 | } 24 | // drop others 25 | err = iptables.NewIPTables().Table(iptables.TableTypeFilter).Chain(iptables.ChainTypeINPUT).Policy(iptables.TargetTypeDrop) 26 | if err != nil { 27 | log.Fatal(err) 28 | return 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/sandbox/sandbox.go: -------------------------------------------------------------------------------- 1 | package sandbox 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/gliderlabs/ssh" 7 | ) 8 | 9 | type Sandbox struct { 10 | server *ssh.Server 11 | } 12 | 13 | func NewSandbox(addr, user, password string) *Sandbox { 14 | server := &ssh.Server{ 15 | Addr: addr, 16 | Handler: func(sn ssh.Session) { 17 | io.WriteString(sn, "sandbox") 18 | }, 19 | } 20 | opt := ssh.PasswordAuth(func(ctx ssh.Context, pass string) bool { 21 | return pass == password 22 | }) 23 | server.SetOption(opt) 24 | return &Sandbox{server} 25 | } 26 | 27 | func (sandbox *Sandbox) SetReturnString(str string) { 28 | sandbox.server.Handler = func(sn ssh.Session) { 29 | io.WriteString(sn, str) 30 | } 31 | } 32 | 33 | func (sandbox *Sandbox) ListenAndServe() error { 34 | return sandbox.server.ListenAndServe() 35 | } 36 | 37 | func (sandbox *Sandbox) Close() error { 38 | return sandbox.server.Close() 39 | } 40 | -------------------------------------------------------------------------------- /iptables/test/anti_ddos.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/singchia/go-xtables" 5 | "github.com/singchia/go-xtables/iptables" 6 | "github.com/singchia/go-xtables/pkg/network" 7 | ) 8 | 9 | func main() { 10 | set() 11 | defer unset() 12 | 13 | custom := "SYN_FLOOD" 14 | ipt := iptables.NewIPTables().Table(iptables.TableTypeFilter) 15 | ipt.NewChain(custom) 16 | ipt.Chain(iptables.ChainTypeINPUT). 17 | MatchProtocol(false, network.ProtocolTCP). 18 | MatchTCP(iptables.WithMatchTCPSYN(false)). 19 | TargetJumpChain(custom). 20 | Append() 21 | 22 | userDefined := iptables.ChainTypeUserDefined 23 | userDefined.SetName(custom) 24 | rate := xtables.Rate{Rate: 1, Unit: xtables.Second} 25 | ipt.Chain(userDefined). 26 | MatchLimit( 27 | iptables.WithMatchLimit(rate), 28 | iptables.WithMatchLimitBurst(3)). 29 | TargetReturn(). 30 | Append() 31 | ipt.Chain(userDefined). 32 | TargetDrop(). 33 | Append() 34 | } 35 | -------------------------------------------------------------------------------- /iptables/test/allow_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/singchia/go-xtables/iptables" 5 | "github.com/singchia/go-xtables/pkg/network" 6 | ) 7 | 8 | func AllowOutput() { 9 | set() 10 | defer unset() 11 | 12 | ipt := iptables.NewIPTables().Table(iptables.TableTypeFilter) 13 | ipt.Chain(iptables.ChainTypeINPUT). 14 | MatchInInterface(false, "lo"). 15 | TargetAccept(). 16 | Append() 17 | ipt.Chain(iptables.ChainTypeINPUT). 18 | MatchState(iptables.ESTABLISHED | iptables.RELATED). 19 | TargetAccept(). 20 | Append() 21 | ipt.Chain(iptables.ChainTypeINPUT). 22 | MatchProtocol(false, network.ProtocolTCP). 23 | MatchTCP(iptables.WithMatchTCPDstPort(false, 22)). 24 | TargetAccept(). 25 | Append() 26 | ipt.Chain(iptables.ChainTypeINPUT).Policy(iptables.TargetTypeDrop) 27 | ipt.Chain(iptables.ChainTypeFORWARD).Policy(iptables.TargetTypeDrop) 28 | ipt.Chain(iptables.ChainTypeOUTPUT).Policy(iptables.TargetTypeAccept) 29 | } 30 | -------------------------------------------------------------------------------- /ebtables/test/env.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | "bou.ke/monkey" 8 | "github.com/singchia/go-xtables/pkg/cmd" 9 | "github.com/vishvananda/netns" 10 | ) 11 | 12 | var ( 13 | originns netns.NsHandle 14 | newns netns.NsHandle 15 | ) 16 | 17 | func set() { 18 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 19 | if sandboxAddr != "" { 20 | sandboxUser := os.Getenv("SANDBOX_USER") 21 | sandboxPassword := os.Getenv("SANDBOX_PASSWORD") 22 | 23 | monkey.Patch(cmd.Cmd, func(name string, arg ...string) ([]byte, []byte, error) { 24 | return cmd.SSHCmdPassword(sandboxAddr, sandboxUser, sandboxPassword, 25 | name, arg...) 26 | }) 27 | } else { 28 | runtime.LockOSThread() 29 | originns, _ = netns.Get() 30 | newns, _ = netns.New() 31 | } 32 | } 33 | 34 | func unset() { 35 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 36 | if sandboxAddr != "" { 37 | monkey.UnpatchAll() 38 | } else { 39 | runtime.UnlockOSThread() 40 | newns.Close() 41 | originns.Close() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /iptables/test/env.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | "bou.ke/monkey" 8 | "github.com/singchia/go-xtables/pkg/cmd" 9 | "github.com/vishvananda/netns" 10 | ) 11 | 12 | var ( 13 | originns netns.NsHandle 14 | newns netns.NsHandle 15 | ) 16 | 17 | func set() { 18 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 19 | if sandboxAddr != "" { 20 | sandboxUser := os.Getenv("SANDBOX_USER") 21 | sandboxPassword := os.Getenv("SANDBOX_PASSWORD") 22 | 23 | monkey.Patch(cmd.Cmd, func(name string, arg ...string) ([]byte, []byte, error) { 24 | return cmd.SSHCmdPassword(sandboxAddr, sandboxUser, sandboxPassword, 25 | name, arg...) 26 | }) 27 | } else { 28 | runtime.LockOSThread() 29 | originns, _ = netns.Get() 30 | newns, _ = netns.New() 31 | } 32 | } 33 | 34 | func unset() { 35 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 36 | if sandboxAddr != "" { 37 | monkey.UnpatchAll() 38 | } else { 39 | runtime.UnlockOSThread() 40 | newns.Close() 41 | originns.Close() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/network/pkttype.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import "errors" 4 | 5 | type PktType int 6 | 7 | func (pktType PktType) String() string { 8 | switch pktType { 9 | case PktTypeUnicast: 10 | return "unicast" 11 | case PktTypeBroadcast: 12 | return "broadcast" 13 | case PktTypeMulticast: 14 | return "multicast" 15 | case PktTypeHost: 16 | return "host" 17 | case PktTypeOtherHost: 18 | return "otherhost" 19 | default: 20 | return "" 21 | } 22 | } 23 | 24 | const ( 25 | PktTypeUnicast PktType = 1 << iota 26 | PktTypeBroadcast 27 | PktTypeMulticast 28 | PktTypeHost 29 | PktTypeOtherHost 30 | ) 31 | 32 | func ParsePktType(typ string) (PktType, error) { 33 | switch typ { 34 | case "unicast": 35 | return PktTypeUnicast, nil 36 | case "broadcast": 37 | return PktTypeBroadcast, nil 38 | case "multicast": 39 | return PktTypeMulticast, nil 40 | case "host": 41 | return PktTypeHost, nil 42 | case "otherhost": 43 | return PktTypeOtherHost, nil 44 | } 45 | return 0, errors.New("unknown pkt type") 46 | } 47 | -------------------------------------------------------------------------------- /pkg/network/ip4.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type IPType uint8 10 | 11 | const ( 12 | IPv4 IPType = 1 << iota 13 | IPv6 14 | IPALL = IPv4 | IPv6 15 | ) 16 | 17 | type TOS int8 18 | 19 | func (tos TOS) String() string { 20 | return fmt.Sprintf("0x%02x", int8(tos)) 21 | } 22 | 23 | const ( 24 | _ TOS = 1 << iota 25 | TOSMinCost 26 | TOSMaxReliability 27 | TOSMaxThroughput 28 | TOSMinDelay 29 | TOSNormal TOS = 0 30 | ) 31 | 32 | var ( 33 | TOSMap = map[string]TOS{ 34 | "Minimize-Delay": TOSMinDelay, 35 | "Maximize-Throughput": TOSMaxThroughput, 36 | "Maximize-Reliability": TOSMaxReliability, 37 | "Minimize-Cost": TOSMinCost, 38 | "Normal-Service": TOSNormal, 39 | } 40 | ) 41 | 42 | func ParseTOS(tos string) (TOS, error) { 43 | t, err := strconv.ParseInt(tos, 16, 8) 44 | if err == nil { 45 | return TOS(t), nil 46 | } 47 | v, ok := TOSMap[strings.ToUpper(tos)] 48 | if ok { 49 | return v, nil 50 | } 51 | return 0, err 52 | } 53 | -------------------------------------------------------------------------------- /rate.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | import "strconv" 4 | 5 | // time related 6 | type Unit int 7 | 8 | const ( 9 | _ Unit = iota 10 | Microsecond 11 | Millisecond 12 | Second 13 | Minute 14 | Hour 15 | Day 16 | BPS // bytes per second 17 | KBPS // kilo bytes per second 18 | MBPS // million bytes per second 19 | ) 20 | 21 | type Rate struct { 22 | Rate int 23 | Unit Unit 24 | } 25 | 26 | func (rate Rate) String() string { 27 | unit := "second" 28 | switch rate.Unit { 29 | case Minute: 30 | unit = "minute" 31 | case Hour: 32 | unit = "hour" 33 | case Day: 34 | unit = "day" 35 | } 36 | return strconv.Itoa(rate.Rate) + "/" + unit 37 | } 38 | 39 | type RateFloat struct { 40 | Rate float64 41 | Unit Unit 42 | } 43 | 44 | func (rateFloat RateFloat) Sting() string { 45 | unit := "second" 46 | switch rateFloat.Unit { 47 | case Microsecond: 48 | unit = "us" 49 | case Millisecond: 50 | unit = "ms" 51 | case Second: 52 | unit = "s" 53 | } 54 | return strconv.FormatFloat(rateFloat.Rate, 'f', 2, 64) + unit 55 | } 56 | -------------------------------------------------------------------------------- /ebtables/table.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import "strconv" 4 | 5 | type TableType int 6 | 7 | func (tt TableType) Type() string { 8 | return "TableType" 9 | } 10 | 11 | func (tt TableType) Value() string { 12 | return strconv.Itoa(int(tt)) 13 | } 14 | 15 | func (tt TableType) String() string { 16 | switch tt { 17 | case TableTypeFilter: 18 | return "filter" 19 | case TableTypeNat: 20 | return "nat" 21 | case TableTypeBRoute: 22 | return "broute" 23 | } 24 | return "unknown" 25 | } 26 | 27 | const ( 28 | TableTypeNull TableType = iota 29 | TableTypeFilter // filter 30 | TableTypeNat // nat 31 | TableTypeBRoute // broute 32 | ) 33 | 34 | var ( 35 | TableChains = map[TableType][]ChainType{ 36 | TableTypeFilter: { 37 | ChainTypeINPUT, 38 | ChainTypeOUTPUT, 39 | ChainTypeFORWARD, 40 | }, 41 | TableTypeNat: { 42 | ChainTypeOUTPUT, 43 | ChainTypePREROUTING, 44 | ChainTypePOSTROUTING, 45 | }, 46 | TableTypeBRoute: { 47 | ChainTypeBROUTING, 48 | }, 49 | } 50 | ) 51 | -------------------------------------------------------------------------------- /operator.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | type Operator uint32 4 | 5 | func (operator Operator) String() string { 6 | switch operator { 7 | case OperatorNull: 8 | return "" 9 | case OperatorEQ: 10 | return "==" 11 | case OperatorNE: 12 | return "!=" 13 | case OperatorLT: 14 | return "<" 15 | case OperatorGT: 16 | return ">" 17 | case OperatorINC: 18 | return "+" 19 | case OperatorDEC: 20 | return "-" 21 | case OperatorSET: 22 | return "=" 23 | case OperatorXSET: 24 | return "^=" 25 | case OperatorAND: 26 | return "&" 27 | case OperatorOR: 28 | return "|" 29 | case OperatorXOR: 30 | return "^|" 31 | } 32 | return "" 33 | } 34 | 35 | const ( 36 | OperatorNull Operator = iota 37 | OperatorEQ // == 38 | OperatorNE // != 39 | OperatorLT // < 40 | OperatorGT // > 41 | OperatorINC // + 42 | OperatorDEC // - 43 | OperatorSET // = 44 | OperatorXSET // ^= 45 | OperatorAND // & 46 | OperatorOR // | 47 | OperatorXOR // ^| 48 | ) 49 | -------------------------------------------------------------------------------- /test/stdout/list_ebtables_filter: -------------------------------------------------------------------------------- 1 | Bridge table: filter 2 | 3 | Bridge chain: INPUT, entries: 8, policy: ACCEPT 4 | 1. -p ARP -j SINGCHIA, pcnt = 100 -- bcnt = 0 5 | 2. -p ARP --ulog-prefix "" --ulog-nlgroup 1 --ulog-cprange default_cprange --ulog-qthreshold 1 -j mark --mark-set 0xa --mark-target ACCEPT, pcnt = 0 -- bcnt = 0 6 | 3. -p ARP --ulog-prefix "" --ulog-nlgroup 1 --ulog-cprange default_cprange --ulog-qthreshold 1 -j ACCEPT , pcnt = 0 -- bcnt = 0 7 | 4. -p ARP --ulog-prefix "" --ulog-nlgroup 1 --ulog-cprange default_cprange --ulog-qthreshold 1 -j ACCEPT , pcnt = 0 -- bcnt = 0 8 | 5. -p ARP --log-level emerg --log-prefix "" -j ACCEPT , pcnt = 0 -- bcnt = 0 9 | 6. -p ARP --log-level notice --log-prefix "" -j ACCEPT , pcnt = 0 -- bcnt = 0 10 | 7. -p ARP --log-level alert --log-prefix "" -j ACCEPT , pcnt = 0 -- bcnt = 0 11 | 8. -p ARP --log-level notice --log-prefix "" -j ACCEPT , pcnt = 0 -- bcnt = 0 12 | 13 | Bridge chain: FORWARD, entries: 0, policy: ACCEPT 14 | 15 | Bridge chain: OUTPUT, entries: 0, policy: ACCEPT 16 | 17 | Bridge chain: SINGCHIA, entries: 0, policy: DROP 18 | -------------------------------------------------------------------------------- /examples/allow_dports_accept/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/singchia/go-xtables/iptables" 7 | "github.com/singchia/go-xtables/pkg/network" 8 | ) 9 | 10 | func main() { 11 | ipt := iptables.NewIPTables(). 12 | Table(iptables.TableTypeFilter). 13 | Chain(iptables.ChainTypeINPUT). 14 | MatchProtocol(false, network.ProtocolTCP) 15 | 16 | // allow ssh 17 | err := ipt.MatchTCP(iptables.WithMatchTCPDstPort(false, 22)).TargetAccept().Insert() 18 | if err != nil { 19 | log.Fatal(err) 20 | return 21 | } 22 | // allow http 23 | err = ipt.MatchTCP(iptables.WithMatchTCPDstPort(false, 80)).TargetAccept().Insert() 24 | if err != nil { 25 | log.Fatal(err) 26 | return 27 | } 28 | // allow https 29 | err = ipt.MatchTCP(iptables.WithMatchTCPDstPort(false, 443)).TargetAccept().Insert() 30 | if err != nil { 31 | log.Fatal(err) 32 | return 33 | } 34 | // drop others 35 | err = iptables.NewIPTables().Table(iptables.TableTypeFilter).Chain(iptables.ChainTypeINPUT).Policy(iptables.TargetTypeDrop) 36 | if err != nil { 37 | log.Fatal(err) 38 | return 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iptables/table.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import "strconv" 4 | 5 | type TableType int 6 | 7 | func (tt TableType) Type() string { 8 | return "TableType" 9 | } 10 | 11 | func (tt TableType) Value() string { 12 | return strconv.Itoa(int(tt)) 13 | } 14 | 15 | const ( 16 | TableTypeNull TableType = iota 17 | TableTypeFilter // filter 18 | TableTypeNat // nat 19 | TableTypeMangle // mangle 20 | TableTypeRaw // raw 21 | TableTypeSecurity // security 22 | ) 23 | 24 | var ( 25 | TableChains = map[TableType][]ChainType{ 26 | TableTypeFilter: { 27 | ChainTypeINPUT, 28 | ChainTypeOUTPUT, 29 | ChainTypeFORWARD, 30 | }, 31 | TableTypeNat: { 32 | ChainTypeOUTPUT, 33 | ChainTypePREROUTING, 34 | ChainTypePOSTROUTING, 35 | }, 36 | TableTypeMangle: { 37 | ChainTypeINPUT, 38 | ChainTypeOUTPUT, 39 | ChainTypeFORWARD, 40 | ChainTypePREROUTING, 41 | ChainTypePOSTROUTING, 42 | }, 43 | TableTypeRaw: { 44 | ChainTypeOUTPUT, 45 | ChainTypePREROUTING, 46 | }, 47 | TableTypeSecurity: { 48 | ChainTypeINPUT, 49 | ChainTypeOUTPUT, 50 | ChainTypeFORWARD, 51 | }, 52 | } 53 | ) 54 | -------------------------------------------------------------------------------- /time_test.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | import "testing" 4 | 5 | func TestDaytime(t *testing.T) { 6 | dt := &Daytime{ 7 | Hour: -1, 8 | Minute: -1, 9 | Second: -1, 10 | } 11 | t.Log(dt.String()) 12 | dt = &Daytime{ 13 | Hour: 23, 14 | Minute: -1, 15 | Second: -1, 16 | } 17 | t.Log(dt.String()) 18 | dt = &Daytime{ 19 | Hour: 23, 20 | Minute: 59, 21 | Second: 59} 22 | t.Log(dt.String()) 23 | } 24 | 25 | func TestYeartime(t *testing.T) { 26 | yt := &Yeartime{ 27 | Year: -1, 28 | Month: -1, 29 | Day: -1, 30 | } 31 | t.Log(yt.String()) 32 | yt = &Yeartime{ 33 | Year: 2022, 34 | Month: -1, 35 | Day: -1, 36 | } 37 | t.Log(yt.String()) 38 | yt = &Yeartime{ 39 | Year: 2022, 40 | Month: 12, 41 | Day: 31, 42 | } 43 | t.Log(yt.String()) 44 | } 45 | 46 | func TestDate(t *testing.T) { 47 | date := &Date{} 48 | date.Yeartime = &Yeartime{ 49 | Year: 2022, 50 | Month: 12, 51 | Day: 31} 52 | date.Daytime = &Daytime{ 53 | Hour: 23, 54 | Minute: 59, 55 | Second: 59} 56 | t.Log(date.String()) 57 | } 58 | 59 | func TestWeekday(t *testing.T) { 60 | weekday := Weekday(Monday | Tuesday | Wednesday) 61 | t.Log(weekday.String()) 62 | } 63 | 64 | func TestMonthday(t *testing.T) { 65 | weekday := Monthday(1<<(1-1) | 1<<(31-1)) 66 | t.Log(weekday.String()) 67 | } 68 | -------------------------------------------------------------------------------- /test/stdout/list_arptables_filter: -------------------------------------------------------------------------------- 1 | Chain INPUT (policy ACCEPT 104K packets, 2919K bytes) 2 | 1 -j mangle -i * -o * ! -d 192.168.18.199 --mangle-ip-d 1.2.3.5 , pcnt=240K -- bcnt=6729K 3 | 2 -j ACCEPT ! -i enp0s3 -o * , pcnt=0 -- bcnt=0 4 | 3 -j ACCEPT -i enp0s3 -o * , pcnt=536 -- bcnt=15008 5 | 4 -j ACCEPT -i * -o * --opcode 1/1 , pcnt=102K -- bcnt=2858K 6 | 5 -j ACCEPT -i * -o * --opcode 1 , pcnt=1203 -- bcnt=33684 7 | 6 -j ACCEPT -i * -o * --opcode 6/10 , pcnt=0 -- bcnt=0 8 | 7 -j ACCEPT -i * -o * --opcode 6 , pcnt=0 -- bcnt=0 9 | 8 -j ACCEPT -i * -o * --h-length 10/11 , pcnt=0 -- bcnt=0 10 | 9 -j ACCEPT -i * -o * , pcnt=5643 -- bcnt=158K 11 | 10 -j ACCEPT -i * -o * , pcnt=20 -- bcnt=560 12 | 11 -j ACCEPT -i * -o * --h-length 10/25 , pcnt=0 -- bcnt=0 13 | 12 -j ACCEPT -i * -o * --h-length 10 , pcnt=0 -- bcnt=0 14 | 13 -j ACCEPT -i * -o * --dst-mac 00:01:02:03:04:05 , pcnt=0 -- bcnt=0 15 | 14 -j mangle -i * -o * -d 192.168.18.199 --mangle-ip-d 1.2.3.5 , pcnt=0 -- bcnt=0 16 | 15 -i * -o * -d 192.168.18.199 , pcnt=0 -- bcnt=0 17 | 18 | Chain OUTPUT (policy ACCEPT 1181 packets, 33068 bytes) 19 | 1 -j CLASSIFY -i * -o * --set-class 1111:0 , pcnt=479 -- bcnt=13412 20 | 2 -j CLASSIFY -i * -o * --set-class 11:0 , pcnt=479 -- bcnt=13412 21 | 3 -j CLASSIFY -i * -o * --set-class ffff:0 , pcnt=479 -- bcnt=13412 22 | 23 | Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) 24 | -------------------------------------------------------------------------------- /ebtables/parser_test.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestParseChain(t *testing.T) { 10 | data, err := ioutil.ReadFile("../test/stdout/list_ebtables_filter") 11 | if err != nil { 12 | t.Error(err) 13 | return 14 | } 15 | ebtables := NewEBTables() 16 | chains, rules, err := ebtables.parse(data, 17 | ebtables.parseTable, ebtables.parseChain, ebtables.parseRule) 18 | if err != nil { 19 | t.Error(err) 20 | return 21 | } 22 | iterateChains(t, chains) 23 | iterateRules(t, rules) 24 | } 25 | 26 | func iterateChains(t *testing.T, chains []*Chain) { 27 | for _, chain := range chains { 28 | t.Log(chain.chainType.String(), chain.chainType.userDefined, 29 | chain.chainType.name, chain.policy.Type().String()) 30 | } 31 | } 32 | 33 | func iterateRules(t *testing.T, rules []*Rule) { 34 | for _, rule := range rules { 35 | matches, err := json.Marshal(rule.matchMap) 36 | if err != nil { 37 | continue 38 | } 39 | options, err := json.Marshal(rule.optionMap) 40 | if err != nil { 41 | continue 42 | } 43 | watchers, err := json.Marshal(rule.watcherMap) 44 | if err != nil { 45 | continue 46 | } 47 | t.Log(rule.ChainType().String(), rule.chain.chainType.userDefined, rule.lineNumber, 48 | string(matches), string(options), string(watchers), rule.target.Type().String()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /iptables/parser_test.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/singchia/go-xtables/internal/xutil" 10 | ) 11 | 12 | func TestUnfoldDecimal(t *testing.T) { 13 | test := []string{ 14 | "12", 15 | "34K", 16 | "56M", 17 | "78G", 18 | "910T", 19 | "1011P", 20 | "1112Z", 21 | } 22 | for _, elem := range test { 23 | num, err := xutil.UnfoldDecimal(elem) 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | t.Log(num) 29 | } 30 | 31 | test = []string{ 32 | "1a2", 33 | "foo", 34 | } 35 | for _, elem := range test { 36 | num, err := xutil.UnfoldDecimal(elem) 37 | if err == nil { 38 | t.Error("err") 39 | return 40 | } 41 | t.Log(num) 42 | } 43 | } 44 | 45 | func TestParseChain(t *testing.T) { 46 | data, err := ioutil.ReadFile("../test/stdout/list_iptables_filter") 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | buf := bytes.NewBuffer(data) 52 | scanner := bufio.NewScanner(buf) 53 | iptables := NewIPTables() 54 | 55 | for scanner.Scan() { 56 | line := scanner.Bytes() 57 | if bytes.HasPrefix(line, []byte("Chain")) { 58 | chain, err := iptables.parseChain(line) 59 | if err != nil { 60 | t.Error(err) 61 | return 62 | } 63 | t.Log(chain.chainType.chainType, chain.chainType.name, 64 | chain.references, chain.policy, chain.packets, chain.bytes) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Go 2 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 3 | *.o 4 | *.a 5 | *.so 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | .DS_Store 27 | .*.swp 28 | 29 | # ---> JetBrains 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 31 | 32 | *.iml 33 | 34 | ## Directory-based project format: 35 | .idea/ 36 | # if you remove the above rule, at least ignore the following: 37 | 38 | # User-specific stuff: 39 | # .idea/workspace.xml 40 | # .idea/tasks.xml 41 | # .idea/dictionaries 42 | 43 | # Sensitive or high-churn files: 44 | # .idea/dataSources.ids 45 | # .idea/dataSources.xml 46 | # .idea/sqlDataSources.xml 47 | # .idea/dynamic.xml 48 | # .idea/uiDesigner.xml 49 | 50 | # Gradle: 51 | # .idea/gradle.xml 52 | # .idea/libraries 53 | 54 | # Mongo Explorer plugin: 55 | # .idea/mongoSettings.xml 56 | 57 | ## File-based project format: 58 | *.ipr 59 | *.iws 60 | 61 | ## Plugin-specific files: 62 | 63 | # IntelliJ 64 | /out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | -------------------------------------------------------------------------------- /pkg/network/address_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import "testing" 4 | 5 | func TestParseAddress(t *testing.T) { 6 | ads, err := ParseAddress("google.com") 7 | if err != nil { 8 | t.Error(err) 9 | return 10 | } 11 | t.Log(ads.String(), "\n") 12 | 13 | ads, err = ParseAddress("one.one.one.one/16") 14 | if err != nil { 15 | t.Error(err) 16 | return 17 | } 18 | t.Log(ads.String(), "\n") 19 | 20 | ads, err = ParseAddress("goo-gle.com") 21 | if err != nil { 22 | t.Error(err) 23 | return 24 | } 25 | t.Log(ads.String(), "\n") 26 | 27 | ads, err = ParseAddress("google") 28 | if err != nil { 29 | t.Error(err) 30 | return 31 | } 32 | t.Log(ads.String(), "\n") 33 | 34 | ads, err = ParseAddress("192.168.0.2") 35 | if err != nil { 36 | t.Error(err) 37 | return 38 | } 39 | t.Log(ads.String(), "\n") 40 | 41 | ads, err = ParseAddress("192.168.0.0/16") 42 | if err != nil { 43 | t.Error(err) 44 | return 45 | } 46 | t.Log(ads.String(), "\n") 47 | 48 | ads, err = ParseAddress(".google.com") 49 | if err == nil { 50 | t.Error("parse missed") 51 | return 52 | } 53 | t.Log(err, "\n") 54 | 55 | ads, err = ParseAddress("-google.com") 56 | if err == nil { 57 | t.Error("parse missed") 58 | return 59 | } 60 | t.Log(err, "\n") 61 | 62 | ads, err = ParseAddress("google-.com") 63 | if err == nil { 64 | t.Error("parse missed") 65 | return 66 | } 67 | t.Log(err, "\n") 68 | 69 | ads, err = ParseAddress("-.google.com") 70 | if err == nil { 71 | t.Error("parse missed") 72 | return 73 | } 74 | t.Log(err, "\n") 75 | } 76 | -------------------------------------------------------------------------------- /iptables/rule.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | type Rule struct { 4 | tableType TableType 5 | // chain info 6 | chain *Chain 7 | 8 | // line number 9 | lineNumber int 10 | 11 | // matches 12 | matches []Match 13 | matchMap map[MatchType]Match 14 | 15 | // options 16 | options []Option 17 | optionMap map[OptionType]Option 18 | 19 | // target 20 | target Target 21 | 22 | packets int64 23 | bytes int64 24 | opt string 25 | } 26 | 27 | func (rule *Rule) HasAllOptions(options map[OptionType]Option) bool { 28 | OUTER: 29 | for _, opt := range options { 30 | for _, v := range rule.optionMap { 31 | ok := opt.Equal(v) 32 | if ok { 33 | continue OUTER 34 | } 35 | } 36 | // unmatched 37 | return false 38 | } 39 | return true 40 | } 41 | 42 | func (rule *Rule) HasAllMatches(matches map[MatchType]Match) bool { 43 | OUTER: 44 | for _, mth := range matches { 45 | for _, v := range rule.matchMap { 46 | ok := mth.Equal(v) 47 | if ok { 48 | continue OUTER 49 | } 50 | } 51 | // unmatched 52 | return false 53 | } 54 | return true 55 | } 56 | 57 | func (rule *Rule) HasTarget(target Target) bool { 58 | if target == nil { 59 | return true 60 | } else if rule.target == nil { 61 | return false 62 | } 63 | return rule.target.Equal(target) 64 | } 65 | 66 | func (rule *Rule) Table() TableType { 67 | return rule.tableType 68 | } 69 | 70 | func (rule *Rule) Chain() ChainType { 71 | return rule.chain.chainType 72 | } 73 | 74 | func (rule *Rule) Target() Target { 75 | return rule.target 76 | } 77 | 78 | func (rule *Rule) Matches() []Match { 79 | return rule.matches 80 | } 81 | 82 | func (rule *Rule) Options() []Option { 83 | return rule.options 84 | } 85 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | ErrUnsupportedAddress = errors.New("unsupported address") 10 | ErrIllegalAddress = errors.New("illegal address") 11 | ErrChainRequired = errors.New("chain required") 12 | ErrCommandRequired = errors.New("command required") 13 | ErrRulenumMustNot0 = errors.New("rulenum mustn't be 0") 14 | ErrChainLineTooShort = errors.New("chain line too short") 15 | ErrChainAttrsNotRecognized = errors.New("chain attrs not recognized") 16 | ErrArgs = errors.New("wrong args") 17 | ErrTargetNotFound = errors.New("target not found") 18 | ErrMatchParams = errors.New("illegal match params") 19 | ErrWatcherParams = errors.New("illegal watcher params") 20 | ErrTargetParams = errors.New("illegal target params") 21 | ErrAtLeastOneOptionRequired = errors.New("at least one option required") 22 | ErrTargetParseFailed = errors.New("target parse failed") 23 | ErrIllegalTargetType = errors.New("illegal target type") 24 | ErrArgsWithoutMAC = errors.New("args without mac address") 25 | ) 26 | 27 | type CommandError struct { 28 | Err error 29 | Message string 30 | } 31 | 32 | func (ce *CommandError) Error() string { 33 | return ce.Err.Error() + ";" + ce.Message 34 | } 35 | 36 | func (ce *CommandError) IsRuleNotExistError() bool { 37 | return strings.Contains(ce.Message, "rule does not exist") || 38 | strings.Contains(ce.Message, "does a matching rule exist in that chain?") 39 | } 40 | 41 | func ErrAndStdErr(err error, stderr []byte) error { 42 | ce := &CommandError{ 43 | Err: err, 44 | Message: string(stderr), 45 | } 46 | return ce 47 | } 48 | -------------------------------------------------------------------------------- /iptables/chain.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import "strconv" 4 | 5 | const ( 6 | chainTypeNull int = iota 7 | chainTypePREROUTING // PREROUTING 8 | chainTypeINPUT // INPUT 9 | chainTypeFORWARD // FORWARD 10 | chainTypeOUTPUT // OUTPUT 11 | chainTypePOSTROUTING // POSTROUTING 12 | chainTypeUserDefined // USER-DEFINED 13 | ) 14 | 15 | type ChainType struct { 16 | chainType int 17 | userDefined bool 18 | name string 19 | } 20 | 21 | func (ct *ChainType) SetName(name string) { 22 | ct.name = name 23 | } 24 | 25 | func (ct ChainType) Type() string { 26 | return "ChainType" 27 | } 28 | 29 | func (ct ChainType) Value() string { 30 | return strconv.Itoa(ct.chainType) 31 | } 32 | 33 | func (ct ChainType) String() string { 34 | switch ct.chainType { 35 | case chainTypePREROUTING: 36 | return "PREROUTING" 37 | case chainTypeINPUT: 38 | return "INPUT" 39 | case chainTypeFORWARD: 40 | return "FORWARD" 41 | case chainTypeOUTPUT: 42 | return "OUTPUT" 43 | case chainTypePOSTROUTING: 44 | return "POSTROUTING" 45 | case chainTypeUserDefined: 46 | return ct.name 47 | } 48 | return "Unknown" 49 | } 50 | 51 | var ( 52 | ChainTypeNull = ChainType{chainTypeNull, false, ""} 53 | ChainTypePREROUTING = ChainType{chainTypePREROUTING, false, ""} 54 | ChainTypeINPUT = ChainType{chainTypeINPUT, false, ""} 55 | ChainTypeFORWARD = ChainType{chainTypeFORWARD, false, ""} 56 | ChainTypeOUTPUT = ChainType{chainTypeOUTPUT, false, ""} 57 | ChainTypePOSTROUTING = ChainType{chainTypePOSTROUTING, false, ""} 58 | ChainTypeUserDefined = ChainType{chainTypeUserDefined, true, ""} 59 | ) 60 | 61 | type Chain struct { 62 | chainType ChainType 63 | tableType TableType 64 | // userDefined bool 65 | // name string 66 | references int 67 | policy Target 68 | packets int64 69 | bytes int64 70 | } 71 | -------------------------------------------------------------------------------- /ebtables/chain.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import "strconv" 4 | 5 | const ( 6 | chainTypeNull int = iota 7 | chainTypePREROUTING // PREROUTING 8 | chainTypeINPUT // INPUT 9 | chainTypeFORWARD // FORWARD 10 | chainTypeOUTPUT // OUTPUT 11 | chainTypeBROUTING // BROUTING 12 | chainTypePOSTROUTING // POSTROUTING 13 | chainTypeUserDefined // USER-DEFINED 14 | ) 15 | 16 | type ChainType struct { 17 | chainType int 18 | userDefined bool 19 | name string 20 | } 21 | 22 | func (ct *ChainType) SetName(name string) { 23 | ct.name = name 24 | } 25 | 26 | func (ct ChainType) Type() string { 27 | return "ChainType" 28 | } 29 | 30 | func (ct ChainType) Value() string { 31 | return strconv.Itoa(ct.chainType) 32 | } 33 | 34 | func (ct ChainType) String() string { 35 | switch ct.chainType { 36 | case chainTypePREROUTING: 37 | return "PREROUTING" 38 | case chainTypeINPUT: 39 | return "INPUT" 40 | case chainTypeFORWARD: 41 | return "FORWARD" 42 | case chainTypeOUTPUT: 43 | return "OUTPUT" 44 | case chainTypeBROUTING: 45 | return "BROUTING" 46 | case chainTypePOSTROUTING: 47 | return "POSTROUTING" 48 | case chainTypeUserDefined: 49 | return ct.name 50 | } 51 | return "Unknown" 52 | } 53 | 54 | var ( 55 | ChainTypeNull = ChainType{chainTypeNull, false, ""} 56 | ChainTypePREROUTING = ChainType{chainTypePREROUTING, false, ""} 57 | ChainTypeINPUT = ChainType{chainTypeINPUT, false, ""} 58 | ChainTypeFORWARD = ChainType{chainTypeFORWARD, false, ""} 59 | ChainTypeOUTPUT = ChainType{chainTypeOUTPUT, false, ""} 60 | ChainTypeBROUTING = ChainType{chainTypeBROUTING, false, ""} 61 | ChainTypePOSTROUTING = ChainType{chainTypePOSTROUTING, false, ""} 62 | ChainTypeUserDefined = ChainType{chainTypeUserDefined, true, ""} 63 | ) 64 | 65 | type Chain struct { 66 | chainType ChainType 67 | tableType TableType 68 | policy Target 69 | entries int 70 | } 71 | -------------------------------------------------------------------------------- /internal/xutil/utils.go: -------------------------------------------------------------------------------- 1 | package xutil 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1} 9 | 10 | func ErrAndStdErr(err error, stderr []byte) error { 11 | return errors.New(err.Error() + ";" + string(stderr)) 12 | } 13 | 14 | func NFields(s []byte, n int) ([][]byte, int) { 15 | a := make([][]byte, n) 16 | na := 0 17 | fieldStart := 0 18 | i := 0 19 | // Skip spaces in the front of the input. 20 | for i < len(s) && asciiSpace[s[i]] != 0 { 21 | i++ 22 | } 23 | fieldStart = i 24 | for i < len(s) { 25 | if asciiSpace[s[i]] == 0 { 26 | i++ 27 | continue 28 | } 29 | a[na] = s[fieldStart:i:i] 30 | na++ 31 | i++ 32 | // Skip spaces in between fields. 33 | for i < len(s) && asciiSpace[s[i]] != 0 { 34 | i++ 35 | } 36 | fieldStart = i 37 | if na == n { 38 | break 39 | } 40 | } 41 | return a, i 42 | } 43 | 44 | func UnfoldDecimal(decimal string) (int64, error) { 45 | lastPart := decimal[len(decimal)-1] 46 | numPart := decimal[0 : len(decimal)-1] 47 | switch lastPart { 48 | case 'k', 'K': 49 | num, err := strconv.ParseInt(numPart, 10, 64) 50 | if err != nil { 51 | return num, err 52 | } 53 | return num * 1024, nil 54 | case 'm', 'M': 55 | num, err := strconv.ParseInt(numPart, 10, 64) 56 | if err != nil { 57 | return num, err 58 | } 59 | return num * 1024 * 1024, nil 60 | case 'g', 'G': 61 | num, err := strconv.ParseInt(numPart, 10, 64) 62 | if err != nil { 63 | return num, err 64 | } 65 | return num * 1024 * 1024 * 1024, nil 66 | case 't', 'T': 67 | num, err := strconv.ParseInt(numPart, 10, 64) 68 | if err != nil { 69 | return num, err 70 | } 71 | return num * 1024 * 1024 * 1024 * 1024, nil 72 | case 'p', 'P': 73 | num, err := strconv.ParseInt(numPart, 10, 64) 74 | if err != nil { 75 | return num, err 76 | } 77 | return num * 1024 * 1024 * 1024 * 1024 * 1024, nil 78 | case 'z', 'Z': 79 | num, err := strconv.ParseInt(numPart, 10, 64) 80 | if err != nil { 81 | return num, err 82 | } 83 | return num * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, nil 84 | } 85 | return strconv.ParseInt(decimal, 10, 64) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/network/tcp.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type TCPFlag int 4 | 5 | func (tcpFlag TCPFlag) String() string { 6 | flag := "" 7 | sep := "" 8 | if tcpFlag&TCPFlagFIN != 0 { 9 | flag += sep + "FIN" 10 | sep = "," 11 | } 12 | if tcpFlag&TCPFlagSYN != 0 { 13 | flag += sep + "SYN" 14 | sep = "," 15 | } 16 | if tcpFlag&TCPFlagRST != 0 { 17 | flag += sep + "RST" 18 | sep = "," 19 | } 20 | if tcpFlag&TCPFlagPSH != 0 { 21 | flag += sep + "PSH" 22 | sep = "," 23 | } 24 | if tcpFlag&TCPFlagACK != 0 { 25 | flag += sep + "ACK" 26 | sep = "," 27 | } 28 | if tcpFlag&TCPFlagURG != 0 { 29 | flag += sep + "URG" 30 | sep = "," 31 | } 32 | return flag 33 | } 34 | 35 | const ( 36 | TCPFlagFIN TCPFlag = 1 << iota 37 | TCPFlagSYN 38 | TCPFlagRST 39 | TCPFlagPSH 40 | TCPFlagACK 41 | TCPFlagURG 42 | TCPFlagALL TCPFlag = TCPFlagFIN | TCPFlagSYN | TCPFlagRST | TCPFlagPSH | TCPFlagACK | TCPFlagURG 43 | TCPFlagNONE TCPFlag = 0 44 | ) 45 | 46 | var ( 47 | TCPFlags = map[string]TCPFlag{ 48 | "NONE": TCPFlagNONE, 49 | "FIN": TCPFlagFIN, 50 | "SYN": TCPFlagSYN, 51 | "RST": TCPFlagRST, 52 | "PSH": TCPFlagPSH, 53 | "ACK": TCPFlagACK, 54 | "URG": TCPFlagURG, 55 | "ALL": TCPFlagALL, 56 | } 57 | ) 58 | 59 | type TCPOpt uint8 60 | 61 | func (tcpOpt TCPOpt) String() string { 62 | switch tcpOpt { 63 | case TCPOptMD5: 64 | return "md5" 65 | case TCPOptMSS: 66 | return "mss" 67 | case TCPOptWindowScale: 68 | return "wscale" 69 | case TCPOptSACKPermitted: 70 | return "sack-permitted" 71 | case TCPOptSACK: 72 | return "sack" 73 | case TCPOptTimestamp: 74 | return "timestamp" 75 | default: 76 | return "" 77 | } 78 | } 79 | 80 | const ( 81 | TCPOptMD5 TCPOpt = 19 82 | TCPOptMSS TCPOpt = 2 83 | TCPOptWindowScale TCPOpt = 3 84 | TCPOptSACKPermitted TCPOpt = 4 85 | TCPOptSACK TCPOpt = 5 86 | TCPOptTimestamp TCPOpt = 8 87 | ) 88 | 89 | var ( 90 | TCPOpts = map[string]TCPOpt{ 91 | "wscale": TCPOptWindowScale, 92 | "mss": TCPOptMSS, 93 | "sack-permitted": TCPOptSACKPermitted, 94 | "sack": TCPOptSACK, 95 | "timestamp": TCPOptTimestamp, 96 | "md5": TCPOptMD5, 97 | } 98 | ) 99 | -------------------------------------------------------------------------------- /pkg/constraint/constraint.go: -------------------------------------------------------------------------------- 1 | package constraint 2 | 3 | type operator int 4 | 5 | func (operator operator) String() string { 6 | switch operator { 7 | case Must: 8 | return "Must" 9 | case Mustnot: 10 | return "Mustnot" 11 | } 12 | return "" 13 | } 14 | 15 | const ( 16 | _ operator = iota 17 | Must 18 | Mustnot 19 | ) 20 | 21 | type Constraints struct { 22 | constraints map[string]*constraint 23 | } 24 | 25 | func NewConstraints() *Constraints { 26 | return &Constraints{ 27 | constraints: make(map[string]*constraint), 28 | } 29 | } 30 | 31 | func (constraints *Constraints) Add(operator operator, 32 | firstType, first, secondType string, seconds ...string) { 33 | key := operator.String() + firstType + first + secondType 34 | value, ok := constraints.constraints[key] 35 | if !ok { 36 | constraints.constraints[key] = &constraint{ 37 | operator: operator, 38 | firstType: firstType, 39 | first: first, 40 | secondType: secondType, 41 | seconds: seconds, 42 | } 43 | } else { 44 | exist := false 45 | for _, second := range seconds { 46 | for _, elem := range value.seconds { 47 | if second == elem { 48 | exist = true 49 | break 50 | } 51 | } 52 | if exist { 53 | break 54 | } 55 | } 56 | if !exist { 57 | value.seconds = append(value.seconds, seconds...) 58 | } 59 | } 60 | } 61 | 62 | func (constraints *Constraints) Conflict(firstType, first, secondType, second string) bool { 63 | // must 64 | key := Must.String() + firstType + first + secondType 65 | value, ok := constraints.constraints[key] 66 | if ok { 67 | found := false 68 | for _, elem := range value.seconds { 69 | if second == elem { 70 | found = true 71 | break 72 | } 73 | } 74 | if !found { 75 | return true 76 | } 77 | } 78 | 79 | // mustnot 80 | key = Mustnot.String() + firstType + first + secondType 81 | value, ok = constraints.constraints[key] 82 | if ok { 83 | for _, elem := range value.seconds { 84 | if second == elem { 85 | return true 86 | } 87 | } 88 | } 89 | return false 90 | } 91 | 92 | type constraint struct { 93 | operator operator 94 | firstType string 95 | first string 96 | secondType string 97 | seconds []string 98 | } 99 | -------------------------------------------------------------------------------- /pkg/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | "strings" 7 | 8 | "github.com/singchia/go-xtables/pkg/log" 9 | "golang.org/x/crypto/ssh" 10 | ) 11 | 12 | func Cmd(name string, arg ...string) ([]byte, []byte, error) { 13 | cmd := exec.Command(name, arg...) 14 | infoO := new(bytes.Buffer) 15 | infoE := new(bytes.Buffer) 16 | cmd.Stdout = infoO 17 | cmd.Stderr = infoE 18 | err := cmd.Run() 19 | return infoO.Bytes(), infoE.Bytes(), err 20 | } 21 | 22 | func SSHCmdPassword(addr string, user, password string, 23 | name string, arg ...string) ([]byte, []byte, error) { 24 | 25 | config := &ssh.ClientConfig{ 26 | User: user, 27 | Auth: []ssh.AuthMethod{ 28 | ssh.Password(password), 29 | }, 30 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 31 | } 32 | client, err := ssh.Dial("tcp", addr, config) 33 | if err != nil { 34 | return nil, nil, err 35 | } 36 | session, err := client.NewSession() 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | defer session.Close() 41 | 42 | infoO := new(bytes.Buffer) 43 | infoE := new(bytes.Buffer) 44 | session.Stdout = infoO 45 | session.Stderr = infoE 46 | 47 | cmd := name 48 | if arg != nil { 49 | cmd += " " + strings.Join(arg, " ") 50 | } 51 | log.Debugf("session to run: %s", cmd) 52 | err = session.Run(cmd) 53 | 54 | return infoO.Bytes(), infoE.Bytes(), err 55 | } 56 | 57 | func SSHCmdPublicKey(addr string, user string, privateKey []byte, 58 | name string, arg ...string) ([]byte, []byte, error) { 59 | 60 | key, err := ssh.ParsePrivateKey(privateKey) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | config := &ssh.ClientConfig{ 66 | User: user, 67 | Auth: []ssh.AuthMethod{ 68 | ssh.PublicKeys(key), 69 | }, 70 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 71 | } 72 | client, err := ssh.Dial("tcp", addr, config) 73 | if err != nil { 74 | return nil, nil, err 75 | } 76 | session, err := client.NewSession() 77 | if err != nil { 78 | return nil, nil, err 79 | } 80 | defer session.Close() 81 | 82 | infoO := new(bytes.Buffer) 83 | infoE := new(bytes.Buffer) 84 | session.Stdout = infoO 85 | session.Stderr = infoE 86 | 87 | cmd := name 88 | if arg != nil { 89 | cmd += " " + strings.Join(arg, " ") 90 | } 91 | err = session.Run(cmd) 92 | return infoO.Bytes(), infoE.Bytes(), err 93 | } 94 | -------------------------------------------------------------------------------- /pkg/network/ethernet_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import "testing" 4 | 5 | func TestEthernetType(t *testing.T) { 6 | maps := map[EthernetType]string{ 7 | EthernetTypeLLC: "0x0000", 8 | EthernetTypeIPv4: "0x0800", 9 | EthernetTypeX25: "0x0805", 10 | EthernetTypeARP: "0x0806", 11 | EthernetTypeFR_ARP: "0x0808", 12 | EthernetTypeBPQ: "0x08ff", 13 | EthernetTypeDEC: "0x6000", 14 | EthernetTypeDNA_DL: "0x6001", 15 | EthernetTypeDNA_RC: "0x6002", 16 | EthernetTypeDNA_RT: "0x6003", 17 | EthernetTypeLAT: "0x6004", 18 | EthernetTypeDIAG: "0x6005", 19 | EthernetTypeCUST: "0x6006", 20 | EthernetTypeSCA: "0x6007", 21 | EthernetTypeRAW_FR: "0x6559", 22 | EthernetTypeAARP: "0x80f3", 23 | EthernetTypeATALK: "0x808b", 24 | EthernetType802_1Q: "0x8100", 25 | EthernetTypeIPX: "0x8137", 26 | EthernetTypeNetBEUI: "0x8191", 27 | EthernetTypeIPv6: "0x8dd6", 28 | EthernetTypeCiscoDiscovery: "0x2000", 29 | EthernetTypeNortelDiscovery: "0x01a2", 30 | EthernetTypeTransparentEthernetBridging: "0x6558", 31 | EthernetTypePPP: "0x880b", 32 | EthernetTypePPPoEDiscovery: "0x8863", 33 | EthernetTypePPPoESession: "0x8864", 34 | EthernetTypeMPLSUnicast: "0x8847", 35 | EthernetTypeATMMPOA: "0x884c", 36 | EthernetTypeMPLSMulticast: "0x8848", 37 | EthernetTypeATMFATE: "0x8884", 38 | EthernetTypeEAPOL: "0x888e", 39 | EthernetTypeERSPAN: "0x88be", 40 | EthernetTypeQinQ: "0x88a8", 41 | EthernetTypeLinkLayerDiscovery: "0x88cc", 42 | EthernetTypeEthernetCTP: "0x9000", 43 | } 44 | for k, v := range maps { 45 | if k.String() != v { 46 | t.Error("unmatched ethernet type", k.String(), v) 47 | return 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ebtables/ebtables.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022-2025 Austin Zhai. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package ebtables 17 | 18 | import ( 19 | "io" 20 | 21 | "github.com/singchia/go-xtables/pkg/log" 22 | ) 23 | 24 | type EBTablesOption func(*EBTables) 25 | 26 | func OptionEBTablesLogger(logger log.Logger) EBTablesOption { 27 | return func(ebtables *EBTables) { 28 | ebtables.log = logger 29 | } 30 | } 31 | 32 | func OptionEBTablesCmdPath(path string) EBTablesOption { 33 | return func(ebtables *EBTables) { 34 | ebtables.cmdName = path 35 | } 36 | } 37 | 38 | type EBTables struct { 39 | statement *Statement 40 | cmdName string 41 | log log.Logger 42 | 43 | dr bool 44 | drWriter io.Writer 45 | } 46 | 47 | func NewEBTables(opts ...EBTablesOption) *EBTables { 48 | tables := &EBTables{ 49 | statement: NewStatement(), 50 | cmdName: "ebtables", 51 | } 52 | for _, opt := range opts { 53 | opt(tables) 54 | } 55 | if tables.log == nil { 56 | tables.log = log.DefaultLog 57 | } 58 | return tables 59 | } 60 | 61 | func (ebtables *EBTables) dump() *EBTables { 62 | newebtables := &EBTables{ 63 | statement: &Statement{ 64 | err: ebtables.statement.err, 65 | table: ebtables.statement.table, 66 | chain: ebtables.statement.chain, 67 | matches: make(map[MatchType]Match), 68 | options: make(map[OptionType]Option), 69 | watchers: make(map[WatcherType]Watcher), 70 | target: ebtables.statement.target, 71 | command: ebtables.statement.command, 72 | }, 73 | cmdName: ebtables.cmdName, 74 | log: ebtables.log, 75 | dr: ebtables.dr, 76 | drWriter: ebtables.drWriter, 77 | } 78 | for k, v := range ebtables.statement.matches { 79 | newebtables.statement.matches[k] = v 80 | } 81 | for k, v := range ebtables.statement.options { 82 | newebtables.statement.options[k] = v 83 | } 84 | for k, v := range ebtables.statement.watchers { 85 | newebtables.statement.watchers[k] = v 86 | } 87 | return newebtables 88 | } 89 | -------------------------------------------------------------------------------- /gen/gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Apache License 2.0 3 | * 4 | * Copyright (c) 2022, Austin Zhai 5 | * All rights reserved. 6 | */ 7 | package main 8 | 9 | import ( 10 | "bufio" 11 | "flag" 12 | "fmt" 13 | "io" 14 | "os" 15 | "os/exec" 16 | "strings" 17 | 18 | "github.com/singchia/go-xtables/pkg/cmd" 19 | ) 20 | 21 | var ( 22 | protocolLowerMaps = flag.Bool("proto_lower_maps", false, "gen protocol lower maps") 23 | serviceDefine = flag.Bool("service_define", true, "whether service define") 24 | serviceTypeMaps = flag.Bool("service_maps", false, "gen service maps") 25 | ) 26 | 27 | const ( 28 | linuxProtocolFile = "protocols" 29 | linuxServiceFile = "services" 30 | ) 31 | 32 | func iterate(file string, cb func(row []string) error) ([][]string, error) { 33 | fs, err := os.Open(file) 34 | if err != nil { 35 | return nil, err 36 | } 37 | defer fs.Close() 38 | 39 | rowses := [][]string{} 40 | 41 | reader := bufio.NewReader(fs) 42 | for { 43 | row, err := reader.ReadString('\n') 44 | if err != nil { 45 | if err == io.EOF { 46 | break 47 | } 48 | return nil, err 49 | } 50 | if len(row) < 1 || row[0] == '#' || row[0] == ' ' { 51 | continue 52 | } 53 | rows := strings.Fields(row) 54 | if cb != nil { 55 | err = cb(rows) 56 | if err != nil { 57 | return nil, err 58 | } 59 | } 60 | rowses = append(rowses, rows) 61 | } 62 | return rowses, nil 63 | } 64 | 65 | const ( 66 | outProtocol = "../pkg/network/protocol.go" 67 | outService = "../pkg/network/service.go" 68 | ) 69 | 70 | func main() { 71 | flag.Parse() 72 | // protocols 73 | arrays, err := iterate(linuxProtocolFile, nil) 74 | if err != nil { 75 | fmt.Println(err) 76 | return 77 | } 78 | 79 | fdProtocol, err := os.OpenFile(outProtocol, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 80 | if err != nil { 81 | fmt.Println(err) 82 | return 83 | } 84 | defer fdProtocol.Close() 85 | 86 | genProtocols(arrays, fdProtocol) 87 | 88 | // services 89 | arrays, err = iterate(linuxServiceFile, nil) 90 | if err != nil { 91 | fmt.Println(err) 92 | return 93 | } 94 | 95 | fdService, err := os.OpenFile(outService, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 96 | if err != nil { 97 | fmt.Println(err) 98 | return 99 | } 100 | defer fdService.Close() 101 | 102 | genServices(arrays, fdService) 103 | 104 | // format 105 | gofmt, err := exec.LookPath("gofmt") 106 | if err == nil { 107 | cmd.Cmd(gofmt, "-s", "-w", outProtocol) 108 | cmd.Cmd(gofmt, "-s", "-w", outService) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pkg/network/arp.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // refer to https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml 9 | type ARPOpCode uint16 10 | 11 | func (opCode ARPOpCode) String() string { 12 | return strconv.Itoa(int(opCode)) 13 | } 14 | 15 | func (opCode ARPOpCode) Hex() [2]byte { 16 | buf := [2]byte{} 17 | // little endian 18 | buf[0] = byte(opCode) 19 | buf[1] = byte(opCode >> 8) 20 | return buf 21 | } 22 | 23 | const ( 24 | ARPOpCodeReserved ARPOpCode = 0 25 | ARPOpCodeRequest ARPOpCode = 1 26 | ARPOpCodeReply ARPOpCode = 2 27 | ARPOpCodeRequestReverse ARPOpCode = 3 28 | ARPOpCodeReplyReverse ARPOpCode = 4 29 | ARPOpCodeDRARPRequest ARPOpCode = 5 30 | ARPOpCodeDRARPReply ARPOpCode = 6 31 | ARPOpCodeDRARPError ARPOpCode = 7 32 | ARPOpCodeInARPRequest ARPOpCode = 8 33 | ARPOpCodeInARPReply ARPOpCode = 9 34 | ARPOpCodeInARPNAK ARPOpCode = 10 35 | ARPOpCodeMARSRequest ARPOpCode = 11 36 | ARPOpCodeMARSMulti ARPOpCode = 12 37 | ARPOpCodeMARSJoin ARPOpCode = 14 38 | ARPOpCodeMARSLeave ARPOpCode = 15 39 | ARPOpCodeMARSNAK ARPOpCode = 16 40 | ARPOpCodeMARSUnserv ARPOpCode = 17 41 | ARPOpCodeMARSSJoin ARPOpCode = 18 42 | ARPOpCodeMARSSLeave ARPOpCode = 19 43 | ARPOpCodeMARSSGrouplistRequest ARPOpCode = 20 44 | ARPOpCodeMARSSGrouplistReply ARPOpCode = 21 45 | ARPOpCodeMARSSGrouplistMap ARPOpCode = 22 46 | ARPOpCodeMAPOSUNARP ARPOpCode = 23 47 | ARPOpCodeOPEXP1 ARPOpCode = 24 48 | ARPOpCodeOPEXP2 ARPOpCode = 25 49 | ) 50 | 51 | func ParseARPOpCode(code string) (ARPOpCode, error) { 52 | cd, err := strconv.ParseUint(code, 10, 16) 53 | if err == nil { 54 | return ARPOpCode(cd), nil 55 | } 56 | switch strings.ToUpper(code) { 57 | case "REQUEST": 58 | return ARPOpCodeRequest, nil 59 | case "REPLY": 60 | return ARPOpCodeReply, nil 61 | case "REQUEST_REVERSE": 62 | return ARPOpCodeRequestReverse, nil 63 | case "REPLY_REVERSE": 64 | return ARPOpCodeReplyReverse, nil 65 | case "DRARP_REQUEST": 66 | return ARPOpCodeDRARPRequest, nil 67 | case "DRARP_REPLY": 68 | return ARPOpCodeDRARPReply, nil 69 | case "DRARP_ERROR": 70 | return ARPOpCodeDRARPError, nil 71 | case "INARP_REQUEST": 72 | return ARPOpCodeInARPRequest, nil 73 | case "ARP_NAK": 74 | // ebtables bug, the decimal should be 9. 75 | return ARPOpCodeInARPNAK, nil 76 | } 77 | return 0, err 78 | } 79 | -------------------------------------------------------------------------------- /ebtables/rule.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Rule struct { 8 | tableType TableType 9 | // chain info 10 | chain *Chain 11 | 12 | // line number 13 | lineNumber int 14 | 15 | // matches 16 | matchMap map[MatchType]Match 17 | 18 | // watchers 19 | watcherMap map[WatcherType]Watcher 20 | 21 | // options 22 | optionMap map[OptionType]Option 23 | 24 | // target 25 | target Target 26 | 27 | // counters 28 | packetCounter int64 29 | byteCounter int64 30 | } 31 | 32 | func (rule *Rule) String() string { 33 | elems := []string{} 34 | // table 35 | elems = append(elems, "-t", rule.tableType.String()) 36 | 37 | // chain 38 | elems = append(elems, rule.chain.chainType.String()) 39 | 40 | // options 41 | for _, option := range rule.optionMap { 42 | args := option.ShortArgs() 43 | if args != nil { 44 | elems = append(elems, args...) 45 | } 46 | } 47 | 48 | // matches 49 | for _, match := range rule.matchMap { 50 | args := match.ShortArgs() 51 | if args != nil { 52 | elems = append(elems, args...) 53 | } 54 | } 55 | 56 | // watches 57 | for _, watcher := range rule.watcherMap { 58 | args := watcher.ShortArgs() 59 | if args != nil { 60 | elems = append(elems, args...) 61 | } 62 | } 63 | 64 | // target 65 | elems = append(elems, rule.target.ShortArgs()...) 66 | return strings.Join(elems, " ") 67 | } 68 | 69 | func (rule *Rule) HasAllOptions(options map[OptionType]Option) bool { 70 | OUTER: 71 | for _, opt := range options { 72 | for _, v := range rule.optionMap { 73 | ok := opt.Equal(v) 74 | if ok { 75 | continue OUTER 76 | } 77 | } 78 | // unmatched 79 | return false 80 | } 81 | return true 82 | } 83 | 84 | func (rule *Rule) HasAllMatchers(matches map[MatchType]Match) bool { 85 | OUTER: 86 | for _, mth := range matches { 87 | for _, v := range rule.matchMap { 88 | ok := mth.Equal(v) 89 | if ok { 90 | continue OUTER 91 | } 92 | } 93 | // unmatched 94 | return false 95 | } 96 | return true 97 | } 98 | 99 | func (rule *Rule) HasAllWatchers(watchers map[WatcherType]Watcher) bool { 100 | OUTER: 101 | for _, wth := range watchers { 102 | for _, v := range rule.watcherMap { 103 | ok := wth.Equal(v) 104 | if ok { 105 | continue OUTER 106 | } 107 | } 108 | // unmatched 109 | return false 110 | } 111 | return true 112 | } 113 | 114 | func (rule *Rule) HasTarget(target Target) bool { 115 | if target == nil { 116 | return true 117 | } else if rule.target == nil { 118 | return false 119 | } 120 | return rule.target.Equal(target) 121 | } 122 | 123 | func (rule *Rule) TableType() TableType { 124 | return rule.tableType 125 | } 126 | 127 | func (rule *Rule) ChainType() ChainType { 128 | return rule.chain.chainType 129 | } 130 | -------------------------------------------------------------------------------- /iptables/iptables.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022-2025 Austin Zhai. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package iptables 17 | 18 | import ( 19 | "io" 20 | 21 | "github.com/singchia/go-xtables/pkg/log" 22 | ) 23 | 24 | type IPTablesOption func(*IPTables) 25 | 26 | func OptionIPTablesLogger(logger log.Logger) IPTablesOption { 27 | return func(iptables *IPTables) { 28 | iptables.log = logger 29 | } 30 | } 31 | 32 | // like "/usr/sbin/iptables" 33 | func OptionIPTablesCmdPath(path string) IPTablesOption { 34 | return func(iptables *IPTables) { 35 | iptables.cmdName = path 36 | } 37 | } 38 | 39 | type IPTables struct { 40 | statement *Statement 41 | cmdName string 42 | log log.Logger 43 | 44 | dr bool 45 | drWriter io.Writer 46 | } 47 | 48 | func NewIPTables(opts ...IPTablesOption) *IPTables { 49 | tables := &IPTables{ 50 | statement: NewStatement(), 51 | cmdName: "iptables", 52 | } 53 | for _, opt := range opts { 54 | opt(tables) 55 | } 56 | if tables.log == nil { 57 | tables.log = log.DefaultLog 58 | } 59 | return tables 60 | } 61 | 62 | func (iptables *IPTables) dump() *IPTables { 63 | newiptables := &IPTables{ 64 | statement: &Statement{ 65 | err: iptables.statement.err, 66 | table: iptables.statement.table, 67 | chain: iptables.statement.chain, 68 | matches: make(map[MatchType]Match), 69 | matchTypeOrder: make([]MatchType, 0), 70 | options: make(map[OptionType]Option), 71 | optionTypeOrder: make([]OptionType, 0), 72 | target: iptables.statement.target, 73 | command: iptables.statement.command, 74 | }, 75 | cmdName: iptables.cmdName, 76 | log: iptables.log, 77 | dr: iptables.dr, 78 | drWriter: iptables.drWriter, 79 | } 80 | 81 | for _, k := range iptables.statement.matchTypeOrder { 82 | newiptables.statement.matchTypeOrder = append(newiptables.statement.matchTypeOrder, k) 83 | } 84 | for k, v := range iptables.statement.matches { 85 | newiptables.statement.matches[k] = v 86 | } 87 | 88 | for _, k := range iptables.statement.optionTypeOrder { 89 | newiptables.statement.optionTypeOrder = append(newiptables.statement.optionTypeOrder, k) 90 | } 91 | for k, v := range iptables.statement.options { 92 | newiptables.statement.options[k] = v 93 | } 94 | 95 | return newiptables 96 | } 97 | -------------------------------------------------------------------------------- /pkg/network/ip6.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | // see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/in[6].h 4 | type IPv6Option struct { 5 | Type int 6 | Length int 7 | } 8 | 9 | // IP headers 10 | type IPv6HeaderType uint8 11 | 12 | func (ipHeaderType IPv6HeaderType) String() string { 13 | switch ipHeaderType { 14 | case IPv6HeaderTypeHOPOPTS: 15 | return "hop" 16 | case IPv6HeaderTypeDSTOPTS: 17 | return "dst" 18 | case IPv6HeaderTypeROUTING: 19 | return "route" 20 | case IPv6HeaderTypeFRAGMENT: 21 | return "frag" 22 | case IPv6HeaderTypeAH: 23 | return "auth" 24 | case IPv6HeaderTypeESP: 25 | return "esp" 26 | case IPv6HeaderTypeNONE: 27 | return "none" 28 | case IPv6HeaderTypeRAW: 29 | return "proto" 30 | default: 31 | return "" 32 | } 33 | } 34 | 35 | const ( 36 | IPv6HeaderTypeESP IPv6HeaderType = 50 37 | IPv6HeaderTypeAH IPv6HeaderType = 51 38 | IPv6HeaderTypeRAW IPv6HeaderType = 255 39 | // IPv6 extension headers 40 | IPv6HeaderTypeHOPOPTS IPv6HeaderType = 0 41 | IPv6HeaderTypeROUTING IPv6HeaderType = 43 42 | IPv6HeaderTypeFRAGMENT IPv6HeaderType = 44 43 | IPv6HeaderTypeICMPV6 IPv6HeaderType = 58 44 | IPv6HeaderTypeNONE IPv6HeaderType = 59 45 | IPv6HeaderTypeDSTOPTS IPv6HeaderType = 60 46 | IPv6HeaderTypeMH IPv6HeaderType = 135 47 | 48 | // mask 49 | MaskHOPOPTS IPv6HeaderType = 128 50 | MaskDSTOPTS IPv6HeaderType = 64 51 | MaskROUTING IPv6HeaderType = 32 52 | MaskFRAGMENT IPv6HeaderType = 16 53 | MaskAH IPv6HeaderType = 8 54 | MaskESP IPv6HeaderType = 4 55 | MaskNONE IPv6HeaderType = 2 56 | MaskPROTO IPv6HeaderType = 1 57 | ) 58 | 59 | var ( 60 | IPv6HeaderTypeMasks = [...]IPv6HeaderType{ 61 | MaskHOPOPTS, MaskDSTOPTS, MaskROUTING, MaskFRAGMENT, 62 | MaskAH, MaskESP, MaskNONE, MaskPROTO} 63 | 64 | IPv6HeaderTypeMaskMap = map[IPv6HeaderType]IPv6HeaderType{ 65 | MaskHOPOPTS: IPv6HeaderTypeHOPOPTS, 66 | MaskDSTOPTS: IPv6HeaderTypeDSTOPTS, 67 | MaskROUTING: IPv6HeaderTypeROUTING, 68 | MaskFRAGMENT: IPv6HeaderTypeFRAGMENT, 69 | MaskAH: IPv6HeaderTypeAH, 70 | MaskESP: IPv6HeaderTypeESP, 71 | MaskNONE: IPv6HeaderTypeNONE, 72 | MaskPROTO: IPv6HeaderTypeRAW, 73 | } 74 | ) 75 | 76 | var ( 77 | IPv6HeaderTypes = map[string]IPv6HeaderType{ 78 | "hop": IPv6HeaderTypeHOPOPTS, 79 | "hop-by-hop": IPv6HeaderTypeHOPOPTS, 80 | "dst": IPv6HeaderTypeDSTOPTS, 81 | "ipv6-opts": IPv6HeaderTypeDSTOPTS, 82 | "route": IPv6HeaderTypeROUTING, 83 | "ipv6-route": IPv6HeaderTypeROUTING, 84 | "frag": IPv6HeaderTypeFRAGMENT, 85 | "ipv6-frag": IPv6HeaderTypeFRAGMENT, 86 | "auth": IPv6HeaderTypeAH, 87 | "ah": IPv6HeaderTypeAH, 88 | "esp": IPv6HeaderTypeESP, 89 | "none": IPv6HeaderTypeNONE, 90 | "ipv6-nonxt": IPv6HeaderTypeNONE, 91 | "prot": IPv6HeaderTypeRAW, 92 | "protocol": IPv6HeaderTypeRAW, 93 | } 94 | ) 95 | -------------------------------------------------------------------------------- /test/stdout/list_iptables_filter: -------------------------------------------------------------------------------- 1 | Chain INPUT (policy ACCEPT) 2 | target prot opt source destination 3 | ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:10050 4 | ACCEPT all -- 172.18.0.1 0.0.0.0/0 5 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 6 | ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:514 7 | ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 8 | ACCEPT all -- 192.168.0.1 0.0.0.0/0 9 | ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:514 10 | ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 11 | ACCEPT all -- 172.18.0.0/16 0.0.0.0/0 12 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 13 | ACCEPT tcp -- 0.0.0.0/0 192.168.0.1 tcp dpt:8888 14 | ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmptype 8 15 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 16 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 17 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 18 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 19 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 20 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 21 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 22 | DROP all -- 0.0.0.0/0 0.0.0.0/0 23 | 24 | Chain FORWARD (policy DROP) 25 | target prot opt source destination 26 | DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0 27 | DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0 28 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 29 | DOCKER all -- 0.0.0.0/0 0.0.0.0/0 30 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 31 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 32 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 33 | DOCKER all -- 0.0.0.0/0 0.0.0.0/0 34 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 35 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 36 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 37 | DOCKER all -- 0.0.0.0/0 0.0.0.0/0 38 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 39 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 40 | DOCKER all -- 0.0.0.0/0 0.0.0.0/0 41 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 42 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 43 | ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 44 | 45 | Chain OUTPUT (policy ACCEPT) 46 | target prot opt source destination 47 | 48 | Chain DOCKER (4 references) 49 | target prot opt source destination 50 | 51 | Chain DOCKER-ISOLATION-STAGE-1 (1 references) 52 | target prot opt source destination 53 | DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0 54 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 55 | 56 | Chain DOCKER-ISOLATION-STAGE-2 (1 references) 57 | target prot opt source destination 58 | DROP all -- 0.0.0.0/0 0.0.0.0/0 59 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 60 | 61 | Chain DOCKER-USER (1 references) 62 | target prot opt source destination 63 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 64 | 65 | Chain f2b-SSH (0 references) 66 | target prot opt source destination 67 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 68 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 69 | RETURN all -- 0.0.0.0/0 0.0.0.0/0 70 | -------------------------------------------------------------------------------- /pkg/network/hardware.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // refer to github.com/google/gopacket and iana arp-parameters. 8 | // HardwareType is an enumeration of link types, and acts as a decoder for any 9 | // link type it supports. 10 | type HardwareType uint16 11 | 12 | func (hwType HardwareType) String() string { 13 | return strconv.Itoa(int(hwType)) 14 | } 15 | 16 | func (hwType HardwareType) Hex() [2]byte { 17 | buf := [2]byte{} 18 | // little endian 19 | buf[0] = byte(hwType) 20 | buf[1] = byte(hwType >> 8) 21 | return buf 22 | } 23 | 24 | const ( 25 | // According to pcap-linktype(7) and http://www.tcpdump.org/linktypes.html 26 | HardwareTypeNull HardwareType = 0 27 | HardwareTypeEthernet HardwareType = 1 28 | HardwareTypeExperimentalEthernet HardwareType = 2 29 | HardwareTypeAX25 HardwareType = 3 30 | HardwareTypeProteonProNETTokenRing HardwareType = 4 31 | HardwareTypeChaos HardwareType = 5 32 | HardwareTypeTokenRing HardwareType = 6 33 | HardwareTypeArcNet HardwareType = 7 34 | HardwareTypeSLIP HardwareType = 8 35 | HardwareTypePPP HardwareType = 9 36 | HardwareTypeFDDI HardwareType = 10 37 | HardwareTypeLocalTalk HardwareType = 11 38 | HardwareTypeLocalNet HardwareType = 12 39 | HardwareTypeUltralink HardwareType = 13 40 | HardwareTypeSMDS HardwareType = 14 41 | HardwareTypeFrameRelay HardwareType = 15 42 | HardwareTypeATM16 HardwareType = 16 43 | HardwareTypeHDLC HardwareType = 17 44 | HardwareTypeFibreChannel HardwareType = 18 45 | HardwareTypeATM19 HardwareType = 19 46 | HardwareTypeSerialline HardwareType = 20 47 | HardwareTypeATM21 HardwareType = 21 48 | HardwareTypePPP_HDLC HardwareType = 50 49 | HardwareTypePPPEthernet HardwareType = 51 50 | HardwareTypeATM_RFC1483 HardwareType = 100 51 | HardwareTypeRaw HardwareType = 101 52 | HardwareTypeC_HDLC HardwareType = 104 53 | HardwareTypeIEEE802_11 HardwareType = 105 54 | HardwareTypeFRelay HardwareType = 107 55 | HardwareTypeLoop HardwareType = 108 56 | HardwareTypeLinuxSLL HardwareType = 113 57 | HardwareTypeLTalk HardwareType = 114 58 | HardwareTypePFLog HardwareType = 117 59 | HardwareTypePrismHeader HardwareType = 119 60 | HardwareTypeIPOverFC HardwareType = 122 61 | HardwareTypeSunATM HardwareType = 123 62 | HardwareTypeIEEE80211Radio HardwareType = 127 63 | HardwareTypeARCNetLinux HardwareType = 129 64 | HardwareTypeIPOver1394 HardwareType = 138 65 | HardwareTypeMTP2Phdr HardwareType = 139 66 | HardwareTypeMTP2 HardwareType = 140 67 | HardwareTypeMTP3 HardwareType = 141 68 | HardwareTypeSCCP HardwareType = 142 69 | HardwareTypeDOCSIS HardwareType = 143 70 | HardwareTypeLinuxIRDA HardwareType = 144 71 | HardwareTypeLinuxLAPD HardwareType = 177 72 | HardwareTypeLinuxUSB HardwareType = 220 73 | HardwareTypeFC2 HardwareType = 224 74 | HardwareTypeFC2Framed HardwareType = 225 75 | HardwareTypeIPv4 HardwareType = 228 76 | HardwareTypeIPv6 HardwareType = 229 77 | HardwareTypeAEthernet HardwareType = 257 78 | ) 79 | 80 | func ParseHardwareType(hwtype string) (HardwareType, error) { 81 | typ, err := strconv.ParseUint(hwtype, 10, 16) 82 | if err != nil { 83 | return 0, err 84 | } 85 | return HardwareType(typ), nil 86 | } 87 | -------------------------------------------------------------------------------- /ebtables/statement.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/singchia/go-xtables" 7 | ) 8 | 9 | type Statement struct { 10 | err error 11 | table TableType 12 | chain ChainType 13 | matches map[MatchType]Match 14 | options map[OptionType]Option 15 | watchers map[WatcherType]Watcher 16 | target Target 17 | command Command 18 | 19 | // TODO 20 | // constraints *constraints 21 | } 22 | 23 | func NewStatement() *Statement { 24 | state := &Statement{ 25 | table: TableTypeNull, 26 | chain: ChainTypeNull, 27 | matches: make(map[MatchType]Match), 28 | options: make(map[OptionType]Option), 29 | watchers: make(map[WatcherType]Watcher), 30 | } 31 | return state 32 | } 33 | 34 | func (statement *Statement) addMatch(match Match) { 35 | statement.matches[match.Type()] = match 36 | } 37 | 38 | func (statement *Statement) addOption(option Option) { 39 | statement.options[option.Type()] = option 40 | } 41 | 42 | func (statement *Statement) Elems() ([]string, error) { 43 | // table 44 | elems := []string{} 45 | elems = append(elems, "-t") 46 | tableName := "filter" 47 | switch statement.table { 48 | case TableTypeNat: 49 | tableName = "nat" 50 | case TableTypeBRoute: 51 | tableName = "broute" 52 | } 53 | elems = append(elems, tableName) 54 | 55 | // command 56 | if statement.command == nil { 57 | return nil, xtables.ErrCommandRequired 58 | } 59 | // chain 60 | if statement.command.Type() != CommandTypeAtomicInit && 61 | statement.command.Type() != CommandTypeAtomicSave && 62 | statement.command.Type() != CommandTypeAtomicCommit { 63 | 64 | statement.command.SetChainType(statement.chain) 65 | elems = append(elems, statement.command.ShortArgs()...) 66 | } 67 | 68 | // command tails 69 | switch statement.command.Type() { 70 | case CommandTypeList, CommandTypeListRules, CommandTypeListChains: 71 | // default with --Ln --Lc --Lmac2 72 | ln, ok := statement.options[OptionTypeListNumbers] 73 | if ok { 74 | elems = append(elems, ln.ShortArgs()...) 75 | delete(statement.options, OptionTypeListNumbers) 76 | } 77 | lc, ok := statement.options[OptionTypeListCounters] 78 | if ok { 79 | elems = append(elems, lc.ShortArgs()...) 80 | delete(statement.options, OptionTypeListCounters) 81 | } 82 | lmac2, ok := statement.options[OptionTypeListMACSameLength] 83 | if ok { 84 | elems = append(elems, lmac2.ShortArgs()...) 85 | delete(statement.options, OptionTypeListMACSameLength) 86 | } 87 | 88 | case CommandTypeDump: 89 | // default with --Lx 90 | lx, ok := statement.options[OptionTypeListChange] 91 | if ok { 92 | elems = append(elems, lx.ShortArgs()...) 93 | delete(statement.options, OptionTypeListChange) 94 | } 95 | } 96 | 97 | // options 98 | for _, option := range statement.options { 99 | args := option.ShortArgs() 100 | if args != nil { 101 | elems = append(elems, args...) 102 | } 103 | } 104 | 105 | // matches 106 | for _, match := range statement.matches { 107 | args := match.ShortArgs() 108 | if args != nil { 109 | elems = append(elems, args...) 110 | } 111 | } 112 | 113 | // watcher 114 | for _, watcher := range statement.watchers { 115 | args := watcher.ShortArgs() 116 | if args != nil { 117 | elems = append(elems, args...) 118 | } 119 | } 120 | 121 | // target 122 | if statement.target != nil { 123 | args := statement.target.ShortArgs() 124 | if args != nil { 125 | elems = append(elems, args...) 126 | } 127 | } 128 | 129 | // atomic-xxx command 130 | if statement.command.Type() == CommandTypeAtomicInit || 131 | statement.command.Type() == CommandTypeAtomicSave || 132 | statement.command.Type() == CommandTypeAtomicCommit { 133 | elems = append(elems, statement.command.ShortArgs()...) 134 | } 135 | 136 | return elems, nil 137 | } 138 | 139 | func (statement *Statement) String() (string, error) { 140 | elems, err := statement.Elems() 141 | if err != nil { 142 | return "", err 143 | } 144 | return strings.Join(elems, " "), nil 145 | } 146 | -------------------------------------------------------------------------------- /gen/protocols_gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "text/template" 9 | "time" 10 | ) 11 | 12 | const protocolHeaderFmt = `// 13 | // Apache License 2.0 14 | // 15 | // Copyright (c) 2022, Austin Zhai 16 | // All rights reserved. 17 | // 18 | 19 | package network 20 | 21 | import "strconv" 22 | 23 | // Created by gen.go, don't edit manually 24 | // Generated at %s 25 | 26 | func GetProtocolByName(name string) Protocol { 27 | protocol, ok := ProtocolUpperNameType[name] 28 | if !ok { 29 | return ProtocolUnknown 30 | } 31 | return protocol 32 | } 33 | 34 | type Protocol int 35 | 36 | var ( 37 | ProtocolUnknown Protocol = -1 38 | ) 39 | 40 | func (proto Protocol) Type() string { 41 | return "Protocol" 42 | } 43 | 44 | func (proto Protocol) Value() string { 45 | return strconv.Itoa(int(proto)) 46 | } 47 | 48 | func (proto Protocol) Hex() [2]byte { 49 | buf := [2]byte{} 50 | // little endian 51 | buf[0] = byte(proto) 52 | buf[1] = byte(proto >> 8) 53 | return buf 54 | } 55 | ` 56 | 57 | var protocolTypeTpl = template.Must(template.New("protocolTypeTpl"). 58 | Parse(`{{"\t"}}Protocol{{.ProtocolName}}{{"\t"}}{{"\t"}}Protocol = {{.ProtocolID}} // {{.ProtocolComment}} 59 | `)) 60 | 61 | var protocolUpperTypeMapTpl = template.Must(template.New("protocolUpperTypeMapTpl"). 62 | Parse(`"{{.ProtocolUpperName}}":{{"\t"}}Protocol{{.ProtocolName}}, 63 | `)) 64 | 65 | var protocolLowerTypeMapTpl = template.Must(template.New("protocolLowerTypeMapTpl"). 66 | Parse(`"{{.ProtocolLowerName}}":{{"\t"}}Protocol{{.ProtocolName}}, 67 | `)) 68 | 69 | func genProtocols(arrays [][]string, writer io.Writer) { 70 | buf := new(bytes.Buffer) 71 | header := fmt.Sprintf(protocolHeaderFmt, time.Now().Format("2006-01-02 15:04:05")) 72 | fmt.Fprintln(buf, header) 73 | 74 | fmt.Fprintln(buf, "const (") 75 | for _, array := range arrays { 76 | if len(array) == 0 { 77 | continue 78 | } 79 | tmp := struct { 80 | ProtocolName string 81 | ProtocolID string 82 | ProtocolComment string 83 | }{} 84 | if len(array) < 4 { 85 | tmp.ProtocolComment = "" 86 | } else { 87 | tmp.ProtocolComment = strings.Join(array[4:], " ") 88 | } 89 | name := array[2] 90 | name = strings.ReplaceAll(name, "-", "_") 91 | name = strings.ReplaceAll(name, ".", "Dot") 92 | name = strings.ReplaceAll(name, "/", "_") 93 | name = strings.ReplaceAll(name, "+", "Plus") 94 | tmp.ProtocolName = name 95 | tmp.ProtocolID = array[1] 96 | if err := protocolTypeTpl.Execute(buf, tmp); err != nil { 97 | fmt.Printf("tpl execute err: %s\n", err) 98 | continue 99 | } 100 | } 101 | fmt.Fprintln(buf, ")") 102 | 103 | // variables 104 | fmt.Fprintln(buf, "var (") 105 | fmt.Fprintln(buf, "ProtocolUpperNameType = map[string]Protocol{") 106 | for _, array := range arrays { 107 | if len(array) == 0 { 108 | continue 109 | } 110 | tmp := struct { 111 | ProtocolUpperName string 112 | ProtocolName string 113 | }{} 114 | tmp.ProtocolUpperName = array[2] 115 | name := array[2] 116 | name = strings.ReplaceAll(name, "-", "_") 117 | name = strings.ReplaceAll(name, ".", "Dot") 118 | name = strings.ReplaceAll(name, "/", "_") 119 | name = strings.ReplaceAll(name, "+", "Plus") 120 | tmp.ProtocolName = name 121 | if err := protocolUpperTypeMapTpl.Execute(buf, tmp); err != nil { 122 | fmt.Printf("tpl execute err: %s\n", err) 123 | continue 124 | } 125 | } 126 | fmt.Fprintln(buf, "}") 127 | 128 | if *protocolLowerMaps { 129 | fmt.Fprintln(buf, "ProtocolLowerNameType = map[string]Protocol{") 130 | for _, array := range arrays { 131 | if len(array) == 0 { 132 | continue 133 | } 134 | tmp := struct { 135 | ProtocolLowerName string 136 | ProtocolName string 137 | }{} 138 | tmp.ProtocolLowerName = array[0] 139 | name := array[2] 140 | name = strings.ReplaceAll(name, "-", "_") 141 | name = strings.ReplaceAll(name, ".", "Dot") 142 | name = strings.ReplaceAll(name, "/", "_") 143 | name = strings.ReplaceAll(name, "*", "_") 144 | name = strings.ReplaceAll(name, "+", "Plus") 145 | tmp.ProtocolName = name 146 | if err := protocolLowerTypeMapTpl.Execute(buf, tmp); err != nil { 147 | fmt.Printf("tpl execute err: %s\n", err) 148 | continue 149 | } 150 | } 151 | fmt.Fprintln(buf, "}") 152 | } 153 | fmt.Fprintln(buf, ")") 154 | buf.WriteTo(writer) 155 | } 156 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package xtables 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type Daytime struct { 11 | Hour int8 12 | Minute int8 13 | Second int8 14 | sets int 15 | } 16 | 17 | func (dt *Daytime) String() string { 18 | daytime := "" 19 | sep := "" 20 | if dt.Hour >= 0 && dt.Hour <= 23 { 21 | daytime += sep + fmt.Sprintf("%2d", dt.Hour) 22 | sep = ":" 23 | dt.sets += 1 24 | } 25 | if dt.Minute >= 0 && dt.Minute <= 59 { 26 | daytime += sep + fmt.Sprintf("%2d", dt.Minute) 27 | sep = ":" 28 | dt.sets += 1 29 | } 30 | if dt.Second >= 0 && dt.Second <= 59 { 31 | daytime += sep + fmt.Sprintf("%2d", dt.Second) 32 | sep = ":" 33 | dt.sets += 1 34 | } 35 | return daytime 36 | } 37 | 38 | func ParseDaytime(daytime string) (*Daytime, error) { 39 | dt := &Daytime{-1, -1, -1, 0} 40 | err := error(nil) 41 | parts := strings.Split(daytime, ":") 42 | if len(parts) != 3 { 43 | err = errors.New("wrong elems") 44 | return dt, err 45 | } 46 | for index, part := range parts { 47 | switch index { 48 | case 0: 49 | hour, err := strconv.ParseInt(part, 10, 8) 50 | if err != nil { 51 | return dt, err 52 | } 53 | dt.Hour = int8(hour) 54 | case 1: 55 | minute, err := strconv.ParseInt(part, 10, 8) 56 | if err != nil { 57 | return dt, err 58 | } 59 | dt.Minute = int8(minute) 60 | case 2: 61 | second, err := strconv.ParseInt(part, 10, 8) 62 | if err != nil { 63 | return dt, err 64 | } 65 | dt.Second = int8(second) 66 | } 67 | } 68 | return dt, nil 69 | } 70 | 71 | type Yeartime struct { 72 | Year int16 73 | Month int8 74 | Day int8 75 | sets int 76 | } 77 | 78 | func (yt *Yeartime) String() string { 79 | yeartime := "" 80 | sep := "" 81 | if yt.Year > -1 { 82 | yeartime += sep + fmt.Sprintf("%4d", yt.Year) 83 | sep = ":" 84 | yt.sets += 1 85 | } 86 | if yt.Month >= 1 && yt.Month <= 12 { 87 | yeartime += sep + fmt.Sprintf("%2d", yt.Month) 88 | sep = ":" 89 | yt.sets += 1 90 | } 91 | if yt.Day >= 1 && yt.Day <= 31 { 92 | yeartime += sep + fmt.Sprintf("%2d", yt.Day) 93 | sep = ":" 94 | yt.sets += 1 95 | } 96 | return yeartime 97 | } 98 | 99 | func ParseYeartime(yeartime string) (*Yeartime, error) { 100 | yt := &Yeartime{-1, -1, -1, 0} 101 | err := error(nil) 102 | parts := strings.Split(yeartime, "-") 103 | if len(parts) != 3 { 104 | err = errors.New("wrong elems") 105 | return yt, err 106 | } 107 | for index, part := range parts { 108 | switch index { 109 | case 0: 110 | year, err := strconv.ParseInt(part, 10, 16) 111 | if err != nil { 112 | return yt, err 113 | } 114 | yt.Year = int16(year) 115 | case 1: 116 | month, err := strconv.ParseInt(part, 10, 8) 117 | if err != nil { 118 | return yt, err 119 | } 120 | yt.Month = int8(month) 121 | case 2: 122 | day, err := strconv.ParseInt(part, 10, 8) 123 | if err != nil { 124 | return yt, err 125 | } 126 | yt.Day = int8(day) 127 | } 128 | } 129 | return yt, nil 130 | } 131 | 132 | type Date struct { 133 | *Yeartime 134 | *Daytime 135 | } 136 | 137 | func (date *Date) String() string { 138 | yeartime := date.Yeartime.String() 139 | daytime := date.Daytime.String() 140 | if date.Yeartime.sets == 3 && date.Daytime.sets == 3 { 141 | return yeartime + "T" + daytime 142 | } 143 | return yeartime 144 | } 145 | 146 | func ParseDate(date string) (*Date, error) { 147 | de := &Date{} 148 | err := error(nil) 149 | if len(date) != 19 { 150 | err = errors.New("wrong len") 151 | return de, err 152 | } 153 | yeartime := date[:10] 154 | daytime := date[11:] 155 | yt, err := ParseYeartime(yeartime) 156 | if err != nil { 157 | return de, err 158 | } 159 | dt, err := ParseDaytime(daytime) 160 | if err != nil { 161 | return de, err 162 | } 163 | de.Yeartime = yt 164 | de.Daytime = dt 165 | return de, nil 166 | } 167 | 168 | type Weekday int8 169 | 170 | func (weekday Weekday) String() string { 171 | weekdays := "" 172 | sep := "" 173 | for i := 0; i <= 6; i++ { 174 | if weekday&(1< 253 { 203 | return addrTypeUnknown, nil, ErrIllegalAddress 204 | } 205 | parts := strings.Split(address, "/") 206 | if len(parts) == 2 { 207 | ip := net.ParseIP(parts[1]) 208 | if ip != nil { 209 | // ffff::ffff 210 | mask := net.IPMask(ip) 211 | ones, _ := mask.Size() 212 | address = parts[0] + "/" + strconv.Itoa(ones) 213 | } 214 | } 215 | 216 | // ip net 217 | ip, ipNet, err := net.ParseCIDR(address) 218 | if err == nil { 219 | ones, size := ipNet.Mask.Size() 220 | if (ones == 128 || ones == 32) && size == ones { 221 | return addrTypeIP, ip, nil 222 | } 223 | return addrTypeIPNet, ipNet, nil 224 | } 225 | // ip 226 | ip = net.ParseIP(address) 227 | if ip != nil { 228 | return addrTypeIP, ip, nil 229 | } 230 | 231 | // mac 232 | mac, err := net.ParseMAC(address) 233 | if err == nil { 234 | return addrTypeMAC, mac, nil 235 | } 236 | 237 | // mac with mask 238 | addrMask := strings.Split(address, "/") 239 | if len(addrMask) == 2 { 240 | mac, err := net.ParseMAC(addrMask[0]) 241 | if err == nil { 242 | mask, err := net.ParseMAC(addrMask[1]) 243 | if err == nil { 244 | return addrTypeMACMask, [2]net.HardwareAddr{mac, mask}, nil 245 | } 246 | } 247 | goto HOST 248 | } 249 | 250 | HOST: 251 | // host 252 | host := &Host{} 253 | if len(addrMask) == 2 { 254 | mask, err := strconv.Atoi(addrMask[1]) 255 | if err != nil { 256 | return addrTypeUnknown, nil, ErrIllegalAddress 257 | } 258 | host.mask = mask 259 | } 260 | hostname := addrMask[0] 261 | head, tail := 0, 0 262 | // verification 263 | for i := 0; i < len(hostname); i++ { 264 | t := hostname[i] 265 | if i == head { 266 | if t == '.' || t == '-' { 267 | return addrTypeUnknown, nil, ErrIllegalAddress 268 | } 269 | } 270 | if i == tail+2 && tail >= 0 { 271 | if hostname[tail] == '-' { 272 | return addrTypeUnknown, nil, ErrIllegalAddress 273 | } 274 | } 275 | if t == '.' { 276 | if i-head > 63 { 277 | return addrTypeUnknown, nil, ErrIllegalAddress 278 | } 279 | head = i + 1 280 | tail = i - 1 281 | continue 282 | } 283 | 284 | if !((t >= 'a' && t <= 'z') || 285 | (t >= 'A' && t <= 'Z') || 286 | (t >= '0' && t <= '9') || 287 | t == '-') { 288 | return addrTypeUnknown, nil, ErrIllegalAddress 289 | } 290 | } 291 | host.hostname = hostname 292 | return addrTypeHost, host, nil 293 | } 294 | -------------------------------------------------------------------------------- /gen/protocols: -------------------------------------------------------------------------------- 1 | # /etc/protocols: 2 | # $Id: protocols,v 1.11 2011/05/03 14:45:40 ovasik Exp $ 3 | # 4 | # Internet (IP) protocols 5 | # 6 | # from: @(#)protocols 5.1 (Berkeley) 4/17/89 7 | # 8 | # Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992). 9 | # Last IANA update included dated 2011-05-03 10 | # 11 | # See also http://www.iana.org/assignments/protocol-numbers 12 | 13 | ip 0 IP # internet protocol, pseudo protocol number 14 | hopopt 0 HOPOPT # hop-by-hop options for ipv6 15 | icmp 1 ICMP # internet control message protocol 16 | igmp 2 IGMP # internet group management protocol 17 | ggp 3 GGP # gateway-gateway protocol 18 | ipv4 4 IPv4 # IPv4 encapsulation 19 | st 5 ST # ST datagram mode 20 | tcp 6 TCP # transmission control protocol 21 | cbt 7 CBT # CBT, Tony Ballardie 22 | egp 8 EGP # exterior gateway protocol 23 | igp 9 IGP # any private interior gateway (Cisco: for IGRP) 24 | bbn-rcc 10 BBN-RCC-MON # BBN RCC Monitoring 25 | nvp 11 NVP-II # Network Voice Protocol 26 | pup 12 PUP # PARC universal packet protocol 27 | argus 13 ARGUS # ARGUS 28 | emcon 14 EMCON # EMCON 29 | xnet 15 XNET # Cross Net Debugger 30 | chaos 16 CHAOS # Chaos 31 | udp 17 UDP # user datagram protocol 32 | mux 18 MUX # Multiplexing protocol 33 | dcn 19 DCN-MEAS # DCN Measurement Subsystems 34 | hmp 20 HMP # host monitoring protocol 35 | prm 21 PRM # packet radio measurement protocol 36 | xns-idp 22 XNS-IDP # Xerox NS IDP 37 | trunk-1 23 TRUNK-1 # Trunk-1 38 | trunk-2 24 TRUNK-2 # Trunk-2 39 | leaf-1 25 LEAF-1 # Leaf-1 40 | leaf-2 26 LEAF-2 # Leaf-2 41 | rdp 27 RDP # "reliable datagram" protocol 42 | irtp 28 IRTP # Internet Reliable Transaction Protocol 43 | iso-tp4 29 ISO-TP4 # ISO Transport Protocol Class 4 44 | netblt 30 NETBLT # Bulk Data Transfer Protocol 45 | mfe-nsp 31 MFE-NSP # MFE Network Services Protocol 46 | merit-inp 32 MERIT-INP # MERIT Internodal Protocol 47 | dccp 33 DCCP # Datagram Congestion Control Protocol 48 | 3pc 34 3PC # Third Party Connect Protocol 49 | idpr 35 IDPR # Inter-Domain Policy Routing Protocol 50 | xtp 36 XTP # Xpress Tranfer Protocol 51 | ddp 37 DDP # Datagram Delivery Protocol 52 | idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport Proto 53 | tp++ 39 TP++ # TP++ Transport Protocol 54 | il 40 IL # IL Transport Protocol 55 | ipv6 41 IPv6 # IPv6 encapsulation 56 | sdrp 42 SDRP # Source Demand Routing Protocol 57 | ipv6-route 43 IPv6-Route # Routing Header for IPv6 58 | ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6 59 | idrp 45 IDRP # Inter-Domain Routing Protocol 60 | rsvp 46 RSVP # Resource ReSerVation Protocol 61 | gre 47 GRE # Generic Routing Encapsulation 62 | dsr 48 DSR # Dynamic Source Routing Protocol 63 | bna 49 BNA # BNA 64 | esp 50 ESP # Encap Security Payload 65 | ipv6-crypt 50 IPv6-Crypt # Encryption Header for IPv6 (not in official list) 66 | ah 51 AH # Authentication Header 67 | ipv6-auth 51 IPv6-Auth # Authentication Header for IPv6 (not in official list) 68 | i-nlsp 52 I-NLSP # Integrated Net Layer Security TUBA 69 | swipe 53 SWIPE # IP with Encryption 70 | narp 54 NARP # NBMA Address Resolution Protocol 71 | mobile 55 MOBILE # IP Mobility 72 | tlsp 56 TLSP # Transport Layer Security Protocol 73 | skip 57 SKIP # SKIP 74 | ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6 75 | ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6 76 | ipv6-opts 60 IPv6-Opts # Destination Options for IPv6 77 | # 61 # any host internal protocol 78 | cftp 62 CFTP # CFTP 79 | # 63 # any local network 80 | sat-expak 64 SAT-EXPAK # SATNET and Backroom EXPAK 81 | kryptolan 65 KRYPTOLAN # Kryptolan 82 | rvd 66 RVD # MIT Remote Virtual Disk Protocol 83 | ippc 67 IPPC # Internet Pluribus Packet Core 84 | # 68 # any distributed file system 85 | sat-mon 69 SAT-MON # SATNET Monitoring 86 | visa 70 VISA # VISA Protocol 87 | ipcv 71 IPCV # Internet Packet Core Utility 88 | cpnx 72 CPNX # Computer Protocol Network Executive 89 | cphb 73 CPHB # Computer Protocol Heart Beat 90 | wsn 74 WSN # Wang Span Network 91 | pvp 75 PVP # Packet Video Protocol 92 | br-sat-mon 76 BR-SAT-MON # Backroom SATNET Monitoring 93 | sun-nd 77 SUN-ND # SUN ND PROTOCOL-Temporary 94 | wb-mon 78 WB-MON # WIDEBAND Monitoring 95 | wb-expak 79 WB-EXPAK # WIDEBAND EXPAK 96 | iso-ip 80 ISO-IP # ISO Internet Protocol 97 | vmtp 81 VMTP # Versatile Message Transport 98 | secure-vmtp 82 SECURE-VMTP # SECURE-VMTP 99 | vines 83 VINES # VINES 100 | ttp 84 TTP # TTP 101 | nsfnet-igp 85 NSFNET-IGP # NSFNET-IGP 102 | dgp 86 DGP # Dissimilar Gateway Protocol 103 | tcf 87 TCF # TCF 104 | eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco) 105 | ospf 89 OSPFIGP # Open Shortest Path First IGP 106 | sprite-rpc 90 Sprite-RPC # Sprite RPC Protocol 107 | larp 91 LARP # Locus Address Resolution Protocol 108 | mtp 92 MTP # Multicast Transport Protocol 109 | ax.25 93 AX.25 # AX.25 Frames 110 | ipip 94 IPIP # Yet Another IP encapsulation 111 | micp 95 MICP # Mobile Internetworking Control Pro. 112 | scc-sp 96 SCC-SP # Semaphore Communications Sec. Pro. 113 | etherip 97 ETHERIP # Ethernet-within-IP Encapsulation 114 | encap 98 ENCAP # Yet Another IP encapsulation 115 | # 99 # any private encryption scheme 116 | gmtp 100 GMTP # GMTP 117 | ifmp 101 IFMP # Ipsilon Flow Management Protocol 118 | pnni 102 PNNI # PNNI over IP 119 | pim 103 PIM # Protocol Independent Multicast 120 | aris 104 ARIS # ARIS 121 | scps 105 SCPS # SCPS 122 | qnx 106 QNX # QNX 123 | a/n 107 A/N # Active Networks 124 | ipcomp 108 IPComp # IP Payload Compression Protocol 125 | snp 109 SNP # Sitara Networks Protocol 126 | compaq-peer 110 Compaq-Peer # Compaq Peer Protocol 127 | ipx-in-ip 111 IPX-in-IP # IPX in IP 128 | vrrp 112 VRRP # Virtual Router Redundancy Protocol 129 | pgm 113 PGM # PGM Reliable Transport Protocol 130 | # 114 # any 0-hop protocol 131 | l2tp 115 L2TP # Layer Two Tunneling Protocol 132 | ddx 116 DDX # D-II Data Exchange 133 | iatp 117 IATP # Interactive Agent Transfer Protocol 134 | stp 118 STP # Schedule Transfer 135 | srp 119 SRP # SpectraLink Radio Protocol 136 | uti 120 UTI # UTI 137 | smp 121 SMP # Simple Message Protocol 138 | sm 122 SM # SM 139 | ptp 123 PTP # Performance Transparency Protocol 140 | isis 124 ISIS # ISIS over IPv4 141 | fire 125 FIRE 142 | crtp 126 CRTP # Combat Radio Transport Protocol 143 | crdup 127 CRUDP # Combat Radio User Datagram 144 | sscopmce 128 SSCOPMCE 145 | iplt 129 IPLT 146 | sps 130 SPS # Secure Packet Shield 147 | pipe 131 PIPE # Private IP Encapsulation within IP 148 | sctp 132 SCTP # Stream Control Transmission Protocol 149 | fc 133 FC # Fibre Channel 150 | rsvp-e2e-ignore 134 RSVP-E2E-IGNORE 151 | mobility-header 135 Mobility-Header # Mobility Header 152 | udplite 136 UDPLite 153 | mpls-in-ip 137 MPLS-in-IP 154 | manet 138 manet # MANET Protocols 155 | hip 139 HIP # Host Identity Protocol 156 | shim6 140 Shim6 # Shim6 Protocol 157 | wesp 141 WESP # Wrapped Encapsulating Security Payload 158 | rohc 142 ROHC # Robust Header Compression 159 | # 143-252 Unassigned [IANA] 160 | # 253 Use for experimentation and testing [RFC3692] 161 | # 254 Use for experimentation and testing [RFC3692] 162 | # 255 Reserved [IANA] 163 | -------------------------------------------------------------------------------- /gen/services_gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | "text/template" 11 | "time" 12 | ) 13 | 14 | const serviceHeaderFmt = `// 15 | // Apache License 2.0 16 | // 17 | // Copyright (c) 2022, Austin Zhai 18 | // All rights reserved. 19 | // 20 | 21 | package network 22 | 23 | // Created by gen.go, don't edit manually 24 | // Generated at %s 25 | 26 | func GetPortByServiceAndProtocol(service Service, proto Protocol) int { 27 | pps, ok := ServicePortProtoMaps[service] 28 | if !ok { 29 | return -1 30 | } 31 | for _, pp:= range pps { 32 | if proto == pp.Proto { 33 | return pp.Port 34 | } 35 | } 36 | return -1 37 | } 38 | 39 | type Service string 40 | 41 | var ( 42 | ServiceUnknown Service = "unknown" 43 | ) 44 | 45 | func (service Service) Type() string { 46 | return "Service" 47 | } 48 | 49 | func (service Service) Value() string { 50 | return string(service) 51 | } 52 | 53 | type PortProto struct { 54 | Port int 55 | Proto Protocol 56 | } 57 | ` 58 | 59 | var serviceTypeTpl = template.Must(template.New("serviceTypeTpl"). 60 | Parse(`{{"\t"}}Service{{.ServiceName}}{{"\t"}}{{"\t"}}Service = "{{.ServiceID}}" // {{.ServiceComment}} 61 | `)) 62 | 63 | var serviceTypeMapTpl = template.Must(template.New("serviceTypeMapTpl"). 64 | Parse(`PortProto{ {{.Port}}, {{.Protocol}} }:{{"\t"}}Service{{.ServiceName}}, 65 | `)) 66 | 67 | var servicePortProtoKeyTpl = template.Must(template.New("servicePortProtoKeyTpl"). 68 | Parse(`Service{{.ServiceName}}: `)) 69 | 70 | var servicePortProtoValueTpl = template.Must(template.New("servicePortProtoValueTpl"). 71 | Parse(`[]PortProto{ {{range $index, $pp := . }} PortProto{ {{ $pp.Port }}, {{ $pp.Protocol }}, }, {{end}}}, 72 | `)) 73 | 74 | func genServices(arrays [][]string, writer io.Writer) { 75 | buf := new(bytes.Buffer) 76 | header := fmt.Sprintf(serviceHeaderFmt, time.Now().Format("2006-01-02 15:04:05")) 77 | fmt.Fprintln(buf, header) 78 | 79 | if *serviceDefine { 80 | fmt.Fprintln(buf, "const (") 81 | names := map[string]bool{} 82 | for _, array := range arrays { 83 | if len(array) == 0 { 84 | continue 85 | } 86 | tmp := struct { 87 | ServiceName string 88 | ServiceID string 89 | ServiceComment string 90 | }{} 91 | name := array[0] 92 | { 93 | index := strings.IndexAny(name, "-/*") 94 | for index != -1 { 95 | if name[index+1] <= 'z' && name[index+1] >= 'a' { 96 | name = name[0:index] + string(name[index+1]-32) + name[index+2:] 97 | } else { 98 | name = name[0:index] + name[index+1:] 99 | } 100 | index = strings.IndexAny(name, "-/*") 101 | } 102 | name = strings.ReplaceAll(name, ".", "Dot") 103 | name = strings.ReplaceAll(name, "+", "Plus") 104 | name = strings.Title(name) 105 | _, ok := names[name] 106 | if !ok { 107 | names[name] = true 108 | } else { 109 | continue 110 | } 111 | } 112 | 113 | id := array[0] 114 | comment := "" 115 | 116 | index := 0 117 | for i, elem := range array { 118 | if elem == "#" { 119 | index = i 120 | break 121 | } 122 | } 123 | if index+1 < len(array) { 124 | comment = strings.Join(array[index+1:], " ") 125 | } 126 | tmp.ServiceName = name 127 | tmp.ServiceID = id 128 | tmp.ServiceComment = comment 129 | if err := serviceTypeTpl.Execute(buf, tmp); err != nil { 130 | fmt.Printf("tpl execute err: %s\n", err) 131 | continue 132 | } 133 | } 134 | fmt.Fprintln(buf, ")") 135 | } 136 | 137 | // variables 138 | fmt.Fprintln(buf, "var (") 139 | fmt.Fprintln(buf, "ServicePortProtoMaps = map[Service][]PortProto{") 140 | 141 | type PortProto struct { 142 | Port int 143 | Protocol int 144 | } 145 | maps := map[string][]PortProto{} 146 | for _, array := range arrays { 147 | if len(array) == 0 { 148 | continue 149 | } 150 | name := array[0] 151 | { 152 | index := strings.IndexAny(name, "-/*") 153 | for index != -1 { 154 | if name[index+1] <= 'z' && name[index+1] >= 'a' { 155 | name = name[0:index] + string(name[index+1]-32) + name[index+2:] 156 | } else { 157 | name = name[0:index] + name[index+1:] 158 | } 159 | index = strings.IndexAny(name, "-/*") 160 | } 161 | name = strings.ReplaceAll(name, ".", "Dot") 162 | name = strings.ReplaceAll(name, "+", "Plus") 163 | name = strings.Title(name) 164 | } 165 | portproto := strings.Split(array[1], "/") 166 | port, err := strconv.Atoi(portproto[0]) 167 | if err != nil { 168 | fmt.Printf("conv port err: %s\n", err) 169 | continue 170 | } 171 | proto := 6 172 | switch portproto[1] { 173 | case "udp": 174 | proto = 17 175 | case "sctp": 176 | proto = 132 177 | case "dccp": 178 | proto = 33 179 | } 180 | 181 | pps, ok := maps[name] 182 | if !ok { 183 | maps[name] = []PortProto{PortProto{port, proto}} 184 | } else { 185 | pps = append(pps, PortProto{port, proto}) 186 | maps[name] = pps 187 | } 188 | } 189 | keys := make([]string, len(maps)) 190 | cursor := 0 191 | for name, _ := range maps { 192 | keys[cursor] = name 193 | cursor++ 194 | } 195 | sort.Strings(keys) 196 | 197 | for _, name := range keys { 198 | tmp := struct { 199 | ServiceName string 200 | }{name} 201 | err := servicePortProtoKeyTpl.Execute(buf, tmp) 202 | if err != nil { 203 | fmt.Println("execute key err:", err) 204 | continue 205 | } 206 | pps, _ := maps[name] 207 | err = servicePortProtoValueTpl.Execute(buf, pps) 208 | if err != nil { 209 | fmt.Println("execute value err:", err) 210 | continue 211 | } 212 | } 213 | 214 | fmt.Fprintln(buf, "}") 215 | fmt.Fprintln(buf, ")") 216 | 217 | if *serviceTypeMaps { 218 | fmt.Fprintln(buf, "var (") 219 | fmt.Fprintln(buf, "ServiceType = map[PortProto]Service{") 220 | for _, array := range arrays { 221 | if len(array) == 0 { 222 | continue 223 | } 224 | name := array[0] 225 | { 226 | index := strings.IndexAny(name, "-/*") 227 | for index != -1 { 228 | if name[index+1] <= 'z' && name[index+1] >= 'a' { 229 | name = name[0:index] + string(name[index+1]-32) + name[index+2:] 230 | } else { 231 | name = name[0:index] + name[index+1:] 232 | } 233 | index = strings.IndexAny(name, "-/*") 234 | } 235 | name = strings.ReplaceAll(name, ".", "Dot") 236 | name = strings.ReplaceAll(name, "+", "Plus") 237 | name = strings.Title(name) 238 | } 239 | portproto := strings.Split(array[1], "/") 240 | port, err := strconv.Atoi(portproto[0]) 241 | if err != nil { 242 | fmt.Printf("conv port err: %s\n", err) 243 | continue 244 | } 245 | proto := 6 246 | switch portproto[1] { 247 | case "udp": 248 | proto = 17 249 | case "sctp": 250 | proto = 132 251 | case "dccp": 252 | proto = 33 253 | } 254 | 255 | tmp := struct { 256 | ServiceName string 257 | Port int 258 | Protocol int 259 | }{} 260 | 261 | tmp.ServiceName = name 262 | tmp.Port = port 263 | tmp.Protocol = proto 264 | if err := serviceTypeMapTpl.Execute(buf, tmp); err != nil { 265 | fmt.Printf("tpl execute err: %s\n", err) 266 | continue 267 | } 268 | } 269 | fmt.Fprintln(buf, "}") 270 | fmt.Fprintln(buf, ")") 271 | } 272 | 273 | // end 274 | buf.WriteTo(writer) 275 | } 276 | -------------------------------------------------------------------------------- /ebtables/parser.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/singchia/go-xtables" 11 | "github.com/singchia/go-xtables/internal/xutil" 12 | ) 13 | 14 | type onTableLine func(line []byte) (TableType, error) 15 | type onChainLine func(line []byte) (*Chain, error) 16 | type onRuleLine func(rule []byte, chain *Chain) (*Rule, error) 17 | 18 | func (ebtables *EBTables) parse(data []byte, onTableLine onTableLine, 19 | onChainLine onChainLine, onRuleLine onRuleLine) ( 20 | []*Chain, []*Rule, error) { 21 | 22 | chains := []*Chain{} 23 | rules := []*Rule{} 24 | 25 | buf := bytes.NewBuffer(data) 26 | scanner := bufio.NewScanner(buf) 27 | 28 | var tableType TableType 29 | var chain *Chain 30 | var err error 31 | var index int // index in current chain 32 | 33 | for scanner.Scan() { 34 | line := scanner.Bytes() 35 | if index == 0 { 36 | if bytes.HasPrefix(line, []byte("Bridge table")) { 37 | if onTableLine == nil { 38 | continue 39 | } 40 | tableType, err = onTableLine(line) 41 | if err != nil { 42 | ebtables.log.Errorf("parse table line err: %s", err) 43 | return nil, nil, err 44 | } 45 | } else if bytes.HasPrefix(line, []byte("Bridge chain")) { 46 | if onChainLine == nil { 47 | continue 48 | } 49 | chain, err = onChainLine(line) 50 | if err != nil { 51 | ebtables.log.Errorf("parse chain line err: %s", err) 52 | return nil, nil, err 53 | } 54 | chain.tableType = tableType 55 | chains = append(chains, chain) 56 | } 57 | } else { 58 | // rule or EOC(end of chain) 59 | if len(line) == 0 { 60 | index = 0 61 | continue 62 | } 63 | if onRuleLine == nil { 64 | continue 65 | } 66 | rule, err := onRuleLine(line, chain) 67 | if err != nil { 68 | ebtables.log.Errorf("parse rule line err: %s", err) 69 | return nil, nil, err 70 | } 71 | rule.tableType = tableType 72 | rules = append(rules, rule) 73 | } 74 | index++ 75 | } 76 | return chains, rules, nil 77 | } 78 | 79 | func (ebtables *EBTables) parseTable(line []byte) (TableType, error) { 80 | buf := bytes.NewBuffer(line) 81 | _, err := buf.ReadString(':') 82 | if err != nil { 83 | return TableTypeNull, err 84 | } 85 | 86 | table := bytes.TrimFunc(buf.Bytes(), func(r rune) bool { 87 | if r == ' ' || r == '\n' { 88 | return true 89 | } 90 | return false 91 | }) 92 | 93 | switch string(table) { 94 | case "nat": 95 | return TableTypeNat, nil 96 | case "filter": 97 | return TableTypeFilter, nil 98 | case "broute": 99 | return TableTypeBRoute, nil 100 | } 101 | return TableTypeNull, nil 102 | } 103 | 104 | func (ebtables *EBTables) parseChain(line []byte) (*Chain, error) { 105 | chain := &Chain{} 106 | buf := bytes.NewBuffer(line) 107 | _, err := buf.ReadString(':') 108 | if err != nil { 109 | ebtables.log.Errorf("parse chain read to first space err: %s", err) 110 | return nil, err 111 | } 112 | 113 | chain.chainType.name, err = buf.ReadString(',') 114 | if err != nil { 115 | ebtables.log.Errorf("parse chain read to second space err: %s", err) 116 | return nil, err 117 | } 118 | chain.chainType.name = strings.TrimSpace(chain.chainType.name[:len(chain.chainType.name)-1]) 119 | switch chain.chainType.name { 120 | case "INPUT": 121 | chain.chainType = ChainTypeINPUT 122 | case "FORWARD": 123 | chain.chainType = ChainTypeFORWARD 124 | case "OUTPUT": 125 | chain.chainType = ChainTypeOUTPUT 126 | case "PREROUTING": 127 | chain.chainType = ChainTypePREROUTING 128 | case "BROUTING": 129 | chain.chainType = ChainTypeBROUTING 130 | case "POSTROUTING": 131 | chain.chainType = ChainTypePOSTROUTING 132 | default: 133 | userDefined := ChainTypeUserDefined 134 | userDefined.name = chain.chainType.name 135 | chain.chainType = userDefined 136 | } 137 | 138 | rest := buf.Bytes() 139 | attrs := bytes.FieldsFunc(rest, func(r rune) bool { 140 | if r == ',' || r == ' ' { 141 | return true 142 | } 143 | return false 144 | }) 145 | if len(attrs)%2 != 0 { 146 | return nil, xtables.ErrChainAttrsNotRecognized 147 | } 148 | 149 | chain.policy = newTargetAccept() 150 | 151 | pairs := len(attrs) / 2 152 | for i := 0; i < pairs; i++ { 153 | index := i * 2 154 | first := attrs[index] 155 | second := attrs[index+1] 156 | 157 | // entries 158 | if bytes.HasPrefix(bytes.TrimSpace(first), []byte("entries")) { 159 | num, err := strconv.Atoi(string(second)) 160 | if err != nil { 161 | return nil, err 162 | } 163 | chain.entries = num 164 | } 165 | 166 | // policy 167 | if bytes.HasPrefix(bytes.TrimSpace(first), []byte("policy")) { 168 | switch string(second) { 169 | case "ACCEPT": 170 | case "DROP": 171 | chain.policy = newTargetDrop() 172 | case "RETURN": 173 | chain.policy = newTargetReturn() 174 | } 175 | } 176 | } 177 | return chain, nil 178 | } 179 | 180 | func (ebtables *EBTables) parseRule(line []byte, chain *Chain) (*Rule, error) { 181 | rule := &Rule{ 182 | chain: chain, 183 | matchMap: map[MatchType]Match{}, 184 | optionMap: map[OptionType]Option{}, 185 | watcherMap: map[WatcherType]Watcher{}, 186 | lineNumber: -1, 187 | } 188 | delimiter := []byte{'.', ' '} 189 | index := bytes.Index(line, delimiter) 190 | if index > 0 && index < len(line) { 191 | ln, err := strconv.Atoi(strings.TrimSpace(string(line[:index]))) 192 | if err == nil { 193 | rule.lineNumber = ln 194 | line = line[index+len(delimiter):] 195 | } 196 | } 197 | // then matches 198 | matches, index, err := ebtables.parseMatch(line) 199 | if err != nil { 200 | ebtables.log.Errorf("parse match: %s err: %s", string(line), err) 201 | return nil, err 202 | } 203 | for _, match := range matches { 204 | rule.matchMap[match.Type()] = match 205 | } 206 | line = line[index:] 207 | 208 | // watcher 209 | watchers, index, err := ebtables.parseWatcher(line) 210 | if err != nil { 211 | ebtables.log.Errorf("parse watcher: %s err: %s", string(line), err) 212 | return nil, err 213 | } 214 | for _, watcher := range watchers { 215 | rule.watcherMap[watcher.Type()] = watcher 216 | } 217 | line = line[index:] 218 | 219 | // then target 220 | target, index, err := ebtables.parseTarget(line) 221 | if err != nil { 222 | if err != xtables.ErrTargetNotFound { 223 | ebtables.log.Errorf("parse target: %s err: %s", string(line), err) 224 | return nil, err 225 | } 226 | rule.target = nil 227 | } else { 228 | rule.target = target 229 | line = line[index:] 230 | } 231 | 232 | // then pkt and bytes count 233 | pcnt, bcnt, ok := parsePktsAndBytes(line) 234 | if ok { 235 | opt, _ := newOptionCounters(pcnt, bcnt) 236 | rule.optionMap[opt.Type()] = opt 237 | rule.packetCounter = pcnt 238 | rule.byteCounter = bcnt 239 | } 240 | 241 | return rule, nil 242 | } 243 | 244 | func parsePktsAndBytes(params []byte) (int64, int64, bool) { 245 | pattern := `,? *pcnt = ([0-9A-Za-z]+) -- bcnt = ([0-9A-Za-z]+) *` 246 | reg := regexp.MustCompile(pattern) 247 | matches := reg.FindSubmatch(params) 248 | if len(matches) != 3 { 249 | return 0, 0, false 250 | } 251 | pcnt, err := xutil.UnfoldDecimal(string(matches[1])) 252 | if err != nil { 253 | return 0, 0, false 254 | } 255 | bcnt, err := xutil.UnfoldDecimal(string(matches[2])) 256 | if err != nil { 257 | return 0, 0, false 258 | } 259 | return pcnt, bcnt, true 260 | } 261 | -------------------------------------------------------------------------------- /pkg/network/ethernet.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // refer to github.com/google/gopacket and /etc/ethertypes 9 | type EthernetType uint16 10 | 11 | func (ethernetType EthernetType) String() string { 12 | return fmt.Sprintf("0x%04x", uint16(ethernetType)) 13 | } 14 | 15 | const ( 16 | // EthernetTypeLLC is not an actual ethernet type. It is instead a 17 | // placeholder we use in Ethernet frames that use the 802.3 standard of 18 | // srcmac|dstmac|length|LLC instead of srcmac|dstmac|ethertype. 19 | EthernetTypeLLC EthernetType = 0x0000 20 | EthernetTypeIPv4 EthernetType = 0x0800 21 | EthernetTypeX25 EthernetType = 0x0805 22 | EthernetTypeARP EthernetType = 0x0806 23 | EthernetTypeFR_ARP EthernetType = 0x0808 // Frame Relay ARP [RFC1701] 24 | EthernetTypeBPQ EthernetType = 0x08ff // G8BPQ AX.25 Ethernet Packet 25 | EthernetTypeDEC EthernetType = 0x6000 // DEC Assigned proto 26 | EthernetTypeDNA_DL EthernetType = 0x6001 // DEC DNA Dump/Load 27 | EthernetTypeDNA_RC EthernetType = 0x6002 // DEC DNA Remote Console 28 | EthernetTypeDNA_RT EthernetType = 0x6003 // DEC DNA Routing 29 | EthernetTypeLAT EthernetType = 0x6004 // DEC LAT 30 | EthernetTypeDIAG EthernetType = 0x6005 // DEC Diagnostics 31 | EthernetTypeCUST EthernetType = 0x6006 // DEC Customer use 32 | EthernetTypeSCA EthernetType = 0x6007 // DEC Systems Comms Arch 33 | EthernetTypeRAW_FR EthernetType = 0x6559 // Raw Frame Relay [RFC1701] 34 | EthernetTypeRARP EthernetType = 0x8035 // RARP 35 | EthernetTypeATALK EthernetType = 0x808b // Appletalk 36 | EthernetTypeAARP EthernetType = 0x80f3 // Appletalk AARP 37 | EthernetType802_1Q EthernetType = 0x8100 // 802.1Q Virtual LAN tagged frame 38 | EthernetTypeIPX EthernetType = 0x8137 // Novell IPX 39 | EthernetTypeNetBEUI EthernetType = 0x8191 40 | EthernetTypeIPv6 EthernetType = 0x86dd 41 | EthernetTypeCiscoDiscovery EthernetType = 0x2000 42 | EthernetTypeNortelDiscovery EthernetType = 0x01a2 43 | EthernetTypeTransparentEthernetBridging EthernetType = 0x6558 44 | EthernetTypePPP EthernetType = 0x880b 45 | EthernetTypeMPLS EthernetType = 0x8847 46 | EthernetTypeMPLS_UNICAST EthernetType = 0x8847 47 | EthernetTypeMPLSUnicast EthernetType = 0x8847 48 | EthernetTypeMPLSMulticast EthernetType = 0x8848 49 | EthernetTypeMPLS_MULTI EthernetType = 0x8848 50 | EthernetTypeATMMPOA EthernetType = 0x884c 51 | EthernetTypePPP_DISC EthernetType = 0x8863 52 | EthernetTypePPPoEDiscovery EthernetType = 0x8863 // PPPoE discoverty messages 53 | EthernetTypePPP_SES EthernetType = 0x8864 54 | EthernetTypePPPoESession EthernetType = 0x8864 // PPPoE session messages 55 | EthernetTypeATMFATE EthernetType = 0x8884 // Frame-based ATM Transport over Ethernet 56 | EthernetTypeEAPOL EthernetType = 0x888e 57 | EthernetTypeERSPAN EthernetType = 0x88be 58 | EthernetTypeS_TAG EthernetType = 0x88a8 59 | EthernetTypeQinQ EthernetType = 0x88a8 60 | EthernetTypeEAP_PREAUTH EthernetType = 0x88c7 61 | EthernetTypeLLDP EthernetType = 0x88cc 62 | EthernetTypeLinkLayerDiscovery EthernetType = 0x88cc 63 | EthernetTypeMACSEC EthernetType = 0x88e5 64 | EthernetTypePBB EthernetType = 0x88e7 65 | EthernetTypeMVRP EthernetType = 0x88f5 66 | EthernetTypePTP EthernetType = 0x88f7 67 | EthernetTypeFCOE EthernetType = 0x8906 68 | EthernetTypeFIP EthernetType = 0x8914 69 | EthernetTypeROCE EthernetType = 0x8915 70 | EthernetTypeEthernetCTP EthernetType = 0x9000 71 | ) 72 | 73 | var ( 74 | EthernetTypes = map[string]EthernetType{ 75 | "IPv4": EthernetTypeIPv4, 76 | "X25": EthernetTypeX25, 77 | "ARP": EthernetTypeARP, 78 | "FR_ARP": EthernetTypeFR_ARP, 79 | "BPQ": EthernetTypeBPQ, 80 | "DEC": EthernetTypeDEC, 81 | "DNA_DL": EthernetTypeDNA_DL, 82 | "DNA_RC": EthernetTypeDNA_RC, 83 | "DNA_RT": EthernetTypeDNA_RT, 84 | "LAT": EthernetTypeLAT, 85 | "DIAG": EthernetTypeDIAG, 86 | "CUST": EthernetTypeCUST, 87 | "SCA": EthernetTypeSCA, 88 | "RAW_FR": EthernetTypeRAW_FR, 89 | "RARP": EthernetTypeRARP, 90 | "ATALK": EthernetTypeATALK, 91 | "802_1Q": EthernetType802_1Q, 92 | "IPX": EthernetTypeIPX, 93 | "NetBEUI": EthernetTypeNetBEUI, 94 | "IPv6": EthernetTypeIPv6, 95 | "CiscoDiscovery": EthernetTypeCiscoDiscovery, 96 | "NortelDiscovery": EthernetTypeNortelDiscovery, 97 | "TransparentEthernetBridging": EthernetTypeTransparentEthernetBridging, 98 | "PPP": EthernetTypePPP, 99 | "MPLS": EthernetTypeMPLS, 100 | "MPLS_UNICAST": EthernetTypeMPLS_UNICAST, 101 | "MPLSMulticast": EthernetTypeMPLSMulticast, 102 | "MPLS_MULTI": EthernetTypeMPLS_MULTI, 103 | "ATMMPOA": EthernetTypeATMMPOA, 104 | "PPP_DISC": EthernetTypePPP_DISC, 105 | "PPPoeDiscovery": EthernetTypePPPoEDiscovery, 106 | "PPP_SES": EthernetTypePPP_SES, 107 | "PPPoeSession": EthernetTypePPPoESession, 108 | "ATMFATE": EthernetTypeATMFATE, 109 | "EAPOL": EthernetTypeEAPOL, 110 | "ERSPAN": EthernetTypeERSPAN, 111 | "S_TAG": EthernetTypeS_TAG, 112 | "QinQ": EthernetTypeQinQ, 113 | "EAP_PREAUTH": EthernetTypeEAP_PREAUTH, 114 | "LLDP": EthernetTypeLinkLayerDiscovery, 115 | "MACSEC": EthernetTypeMACSEC, 116 | "PBB": EthernetTypePBB, 117 | "MVRP": EthernetTypeMVRP, 118 | "PTP": EthernetTypePTP, 119 | "FCOE": EthernetTypeFCOE, 120 | "FIP": EthernetTypeFIP, 121 | "ROCE": EthernetTypeROCE, 122 | "EthernetTCP": EthernetTypeEthernetCTP, 123 | } 124 | ) 125 | 126 | func ParseEthernetType(etype string) (EthernetType, error) { 127 | typ, err := strconv.ParseUint(etype, 10, 16) 128 | if err == nil { 129 | return EthernetType(typ), nil 130 | } 131 | value, ok := EthernetTypes[etype] 132 | if ok { 133 | return value, nil 134 | } 135 | return 0, err 136 | } 137 | -------------------------------------------------------------------------------- /ebtables/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Apache License 2.0 3 | * 4 | * Copyright (c) 2022, Austin Zhai 5 | * All rights reserved. 6 | */ 7 | package ebtables 8 | 9 | import ( 10 | "fmt" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type OptionType int 16 | 17 | func (ot OptionType) Type() string { 18 | return "OptionType" 19 | } 20 | 21 | func (ot OptionType) Value() string { 22 | return strconv.Itoa(int(ot)) 23 | } 24 | 25 | const ( 26 | _ OptionType = iota 27 | OptionTypeConcurrent 28 | OptionTypeListNumbers 29 | OptionTypeListCounters 30 | OptionTypeListChange 31 | OptionTypeListMACSameLength 32 | OptionTypeModprobe 33 | OptionTypeCounters 34 | OptionTypeAtomicFile 35 | ) 36 | 37 | type Option interface { 38 | Type() OptionType 39 | Short() string 40 | ShortArgs() []string 41 | Long() string 42 | LongArgs() []string 43 | Equal(Option) bool 44 | } 45 | 46 | type baseOption struct { 47 | optionType OptionType 48 | child Option 49 | invert bool 50 | } 51 | 52 | func (bo *baseOption) setChild(child Option) { 53 | bo.child = child 54 | } 55 | 56 | func (bo *baseOption) Type() OptionType { 57 | return bo.optionType 58 | } 59 | 60 | func (bo *baseOption) Short() string { 61 | if bo.child != nil { 62 | return bo.child.Short() 63 | } 64 | return "" 65 | } 66 | 67 | func (bo *baseOption) ShortArgs() []string { 68 | if bo.child != nil { 69 | return bo.child.ShortArgs() 70 | } 71 | return nil 72 | } 73 | 74 | func (bo *baseOption) Long() string { 75 | return bo.Short() 76 | } 77 | 78 | func (bo *baseOption) LongArgs() []string { 79 | return bo.ShortArgs() 80 | } 81 | 82 | func (bo *baseOption) Equal(opt Option) bool { 83 | if bo.child != nil { 84 | return bo.child.Short() == opt.Short() 85 | } 86 | return false 87 | } 88 | 89 | // Use a file lock to support concurrent scripts updating the 90 | // ebtables kernel tables. 91 | type OptionConcurrent struct { 92 | *baseOption 93 | } 94 | 95 | func newOptionConcurrent() (*OptionConcurrent, error) { 96 | option := &OptionConcurrent{ 97 | baseOption: &baseOption{ 98 | optionType: OptionTypeConcurrent, 99 | }, 100 | } 101 | option.setChild(option) 102 | return option, nil 103 | } 104 | 105 | func (opt *OptionConcurrent) Short() string { 106 | return strings.Join(opt.ShortArgs(), " ") 107 | } 108 | 109 | func (opt *OptionConcurrent) ShortArgs() []string { 110 | return []string{"--concurrent"} 111 | } 112 | 113 | // Must be shown with List command. 114 | type OptionListNumbers struct { 115 | *baseOption 116 | } 117 | 118 | func newOptionListNumbers() (*OptionListNumbers, error) { 119 | option := &OptionListNumbers{ 120 | baseOption: &baseOption{ 121 | optionType: OptionTypeListNumbers, 122 | }, 123 | } 124 | option.setChild(option) 125 | return option, nil 126 | } 127 | 128 | func (opt *OptionListNumbers) Short() string { 129 | return strings.Join(opt.ShortArgs(), " ") 130 | } 131 | 132 | func (opt *OptionListNumbers) ShortArgs() []string { 133 | return []string{"--Ln"} 134 | } 135 | 136 | // Shows the counters. 137 | type OptionListCounters struct { 138 | *baseOption 139 | } 140 | 141 | func newOptionListCounters() (*OptionListCounters, error) { 142 | option := &OptionListCounters{ 143 | baseOption: &baseOption{ 144 | optionType: OptionTypeListCounters, 145 | }, 146 | } 147 | option.setChild(option) 148 | return option, nil 149 | } 150 | 151 | func (opt *OptionListCounters) Short() string { 152 | return strings.Join(opt.ShortArgs(), " ") 153 | } 154 | 155 | func (opt *OptionListCounters) ShortArgs() []string { 156 | return []string{"--Lc"} 157 | } 158 | 159 | // Changes the output so that it produces a set of ebtables commands that 160 | // construct the contents of the chain. 161 | type OptionListChange struct { 162 | *baseOption 163 | } 164 | 165 | func newOptionListChange() (*OptionListChange, error) { 166 | option := &OptionListChange{ 167 | baseOption: &baseOption{ 168 | optionType: OptionTypeListChange, 169 | }, 170 | } 171 | option.setChild(option) 172 | return option, nil 173 | } 174 | 175 | func (opt *OptionListChange) ShortArgs() []string { 176 | return []string{"--Lx"} 177 | } 178 | 179 | func (opt *OptionListChange) Short() string { 180 | return strings.Join(opt.ShortArgs(), " ") 181 | } 182 | 183 | // Shows all MAC addresses with the same length. 184 | type OptionListMACSameLength struct { 185 | *baseOption 186 | } 187 | 188 | func newOptionListMACSameLength() (*OptionListMACSameLength, error) { 189 | option := &OptionListMACSameLength{ 190 | baseOption: &baseOption{ 191 | optionType: OptionTypeListMACSameLength, 192 | }, 193 | } 194 | option.setChild(option) 195 | return option, nil 196 | } 197 | 198 | func (opt *OptionListMACSameLength) Short() string { 199 | return strings.Join(opt.ShortArgs(), " ") 200 | } 201 | 202 | func (opt *OptionListMACSameLength) ShortArgs() []string { 203 | return []string{"--Lmac2"} 204 | } 205 | 206 | // When talking to the kernel, use this program to try to automatically 207 | // load missing kernel modules. 208 | type OptionModprobe struct { 209 | *baseOption 210 | program string 211 | } 212 | 213 | func newOptionModprobe(program string) (*OptionModprobe, error) { 214 | option := &OptionModprobe{ 215 | baseOption: &baseOption{ 216 | optionType: OptionTypeModprobe, 217 | }, 218 | } 219 | option.setChild(option) 220 | return option, nil 221 | } 222 | 223 | func (opt *OptionModprobe) Short() string { 224 | return strings.Join(opt.ShortArgs(), " ") 225 | } 226 | 227 | func (opt *OptionModprobe) ShortArgs() []string { 228 | return []string{"--modprobe", opt.program} 229 | } 230 | 231 | // If used with Append or Insert, then the packet and byte counters of 232 | // the new rule will be set to packets, resp. bytes. If used with the 233 | // Check or Delete commands, only rules with a packet and byte count 234 | // queal to packets, resp. bytes will match. 235 | type OptionCounters struct { 236 | *baseOption 237 | packets int64 238 | bytes int64 239 | } 240 | 241 | func newOptionCounters(packets, bytes int64) (*OptionCounters, error) { 242 | option := &OptionCounters{ 243 | baseOption: &baseOption{ 244 | optionType: OptionTypeCounters, 245 | }, 246 | packets: packets, 247 | bytes: bytes, 248 | } 249 | option.setChild(option) 250 | return option, nil 251 | } 252 | 253 | func (opt *OptionCounters) Short() string { 254 | return fmt.Sprintf("-c %d %d", opt.packets, opt.bytes) 255 | } 256 | 257 | func (opt *OptionCounters) ShortArgs() []string { 258 | return []string{"-c", 259 | strconv.FormatInt(opt.packets, 10), 260 | strconv.FormatInt(opt.bytes, 10), 261 | } 262 | } 263 | 264 | func (opt *OptionCounters) Long() string { 265 | return fmt.Sprintf("--set-counters %d %d", opt.packets, opt.bytes) 266 | } 267 | 268 | func (opt *OptionCounters) LongArgs() []string { 269 | return []string{"--set-counters", 270 | strconv.FormatInt(opt.packets, 10), 271 | strconv.FormatInt(opt.bytes, 10), 272 | } 273 | } 274 | 275 | // Let the command operate on the specified file. The data of the table 276 | // to operate on will be extracted from the file and the result of the 277 | // operation will be saved back into the file. 278 | type OptionAtomicFile struct { 279 | *baseOption 280 | path string 281 | } 282 | 283 | func newOptionAtomicFile(path string) (*OptionAtomicFile, error) { 284 | option := &OptionAtomicFile{ 285 | baseOption: &baseOption{ 286 | optionType: OptionTypeAtomicFile, 287 | }, 288 | path: path, 289 | } 290 | option.setChild(option) 291 | return option, nil 292 | } 293 | 294 | func (opt *OptionAtomicFile) Short() string { 295 | return strings.Join(opt.ShortArgs(), " ") 296 | } 297 | 298 | func (opt *OptionAtomicFile) ShortArgs() []string { 299 | return []string{"--atomic-file", opt.path} 300 | } 301 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Apache License 2.0 3 | * 4 | * Copyright (c) 2022, Austin Zhai 5 | * All rights reserved. 6 | */ 7 | package log 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io" 13 | "log" 14 | "os" 15 | "strings" 16 | "sync" 17 | ) 18 | 19 | var ( 20 | DefaultLog *Log 21 | 22 | ErrUnsupportedLogLevel = errors.New("unsupported log level") 23 | ) 24 | 25 | type Logger interface { 26 | Trace(v ...interface{}) 27 | Tracef(format string, v ...interface{}) 28 | Debug(v ...interface{}) 29 | Debugf(format string, v ...interface{}) 30 | Info(v ...interface{}) 31 | Infof(format string, v ...interface{}) 32 | Warn(v ...interface{}) 33 | Warnf(format string, v ...interface{}) 34 | Error(v ...interface{}) 35 | Errorf(format string, v ...interface{}) 36 | } 37 | 38 | type Level int 39 | 40 | const ( 41 | LevelNull Level = 0 42 | LevelTrace Level = 1 43 | LevelDebug Level = 2 44 | LevelInfo Level = 3 45 | LevelWarn Level = 4 46 | LevelError Level = 5 47 | LevelFatal Level = 6 48 | 49 | traceS = "TRACE" 50 | debugS = "DEBUG" 51 | infoS = "INFO" 52 | warnS = "WARN" 53 | errorS = "ERROR" 54 | fatalS = "FATAL" 55 | ) 56 | 57 | var ( 58 | levelStrings = map[Level]string{ 59 | LevelTrace: traceS, 60 | LevelDebug: debugS, 61 | LevelInfo: infoS, 62 | LevelWarn: warnS, 63 | LevelError: errorS, 64 | LevelFatal: fatalS, 65 | } 66 | 67 | levelInts = map[string]Level{ 68 | traceS: LevelTrace, 69 | debugS: LevelDebug, 70 | infoS: LevelInfo, 71 | warnS: LevelWarn, 72 | errorS: LevelError, 73 | fatalS: LevelFatal, 74 | } 75 | ) 76 | 77 | func ParseLevel(levelS string) (Level, error) { 78 | level, ok := levelInts[strings.ToUpper(levelS)] 79 | if !ok { 80 | return LevelNull, ErrUnsupportedLogLevel 81 | } 82 | return level, nil 83 | } 84 | 85 | type Log struct { 86 | logger *log.Logger 87 | level Level 88 | mu sync.RWMutex 89 | } 90 | 91 | func init() { 92 | DefaultLog = NewLog() 93 | } 94 | 95 | func WithLevel(level Level) *Log { 96 | DefaultLog.mu.Lock() 97 | defer DefaultLog.mu.Unlock() 98 | DefaultLog.level = level 99 | return DefaultLog 100 | } 101 | 102 | func WithOutput(out io.Writer) *Log { 103 | DefaultLog.logger.SetOutput(out) 104 | return DefaultLog 105 | } 106 | 107 | func WithFlags(flag int) *Log { 108 | DefaultLog.logger.SetFlags(flag) 109 | return DefaultLog 110 | } 111 | 112 | func SetOutput(out io.Writer) { 113 | DefaultLog.logger.SetOutput(out) 114 | } 115 | 116 | func SetLevel(level Level) { 117 | DefaultLog.mu.Lock() 118 | defer DefaultLog.mu.Unlock() 119 | DefaultLog.level = level 120 | } 121 | 122 | func SetFlags(flag int) { 123 | DefaultLog.logger.SetFlags(flag) 124 | } 125 | 126 | func Println(level Level, v ...interface{}) { 127 | prefix, _ := levelStrings[level] 128 | prefix = fmt.Sprintf("%-6s", prefix) 129 | DefaultLog.outputln(level, prefix, v...) 130 | } 131 | 132 | func Printf(level Level, format string, v ...interface{}) { 133 | prefix, _ := levelStrings[level] 134 | prefix = fmt.Sprintf("%-6s", prefix) 135 | DefaultLog.outputf(false, level, prefix, format, v...) 136 | } 137 | 138 | func Trace(v ...interface{}) { 139 | prefix := fmt.Sprintf("%-6s", traceS) 140 | DefaultLog.outputln(LevelTrace, prefix, v...) 141 | } 142 | 143 | func Tracef(format string, v ...interface{}) { 144 | prefix := fmt.Sprintf("%-6s", traceS) 145 | DefaultLog.outputf(true, LevelTrace, prefix, format, v...) 146 | } 147 | 148 | func Debug(v ...interface{}) { 149 | prefix := fmt.Sprintf("%-6s", debugS) 150 | DefaultLog.outputln(LevelDebug, prefix, v...) 151 | } 152 | 153 | func Debugf(format string, v ...interface{}) { 154 | prefix := fmt.Sprintf("%-6s", debugS) 155 | DefaultLog.outputf(true, LevelDebug, prefix, format, v...) 156 | } 157 | 158 | func Info(v ...interface{}) { 159 | prefix := fmt.Sprintf("%-6s", infoS) 160 | DefaultLog.outputln(LevelInfo, prefix, v...) 161 | } 162 | 163 | func Infof(format string, v ...interface{}) { 164 | prefix := fmt.Sprintf("%-6s", infoS) 165 | DefaultLog.outputf(true, LevelInfo, prefix, format, v...) 166 | } 167 | 168 | func Warn(v ...interface{}) { 169 | prefix := fmt.Sprintf("%-6s", warnS) 170 | DefaultLog.outputln(LevelWarn, prefix, v...) 171 | } 172 | 173 | func Warnf(format string, v ...interface{}) { 174 | prefix := fmt.Sprintf("%-6s", warnS) 175 | DefaultLog.outputf(true, LevelWarn, prefix, format, v...) 176 | } 177 | 178 | func Error(v ...interface{}) { 179 | prefix := fmt.Sprintf("%-6s", errorS) 180 | DefaultLog.outputln(LevelError, prefix, v...) 181 | } 182 | 183 | func Errorf(format string, v ...interface{}) { 184 | prefix := fmt.Sprintf("%-6s", errorS) 185 | DefaultLog.outputf(true, LevelError, prefix, format, v...) 186 | } 187 | 188 | func Fatal(v ...interface{}) { 189 | prefix := fmt.Sprintf("%-6s", fatalS) 190 | DefaultLog.outputln(LevelFatal, prefix, v...) 191 | } 192 | 193 | func Fatalf(format string, v ...interface{}) { 194 | prefix := fmt.Sprintf("%-6s", fatalS) 195 | DefaultLog.outputf(true, LevelFatal, prefix, format, v...) 196 | } 197 | 198 | func NewLog() *Log { 199 | logger := log.New(os.Stdout, "", log.LstdFlags) 200 | return &Log{ 201 | logger: logger, 202 | level: LevelTrace, 203 | } 204 | } 205 | 206 | func (log *Log) WithLevel(level Level) *Log { 207 | log.mu.Lock() 208 | defer log.mu.Unlock() 209 | log.level = level 210 | return log 211 | } 212 | 213 | func (log *Log) WithOutput(out io.Writer) *Log { 214 | log.logger.SetOutput(out) 215 | return log 216 | } 217 | 218 | func (log *Log) WithFlags(flag int) *Log { 219 | log.logger.SetFlags(flag) 220 | return log 221 | } 222 | 223 | func (log *Log) SetOutput(out io.Writer) { 224 | log.logger.SetOutput(out) 225 | return 226 | } 227 | 228 | func (log *Log) SetLevel(level Level) { 229 | log.mu.Lock() 230 | defer log.mu.Unlock() 231 | log.level = level 232 | return 233 | } 234 | 235 | func (log *Log) SetFlags(flag int) { 236 | log.logger.SetFlags(flag) 237 | return 238 | } 239 | 240 | func (log *Log) Println(level Level, v ...interface{}) { 241 | prefix, _ := levelStrings[level] 242 | prefix = fmt.Sprintf("%-6s", prefix) 243 | log.outputln(level, prefix, v...) 244 | } 245 | 246 | func (log *Log) Printf(level Level, format string, v ...interface{}) { 247 | prefix, _ := levelStrings[level] 248 | prefix = fmt.Sprintf("%-6s", prefix) 249 | log.outputf(false, level, prefix, format, v...) 250 | } 251 | 252 | func (log *Log) Trace(v ...interface{}) { 253 | prefix := fmt.Sprintf("%-6s", traceS) 254 | log.outputln(LevelTrace, prefix, v...) 255 | } 256 | 257 | func (log *Log) Tracef(format string, v ...interface{}) { 258 | prefix := fmt.Sprintf("%-6s", traceS) 259 | log.outputf(true, LevelTrace, prefix, format, v...) 260 | } 261 | 262 | func (log *Log) Debug(v ...interface{}) { 263 | prefix := fmt.Sprintf("%-6s", debugS) 264 | log.outputln(LevelDebug, prefix, v...) 265 | } 266 | 267 | func (log *Log) Debugf(format string, v ...interface{}) { 268 | prefix := fmt.Sprintf("%-6s", debugS) 269 | log.outputf(true, LevelDebug, prefix, format, v...) 270 | } 271 | 272 | func (log *Log) Info(v ...interface{}) { 273 | prefix := fmt.Sprintf("%-6s", infoS) 274 | log.outputln(LevelInfo, prefix, v...) 275 | } 276 | 277 | func (log *Log) Infof(format string, v ...interface{}) { 278 | prefix := fmt.Sprintf("%-6s", infoS) 279 | log.outputf(true, LevelInfo, prefix, format, v...) 280 | } 281 | 282 | func (log *Log) Warn(v ...interface{}) { 283 | prefix := fmt.Sprintf("%-6s", warnS) 284 | log.outputln(LevelWarn, prefix, v...) 285 | } 286 | 287 | func (log *Log) Warnf(format string, v ...interface{}) { 288 | prefix := fmt.Sprintf("%-6s", warnS) 289 | log.outputf(true, LevelWarn, prefix, format, v...) 290 | } 291 | 292 | func (log *Log) Error(v ...interface{}) { 293 | prefix := fmt.Sprintf("%-6s", errorS) 294 | log.outputln(LevelError, prefix, v...) 295 | } 296 | 297 | func (log *Log) Errorf(format string, v ...interface{}) { 298 | prefix := fmt.Sprintf("%-6s", errorS) 299 | log.outputf(true, LevelError, prefix, format, v...) 300 | } 301 | 302 | func (log *Log) Fatal(v ...interface{}) { 303 | prefix := fmt.Sprintf("%-6s", fatalS) 304 | log.outputln(LevelFatal, prefix, v...) 305 | } 306 | 307 | func (log *Log) Fatalf(format string, v ...interface{}) { 308 | prefix := fmt.Sprintf("%-6s", fatalS) 309 | log.outputf(true, LevelFatal, prefix, format, v...) 310 | } 311 | 312 | func (log *Log) outputln(level Level, prefix string, v ...interface{}) { 313 | log.mu.RLock() 314 | defer log.mu.RUnlock() 315 | if level < log.level { 316 | return 317 | } 318 | 319 | line := fmt.Sprintln(v...) 320 | line = prefix + line 321 | log.logger.Output(2, line) 322 | if level == LevelFatal { 323 | os.Exit(1) 324 | } 325 | } 326 | 327 | func (log *Log) outputf(newline bool, level Level, prefix, format string, v ...interface{}) { 328 | log.mu.RLock() 329 | defer log.mu.RUnlock() 330 | if level < log.level { 331 | return 332 | } 333 | 334 | line := fmt.Sprintf(format, v...) 335 | line = prefix + line 336 | if newline { 337 | line += "\n" 338 | } 339 | log.logger.Output(2, line) 340 | if level == LevelFatal { 341 | os.Exit(1) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /iptables/option.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | type OptionType int 9 | 10 | func (ot OptionType) Type() string { 11 | return "OptionType" 12 | } 13 | 14 | func (ot OptionType) Value() string { 15 | return strconv.Itoa(int(ot)) 16 | } 17 | 18 | const ( 19 | OptionTypeFragment OptionType = iota 20 | OptionTypeCounters 21 | OptionTypeVerbose 22 | OptionTypeWait 23 | OptionTypeWaitInterval 24 | OptionTypeNumeric 25 | OptionTypeNotNumeric 26 | OptionTypeExact 27 | OptionTypeLineNumbers 28 | OptionTypeModprobe 29 | ) 30 | 31 | type Option interface { 32 | Type() OptionType 33 | Short() string 34 | ShortArgs() []string 35 | Long() string 36 | LongArgs() []string 37 | Equal(Option) bool 38 | } 39 | 40 | type baseOption struct { 41 | optionType OptionType 42 | child Option 43 | invert bool 44 | } 45 | 46 | func (bo *baseOption) setChild(child Option) { 47 | bo.child = child 48 | } 49 | 50 | func (bo *baseOption) Type() OptionType { 51 | return bo.optionType 52 | } 53 | 54 | func (bo *baseOption) Short() string { 55 | if bo.child != nil { 56 | return bo.child.Short() 57 | } 58 | return "" 59 | } 60 | 61 | func (bo *baseOption) ShortArgs() []string { 62 | if bo.child != nil { 63 | return bo.child.ShortArgs() 64 | } 65 | return nil 66 | } 67 | 68 | func (bo *baseOption) Long() string { 69 | return bo.Short() 70 | } 71 | 72 | func (bo *baseOption) LongArgs() []string { 73 | return bo.ShortArgs() 74 | } 75 | 76 | func (bo *baseOption) Equal(opt Option) bool { 77 | if bo.child != nil { 78 | return bo.child.Short() == opt.Short() 79 | } 80 | return false 81 | } 82 | 83 | type OptionFragment struct { 84 | *baseOption 85 | } 86 | 87 | func newOptionFragment(invert bool) (*OptionFragment, error) { 88 | option := &OptionFragment{ 89 | baseOption: &baseOption{ 90 | optionType: OptionTypeFragment, 91 | invert: invert, 92 | }, 93 | } 94 | option.setChild(option) 95 | return option, nil 96 | } 97 | 98 | func (opt *OptionFragment) Short() string { 99 | if opt.invert { 100 | return "! -f" 101 | } 102 | return "-f" 103 | } 104 | 105 | func (opt *OptionFragment) ShortArgs() []string { 106 | if opt.invert { 107 | return []string{"!", "-f"} 108 | } 109 | return []string{"-f"} 110 | } 111 | 112 | func (opt *OptionFragment) Long() string { 113 | if opt.invert { 114 | return "! --fragment" 115 | } 116 | return "--fragment" 117 | } 118 | 119 | func (opt *OptionFragment) LongArgs() []string { 120 | if opt.invert { 121 | return []string{"!", "--fragment"} 122 | } 123 | return []string{"--fragment"} 124 | } 125 | 126 | func newOptionCounters(packets, bytes uint64) (*OptionCounters, error) { 127 | option := &OptionCounters{ 128 | baseOption: &baseOption{ 129 | optionType: OptionTypeCounters, 130 | }, 131 | packets: packets, 132 | bytes: bytes, 133 | } 134 | option.setChild(option) 135 | return option, nil 136 | } 137 | 138 | type OptionCounters struct { 139 | *baseOption 140 | packets uint64 141 | bytes uint64 142 | } 143 | 144 | func (opt *OptionCounters) Short() string { 145 | return fmt.Sprintf("-c %d %d", opt.packets, opt.bytes) 146 | } 147 | 148 | func (opt *OptionCounters) ShortArgs() []string { 149 | return []string{"-c", 150 | strconv.FormatUint(opt.packets, 10), 151 | strconv.FormatUint(opt.bytes, 10), 152 | } 153 | } 154 | 155 | func (opt *OptionCounters) Long() string { 156 | return fmt.Sprintf("--set-counters %d %d", opt.packets, opt.bytes) 157 | } 158 | 159 | func (opt *OptionCounters) LongArgs() []string { 160 | return []string{"--set-counters", 161 | strconv.FormatUint(opt.packets, 10), 162 | strconv.FormatUint(opt.bytes, 10), 163 | } 164 | } 165 | 166 | func newOptionVerbose() (*OptionVerbose, error) { 167 | option := &OptionVerbose{ 168 | baseOption: &baseOption{ 169 | optionType: OptionTypeVerbose, 170 | }, 171 | } 172 | option.setChild(option) 173 | return option, nil 174 | } 175 | 176 | type OptionVerbose struct { 177 | *baseOption 178 | } 179 | 180 | func (opt *OptionVerbose) Short() string { 181 | return "-v" 182 | } 183 | 184 | func (opt *OptionVerbose) ShortArgs() []string { 185 | return []string{"-v"} 186 | } 187 | 188 | func (opt *OptionVerbose) Long() string { 189 | return "--verbose" 190 | } 191 | 192 | func (opt *OptionVerbose) LongArgs() []string { 193 | return []string{"--verbose"} 194 | } 195 | 196 | func newOptionWait(seconds uint32) (*OptionWait, error) { 197 | option := &OptionWait{ 198 | baseOption: &baseOption{ 199 | optionType: OptionTypeWait, 200 | }, 201 | seconds: seconds, 202 | } 203 | option.setChild(option) 204 | return option, nil 205 | } 206 | 207 | type OptionWait struct { 208 | *baseOption 209 | seconds uint32 210 | } 211 | 212 | func (opt *OptionWait) Short() string { 213 | if opt.seconds == 0 { 214 | // indefinitely 215 | return "-w" 216 | } 217 | return fmt.Sprintf("-w %d", opt.seconds) 218 | } 219 | 220 | func (opt *OptionWait) ShortArgs() []string { 221 | if opt.seconds == 0 { 222 | // indefinitely 223 | return []string{"-w"} 224 | } 225 | return []string{"-w", strconv.FormatUint(uint64(opt.seconds), 10)} 226 | } 227 | 228 | func (opt *OptionWait) Long() string { 229 | if opt.seconds == 0 { 230 | // indefinitely 231 | return "--wait" 232 | } 233 | return fmt.Sprintf("--wait %d", opt.seconds) 234 | } 235 | 236 | func (opt *OptionWait) LongArgs() []string { 237 | if opt.seconds == 0 { 238 | // indefinitely 239 | return []string{"--wait"} 240 | } 241 | return []string{"--wait", strconv.FormatUint(uint64(opt.seconds), 10)} 242 | } 243 | 244 | func newOptionWaitInterval(microseconds uint64) (*OptionWaitInterval, error) { 245 | option := &OptionWaitInterval{ 246 | baseOption: &baseOption{ 247 | optionType: OptionTypeWaitInterval, 248 | }, 249 | microseconds: microseconds, 250 | } 251 | option.setChild(option) 252 | return option, nil 253 | } 254 | 255 | type OptionWaitInterval struct { 256 | *baseOption 257 | microseconds uint64 258 | } 259 | 260 | func (opt *OptionWaitInterval) Short() string { 261 | return fmt.Sprintf("-W %d", opt.microseconds) 262 | } 263 | 264 | func (opt *OptionWaitInterval) ShortArgs() []string { 265 | return []string{"-W", strconv.FormatUint(opt.microseconds, 10)} 266 | } 267 | 268 | func (opt *OptionWaitInterval) Long() string { 269 | return fmt.Sprintf("--wait-interval %d", opt.microseconds) 270 | } 271 | 272 | func (opt *OptionWaitInterval) LongArgs() []string { 273 | return []string{"--wait-interval", strconv.FormatUint(opt.microseconds, 10)} 274 | } 275 | 276 | func newOptionNumeric() (*OptionNumeric, error) { 277 | option := &OptionNumeric{ 278 | baseOption: &baseOption{ 279 | optionType: OptionTypeNumeric, 280 | }, 281 | } 282 | option.setChild(option) 283 | return option, nil 284 | } 285 | 286 | type OptionNumeric struct { 287 | *baseOption 288 | } 289 | 290 | func (opt *OptionNumeric) Short() string { 291 | return "-n" 292 | } 293 | 294 | func (opt *OptionNumeric) ShortArgs() []string { 295 | return []string{"-n"} 296 | } 297 | 298 | func newOptionExact() (*OptionExact, error) { 299 | option := &OptionExact{ 300 | baseOption: &baseOption{ 301 | optionType: OptionTypeExact, 302 | }, 303 | } 304 | option.setChild(option) 305 | return option, nil 306 | } 307 | 308 | // Display the exact value of the packet and byte counters, instead 309 | // of only the rounded number in K's(multiples of 1000) M's(multiples 310 | // of 1000K) or G's(multiples of 1000M). this option if only relevant 311 | // for the List command. 312 | type OptionExact struct { 313 | *baseOption 314 | } 315 | 316 | func (opt *OptionExact) Short() string { 317 | return "-x" 318 | } 319 | 320 | func (opt *OptionExact) ShortArgs() []string { 321 | return []string{"-x"} 322 | } 323 | 324 | func (opt *OptionExact) Long() string { 325 | return "--exact" 326 | } 327 | 328 | func (opt *OptionExact) LongArgs() []string { 329 | return []string{"--exact"} 330 | } 331 | 332 | func newOptionLineNumbers() (*OptionLineNumbers, error) { 333 | option := &OptionLineNumbers{ 334 | baseOption: &baseOption{ 335 | optionType: OptionTypeLineNumbers, 336 | }, 337 | } 338 | option.setChild(option) 339 | return option, nil 340 | } 341 | 342 | // List with line numbers of each rule, corresponding to that rule's 343 | // position in the chain. 344 | type OptionLineNumbers struct { 345 | *baseOption 346 | } 347 | 348 | func (opt *OptionLineNumbers) Short() string { 349 | return "--line-numbers" 350 | } 351 | 352 | func (opt *OptionLineNumbers) ShortArgs() []string { 353 | return []string{"--line-numbers"} 354 | } 355 | 356 | func newOptionModprobe(command string) (*OptionModprobe, error) { 357 | option := &OptionModprobe{ 358 | baseOption: &baseOption{ 359 | optionType: OptionTypeModprobe, 360 | }, 361 | command: command, 362 | } 363 | return option, nil 364 | } 365 | 366 | // When adding or inserting rule into a chain, use command to load any 367 | // necessary modules(targets, match extensions, etc). 368 | type OptionModprobe struct { 369 | *baseOption 370 | command string 371 | } 372 | 373 | func (opt *OptionModprobe) Short() string { 374 | return fmt.Sprintf("--modprobe=%s", opt.command) 375 | } 376 | 377 | func (opt *OptionModprobe) ShortArgs() []string { 378 | return []string{fmt.Sprintf("--modprobe=%s", opt.command)} 379 | } 380 | -------------------------------------------------------------------------------- /iptables/end_test.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | 10 | "bou.ke/monkey" 11 | "github.com/singchia/go-xtables/pkg/cmd" 12 | "github.com/singchia/go-xtables/pkg/network" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/vishvananda/netns" 15 | ) 16 | 17 | var ( 18 | originns netns.NsHandle 19 | newns netns.NsHandle 20 | ) 21 | 22 | func set() { 23 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 24 | if sandboxAddr != "" { 25 | sandboxUser := os.Getenv("SANDBOX_USER") 26 | sandboxPassword := os.Getenv("SANDBOX_PASSWORD") 27 | 28 | monkey.Patch(cmd.Cmd, func(name string, arg ...string) ([]byte, []byte, error) { 29 | return cmd.SSHCmdPassword(sandboxAddr, sandboxUser, sandboxPassword, 30 | name, arg...) 31 | }) 32 | } else { 33 | runtime.LockOSThread() 34 | originns, _ = netns.Get() 35 | newns, _ = netns.New() 36 | } 37 | } 38 | 39 | func unset() { 40 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 41 | if sandboxAddr != "" { 42 | monkey.UnpatchAll() 43 | } else { 44 | runtime.UnlockOSThread() 45 | newns.Close() 46 | originns.Close() 47 | } 48 | } 49 | 50 | func initIPTables(t *testing.T) { 51 | err := NewIPTables().Flush() 52 | assert.Equal(t, nil, err) 53 | 54 | err = NewIPTables().DeleteChain() 55 | assert.Equal(t, nil, err) 56 | 57 | err = NewIPTables().Policy(TargetTypeAccept) 58 | assert.Equal(t, nil, err) 59 | } 60 | 61 | func TestFlush(t *testing.T) { 62 | set() 63 | defer unset() 64 | 65 | err := NewIPTables().Flush() 66 | assert.Equal(t, nil, err) 67 | } 68 | 69 | func TestFlushTable(t *testing.T) { 70 | set() 71 | defer unset() 72 | 73 | iptables := NewIPTables().Table(TableTypeNat) 74 | 75 | err := iptables.Flush() 76 | assert.Equal(t, nil, err) 77 | 78 | rules, err := iptables.ListRules() 79 | assert.Equal(t, nil, err) 80 | assert.Equal(t, len(rules), 0) 81 | } 82 | 83 | func TestAppend(t *testing.T) { 84 | set() 85 | defer unset() 86 | 87 | iptables := NewIPTables().Table(TableTypeNat). 88 | Chain(ChainTypePREROUTING). 89 | MatchIPv4(). 90 | MatchProtocol(false, network.ProtocolTCP). 91 | MatchTCP(WithMatchTCPDstPort(false, 2432)). 92 | TargetDNAT(WithTargetDNATToAddr(network.ParseIP("192.168.100.230"), 2433)) 93 | 94 | err := iptables.Append() 95 | assert.Equal(t, nil, err) 96 | 97 | rules, err := iptables.FindRules() 98 | assert.Equal(t, nil, err) 99 | assert.Greater(t, len(rules), 0) 100 | 101 | err = iptables.Delete() 102 | assert.Equal(t, nil, err) 103 | } 104 | 105 | func TestCheck(t *testing.T) { 106 | set() 107 | defer unset() 108 | 109 | iptables := NewIPTables().Table(TableTypeNat). 110 | Chain(ChainTypePREROUTING). 111 | MatchIPv4(). 112 | MatchProtocol(false, network.ProtocolTCP). 113 | MatchTCP(WithMatchTCPDstPort(false, 2432)). 114 | TargetDNAT(WithTargetDNATToAddr(network.ParseIP("192.168.100.230"), 2433)) 115 | 116 | err := iptables.Append() 117 | assert.Equal(t, nil, err) 118 | 119 | ok, err := iptables.Check() 120 | assert.Equal(t, nil, err) 121 | assert.Equal(t, true, ok) 122 | 123 | err = iptables.Delete() 124 | assert.Equal(t, nil, err) 125 | } 126 | 127 | func TestDelete(t *testing.T) { 128 | set() 129 | defer unset() 130 | 131 | iptables := NewIPTables().Table(TableTypeNat). 132 | Chain(ChainTypePREROUTING). 133 | MatchIPv4(). 134 | TargetAccept() 135 | 136 | err := iptables.Insert(WithCommandInsertRuleNumber(1)) 137 | assert.Equal(t, nil, err) 138 | 139 | err = iptables.Delete(WithCommandDeleteRuleNumber(1)) 140 | assert.Equal(t, nil, err) 141 | } 142 | 143 | func TestInsert(t *testing.T) { 144 | set() 145 | defer unset() 146 | 147 | iptables := NewIPTables().Table(TableTypeNat). 148 | Chain(ChainTypePREROUTING). 149 | MatchIPv4(). 150 | TargetAccept() 151 | 152 | err := iptables. 153 | Insert(WithCommandInsertRuleNumber(1)) 154 | assert.Equal(t, nil, err) 155 | 156 | ok, err := iptables. 157 | Check() 158 | assert.Equal(t, true, ok) 159 | assert.Equal(t, nil, err) 160 | 161 | err = iptables.Delete() 162 | assert.Equal(t, nil, err) 163 | } 164 | 165 | func TestReplace(t *testing.T) { 166 | set() 167 | defer unset() 168 | 169 | iptables := NewIPTables().Table(TableTypeNat). 170 | Chain(ChainTypePREROUTING). 171 | MatchIPv4(). 172 | TargetAccept() 173 | 174 | err := iptables.Insert(WithCommandInsertRuleNumber(1)) 175 | assert.Equal(t, nil, err) 176 | 177 | err = iptables. 178 | MatchProtocol(false, network.ProtocolTCP). 179 | Replace(1) 180 | assert.Equal(t, nil, err) 181 | 182 | ok, err := iptables. 183 | Check() 184 | assert.Equal(t, false, ok) 185 | assert.Equal(t, nil, err) 186 | 187 | ok, err = iptables. 188 | MatchProtocol(false, network.ProtocolTCP). 189 | Check() 190 | assert.Equal(t, true, ok) 191 | assert.Equal(t, nil, err) 192 | } 193 | 194 | func TestListRules(t *testing.T) { 195 | set() 196 | defer unset() 197 | 198 | port := rand.Intn(65535) + 1 199 | iptables := NewIPTables().Table(TableTypeNat). 200 | Chain(ChainTypePREROUTING). 201 | MatchIPv4(). 202 | MatchProtocol(false, network.ProtocolTCP). 203 | MatchTCP(WithMatchTCPDstPort(false, port)). 204 | TargetAccept() 205 | 206 | // insert into the top 207 | err := iptables. 208 | Insert(WithCommandInsertRuleNumber(1)) 209 | assert.Equal(t, nil, err) 210 | 211 | rules, err := NewIPTables().Table(TableTypeNat). 212 | Chain(ChainTypePREROUTING).ListRules() 213 | assert.Equal(t, nil, err) 214 | assert.Greater(t, len(rules), 1) 215 | assert.Equal(t, TableTypeNat, rules[0].Table()) 216 | assert.Equal(t, ChainTypePREROUTING, rules[0].Chain()) 217 | assert.Equal(t, TargetTypeAccept, rules[0].Target().Type()) 218 | 219 | err = iptables.Delete() 220 | assert.Equal(t, nil, err) 221 | } 222 | 223 | func TestDumpRules(t *testing.T) { 224 | set() 225 | defer unset() 226 | 227 | rules, err := NewIPTables().Table(TableTypeFilter).DumpRules() 228 | assert.Equal(t, nil, err) 229 | t.Log(strings.Join(rules, "; ")) 230 | } 231 | 232 | func TestZero(t *testing.T) { 233 | set() 234 | defer unset() 235 | 236 | port := rand.Intn(65535) + 1 237 | iptables := NewIPTables().Table(TableTypeMangle). 238 | Chain(ChainTypeINPUT). 239 | MatchIPv4(). 240 | MatchProtocol(false, network.ProtocolTCP). 241 | MatchTCP(WithMatchTCPDstPort(false, port)). 242 | TargetTTL(WithTargetTTLSet(20)). 243 | OptionSetCounters(1024, 1024) 244 | 245 | err := iptables.Insert() 246 | assert.Equal(t, nil, err) 247 | 248 | rules, err := iptables.FindRules() 249 | assert.Equal(t, nil, err) 250 | 251 | err = NewIPTables().Table(TableTypeMangle). 252 | Chain(ChainTypeINPUT). 253 | Zero(WithCommandZeroRuleNumber(rules[0].lineNumber)) 254 | assert.Equal(t, nil, err) 255 | } 256 | 257 | func TestNewChain(t *testing.T) { 258 | set() 259 | defer unset() 260 | 261 | iptables := NewIPTables().Table(TableTypeFilter) 262 | 263 | chainName := "AustinZhai" 264 | err := iptables.NewChain(chainName) 265 | assert.Equal(t, nil, err) 266 | 267 | userDefined := ChainTypeUserDefined 268 | userDefined.name = chainName 269 | chains, err := iptables.Chain(userDefined).FindChains() 270 | assert.Equal(t, nil, err) 271 | assert.Equal(t, 1, len(chains)) 272 | 273 | iptables.Chain(userDefined).DeleteChain() 274 | assert.Equal(t, nil, err) 275 | } 276 | 277 | func TestDeleteChain(t *testing.T) { 278 | set() 279 | defer unset() 280 | 281 | iptables := NewIPTables().Table(TableTypeFilter) 282 | 283 | chainName := "AustinZhai2" 284 | err := iptables.NewChain(chainName) 285 | assert.Equal(t, nil, err) 286 | 287 | userDefined := ChainTypeUserDefined 288 | userDefined.name = chainName 289 | err = iptables.Chain(userDefined).DeleteChain() 290 | assert.Equal(t, nil, err) 291 | 292 | chains, err := iptables.Chain(userDefined).FindChains() 293 | assert.Equal(t, nil, err) 294 | assert.Equal(t, 0, len(chains)) 295 | } 296 | 297 | func TestRenameChain(t *testing.T) { 298 | set() 299 | defer unset() 300 | 301 | iptables := NewIPTables().Table(TableTypeFilter) 302 | 303 | chainName := "AustinZhai" 304 | err := iptables.NewChain(chainName) 305 | assert.Equal(t, nil, err) 306 | 307 | userDefined := ChainTypeUserDefined 308 | userDefined.name = chainName 309 | chains, err := iptables.Chain(userDefined).FindChains() 310 | assert.Equal(t, nil, err) 311 | assert.Equal(t, 1, len(chains)) 312 | 313 | iptables.Chain(userDefined).RenameChain("AustinZhai2") 314 | 315 | userDefined.name = "AustinZhai2" 316 | chains, err = iptables.Chain(userDefined).FindChains() 317 | assert.Equal(t, nil, err) 318 | assert.Equal(t, 1, len(chains)) 319 | 320 | err = iptables.Chain(userDefined).DeleteChain() 321 | assert.Equal(t, nil, err) 322 | } 323 | 324 | func TestPolicy(t *testing.T) { 325 | set() 326 | defer unset() 327 | 328 | iptables := NewIPTables().Table(TableTypeFilter).Chain(ChainTypeFORWARD) 329 | 330 | err := iptables.Policy(TargetTypeDrop) 331 | assert.Equal(t, nil, err) 332 | 333 | chains, err := iptables.FindChains() 334 | assert.Equal(t, nil, err) 335 | assert.Equal(t, 1, len(chains)) 336 | assert.Equal(t, TargetTypeDrop, chains[0].policy.Type()) 337 | 338 | err = iptables.Policy(TargetTypeAccept) 339 | assert.Equal(t, nil, err) 340 | } 341 | 342 | func TestDryrun(t *testing.T) { 343 | set() 344 | defer unset() 345 | 346 | err := NewIPTables().Table(TableTypeFilter).Chain(ChainTypeFORWARD). 347 | Dryrun(os.Stdout).Policy(TargetTypeAccept) 348 | assert.Equal(t, nil, err) 349 | } 350 | -------------------------------------------------------------------------------- /ebtables/end_test.go: -------------------------------------------------------------------------------- 1 | package ebtables 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | 10 | "bou.ke/monkey" 11 | "github.com/singchia/go-xtables" 12 | "github.com/singchia/go-xtables/pkg/cmd" 13 | "github.com/singchia/go-xtables/pkg/network" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/vishvananda/netns" 16 | ) 17 | 18 | var ( 19 | originns netns.NsHandle 20 | newns netns.NsHandle 21 | ) 22 | 23 | func set() { 24 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 25 | if sandboxAddr != "" { 26 | sandboxUser := os.Getenv("SANDBOX_USER") 27 | sandboxPassword := os.Getenv("SANDBOX_PASSWORD") 28 | 29 | monkey.Patch(cmd.Cmd, func(name string, arg ...string) ([]byte, []byte, error) { 30 | return cmd.SSHCmdPassword(sandboxAddr, sandboxUser, sandboxPassword, 31 | name, arg...) 32 | }) 33 | } else { 34 | runtime.LockOSThread() 35 | originns, _ = netns.Get() 36 | newns, _ = netns.New() 37 | } 38 | } 39 | 40 | func unset() { 41 | sandboxAddr := os.Getenv("SANDBOX_ADDR") 42 | if sandboxAddr != "" { 43 | monkey.UnpatchAll() 44 | } else { 45 | runtime.UnlockOSThread() 46 | newns.Close() 47 | originns.Close() 48 | } 49 | } 50 | 51 | func initEBTables(t *testing.T) { 52 | err := NewEBTables().Flush() 53 | assert.Equal(t, nil, err) 54 | 55 | err = NewEBTables().DeleteChain() 56 | assert.Equal(t, nil, err) 57 | 58 | err = NewEBTables().Policy(TargetTypeAccept) 59 | assert.Equal(t, nil, err) 60 | } 61 | 62 | func TestFlush(t *testing.T) { 63 | set() 64 | defer unset() 65 | 66 | err := NewEBTables().Flush() 67 | assert.Equal(t, nil, err) 68 | } 69 | 70 | func TestListRules(t *testing.T) { 71 | set() 72 | defer unset() 73 | 74 | err := NewEBTables().Flush() 75 | assert.Equal(t, nil, err) 76 | 77 | rules, err := NewEBTables().Table(TableTypeFilter).ListRules() 78 | assert.Equal(t, nil, err) 79 | assert.Equal(t, 0, len(rules)) 80 | } 81 | 82 | func TestListChains(t *testing.T) { 83 | set() 84 | defer unset() 85 | 86 | err := NewEBTables().Flush() 87 | assert.Equal(t, nil, err) 88 | 89 | chains, err := NewEBTables().Table(TableTypeFilter).ListChains() 90 | assert.Equal(t, nil, err) 91 | assert.Equal(t, 3, len(chains)) 92 | } 93 | 94 | func TestAppend(t *testing.T) { 95 | set() 96 | defer unset() 97 | initEBTables(t) 98 | 99 | err := NewEBTables().Table(TableTypeFilter). 100 | Chain(ChainTypeINPUT). 101 | MatchProtocol(false, network.EthernetTypeARP). 102 | MatchARP(WithMatchARPOpCode(false, network.ARPOpCodeInARPRequest)). 103 | TargetAccept(). 104 | Append() 105 | assert.Equal(t, nil, err) 106 | } 107 | 108 | func TestChangeCounters(t *testing.T) { 109 | set() 110 | defer unset() 111 | 112 | dip := net.ParseIP("2001:db8:3333:4444:5555:6666:7777:8888") 113 | 114 | ebtables := NewEBTables(). 115 | Table(TableTypeFilter). 116 | Chain(ChainTypeOUTPUT). 117 | MatchProtocol(false, network.EthernetTypeIPv6). 118 | MatchIPv6(WithMatchIPv6Destination(false, network.NewIP(dip))). 119 | TargetAccept() 120 | 121 | err := ebtables. 122 | DeleteAll() 123 | 124 | err = ebtables. 125 | OptionCounters(0, 0). 126 | Insert() 127 | assert.Equal(t, nil, err) 128 | 129 | err = ebtables. 130 | ChangeCounters(WithCommandChangeCountersByteCount(1024, xtables.OperatorNull), 131 | WithCommandChangeCountersPacketCount(1024, xtables.OperatorNull)) 132 | assert.Equal(t, nil, err) 133 | 134 | rules, err := ebtables. 135 | OptionCounters(1024, 1024). 136 | FindRules() 137 | assert.Equal(t, nil, err) 138 | assert.Equal(t, 1, len(rules)) 139 | } 140 | 141 | func BenchmarkChangeCounters(b *testing.B) { 142 | dip := net.ParseIP("2001:db8:3333:4444:5555:6666:7777:8888") 143 | 144 | ebtables := NewEBTables(). 145 | Table(TableTypeFilter). 146 | Chain(ChainTypeOUTPUT). 147 | MatchProtocol(false, network.EthernetTypeIPv6). 148 | MatchIPv6(WithMatchIPv6Destination(false, network.NewIP(dip))). 149 | TargetAccept() 150 | for i := 0; i < b.N; i++ { 151 | ebtables.MatchLogicalIn(false, "eth0") 152 | } 153 | } 154 | 155 | func TestDelete(t *testing.T) { 156 | set() 157 | defer unset() 158 | 159 | sip := net.ParseIP("192.168.0.2") 160 | 161 | err := NewEBTables().Table(TableTypeNat). 162 | Chain(ChainTypePREROUTING). 163 | MatchProtocol(false, network.EthernetTypeIPv4). 164 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 165 | TargetAccept(). 166 | Insert() 167 | assert.Equal(t, nil, err) 168 | 169 | err = NewEBTables().Table(TableTypeNat). 170 | Chain(ChainTypePREROUTING). 171 | MatchProtocol(false, network.EthernetTypeIPv4). 172 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 173 | TargetAccept(). 174 | Delete() 175 | assert.Equal(t, nil, err) 176 | } 177 | 178 | func TestDeleteAll(t *testing.T) { 179 | set() 180 | defer unset() 181 | 182 | var err error 183 | sip := net.ParseIP("192.168.0.2") 184 | 185 | iptables := NewEBTables().Table(TableTypeNat). 186 | Chain(ChainTypePREROUTING). 187 | MatchProtocol(false, network.EthernetTypeIPv4). 188 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 189 | TargetAccept() 190 | 191 | for i := 0; i < 10; i++ { 192 | err = iptables. 193 | Insert() 194 | assert.Equal(t, nil, err) 195 | } 196 | 197 | err = iptables. 198 | DeleteAll() 199 | assert.Equal(t, nil, err) 200 | 201 | rules, err := iptables. 202 | FindRules() 203 | assert.Equal(t, nil, err) 204 | assert.Equal(t, 0, len(rules)) 205 | } 206 | 207 | func TestInsert(t *testing.T) { 208 | set() 209 | defer unset() 210 | 211 | sip := net.ParseIP("192.168.0.1") 212 | 213 | err := NewEBTables().Table(TableTypeFilter). 214 | Chain(ChainTypeINPUT). 215 | MatchProtocol(false, network.EthernetTypeIPv4). 216 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 217 | TargetAccept(). 218 | Insert() 219 | assert.Equal(t, nil, err) 220 | } 221 | 222 | func TestPolicy(t *testing.T) { 223 | set() 224 | defer unset() 225 | 226 | err := NewEBTables(). 227 | Table(TableTypeFilter). 228 | Chain(ChainTypeFORWARD). 229 | Policy(TargetTypeDrop) 230 | assert.Equal(t, nil, err) 231 | 232 | chains, err := NewEBTables(). 233 | Table(TableTypeFilter). 234 | Chain(ChainTypeFORWARD). 235 | FindChains() 236 | assert.Equal(t, nil, err) 237 | assert.Equal(t, 1, len(chains)) 238 | //assert.Equal(t, TargetTypeDrop, chains[0].policy.Type()) 239 | } 240 | 241 | func TestZero(t *testing.T) { 242 | set() 243 | defer unset() 244 | 245 | err := NewEBTables(). 246 | Table(TableTypeFilter). 247 | Chain(ChainTypeOUTPUT).Zero() 248 | assert.Equal(t, nil, err) 249 | 250 | rules, err := NewEBTables().Table(TableTypeFilter).Chain(ChainTypeOUTPUT).FindRules() 251 | assert.Equal(t, nil, err) 252 | 253 | for _, rule := range rules { 254 | assert.Equal(t, int64(0), rule.packetCounter) 255 | assert.Equal(t, int64(0), rule.byteCounter) 256 | } 257 | } 258 | 259 | func TestDump(t *testing.T) { 260 | set() 261 | defer unset() 262 | 263 | rules, err := NewEBTables().Table(TableTypeFilter).Dump() 264 | assert.Equal(t, nil, err) 265 | t.Log(strings.Join(rules, "; ")) 266 | } 267 | 268 | func TestNewChain(t *testing.T) { 269 | set() 270 | defer unset() 271 | 272 | err := NewEBTables().Table(TableTypeFilter).NewChain("AustinZhai") 273 | assert.Equal(t, nil, err) 274 | 275 | userDefined := ChainTypeUserDefined 276 | userDefined.name = "AustinZhai" 277 | chains, err := NewEBTables().Table(TableTypeFilter).Chain(userDefined).FindChains() 278 | assert.Equal(t, nil, err) 279 | assert.Equal(t, 1, len(chains)) 280 | } 281 | 282 | func TestDeleteChain(t *testing.T) { 283 | set() 284 | defer unset() 285 | 286 | chainName := "AustinZhai2" 287 | 288 | err := NewEBTables().Table(TableTypeFilter).NewChain(chainName) 289 | assert.Equal(t, nil, err) 290 | 291 | userDefined := ChainTypeUserDefined 292 | userDefined.name = chainName 293 | err = NewEBTables().Table(TableTypeFilter).Chain(userDefined).DeleteChain() 294 | assert.Equal(t, nil, err) 295 | 296 | chains, err := NewEBTables().Table(TableTypeFilter).Chain(userDefined).FindChains() 297 | assert.Equal(t, nil, err) 298 | assert.Equal(t, 0, len(chains)) 299 | } 300 | 301 | func TestRenameChain(t *testing.T) { 302 | set() 303 | defer unset() 304 | 305 | chainName := "AustinZhai3" 306 | chainNameNew := "AustinZhai4" 307 | err := NewEBTables().Table(TableTypeFilter).NewChain(chainName) 308 | assert.Equal(t, nil, err) 309 | 310 | userDefinedOld := ChainTypeUserDefined 311 | userDefinedOld.name = chainName 312 | err = NewEBTables().Table(TableTypeFilter).Chain(userDefinedOld).RenameChain(chainNameNew) 313 | assert.Equal(t, nil, err) 314 | 315 | userDefinedNew := ChainTypeUserDefined 316 | userDefinedNew.name = chainNameNew 317 | chains, err := NewEBTables().Table(TableTypeFilter).Chain(userDefinedNew).FindChains() 318 | assert.Equal(t, nil, err) 319 | assert.Equal(t, 1, len(chains)) 320 | } 321 | 322 | func TestInitTable(t *testing.T) { 323 | set() 324 | defer unset() 325 | 326 | err := NewEBTables().Table(TableTypeFilter).InitTable() 327 | assert.Equal(t, nil, err) 328 | 329 | rules, err := NewEBTables().Table(TableTypeFilter).FindRules() 330 | assert.Equal(t, nil, err) 331 | assert.Equal(t, 0, len(rules)) 332 | } 333 | 334 | func TestAtomicInit(t *testing.T) { 335 | set() 336 | defer unset() 337 | 338 | err := NewEBTables().Table(TableTypeFilter).OptionAtomicFile("~/ebtable.init"). 339 | AtomicInit() 340 | assert.Equal(t, nil, err) 341 | } 342 | 343 | func TestAtomicSave(t *testing.T) { 344 | set() 345 | defer unset() 346 | 347 | err := NewEBTables().Table(TableTypeFilter).OptionAtomicFile("~/ebtable.save"). 348 | AtomicSave() 349 | assert.Equal(t, nil, err) 350 | } 351 | 352 | func TestAtomicCommit(t *testing.T) { 353 | set() 354 | defer unset() 355 | 356 | sip := net.ParseIP("192.168.0.1") 357 | 358 | err := NewEBTables().Table(TableTypeFilter). 359 | Chain(ChainTypeINPUT). 360 | MatchProtocol(false, network.EthernetTypeIPv4). 361 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 362 | TargetAccept(). 363 | Insert() 364 | assert.Equal(t, nil, err) 365 | 366 | err = NewEBTables().Table(TableTypeFilter).OptionAtomicFile("~/ebtable.save"). 367 | AtomicSave() 368 | assert.Equal(t, nil, err) 369 | 370 | err = NewEBTables().Table(TableTypeFilter).InitTable() 371 | assert.Equal(t, nil, err) 372 | 373 | err = NewEBTables().Table(TableTypeFilter).OptionAtomicFile("~/ebtable.save"). 374 | AtomicCommit() 375 | assert.Equal(t, nil, err) 376 | 377 | rules, err := NewEBTables().Table(TableTypeFilter). 378 | Chain(ChainTypeINPUT). 379 | MatchProtocol(false, network.EthernetTypeIPv4). 380 | MatchIP(WithMatchIPSource(false, network.NewIP(sip))). 381 | TargetAccept(). 382 | FindRules() 383 | assert.Equal(t, nil, err) 384 | assert.Equal(t, 1, len(rules)) 385 | } 386 | 387 | func TestDryrun(t *testing.T) { 388 | set() 389 | defer unset() 390 | 391 | err := NewEBTables().Dryrun(os.Stdout).Policy(TargetTypeAccept) 392 | assert.Equal(t, nil, err) 393 | } 394 | -------------------------------------------------------------------------------- /iptables/parser.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/singchia/go-xtables" 10 | "github.com/singchia/go-xtables/internal/xutil" 11 | "github.com/singchia/go-xtables/pkg/network" 12 | ) 13 | 14 | type onChainLine func(line []byte) (*Chain, error) 15 | type onRuleLine func(rule []byte, head []string, chain *Chain) (*Rule, error) 16 | 17 | func (iptables *IPTables) parse(data []byte, table TableType, onChainLine onChainLine, onRuleLine onRuleLine) ( 18 | []*Chain, []*Rule, error) { 19 | 20 | chains := []*Chain{} 21 | rules := []*Rule{} 22 | 23 | buf := bytes.NewBuffer(data) 24 | scanner := bufio.NewScanner(buf) 25 | 26 | var chain *Chain 27 | var err error 28 | var index int // index in current chain 29 | var head []string 30 | 31 | for scanner.Scan() { 32 | line := scanner.Bytes() 33 | if index == 0 { 34 | if bytes.HasPrefix(line, []byte("Chain")) { 35 | // chain 36 | if onChainLine == nil { 37 | continue 38 | } 39 | chain, err = onChainLine(line) 40 | if err != nil { 41 | iptables.log.Errorf("parse chain line err: %s", err) 42 | return nil, nil, err 43 | } 44 | chain.tableType = table 45 | chains = append(chains, chain) 46 | } 47 | } else if index == 1 { 48 | // rule head 49 | head = strings.Fields(string(line)) 50 | } else { 51 | // rule or EOC(end of chain) 52 | if len(line) == 0 { 53 | index = 0 54 | continue 55 | } 56 | if onRuleLine == nil { 57 | continue 58 | } 59 | rule, err := onRuleLine(line, head, chain) 60 | if err != nil { 61 | iptables.log.Errorf("parse rule line err: %s", err) 62 | return nil, nil, err 63 | } 64 | rule.tableType = table 65 | rules = append(rules, rule) 66 | } 67 | index++ 68 | } 69 | return chains, rules, nil 70 | } 71 | 72 | func (iptables *IPTables) parseRule(line []byte, head []string, chain *Chain) (*Rule, error) { 73 | rule := &Rule{ 74 | chain: chain, 75 | lineNumber: -1, 76 | packets: -1, 77 | bytes: -1, 78 | matches: []Match{}, 79 | options: []Option{}, 80 | matchMap: map[MatchType]Match{}, 81 | optionMap: map[OptionType]Option{}, 82 | } 83 | fields, index := xutil.NFields(line, len(head)) 84 | for i, name := range head { 85 | field := string(fields[i]) 86 | switch name { 87 | case "num": 88 | field = strings.TrimSpace(field) 89 | num, err := strconv.Atoi(field) 90 | if err != nil { 91 | iptables.log.Errorf("num field convert: %s to int err: %s", field, err) 92 | return nil, err 93 | } 94 | rule.lineNumber = num 95 | 96 | case "pkts": 97 | num, err := xutil.UnfoldDecimal(field) 98 | if err != nil { 99 | iptables.log.Errorf("pkts unfold decimal: %s err: %s", field, err) 100 | return nil, err 101 | } 102 | rule.packets = num 103 | case "bytes": 104 | num, err := xutil.UnfoldDecimal(field) 105 | if err != nil { 106 | iptables.log.Errorf("bytes unfold decimal: %s err: %s", field, err) 107 | return nil, err 108 | } 109 | rule.bytes = num 110 | case "target": 111 | value, ok := targetValueType[field] 112 | if !ok { 113 | // user defined chain, wait to see concrete details 114 | target, err := targetFactory(TargetTypeNull, field) 115 | if err != nil { 116 | return nil, err 117 | } 118 | rule.target = target 119 | } else { 120 | target, err := targetFactory(value) 121 | if err != nil { 122 | return nil, err 123 | } 124 | rule.target = target 125 | } 126 | case "prot": 127 | field = strings.ToUpper(field) 128 | invert := false 129 | if len(field) > 1 && field[0] == '!' { 130 | invert = true 131 | field = field[1:] 132 | } 133 | prot := network.GetProtocolByName(strings.ToUpper(field)) 134 | if prot != network.ProtocolUnknown { 135 | match := newMatchProtocol(invert, prot) 136 | rule.matches = append(rule.matches, match) 137 | rule.matchMap[MatchTypeProtocol] = match 138 | } else { 139 | id, err := strconv.Atoi(field) 140 | if err != nil { 141 | iptables.log.Errorf("proto field convert: %s to int err: %s", field, err) 142 | return nil, err 143 | } 144 | match := newMatchProtocol(invert, network.Protocol(id)) 145 | rule.matches = append(rule.matches, match) 146 | rule.matchMap[MatchTypeProtocol] = match 147 | } 148 | case "opt": 149 | rule.opt = field 150 | case "in": 151 | invert := false 152 | iface := field 153 | if len(field) > 1 && field[0] == '!' { 154 | invert = true 155 | iface = field[1:] 156 | } 157 | match, err := newMatchInInterface(invert, iface) 158 | if err != nil { 159 | return nil, err 160 | } 161 | rule.matches = append(rule.matches, match) 162 | rule.matchMap[MatchTypeInInterface] = match 163 | case "out": 164 | invert := false 165 | iface := field 166 | if len(field) > 1 && field[0] == '!' { 167 | invert = false 168 | iface = field[1:] 169 | } 170 | match, err := newMatchOutInterface(invert, iface) 171 | if err != nil { 172 | return nil, err 173 | } 174 | rule.matches = append(rule.matches, match) 175 | rule.matchMap[MatchTypeOutInterface] = match 176 | case "source": 177 | invert := false 178 | source := field 179 | if len(field) > 1 && field[0] == '!' { 180 | invert = true 181 | source = field[1:] 182 | } 183 | 184 | ads, err := network.ParseAddress(source) 185 | if err != nil { 186 | iptables.log.Errorf("parse source: %s address err: %s", source, err) 187 | return nil, err 188 | } 189 | match, err := newMatchSource(invert, ads) 190 | if err != nil { 191 | return nil, err 192 | } 193 | rule.matches = append(rule.matches, match) 194 | rule.matchMap[MatchTypeSource] = match 195 | case "destination": 196 | invert := false 197 | destination := field 198 | if len(field) > 1 && field[0] == '!' { 199 | invert = true 200 | destination = field[1:] 201 | } 202 | 203 | ads, err := network.ParseAddress(destination) 204 | if err != nil { 205 | iptables.log.Errorf("parse destination: %s address err: %s", destination, err) 206 | return nil, err 207 | } 208 | match, err := newMatchDestination(invert, ads) 209 | if err != nil { 210 | return nil, err 211 | } 212 | rule.matches = append(rule.matches, match) 213 | rule.matchMap[MatchTypeDestination] = match 214 | } 215 | } 216 | if rule.bytes != -1 || rule.packets != -1 { 217 | rule.optionMap[OptionTypeCounters], _ = newOptionCounters(uint64(rule.packets), uint64(rule.bytes)) 218 | } 219 | jump := true 220 | // matches or target params 221 | params := line[index:] 222 | if len(params) > 0 { 223 | // see https://git.netfilter.org/iptables/tree/iptables/iptables.c 224 | // the [goto] clause should be before matches 225 | p0, next := xutil.NFields(params, 1) 226 | if bytes.Compare(p0[0], []byte("[goto]")) == 0 { 227 | jump = false 228 | params = params[next:] 229 | } 230 | } 231 | 232 | if rule.target.Type() == TargetTypeNull { 233 | if jump { 234 | target, err := targetFactory(TargetTypeJumpChain, 235 | rule.target.(*TargetUnknown).Unknown()) 236 | if err != nil { 237 | return nil, err 238 | } 239 | rule.target = target 240 | } else { 241 | target, err := targetFactory(TargetTypeGotoChain, 242 | rule.target.(*TargetUnknown).Unknown()) 243 | if err != nil { 244 | return nil, err 245 | } 246 | rule.target = target 247 | } 248 | } 249 | 250 | // then matches 251 | matches, index, err := iptables.parseMatch(params) 252 | if err != nil { 253 | iptables.log.Errorf("parse match: %s err: %s", string(params), err) 254 | return nil, err 255 | } 256 | rule.matches = append(rule.matches, matches...) 257 | for _, match := range matches { 258 | rule.matchMap[match.Type()] = match 259 | } 260 | params = params[index:] 261 | 262 | // then target 263 | params = bytes.TrimSpace(params) 264 | _, ok := rule.target.Parse(params) 265 | if !ok { 266 | iptables.log.Errorf("parse target: %s err: %s", string(params), err) 267 | return nil, xtables.ErrTargetParseFailed 268 | } 269 | return rule, nil 270 | } 271 | 272 | func (iptables *IPTables) parseChain(line []byte) (*Chain, error) { 273 | chain := &Chain{ 274 | references: -1, 275 | } 276 | buf := bytes.NewBuffer(line) 277 | _, err := buf.ReadString(' ') 278 | if err != nil { 279 | iptables.log.Errorf("parse chain read to first space err: %s", err) 280 | return nil, err 281 | } 282 | 283 | chain.chainType.name, err = buf.ReadString(' ') 284 | if err != nil { 285 | iptables.log.Errorf("parse chain read to second space err: %s", err) 286 | return nil, err 287 | } 288 | 289 | chain.chainType.name = strings.TrimSpace(chain.chainType.name[:len(chain.chainType.name)-1]) 290 | switch chain.chainType.name { 291 | case "INPUT": 292 | chain.chainType = ChainTypeINPUT 293 | case "FORWARD": 294 | chain.chainType = ChainTypeFORWARD 295 | case "OUTPUT": 296 | chain.chainType = ChainTypeOUTPUT 297 | case "PREROUTING": 298 | chain.chainType = ChainTypePREROUTING 299 | case "POSTROUTING": 300 | chain.chainType = ChainTypePOSTROUTING 301 | default: 302 | userDefined := ChainTypeUserDefined 303 | userDefined.name = chain.chainType.name 304 | chain.chainType = userDefined 305 | } 306 | 307 | rest := buf.Bytes() 308 | if len(rest) < 2 { 309 | return nil, xtables.ErrChainLineTooShort 310 | } 311 | rest = rest[1 : len(rest)-1] 312 | attrs := bytes.Fields(rest) 313 | if len(attrs)%2 != 0 { 314 | return nil, xtables.ErrChainAttrsNotRecognized 315 | } 316 | 317 | pairs := len(attrs) / 2 318 | for i := 0; i < pairs; i++ { 319 | index := i * 2 320 | first := attrs[index] 321 | second := attrs[index+1] 322 | 323 | // policy 324 | if bytes.HasPrefix(first, []byte("policy")) { 325 | switch string(second) { 326 | case "ACCEPT": 327 | chain.policy = newTargetAccept() 328 | case "DROP": 329 | chain.policy = newTargetDrop() 330 | case "RETURN": 331 | chain.policy = newTargetReturn() 332 | } 333 | } 334 | 335 | // packets 336 | if bytes.HasPrefix(second, []byte("packets")) { 337 | num, err := xutil.UnfoldDecimal(string(first)) 338 | if err != nil { 339 | iptables.log.Errorf("packets unfold decimal: %s in chain err: %s", string(first), err) 340 | return nil, err 341 | } 342 | chain.packets = num 343 | } 344 | 345 | // bytes 346 | if bytes.HasPrefix(second, []byte("bytes")) { 347 | num, err := xutil.UnfoldDecimal(string(first)) 348 | if err != nil { 349 | iptables.log.Errorf("bytes unfold decimal: %s in chain err: %s", string(first), err) 350 | return nil, err 351 | } 352 | chain.bytes = num 353 | } 354 | 355 | // references 356 | if bytes.HasPrefix(second, []byte("references")) { 357 | num, err := xutil.UnfoldDecimal(string(first)) 358 | if err != nil { 359 | iptables.log.Errorf("references unfold decimal: %s in chain err: %s", string(first), err) 360 | return nil, err 361 | } 362 | chain.references = int(num) 363 | } 364 | } 365 | return chain, nil 366 | } 367 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | [![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/go-xtables.svg)](https://pkg.go.dev/badge/github.com/singchia/go-xtables) 8 | [![Go](https://github.com/singchia/go-xtables/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/go-xtables/actions/workflows/go.yml) 9 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | [![Go Report Card](https://goreportcard.com/badge/github.com/singchia/go-xtables)](https://goreportcard.com/report/github.com/singchia/go-xtables) 11 | ![Platform](https://img.shields.io/badge/platform-linux-brightgreen.svg) 12 | 13 | [English](./README.md) | 简体中文 14 | 15 |
16 | 17 | ## 简介 18 | 19 | ### 设计 20 | 21 | ![](docs/design-v2.png) 22 | ### 说明 23 | Netfilter允许数据包在多个表和链进行过滤、转换和修改,其内核态通过提供setsockopt和getsockopt的多个socket option给上层以增删改查的能力,但这些socket option因为没有标准定义并不直接开放给开发者,对于c/c++开发者来说,可以考虑```libiptc ```来与netfilter交互,不过据netfilter官方描述,libiptc从不(NEVER)意味着对公众开放。因此对于go开发者来说,使用系统调用封装socket或使用cgo封装libiptc都不是更好的选择,按照netfilter的说明,更建议开发者使用iptables, ebtables和arptables工具来操作数据包。 24 | 25 | Go-xtables就是对iptables, ebtables和arptables工具进行了封装,相比较其他库,额外提供ebtables和arptables的能力,全特性支持(对所有在man手册提及的扩展能力进行了封装),对外提供了链式调用和option模式,完整继承了几个tables里对用户的抽象,非常方便。 26 | 27 | 查看 [iptables godoc](https://pkg.go.dev/github.com/singchia/go-xtables/iptables) 和 [ebtables godoc](https://pkg.go.dev/github.com/singchia/go-xtables/ebtables) 来了解70+ ```match```能力,50+ ```target```能力以10+ ```option```能力。 28 | 29 | **Matches:** 30 | 31 | - :white_check_mark: MatchTypeAddrType 32 | - :white_check_mark: MatchTypeAH 33 | - :white_check_mark: MatchTypeBPF 34 | - :white_check_mark: MatchTypeCGroup 35 | - :white_check_mark: MatchTypeCluster 36 | - :white_check_mark: MatchTypeComment 37 | - :white_check_mark: MatchTypeConnBytes 38 | - :white_check_mark: MatchTypeConnLabel 39 | - :white_check_mark: MatchTypeConnLimit 40 | - :white_check_mark: MatchTypeConnMark 41 | - :white_check_mark: MatchTypeConnTrack 42 | - :white_check_mark: MatchTypeCPU 43 | - :white_check_mark: MatchTypeDCCP 44 | - :white_check_mark: MatchTypeDestination 45 | - :white_check_mark: MatchTypeDevGroup 46 | - :white_check_mark: MatchTypeDSCP 47 | - :white_check_mark: MatchTypeDst 48 | - :white_check_mark: MatchTypeECN 49 | - :white_check_mark: MatchTypeESP 50 | - :white_check_mark: MatchTypeEUI64 51 | - :white_check_mark: MatchTypeFrag 52 | - :white_check_mark: MatchTypeHashLimit 53 | - :white_check_mark: MatchTypeHBH 54 | - :white_check_mark: MatchTypeHelper 55 | - :white_check_mark: MatchTypeHL 56 | - :white_check_mark: MatchTypeICMP 57 | - :white_check_mark: MatchTypeInInterface 58 | - :white_check_mark: MatchTypeIPRange 59 | - :white_check_mark: MatchTypeIPv4 60 | - :white_check_mark: MatchTypeIPv6 61 | - :white_check_mark: MatchTypeIPv6Header 62 | - :white_check_mark: MatchTypeIPVS 63 | - :white_check_mark: MatchTypeLength 64 | - :white_check_mark: MatchTypeLimit 65 | - :white_check_mark: MatchTypeMAC 66 | - :white_check_mark: MatchTypeMark 67 | - :white_check_mark: MatchTypeMH 68 | - :white_check_mark: MatchTypeMultiPort 69 | - :white_check_mark: MatchTypeNFAcct 70 | - :white_check_mark: MatchTypeOSF 71 | - :white_check_mark: MatchTypeOutInterface 72 | - :white_check_mark: MatchTypeOwner 73 | - :white_check_mark: MatchTypePhysDev 74 | - :white_check_mark: MatchTypePktType 75 | - :white_check_mark: MatchTypePolicy 76 | - :white_check_mark: MatchTypeProtocol 77 | - :white_check_mark: MatchTypeQuota 78 | - :white_check_mark: MatchTypeRateEst 79 | - :white_check_mark: MatchTypeRealm 80 | - :white_check_mark: MatchTypeRecent 81 | - :white_check_mark: MatchTypeRPFilter 82 | - :white_check_mark: MatchTypeRT 83 | - :white_check_mark: MatchTypeSCTP 84 | - :white_check_mark: MatchTypeSet 85 | - :white_check_mark: MatchTypeSocket 86 | - :white_check_mark: MatchTypeSource 87 | - :white_check_mark: MatchTypeSRH 88 | - :white_check_mark: MatchTypeState 89 | - :white_check_mark: MatchTypeStatistic 90 | - :white_check_mark: MatchTypeString 91 | - :white_check_mark: MatchTypeTCP 92 | - :white_check_mark: MatchTypeTCPMSS 93 | - :white_check_mark: MatchTypeTime 94 | - :white_check_mark: MatchTypeTOS 95 | - :white_check_mark: MatchTypeTTL 96 | - :white_check_mark: MatchTypeU32 97 | - :white_check_mark: MatchTypeUDP 98 | 99 | **Targets:** 100 | 101 | - :white_check_mark: TargetTypeAccept 102 | - :white_check_mark: TargetTypeDrop 103 | - :white_check_mark: TargetTypeReturn 104 | - :white_check_mark: TargetTypeJumpChain 105 | - :white_check_mark: TargetTypeGotoChain 106 | - :white_check_mark: TargetTypeAudit 107 | - :white_check_mark: TargetTypeCheckSum 108 | - :white_check_mark: TargetTypeClassify 109 | - :white_check_mark: TargetTypeClusterIP 110 | - :white_check_mark: TargetTypeConnMark 111 | - :white_check_mark: TargetTypeConnSecMark 112 | - :white_check_mark: TargetTypeCT 113 | - :white_check_mark: TargetTypeDNAT 114 | - :white_check_mark: TargetTypeDNPT 115 | - :white_check_mark: TargetTypeDSCP 116 | - :white_check_mark: TargetTypeECN 117 | - :white_check_mark: TargetTypeHL 118 | - :white_check_mark: TargetTypeHMark 119 | - :white_check_mark: TargetTypeIdleTimer 120 | - :white_check_mark: TargetTypeLED 121 | - :white_check_mark: TargetTypeLog 122 | - :white_check_mark: TargetTypeMark 123 | - :white_check_mark: TargetTypeMasquerade 124 | - :white_check_mark: TargetTypeMirror 125 | - :white_check_mark: TargetTypeNetmap 126 | - :white_check_mark: TargetTypeNFLog 127 | - :white_check_mark: TargetTypeNFQueue 128 | - :white_check_mark: TargetTypeNoTrack 129 | - :white_check_mark: TargetTypeRateEst 130 | - :white_check_mark: TargetTypeRedirect 131 | - :white_check_mark: TargetTypeReject 132 | - :white_check_mark: TargetTypeSame 133 | - :white_check_mark: TargetTypeSecMark 134 | - :white_check_mark: TargetTypeSet 135 | - :white_check_mark: TargetTypeSNAT 136 | - :white_check_mark: TargetTypeSNPT 137 | - :white_check_mark: TargetTypeSYNProxy 138 | - :white_check_mark: TargetTypeTCPMSS 139 | - :white_check_mark: TargetTypeTCPOptStrip 140 | - :white_check_mark: TargetTypeTEE 141 | - :white_check_mark: TargetTypeTOS 142 | - :white_check_mark: TargetTypeTProxy 143 | - :white_check_mark: TargetTypeTrace 144 | - :white_check_mark: TargetTypeTTL 145 | - :white_check_mark: TargetTypeULog 146 | 147 | 148 | ### 特性 149 | 150 | * 简单易用 151 | * 多tables支持(iptables, ebtables, arptables) 152 | * 全特性支持(全量matches, options, watchers和其他extensions) 153 | * 链式调用(任意排序,可复用对象) 154 | * Dryrun 155 | * 可控日志(默认日志或logrus等) 156 | 157 | ## 使用 158 | ### 上手一试 159 | #### 仅允许ssh, http和https端口流量 160 | ```golang 161 | package main 162 | 163 | import ( 164 | "log" 165 | 166 | "github.com/singchia/go-xtables/iptables" 167 | "github.com/singchia/go-xtables/pkg/network" 168 | ) 169 | 170 | func main() { 171 | ipt := iptables.NewIPTables().Table(iptables.TableTypeFilter).Chain(iptables.ChainTypeINPUT).MatchProtocol(false, network.ProtocolTCP) 172 | 173 | // allow ssh, http and https 174 | err := ipt.MatchMultiPort(iptables.WithMatchMultiPortDstPorts(false, 22, 80, 443)).TargetAccept().Insert() 175 | if err != nil { 176 | log.Fatal(err) 177 | } 178 | // drop others 179 | err = iptables.NewIPTables().Table(iptables.TableTypeFilter).Chain(iptables.ChainTypeINPUT).Policy(iptables.TargetTypeDrop) 180 | if err != nil { 181 | log.Fatal(err) 182 | } 183 | } 184 | ``` 185 | 186 | ### 简单使用 187 | #### 拒绝特定端口的所有进入流量 188 | ```golang 189 | iptables.NewIPTables(). 190 | Table(iptables.TableTypeFilter). 191 | Chain(iptables.ChainTypeINPUT). 192 | MatchProtocol(false, network.ProtocolTCP). 193 | MatchTCP(iptables.WithMatchTCPDstPort(false, 2432)). 194 | TargetDrop(). 195 | Append() 196 | ``` 197 | #### 允许特定源IP地址的所有进入流量 198 | ```golang 199 | iptables.NewIPTables(). 200 | Table(iptables.TableTypeFilter). 201 | Chain(iptables.ChainTypeINPUT). 202 | MatchSource(false, "192.168.1.100"). 203 | TargetAccept(). 204 | Append() 205 | ``` 206 | #### 查找相关的规则 207 | ```golang 208 | rules, err := iptables.NewIPTables(). 209 | Table(iptables.TableTypeFilter). 210 | Chain(iptables.ChainTypeINPUT). 211 | MatchSource(false, "192.168.1.100"). 212 | TargetAccept(). 213 | FindRules() 214 | ``` 215 | #### 删除所有表的所有规则 216 | ```golang 217 | iptables.NewIPTables().Flush() 218 | ``` 219 | #### 允许每分钟10个连接进入80端口 220 | ```golang 221 | iptables.NewIPTables(). 222 | Table(iptables.TableTypeFilter). 223 | Chain(iptables.ChainTypeINPUT). 224 | MatchProtocol(false, network.ProtocolTCP). 225 | MatchTCP(iptables.WithMatchTCPDstPort(false, 80)). 226 | MatchLimit(iptables.WithMatchLimit(xtables.Rate{10, xtables.Minute})). 227 | TargetAccept(). 228 | Append() 229 | ``` 230 | #### 流量镜像到网关 231 | ```golang 232 | iptables.NewIPTables(). 233 | Table(iptables.TableTypeMangle). 234 | Chain(iptables.ChainTypePREROUTING). 235 | MatchProtocol(false, network.ProtocolTCP). 236 | MatchTCP(iptables.WithMatchTCPDstPort(false, 2432)). 237 | TargetTEE(net.ParseIP("192.168.1.1")). 238 | Insert() 239 | ``` 240 | #### 拒绝特定MAC地址的访问 241 | 242 | 该示例使用ebtables,请注意该规则作用在```linux-bridge```上,请先确保网卡被bridge接管。 243 | 244 | ```golang 245 | ebtables.NewEBTables(). 246 | Table(ebtables.TableTypeFilter). 247 | Chain(ebtables.ChainTypeINPUT). 248 | MatchSource(false, "00:11:22:33:44:55"). 249 | TargetDrop(). 250 | Append() 251 | ``` 252 | ### 现实场景 253 | #### 防止DDos攻击 254 | ```golang 255 | custom := "SYN_FLOOD" 256 | ipt := iptables.NewIPTables().Table(iptables.TableTypeFilter) 257 | ipt.NewChain(custom) 258 | ipt.Chain(iptables.ChainTypeINPUT). 259 | MatchProtocol(false, network.ProtocolTCP). 260 | MatchTCP(iptables.WithMatchTCPSYN(false)). 261 | TargetJumpChain(custom). 262 | Append() 263 | 264 | userDefined := iptables.ChainTypeUserDefined 265 | userDefined.SetName(custom) 266 | rate := xtables.Rate{1, xtables.Second} 267 | ipt.Chain(userDefined). 268 | MatchLimit( 269 | iptables.WithMatchLimit(rate), 270 | iptables.WithMatchLimitBurst(3)). 271 | TargetReturn(). 272 | Append() 273 | ipt.Chain(userDefined). 274 | TargetDrop(). 275 | Append() 276 | ``` 277 | #### 禁PING 278 | ```golang 279 | iptables.NewIPTables(). 280 | Table(iptables.TableTypeFilter). 281 | Chain(iptables.ChainTypeINPUT). 282 | MatchProtocol(false, network.ProtocolICMP). 283 | MatchICMP(false, network.ICMPType(network.EchoRequest)). 284 | TargetDrop(). 285 | Append() 286 | ``` 287 | #### 流量只出不进 288 | ```golang 289 | ipt := iptables.NewIPTables().Table(iptables.TableTypeFilter) 290 | ipt.Chain(iptables.ChainTypeINPUT). 291 | MatchInInterface(false, "lo"). 292 | TargetAccept(). 293 | Append() 294 | ipt.Chain(iptables.ChainTypeINPUT). 295 | MatchState(iptables.ESTABLISHED | iptables.RELATED). 296 | TargetAccept(). 297 | Append() 298 | ipt.Chain(iptables.ChainTypeINPUT). 299 | MatchProtocol(false, network.ProtocolTCP). 300 | MatchTCP(iptables.WithMatchTCPDstPort(false, 22)). 301 | TargetAccept(). 302 | Append() 303 | ipt.Chain(iptables.ChainTypeINPUT).Policy(iptables.TargetTypeDrop) 304 | ipt.Chain(iptables.ChainTypeFORWARD).Policy(iptables.TargetTypeDrop) 305 | ipt.Chain(iptables.ChainTypeOUTPUT).Policy(iptables.TargetTypeAccept) 306 | ``` 307 | ## 注意 308 | 309 | ### 兼容性 310 | 从Linux内核版本4.18开始,nftables成为内核的一部分,并逐步替代iptables。因此,使用linux 4.18以及更高版本的发行版通常会使用nftables而不是iptables。由于nftables并不完全兼容iptables,如果还想要继续使用go-xtables,在使用这些发行版时最好能够切换到iptables以继续使用。 311 | 312 | 以下发行版需要注意兼容性: 313 | 314 | * Debian 10(Buster) 及更高版本 315 | * Ubuntu 18.04(Bionic Beaver) 及更高版本 316 | * Centos 8 及更高版本 317 | * Fedora 18 及更高版本 318 | * OpenSUSE Leap 15.2 及更高版本 319 | * Arch Linux 320 | 321 | ## 参与开发 322 | 当前go-xtables处于生产就绪阶段,如果你发现任何Bug,请随意提出Issue,项目Maintainers会及时响应相关问题。 323 | 324 | 如果你希望能够提交Feature,更快速解决项目问题,满足以下简单条件下欢迎提交PR: 325 | 326 | * 代码风格保持一致 327 | * 每次提交一个Feature 328 | * 提交的代码都携带单元测试 329 | * 通过CI构建 330 | 331 | 经过Code review没问题,就会合入代码。 332 | 333 | ## 谁在使用 334 | 335 | 336 | 337 | 338 | ## 许可证 339 | 340 | 341 | Released under the [Apache License 2.0](https://github.com/singchia/go-xtables/blob/main/LICENSE) --------------------------------------------------------------------------------