├── .github └── workflows │ └── push.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── alignedbuff ├── alignedbuff.go └── alignedbuff_test.go ├── binaryutil ├── binaryutil.go └── binaryutil_test.go ├── chain.go ├── compat_policy.go ├── compat_policy_test.go ├── conn.go ├── counter.go ├── doc.go ├── expr ├── bitwise.go ├── bitwise_test.go ├── byteorder.go ├── connlimit.go ├── counter.go ├── ct.go ├── ct_test.go ├── dup.go ├── dynset.go ├── expr.go ├── exthdr.go ├── exthdr_test.go ├── fib.go ├── flow_offload.go ├── hash.go ├── immediate.go ├── limit.go ├── log.go ├── lookup.go ├── match.go ├── match_test.go ├── meta_test.go ├── nat.go ├── nat_test.go ├── notrack.go ├── numgen.go ├── objref.go ├── payload.go ├── queue.go ├── quota.go ├── range.go ├── redirect.go ├── reject.go ├── rt.go ├── secmark.go ├── socket.go ├── socket_test.go ├── synproxy.go ├── target.go ├── target_test.go ├── tproxy.go └── verdict.go ├── flowtable.go ├── gen.go ├── go.mod ├── go.sum ├── integration ├── nft_test.go └── testdata │ ├── add_chain.nft │ ├── add_flowtables.nft │ └── add_table.nft ├── internal ├── nftest │ ├── nftest.go │ └── system_conn.go └── parseexprfunc │ └── parseexprfunc.go ├── monitor.go ├── monitor_test.go ├── nftables_test.go ├── obj.go ├── quota.go ├── rule.go ├── set.go ├── set_test.go ├── table.go ├── userdata ├── userdata.go ├── userdata_cli_interop_test.go └── userdata_test.go ├── util.go ├── util_test.go └── xt ├── comment.go ├── comment_test.go ├── info.go ├── match_addrtype.go ├── match_addrtype_test.go ├── match_conntrack.go ├── match_conntrack_test.go ├── match_tcp.go ├── match_tcp_test.go ├── match_udp.go ├── match_udp_test.go ├── target_dnat.go ├── target_dnat_test.go ├── target_masquerade_ip.go ├── target_masquerade_ip_test.go ├── unknown.go ├── unknown_test.go ├── util.go └── xt.go /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Push 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: CI 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Go 1.x 19 | uses: actions/setup-go@v4 20 | with: 21 | # Run on the latest minor release of Go 1.20: 22 | go-version: ^1.20 23 | id: go 24 | 25 | 26 | - name: Ensure all files were formatted as per gofmt 27 | run: | 28 | [ "$(gofmt -l $(find . -name '*.go') 2>&1)" = "" ] 29 | 30 | - name: Run tests 31 | run: | 32 | go vet . 33 | go test ./... 34 | go test -c github.com/google/nftables 35 | sudo ./nftables.test -test.v -run_system_tests 36 | go test -c github.com/google/nftables/integration 37 | (cd integration && sudo ../integration.test -test.v -run_system_tests) 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/google/nftables/actions/workflows/push.yml/badge.svg)](https://github.com/google/nftables/actions/workflows/push.yml) 2 | [![GoDoc](https://godoc.org/github.com/google/nftables?status.svg)](https://godoc.org/github.com/google/nftables) 3 | 4 | **This is not the correct repository for issues with the Linux nftables 5 | project!** This repository contains a third-party Go package to programmatically 6 | interact with nftables. Find the official nftables website at 7 | https://wiki.nftables.org/ 8 | 9 | This package manipulates Linux nftables (the iptables successor). It is 10 | implemented in pure Go, i.e. does not wrap libnftnl. 11 | 12 | This is not an official Google product. 13 | 14 | ## Breaking changes 15 | 16 | This package is in very early stages, and only contains enough data types and 17 | functions to install very basic nftables rules. It is likely that mistakes with 18 | the data types/API will be identified as more functionality is added. 19 | 20 | ## Contributions 21 | 22 | Contributions are very welcome! 23 | 24 | 25 | -------------------------------------------------------------------------------- /alignedbuff/alignedbuff_test.go: -------------------------------------------------------------------------------- 1 | package alignedbuff 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAlignmentData(t *testing.T) { 8 | if uint16AlignMask == 0 { 9 | t.Fatal("zero uint16 alignment mask") 10 | } 11 | if uint32AlignMask == 0 { 12 | t.Fatal("zero uint32 alignment mask") 13 | } 14 | if uint64AlignMask == 0 { 15 | t.Fatal("zero uint64 alignment mask") 16 | } 17 | if len(padding) == 0 { 18 | t.Fatal("zero alignment padding sequence") 19 | } 20 | if uintSize == 0 { 21 | t.Fatal("zero uint size") 22 | } 23 | if int32AlignMask == 0 { 24 | t.Fatal("zero uint32 alignment mask") 25 | } 26 | } 27 | 28 | func TestAlignedBuff8(t *testing.T) { 29 | b := NewWithData([]byte{0x42}) 30 | tests := []struct { 31 | name string 32 | v uint8 33 | err error 34 | }{ 35 | { 36 | name: "first read", 37 | v: 0x42, 38 | err: nil, 39 | }, 40 | { 41 | name: "end of buffer", 42 | v: 0, 43 | err: ErrEOF, 44 | }, 45 | } 46 | 47 | for _, tt := range tests { 48 | v, err := b.Uint8() 49 | if v != tt.v || err != tt.err { 50 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 51 | tt.v, tt.err, v, err) 52 | } 53 | } 54 | } 55 | 56 | func TestAlignedBuff16(t *testing.T) { 57 | b0 := New() 58 | b0.PutUint8(0x42) 59 | b0.PutUint16(0x1234) 60 | b0.PutUint16(0x5678) 61 | 62 | b := NewWithData(b0.data) 63 | v, err := b.Uint8() 64 | if v != 0x42 || err != nil { 65 | t.Fatalf("unaligment read failed") 66 | } 67 | tests := []struct { 68 | name string 69 | v uint16 70 | err error 71 | }{ 72 | { 73 | name: "first read", 74 | v: 0x1234, 75 | err: nil, 76 | }, 77 | { 78 | name: "second read", 79 | v: 0x5678, 80 | err: nil, 81 | }, 82 | { 83 | name: "end of buffer", 84 | v: 0, 85 | err: ErrEOF, 86 | }, 87 | } 88 | 89 | for _, tt := range tests { 90 | v, err := b.Uint16() 91 | if v != tt.v || err != tt.err { 92 | t.Errorf("%s failed, expected: %#v %#v, got: %#v, %#v", 93 | tt.name, tt.v, tt.err, v, err) 94 | } 95 | } 96 | } 97 | 98 | func TestAlignedBuff32(t *testing.T) { 99 | b0 := New() 100 | b0.PutUint8(0x42) 101 | b0.PutUint32(0x12345678) 102 | b0.PutUint32(0x01cecafe) 103 | 104 | b := NewWithData(b0.data) 105 | 106 | // Sigh. The Linux kernel expects certain nftables payloads to be padded to 107 | // the uint64 next alignment. Now, on 64bit platforms this will be a 64bit 108 | // alignment, yet on 32bit platforms this will be a 32bit alignment. So, we 109 | // should calculate the expected data length here separately from our 110 | // implementation to be fail safe! However, this might be rather a recipe 111 | // for a safe fail... 112 | expectedlen := 2*(uint32AlignMask+1) + (uint64AlignMask + 1) 113 | 114 | if len(b0.Data()) != expectedlen { 115 | t.Fatalf("alignment padding failed") 116 | } 117 | 118 | v, err := b.Uint8() 119 | if v != 0x42 || err != nil { 120 | t.Fatalf("unaligment read failed") 121 | } 122 | tests := []struct { 123 | name string 124 | v uint32 125 | err error 126 | }{ 127 | { 128 | name: "first read", 129 | v: 0x12345678, 130 | err: nil, 131 | }, 132 | { 133 | name: "second read", 134 | v: 0x01cecafe, 135 | err: nil, 136 | }, 137 | { 138 | name: "end of buffer", 139 | v: 0, 140 | err: ErrEOF, 141 | }, 142 | } 143 | 144 | for _, tt := range tests { 145 | v, err := b.Uint32() 146 | if v != tt.v || err != tt.err { 147 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 148 | tt.v, tt.err, v, err) 149 | } 150 | } 151 | } 152 | 153 | func TestAlignedBuff64(t *testing.T) { 154 | b0 := New() 155 | b0.PutUint8(0x42) 156 | b0.PutUint64(0x1234567823456789) 157 | b0.PutUint64(0x01cecafec001beef) 158 | 159 | b := NewWithData(b0.data) 160 | v, err := b.Uint8() 161 | if v != 0x42 || err != nil { 162 | t.Fatalf("unaligment read failed") 163 | } 164 | tests := []struct { 165 | name string 166 | v uint64 167 | err error 168 | }{ 169 | { 170 | name: "first read", 171 | v: 0x1234567823456789, 172 | err: nil, 173 | }, 174 | { 175 | name: "second read", 176 | v: 0x01cecafec001beef, 177 | err: nil, 178 | }, 179 | { 180 | name: "end of buffer", 181 | v: 0, 182 | err: ErrEOF, 183 | }, 184 | } 185 | 186 | for _, tt := range tests { 187 | v, err := b.Uint64() 188 | if v != tt.v || err != tt.err { 189 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 190 | tt.v, tt.err, v, err) 191 | } 192 | } 193 | } 194 | 195 | func TestAlignedUint(t *testing.T) { 196 | expectedv := uint(^uint32(0) - 1) 197 | b0 := New() 198 | b0.PutUint8(0x55) 199 | b0.PutUint(expectedv) 200 | b0.PutUint8(0xAA) 201 | 202 | b := NewWithData(b0.data) 203 | v, err := b.Uint8() 204 | if v != 0x55 || err != nil { 205 | t.Fatalf("sentinel read failed") 206 | } 207 | uiv, err := b.Uint() 208 | if uiv != expectedv || err != nil { 209 | t.Fatalf("uint read failed, expected: %d, got: %d", expectedv, uiv) 210 | } 211 | v, err = b.Uint8() 212 | if v != 0xAA || err != nil { 213 | t.Fatalf("sentinel read failed") 214 | } 215 | } 216 | 217 | func TestAlignedBuffInt32(t *testing.T) { 218 | b0 := New() 219 | b0.PutUint8(0x42) 220 | b0.PutInt32(0x12345678) 221 | b0.PutInt32(0x01cecafe) 222 | 223 | b := NewWithData(b0.data) 224 | 225 | // Sigh. The Linux kernel expects certain nftables payloads to be padded to 226 | // the uint64 next alignment. Now, on 64bit platforms this will be a 64bit 227 | // alignment, yet on 32bit platforms this will be a 32bit alignment. So, we 228 | // should calculate the expected data length here separately from our 229 | // implementation to be fail safe! However, this might be rather a recipe 230 | // for a safe fail... 231 | expectedlen := 2*(uint32AlignMask+1) + (uint64AlignMask + 1) 232 | 233 | if len(b0.Data()) != expectedlen { 234 | t.Fatalf("alignment padding failed") 235 | } 236 | 237 | v, err := b.Uint8() 238 | if v != 0x42 || err != nil { 239 | t.Fatalf("unaligment read failed") 240 | } 241 | tests := []struct { 242 | name string 243 | v int32 244 | err error 245 | }{ 246 | { 247 | name: "first read", 248 | v: 0x12345678, 249 | err: nil, 250 | }, 251 | { 252 | name: "second read", 253 | v: 0x01cecafe, 254 | err: nil, 255 | }, 256 | { 257 | name: "end of buffer", 258 | v: 0, 259 | err: ErrEOF, 260 | }, 261 | } 262 | 263 | for _, tt := range tests { 264 | v, err := b.Int32() 265 | if v != tt.v || err != tt.err { 266 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 267 | tt.v, tt.err, v, err) 268 | } 269 | } 270 | } 271 | 272 | func TestAlignedBuffPutNullTerminatedString(t *testing.T) { 273 | b0 := New() 274 | b0.PutUint8(0x42) 275 | b0.PutString("test" + "\x00") 276 | 277 | b := NewWithData(b0.data) 278 | 279 | v, err := b.Uint8() 280 | if v != 0x42 || err != nil { 281 | t.Fatalf("unaligment read failed") 282 | } 283 | tests := []struct { 284 | name string 285 | v string 286 | err error 287 | }{ 288 | { 289 | name: "first read", 290 | v: "test", 291 | err: nil, 292 | }, 293 | } 294 | 295 | for _, tt := range tests { 296 | v, err := b.String() 297 | if v != tt.v || err != tt.err { 298 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 299 | tt.v, tt.err, v, err) 300 | } 301 | } 302 | } 303 | 304 | func TestAlignedBuffPutString(t *testing.T) { 305 | b0 := New() 306 | b0.PutUint8(0x42) 307 | b0.PutString("test") 308 | 309 | b := NewWithData(b0.data) 310 | 311 | v, err := b.Uint8() 312 | if v != 0x42 || err != nil { 313 | t.Fatalf("unaligment read failed") 314 | } 315 | tests := []struct { 316 | name string 317 | v string 318 | err error 319 | }{ 320 | { 321 | name: "first read", 322 | v: "test", 323 | err: nil, 324 | }, 325 | } 326 | 327 | for _, tt := range tests { 328 | v, err := b.StringWithLength(len("test")) 329 | if v != tt.v || err != tt.err { 330 | t.Errorf("expected: %#v %#v, got: %#v, %#v", 331 | tt.v, tt.err, v, err) 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /binaryutil/binaryutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package binaryutil contains convenience wrappers around encoding/binary. 16 | package binaryutil 17 | 18 | import ( 19 | "bytes" 20 | "encoding/binary" 21 | "unsafe" 22 | ) 23 | 24 | // ByteOrder is like binary.ByteOrder, but allocates memory and returns byte 25 | // slices, for convenience. 26 | type ByteOrder interface { 27 | PutUint16(v uint16) []byte 28 | PutUint32(v uint32) []byte 29 | PutUint64(v uint64) []byte 30 | Uint16(b []byte) uint16 31 | Uint32(b []byte) uint32 32 | Uint64(b []byte) uint64 33 | } 34 | 35 | // NativeEndian is either little endian or big endian, depending on the native 36 | // endian-ness, and allocates memory and returns byte slices, for convenience. 37 | var NativeEndian ByteOrder = &nativeEndian{} 38 | 39 | type nativeEndian struct{} 40 | 41 | func (nativeEndian) PutUint16(v uint16) []byte { 42 | buf := make([]byte, 2) 43 | *(*uint16)(unsafe.Pointer(&buf[0])) = v 44 | return buf 45 | } 46 | 47 | func (nativeEndian) PutUint32(v uint32) []byte { 48 | buf := make([]byte, 4) 49 | *(*uint32)(unsafe.Pointer(&buf[0])) = v 50 | return buf 51 | } 52 | 53 | func (nativeEndian) PutUint64(v uint64) []byte { 54 | buf := make([]byte, 8) 55 | *(*uint64)(unsafe.Pointer(&buf[0])) = v 56 | return buf 57 | } 58 | 59 | func (nativeEndian) Uint16(b []byte) uint16 { 60 | return *(*uint16)(unsafe.Pointer(&b[0])) 61 | } 62 | 63 | func (nativeEndian) Uint32(b []byte) uint32 { 64 | return *(*uint32)(unsafe.Pointer(&b[0])) 65 | } 66 | 67 | func (nativeEndian) Uint64(b []byte) uint64 { 68 | return *(*uint64)(unsafe.Pointer(&b[0])) 69 | } 70 | 71 | // BigEndian is like binary.BigEndian, but allocates memory and returns byte 72 | // slices, for convenience. 73 | var BigEndian ByteOrder = &bigEndian{} 74 | 75 | type bigEndian struct{} 76 | 77 | func (bigEndian) PutUint16(v uint16) []byte { 78 | buf := make([]byte, 2) 79 | binary.BigEndian.PutUint16(buf, v) 80 | return buf 81 | } 82 | 83 | func (bigEndian) PutUint32(v uint32) []byte { 84 | buf := make([]byte, 4) 85 | binary.BigEndian.PutUint32(buf, v) 86 | return buf 87 | } 88 | 89 | func (bigEndian) PutUint64(v uint64) []byte { 90 | buf := make([]byte, 8) 91 | binary.BigEndian.PutUint64(buf, v) 92 | return buf 93 | } 94 | 95 | func (bigEndian) Uint16(b []byte) uint16 { 96 | return binary.BigEndian.Uint16(b) 97 | } 98 | 99 | func (bigEndian) Uint32(b []byte) uint32 { 100 | return binary.BigEndian.Uint32(b) 101 | } 102 | 103 | func (bigEndian) Uint64(b []byte) uint64 { 104 | return binary.BigEndian.Uint64(b) 105 | } 106 | 107 | // For dealing with types not supported by the encoding/binary interface 108 | 109 | func PutInt32(v int32) []byte { 110 | buf := make([]byte, 4) 111 | *(*int32)(unsafe.Pointer(&buf[0])) = v 112 | return buf 113 | } 114 | 115 | func Int32(b []byte) int32 { 116 | return *(*int32)(unsafe.Pointer(&b[0])) 117 | } 118 | 119 | func PutString(s string) []byte { 120 | return []byte(s) 121 | } 122 | 123 | func String(b []byte) string { 124 | return string(bytes.TrimRight(b, "\x00")) 125 | } 126 | -------------------------------------------------------------------------------- /binaryutil/binaryutil_test.go: -------------------------------------------------------------------------------- 1 | package binaryutil 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "reflect" 7 | "testing" 8 | "unsafe" 9 | ) 10 | 11 | func TestNativeByteOrder(t *testing.T) { 12 | // See https://stackoverflow.com/a/53286786 13 | var natEnd binary.ByteOrder 14 | 15 | canary := [2]byte{} 16 | *(*uint16)(unsafe.Pointer(&canary[0])) = uint16(0xABCD) 17 | 18 | switch canary { 19 | case [2]byte{0xCD, 0xAB}: 20 | natEnd = binary.LittleEndian 21 | case [2]byte{0xAB, 0xCD}: 22 | natEnd = binary.BigEndian 23 | default: 24 | t.Fatalf("unsupported \"mixed\" native endianness") 25 | } 26 | 27 | tests := []struct { 28 | name string 29 | expectedv interface{} 30 | marshal func(v interface{}) []byte 31 | unmarshal func(b []byte) interface{} 32 | reference func(v interface{}, b []byte) 33 | }{ 34 | { 35 | name: "Uint16", 36 | expectedv: uint16(0x1234), 37 | marshal: func(v interface{}) []byte { return NativeEndian.PutUint16(v.(uint16)) }, 38 | unmarshal: func(b []byte) interface{} { return NativeEndian.Uint16(b) }, 39 | reference: func(v interface{}, b []byte) { natEnd.PutUint16(b, v.(uint16)) }, 40 | }, 41 | { 42 | name: "Uint32", 43 | expectedv: uint32(0x12345678), 44 | marshal: func(v interface{}) []byte { return NativeEndian.PutUint32(v.(uint32)) }, 45 | unmarshal: func(b []byte) interface{} { return NativeEndian.Uint32(b) }, 46 | reference: func(v interface{}, b []byte) { natEnd.PutUint32(b, v.(uint32)) }, 47 | }, 48 | { 49 | name: "Uint64", 50 | expectedv: uint64(0x1234567801020304), 51 | marshal: func(v interface{}) []byte { return NativeEndian.PutUint64(v.(uint64)) }, 52 | unmarshal: func(b []byte) interface{} { return NativeEndian.Uint64(b) }, 53 | reference: func(v interface{}, b []byte) { natEnd.PutUint64(b, v.(uint64)) }, 54 | }, 55 | } 56 | 57 | for _, tt := range tests { 58 | expectedb := make([]byte, reflect.TypeOf(tt.expectedv).Size()) 59 | tt.reference(tt.expectedv, expectedb) 60 | actualb := tt.marshal(tt.expectedv) 61 | if !bytes.Equal(actualb, expectedb) { 62 | t.Errorf("NativeEndian.Put%s failure, expected: %#v, got: %#v", tt.name, expectedb, actualb) 63 | } 64 | actualv := tt.unmarshal(actualb) 65 | if !reflect.DeepEqual(tt.expectedv, actualv) { 66 | t.Errorf("NativeEndian.%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actualv) 67 | } 68 | } 69 | } 70 | 71 | func TestBigEndian(t *testing.T) { 72 | tests := []struct { 73 | name string 74 | expected []byte 75 | expectedv interface{} 76 | actual []byte 77 | unmarshal func(b []byte) interface{} 78 | }{ 79 | { 80 | name: "Uint16", 81 | expected: []byte{0x12, 0x34}, 82 | expectedv: uint16(0x1234), 83 | actual: BigEndian.PutUint16(0x1234), 84 | unmarshal: func(b []byte) interface{} { return BigEndian.Uint16(b) }, 85 | }, 86 | { 87 | name: "Uint32", 88 | expected: []byte{0x12, 0x34, 0x56, 0x78}, 89 | expectedv: uint32(0x12345678), 90 | actual: BigEndian.PutUint32(0x12345678), 91 | unmarshal: func(b []byte) interface{} { return BigEndian.Uint32(b) }, 92 | }, 93 | { 94 | name: "Uint64", 95 | expected: []byte{0x12, 0x34, 0x56, 0x78, 0x01, 0x02, 0x03, 0x04}, 96 | expectedv: uint64(0x1234567801020304), 97 | actual: BigEndian.PutUint64(0x1234567801020304), 98 | unmarshal: func(b []byte) interface{} { return BigEndian.Uint64(b) }, 99 | }, 100 | } 101 | for _, tt := range tests { 102 | if bytes.Compare(tt.actual, tt.expected) != 0 { 103 | t.Errorf("BigEndian.Put%s failure, expected: %#v, got: %#v", tt.name, tt.expected, tt.actual) 104 | } 105 | if actual := tt.unmarshal(tt.actual); !reflect.DeepEqual(actual, tt.expectedv) { 106 | t.Errorf("BigEndian.%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actual) 107 | } 108 | } 109 | } 110 | 111 | func TestOtherTypes(t *testing.T) { 112 | tests := []struct { 113 | name string 114 | expected []byte 115 | expectedv interface{} 116 | actual []byte 117 | unmarshal func(b []byte) interface{} 118 | }{ 119 | { 120 | name: "Int32", 121 | expected: []byte{0x78, 0x56, 0x34, 0x12}, 122 | expectedv: int32(0x12345678), 123 | actual: PutInt32(0x12345678), 124 | unmarshal: func(b []byte) interface{} { return Int32(b) }, 125 | }, 126 | { 127 | name: "String", 128 | expected: []byte{0x74, 0x65, 0x73, 0x74}, 129 | expectedv: "test", 130 | actual: PutString("test"), 131 | unmarshal: func(b []byte) interface{} { return String(b) }, 132 | }, 133 | } 134 | for _, tt := range tests { 135 | if bytes.Compare(tt.actual, tt.expected) != 0 { 136 | t.Errorf("Put%s failure, expected: %#v, got: %#v", tt.name, tt.expected, tt.actual) 137 | } 138 | if actual := tt.unmarshal(tt.actual); !reflect.DeepEqual(actual, tt.expectedv) { 139 | t.Errorf("%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actual) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /compat_policy.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/google/nftables/expr" 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | const nft_RULE_COMPAT_F_INV uint32 = (1 << 1) 11 | const nft_RULE_COMPAT_F_MASK uint32 = nft_RULE_COMPAT_F_INV 12 | 13 | // Used by xt match or target like xt_tcpudp to set compat policy between xtables and nftables 14 | // https://elixir.bootlin.com/linux/v5.12/source/net/netfilter/nft_compat.c#L187 15 | type compatPolicy struct { 16 | Proto uint32 17 | Flag uint32 18 | } 19 | 20 | var xtMatchCompatMap map[string]*compatPolicy = map[string]*compatPolicy{ 21 | "tcp": { 22 | Proto: unix.IPPROTO_TCP, 23 | }, 24 | "udp": { 25 | Proto: unix.IPPROTO_UDP, 26 | }, 27 | "udplite": { 28 | Proto: unix.IPPROTO_UDPLITE, 29 | }, 30 | "tcpmss": { 31 | Proto: unix.IPPROTO_TCP, 32 | }, 33 | "sctp": { 34 | Proto: unix.IPPROTO_SCTP, 35 | }, 36 | "osf": { 37 | Proto: unix.IPPROTO_TCP, 38 | }, 39 | "ipcomp": { 40 | Proto: unix.IPPROTO_COMP, 41 | }, 42 | "esp": { 43 | Proto: unix.IPPROTO_ESP, 44 | }, 45 | } 46 | 47 | var xtTargetCompatMap map[string]*compatPolicy = map[string]*compatPolicy{ 48 | "TCPOPTSTRIP": { 49 | Proto: unix.IPPROTO_TCP, 50 | }, 51 | "TCPMSS": { 52 | Proto: unix.IPPROTO_TCP, 53 | }, 54 | } 55 | 56 | func getCompatPolicy(exprs []expr.Any) (*compatPolicy, error) { 57 | var exprItem expr.Any 58 | var compat *compatPolicy 59 | 60 | for _, iter := range exprs { 61 | var tmpExprItem expr.Any 62 | var tmpCompat *compatPolicy 63 | switch item := iter.(type) { 64 | case *expr.Match: 65 | if compat, ok := xtMatchCompatMap[item.Name]; ok { 66 | tmpCompat = compat 67 | tmpExprItem = item 68 | } else { 69 | continue 70 | } 71 | case *expr.Target: 72 | if compat, ok := xtTargetCompatMap[item.Name]; ok { 73 | tmpCompat = compat 74 | tmpExprItem = item 75 | } else { 76 | continue 77 | } 78 | default: 79 | continue 80 | } 81 | if compat == nil { 82 | compat = tmpCompat 83 | exprItem = tmpExprItem 84 | } else if *compat != *tmpCompat { 85 | return nil, fmt.Errorf("%#v and %#v has conflict compat policy %#v vs %#v", exprItem, tmpExprItem, compat, tmpCompat) 86 | } 87 | } 88 | return compat, nil 89 | } 90 | -------------------------------------------------------------------------------- /compat_policy_test.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/nftables/expr" 7 | "github.com/google/nftables/xt" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func TestGetCompatPolicy(t *testing.T) { 12 | // -tcp --dport 0:65534 --sport 0:65534 13 | tcpMatch := &expr.Match{ 14 | Name: "tcp", 15 | Info: &xt.Tcp{ 16 | SrcPorts: [2]uint16{0, 65534}, 17 | DstPorts: [2]uint16{0, 65534}, 18 | }, 19 | } 20 | 21 | // -udp --dport 0:65534 --sport 0:65534 22 | udpMatch := &expr.Match{ 23 | Name: "udp", 24 | Info: &xt.Udp{ 25 | SrcPorts: [2]uint16{0, 65534}, 26 | DstPorts: [2]uint16{0, 65534}, 27 | }, 28 | } 29 | 30 | // -j TCPMSS --set-mss 1460 31 | mess := xt.Unknown([]byte{1460 & 0xff, (1460 >> 8) & 0xff}) 32 | tcpMessTarget := &expr.Target{ 33 | Name: "TCPMESS", 34 | Info: &mess, 35 | } 36 | 37 | // -m state --state ESTABLISHED 38 | ctMatch := &expr.Match{ 39 | Name: "conntrack", 40 | Rev: 1, 41 | Info: &xt.ConntrackMtinfo1{ 42 | ConntrackMtinfoBase: xt.ConntrackMtinfoBase{ 43 | MatchFlags: 0x2001, 44 | }, 45 | StateMask: 0x02, 46 | }, 47 | } 48 | 49 | // compatPolicy.Proto should be tcp 50 | if compatPolicy, err := getCompatPolicy([]expr.Any{ 51 | tcpMatch, 52 | tcpMessTarget, 53 | ctMatch, 54 | }); err != nil { 55 | t.Fatalf("getCompatPolicy fail %#v", err) 56 | } else if compatPolicy.Proto != unix.IPPROTO_TCP { 57 | t.Fatalf("getCompatPolicy wrong %#v", compatPolicy) 58 | } 59 | 60 | // should conflict 61 | if _, err := getCompatPolicy([]expr.Any{ 62 | udpMatch, 63 | tcpMatch, 64 | }, 65 | ); err == nil { 66 | t.Fatalf("getCompatPolicy fail err should not be nil") 67 | } 68 | 69 | // compatPolicy should be nil 70 | if compatPolicy, err := getCompatPolicy([]expr.Any{ 71 | ctMatch, 72 | }); err != nil { 73 | t.Fatalf("getCompatPolicy fail %#v", err) 74 | } else if compatPolicy != nil { 75 | t.Fatalf("getCompatPolicy fail compat policy of conntrack match should be nil") 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nftables 16 | 17 | import ( 18 | "github.com/google/nftables/expr" 19 | "github.com/mdlayher/netlink" 20 | "golang.org/x/sys/unix" 21 | ) 22 | 23 | type CounterObj struct { 24 | Table *Table 25 | Name string // e.g. “fwded” 26 | 27 | Bytes uint64 28 | Packets uint64 29 | } 30 | 31 | func (c *CounterObj) unmarshal(ad *netlink.AttributeDecoder) error { 32 | for ad.Next() { 33 | switch ad.Type() { 34 | case unix.NFTA_COUNTER_BYTES: 35 | c.Bytes = ad.Uint64() 36 | case unix.NFTA_COUNTER_PACKETS: 37 | c.Packets = ad.Uint64() 38 | } 39 | } 40 | return ad.Err() 41 | } 42 | 43 | func (c *CounterObj) data() expr.Any { 44 | return &expr.Counter{ 45 | Bytes: c.Bytes, 46 | Packets: c.Packets, 47 | } 48 | } 49 | 50 | func (c *CounterObj) name() string { 51 | return c.Name 52 | } 53 | func (c *CounterObj) objType() ObjType { 54 | return ObjTypeCounter 55 | } 56 | 57 | func (c *CounterObj) table() *Table { 58 | return c.Table 59 | } 60 | 61 | func (c *CounterObj) family() TableFamily { 62 | return c.Table.Family 63 | } 64 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package nftables manipulates Linux nftables (the iptables successor). 16 | package nftables 17 | -------------------------------------------------------------------------------- /expr/bitwise.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Bitwise struct { 26 | SourceRegister uint32 27 | DestRegister uint32 28 | Len uint32 29 | Mask []byte 30 | Xor []byte 31 | } 32 | 33 | func (e *Bitwise) marshal(fam byte) ([]byte, error) { 34 | data, err := e.marshalData(fam) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return netlink.MarshalAttributes([]netlink.Attribute{ 39 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("bitwise\x00")}, 40 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 41 | }) 42 | } 43 | 44 | func (e *Bitwise) marshalData(fam byte) ([]byte, error) { 45 | mask, err := netlink.MarshalAttributes([]netlink.Attribute{ 46 | {Type: unix.NFTA_DATA_VALUE, Data: e.Mask}, 47 | }) 48 | if err != nil { 49 | return nil, err 50 | } 51 | xor, err := netlink.MarshalAttributes([]netlink.Attribute{ 52 | {Type: unix.NFTA_DATA_VALUE, Data: e.Xor}, 53 | }) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return netlink.MarshalAttributes([]netlink.Attribute{ 59 | {Type: unix.NFTA_BITWISE_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, 60 | {Type: unix.NFTA_BITWISE_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, 61 | {Type: unix.NFTA_BITWISE_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, 62 | {Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_MASK, Data: mask}, 63 | {Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_XOR, Data: xor}, 64 | }) 65 | } 66 | 67 | func (e *Bitwise) unmarshal(fam byte, data []byte) error { 68 | ad, err := netlink.NewAttributeDecoder(data) 69 | if err != nil { 70 | return err 71 | } 72 | ad.ByteOrder = binary.BigEndian 73 | for ad.Next() { 74 | switch ad.Type() { 75 | case unix.NFTA_BITWISE_SREG: 76 | e.SourceRegister = ad.Uint32() 77 | case unix.NFTA_BITWISE_DREG: 78 | e.DestRegister = ad.Uint32() 79 | case unix.NFTA_BITWISE_LEN: 80 | e.Len = ad.Uint32() 81 | case unix.NFTA_BITWISE_MASK: 82 | // Since NFTA_BITWISE_MASK is nested, it requires additional decoding 83 | ad.Nested(func(nad *netlink.AttributeDecoder) error { 84 | for nad.Next() { 85 | switch nad.Type() { 86 | case unix.NFTA_DATA_VALUE: 87 | e.Mask = nad.Bytes() 88 | } 89 | } 90 | return nil 91 | }) 92 | case unix.NFTA_BITWISE_XOR: 93 | // Since NFTA_BITWISE_XOR is nested, it requires additional decoding 94 | ad.Nested(func(nad *netlink.AttributeDecoder) error { 95 | for nad.Next() { 96 | switch nad.Type() { 97 | case unix.NFTA_DATA_VALUE: 98 | e.Xor = nad.Bytes() 99 | } 100 | } 101 | return nil 102 | }) 103 | } 104 | } 105 | return ad.Err() 106 | } 107 | -------------------------------------------------------------------------------- /expr/bitwise_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/netlink" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestBitwise(t *testing.T) { 13 | t.Parallel() 14 | tests := []struct { 15 | name string 16 | bw Bitwise 17 | }{ 18 | { 19 | name: "Unmarshal Bitwise IPv4 case", 20 | bw: Bitwise{ 21 | SourceRegister: 1, 22 | DestRegister: 2, 23 | Len: 4, 24 | // By specifying Xor to 0x0,0x0,0x0,0x0 and Mask to 0xff,0xff,0x0,0x0 25 | // an expression will match /16 IPv4 address. 26 | Xor: []byte{0x0, 0x0, 0x0, 0x0}, 27 | Mask: []byte{0xff, 0xff, 0x0, 0x0}, 28 | }, 29 | }, 30 | } 31 | 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | nbw := Bitwise{} 35 | data, err := tt.bw.marshal(0 /* don't care in this test */) 36 | if err != nil { 37 | t.Fatalf("marshal error: %+v", err) 38 | 39 | } 40 | ad, err := netlink.NewAttributeDecoder(data) 41 | if err != nil { 42 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 43 | } 44 | ad.ByteOrder = binary.BigEndian 45 | for ad.Next() { 46 | if ad.Type() == unix.NFTA_EXPR_DATA { 47 | if err := nbw.unmarshal(0, ad.Bytes()); err != nil { 48 | t.Errorf("unmarshal error: %+v", err) 49 | break 50 | } 51 | } 52 | } 53 | if !reflect.DeepEqual(tt.bw, nbw) { 54 | t.Fatalf("original %+v and recovered %+v Bitwise structs are different", tt.bw, nbw) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /expr/byteorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type ByteorderOp uint32 26 | 27 | const ( 28 | ByteorderNtoh ByteorderOp = unix.NFT_BYTEORDER_NTOH 29 | ByteorderHton ByteorderOp = unix.NFT_BYTEORDER_HTON 30 | ) 31 | 32 | type Byteorder struct { 33 | SourceRegister uint32 34 | DestRegister uint32 35 | Op ByteorderOp 36 | Len uint32 37 | Size uint32 38 | } 39 | 40 | func (e *Byteorder) marshal(fam byte) ([]byte, error) { 41 | data, err := e.marshalData(fam) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return netlink.MarshalAttributes([]netlink.Attribute{ 46 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("byteorder\x00")}, 47 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 48 | }) 49 | } 50 | 51 | func (e *Byteorder) marshalData(fam byte) ([]byte, error) { 52 | return netlink.MarshalAttributes([]netlink.Attribute{ 53 | {Type: unix.NFTA_BYTEORDER_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, 54 | {Type: unix.NFTA_BYTEORDER_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, 55 | {Type: unix.NFTA_BYTEORDER_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}, 56 | {Type: unix.NFTA_BYTEORDER_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, 57 | {Type: unix.NFTA_BYTEORDER_SIZE, Data: binaryutil.BigEndian.PutUint32(e.Size)}, 58 | }) 59 | } 60 | 61 | func (e *Byteorder) unmarshal(fam byte, data []byte) error { 62 | return fmt.Errorf("not yet implemented") 63 | } 64 | -------------------------------------------------------------------------------- /expr/connlimit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | const ( 26 | // Per https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1167 27 | NFTA_CONNLIMIT_UNSPEC = iota 28 | NFTA_CONNLIMIT_COUNT 29 | NFTA_CONNLIMIT_FLAGS 30 | NFT_CONNLIMIT_F_INV = 1 31 | ) 32 | 33 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/connlimit.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c 34 | type Connlimit struct { 35 | Count uint32 36 | Flags uint32 37 | } 38 | 39 | func (e *Connlimit) marshal(fam byte) ([]byte, error) { 40 | data, err := e.marshalData(fam) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return netlink.MarshalAttributes([]netlink.Attribute{ 46 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("connlimit\x00")}, 47 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 48 | }) 49 | } 50 | 51 | func (e *Connlimit) marshalData(fam byte) ([]byte, error) { 52 | return netlink.MarshalAttributes([]netlink.Attribute{ 53 | {Type: NFTA_CONNLIMIT_COUNT, Data: binaryutil.BigEndian.PutUint32(e.Count)}, 54 | {Type: NFTA_CONNLIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}, 55 | }) 56 | } 57 | 58 | func (e *Connlimit) unmarshal(fam byte, data []byte) error { 59 | ad, err := netlink.NewAttributeDecoder(data) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | ad.ByteOrder = binary.BigEndian 65 | for ad.Next() { 66 | switch ad.Type() { 67 | case NFTA_CONNLIMIT_COUNT: 68 | e.Count = binaryutil.BigEndian.Uint32(ad.Bytes()) 69 | case NFTA_CONNLIMIT_FLAGS: 70 | e.Flags = binaryutil.BigEndian.Uint32(ad.Bytes()) 71 | } 72 | } 73 | return ad.Err() 74 | } 75 | -------------------------------------------------------------------------------- /expr/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Counter struct { 26 | Bytes uint64 27 | Packets uint64 28 | } 29 | 30 | func (e *Counter) marshal(fam byte) ([]byte, error) { 31 | data, err := e.marshalData(fam) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return netlink.MarshalAttributes([]netlink.Attribute{ 37 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("counter\x00")}, 38 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 39 | }) 40 | } 41 | 42 | func (e *Counter) marshalData(fam byte) ([]byte, error) { 43 | return netlink.MarshalAttributes([]netlink.Attribute{ 44 | {Type: unix.NFTA_COUNTER_BYTES, Data: binaryutil.BigEndian.PutUint64(e.Bytes)}, 45 | {Type: unix.NFTA_COUNTER_PACKETS, Data: binaryutil.BigEndian.PutUint64(e.Packets)}, 46 | }) 47 | } 48 | 49 | func (e *Counter) unmarshal(fam byte, data []byte) error { 50 | ad, err := netlink.NewAttributeDecoder(data) 51 | if err != nil { 52 | return err 53 | } 54 | ad.ByteOrder = binary.BigEndian 55 | for ad.Next() { 56 | switch ad.Type() { 57 | case unix.NFTA_COUNTER_BYTES: 58 | e.Bytes = ad.Uint64() 59 | case unix.NFTA_COUNTER_PACKETS: 60 | e.Packets = ad.Uint64() 61 | } 62 | } 63 | return ad.Err() 64 | } 65 | -------------------------------------------------------------------------------- /expr/ct_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/netlink" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestCt(t *testing.T) { 13 | t.Parallel() 14 | tests := []struct { 15 | name string 16 | ct Ct 17 | }{ 18 | { 19 | name: "Unmarshal Ct status case", 20 | ct: Ct{ 21 | Register: 1, 22 | Key: CtKeySTATUS, 23 | }, 24 | }, 25 | { 26 | name: "Unmarshal Ct proto-dst direction original case", 27 | ct: Ct{ 28 | Register: 1, 29 | Key: CtKeyPROTODST, 30 | Direction: 0, // direction: original 31 | }, 32 | }, 33 | { 34 | name: "Unmarshal Ct src direction reply case", 35 | ct: Ct{ 36 | Register: 1, 37 | Key: CtKeySRC, 38 | Direction: 1, // direction: reply 39 | }, 40 | }, 41 | { 42 | name: "Unmarshal Ct source register case", 43 | ct: Ct{ 44 | Register: 1, 45 | Key: CtKeySRC, 46 | SourceRegister: true, 47 | }, 48 | }, 49 | { 50 | name: "Unmarshal Ct ip direction original case", 51 | ct: Ct{ 52 | Register: 1, 53 | Key: CtKeySRCIP, 54 | Direction: 0, 55 | }, 56 | }, 57 | { 58 | name: "Unmarshal Ct ip direction reply case", 59 | ct: Ct{ 60 | Register: 1, 61 | Key: CtKeySRCIP, 62 | Direction: 1, 63 | }, 64 | }, 65 | { 66 | name: "Unmarshal Ct ip6 direction original case", 67 | ct: Ct{ 68 | Register: 1, 69 | Key: CtKeySRCIP6, 70 | Direction: 0, 71 | }, 72 | }, 73 | { 74 | name: "Unmarshal Ct ip6 direction reply case", 75 | ct: Ct{ 76 | Register: 1, 77 | Key: CtKeyDSTIP6, 78 | Direction: 1, 79 | }, 80 | }, 81 | } 82 | 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | nct := Ct{} 86 | data, err := tt.ct.marshal(0 /* don't care in this test */) 87 | if err != nil { 88 | t.Fatalf("marshal error: %+v", err) 89 | 90 | } 91 | ad, err := netlink.NewAttributeDecoder(data) 92 | if err != nil { 93 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 94 | } 95 | ad.ByteOrder = binary.BigEndian 96 | for ad.Next() { 97 | if ad.Type() == unix.NFTA_EXPR_DATA { 98 | if err := nct.unmarshal(0, ad.Bytes()); err != nil { 99 | t.Errorf("unmarshal error: %+v", err) 100 | break 101 | } 102 | } 103 | } 104 | if !reflect.DeepEqual(tt.ct, nct) { 105 | t.Fatalf("original %+v and recovered %+v Ct structs are different", tt.ct, nct) 106 | } 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /expr/dup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Dup struct { 26 | RegAddr uint32 27 | RegDev uint32 28 | IsRegDevSet bool 29 | } 30 | 31 | func (e *Dup) marshal(fam byte) ([]byte, error) { 32 | data, err := e.marshalData(fam) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return netlink.MarshalAttributes([]netlink.Attribute{ 38 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("dup\x00")}, 39 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 40 | }) 41 | } 42 | 43 | func (e *Dup) marshalData(fam byte) ([]byte, error) { 44 | attrs := []netlink.Attribute{ 45 | {Type: unix.NFTA_DUP_SREG_ADDR, Data: binaryutil.BigEndian.PutUint32(e.RegAddr)}, 46 | } 47 | 48 | if e.IsRegDevSet { 49 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_DUP_SREG_DEV, Data: binaryutil.BigEndian.PutUint32(e.RegDev)}) 50 | } 51 | 52 | return netlink.MarshalAttributes(attrs) 53 | } 54 | 55 | func (e *Dup) unmarshal(fam byte, data []byte) error { 56 | ad, err := netlink.NewAttributeDecoder(data) 57 | if err != nil { 58 | return err 59 | } 60 | ad.ByteOrder = binary.BigEndian 61 | for ad.Next() { 62 | switch ad.Type() { 63 | case unix.NFTA_DUP_SREG_ADDR: 64 | e.RegAddr = ad.Uint32() 65 | case unix.NFTA_DUP_SREG_DEV: 66 | e.RegDev = ad.Uint32() 67 | } 68 | } 69 | return ad.Err() 70 | } 71 | -------------------------------------------------------------------------------- /expr/dynset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | "time" 20 | 21 | "github.com/google/nftables/binaryutil" 22 | "github.com/google/nftables/internal/parseexprfunc" 23 | "github.com/mdlayher/netlink" 24 | "golang.org/x/sys/unix" 25 | ) 26 | 27 | // Not yet supported by unix package 28 | // https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=2027-2036 29 | const ( 30 | NFTA_DYNSET_EXPRESSIONS = 0xa 31 | NFT_DYNSET_F_EXPR = (1 << 1) 32 | ) 33 | 34 | // Dynset represent a rule dynamically adding or updating a set or a map based on an incoming packet. 35 | type Dynset struct { 36 | SrcRegKey uint32 37 | SrcRegData uint32 38 | SetID uint32 39 | SetName string 40 | Operation uint32 41 | Timeout time.Duration 42 | Invert bool 43 | Exprs []Any 44 | } 45 | 46 | func (e *Dynset) marshal(fam byte) ([]byte, error) { 47 | opData, err := e.marshalData(fam) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return netlink.MarshalAttributes([]netlink.Attribute{ 53 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("dynset\x00")}, 54 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: opData}, 55 | }) 56 | } 57 | 58 | func (e *Dynset) marshalData(fam byte) ([]byte, error) { 59 | // See: https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c 60 | var opAttrs []netlink.Attribute 61 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_KEY, Data: binaryutil.BigEndian.PutUint32(e.SrcRegKey)}) 62 | if e.SrcRegData != 0 { 63 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_DATA, Data: binaryutil.BigEndian.PutUint32(e.SrcRegData)}) 64 | } 65 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_OP, Data: binaryutil.BigEndian.PutUint32(e.Operation)}) 66 | if e.Timeout != 0 { 67 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))}) 68 | } 69 | var flags uint32 70 | if e.Invert { 71 | flags |= unix.NFT_DYNSET_F_INV 72 | } 73 | 74 | opAttrs = append(opAttrs, 75 | netlink.Attribute{Type: unix.NFTA_DYNSET_SET_NAME, Data: []byte(e.SetName + "\x00")}, 76 | netlink.Attribute{Type: unix.NFTA_DYNSET_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)}) 77 | 78 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n170 79 | if len(e.Exprs) > 0 { 80 | switch len(e.Exprs) { 81 | case 1: 82 | exprData, err := Marshal(fam, e.Exprs[0]) 83 | if err != nil { 84 | return nil, err 85 | } 86 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_EXPR, Data: exprData}) 87 | default: 88 | flags |= NFT_DYNSET_F_EXPR 89 | var elemAttrs []netlink.Attribute 90 | for _, ex := range e.Exprs { 91 | exprData, err := Marshal(fam, ex) 92 | if err != nil { 93 | return nil, err 94 | } 95 | elemAttrs = append(elemAttrs, netlink.Attribute{Type: unix.NFTA_LIST_ELEM, Data: exprData}) 96 | } 97 | elemData, err := netlink.MarshalAttributes(elemAttrs) 98 | if err != nil { 99 | return nil, err 100 | } 101 | opAttrs = append(opAttrs, netlink.Attribute{Type: NFTA_DYNSET_EXPRESSIONS, Data: elemData}) 102 | } 103 | } 104 | 105 | opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}) 106 | return netlink.MarshalAttributes(opAttrs) 107 | } 108 | 109 | func (e *Dynset) unmarshal(fam byte, data []byte) error { 110 | ad, err := netlink.NewAttributeDecoder(data) 111 | if err != nil { 112 | return err 113 | } 114 | ad.ByteOrder = binary.BigEndian 115 | for ad.Next() { 116 | switch ad.Type() { 117 | case unix.NFTA_DYNSET_SET_NAME: 118 | e.SetName = ad.String() 119 | case unix.NFTA_DYNSET_SET_ID: 120 | e.SetID = ad.Uint32() 121 | case unix.NFTA_DYNSET_SREG_KEY: 122 | e.SrcRegKey = ad.Uint32() 123 | case unix.NFTA_DYNSET_SREG_DATA: 124 | e.SrcRegData = ad.Uint32() 125 | case unix.NFTA_DYNSET_OP: 126 | e.Operation = ad.Uint32() 127 | case unix.NFTA_DYNSET_TIMEOUT: 128 | e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64())) 129 | case unix.NFTA_DYNSET_FLAGS: 130 | e.Invert = (ad.Uint32() & unix.NFT_DYNSET_F_INV) != 0 131 | case unix.NFTA_DYNSET_EXPR: 132 | exprs, err := parseexprfunc.ParseExprBytesFunc(fam, ad) 133 | if err != nil { 134 | return err 135 | } 136 | e.setInterfaceExprs(exprs) 137 | case NFTA_DYNSET_EXPRESSIONS: 138 | exprs, err := parseexprfunc.ParseExprMsgFunc(fam, ad.Bytes()) 139 | if err != nil { 140 | return err 141 | } 142 | e.setInterfaceExprs(exprs) 143 | } 144 | } 145 | return ad.Err() 146 | } 147 | 148 | func (e *Dynset) setInterfaceExprs(exprs []interface{}) { 149 | e.Exprs = make([]Any, len(exprs)) 150 | for i := range exprs { 151 | e.Exprs[i] = exprs[i].(Any) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /expr/exthdr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type ExthdrOp uint32 26 | 27 | const ( 28 | ExthdrOpIpv6 ExthdrOp = unix.NFT_EXTHDR_OP_IPV6 29 | ExthdrOpTcpopt ExthdrOp = unix.NFT_EXTHDR_OP_TCPOPT 30 | ) 31 | 32 | type Exthdr struct { 33 | DestRegister uint32 34 | Type uint8 35 | Offset uint32 36 | Len uint32 37 | Flags uint32 38 | Op ExthdrOp 39 | SourceRegister uint32 40 | } 41 | 42 | func (e *Exthdr) marshal(fam byte) ([]byte, error) { 43 | data, err := e.marshalData(fam) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return netlink.MarshalAttributes([]netlink.Attribute{ 48 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("exthdr\x00")}, 49 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 50 | }) 51 | } 52 | 53 | func (e *Exthdr) marshalData(fam byte) ([]byte, error) { 54 | var attr []netlink.Attribute 55 | 56 | // Operations are differentiated by the Op and whether the SourceRegister 57 | // or DestRegister is set. Mixing them results in EOPNOTSUPP. 58 | if e.SourceRegister != 0 { 59 | attr = []netlink.Attribute{ 60 | {Type: unix.NFTA_EXTHDR_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}} 61 | } else { 62 | attr = []netlink.Attribute{ 63 | {Type: unix.NFTA_EXTHDR_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}} 64 | } 65 | 66 | attr = append(attr, 67 | netlink.Attribute{Type: unix.NFTA_EXTHDR_TYPE, Data: []byte{e.Type}}, 68 | netlink.Attribute{Type: unix.NFTA_EXTHDR_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, 69 | netlink.Attribute{Type: unix.NFTA_EXTHDR_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, 70 | netlink.Attribute{Type: unix.NFTA_EXTHDR_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}) 71 | 72 | // Flags is only set if DREG is set 73 | if e.DestRegister != 0 { 74 | attr = append(attr, 75 | netlink.Attribute{Type: unix.NFTA_EXTHDR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}) 76 | } 77 | 78 | return netlink.MarshalAttributes(attr) 79 | } 80 | 81 | func (e *Exthdr) unmarshal(fam byte, data []byte) error { 82 | ad, err := netlink.NewAttributeDecoder(data) 83 | if err != nil { 84 | return err 85 | } 86 | ad.ByteOrder = binary.BigEndian 87 | for ad.Next() { 88 | switch ad.Type() { 89 | case unix.NFTA_EXTHDR_DREG: 90 | e.DestRegister = ad.Uint32() 91 | case unix.NFTA_EXTHDR_TYPE: 92 | e.Type = ad.Uint8() 93 | case unix.NFTA_EXTHDR_OFFSET: 94 | e.Offset = ad.Uint32() 95 | case unix.NFTA_EXTHDR_LEN: 96 | e.Len = ad.Uint32() 97 | case unix.NFTA_EXTHDR_FLAGS: 98 | e.Flags = ad.Uint32() 99 | case unix.NFTA_EXTHDR_OP: 100 | e.Op = ExthdrOp(ad.Uint32()) 101 | case unix.NFTA_EXTHDR_SREG: 102 | e.SourceRegister = ad.Uint32() 103 | } 104 | } 105 | return ad.Err() 106 | } 107 | -------------------------------------------------------------------------------- /expr/exthdr_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/netlink" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestExthdr(t *testing.T) { 13 | t.Parallel() 14 | tests := []struct { 15 | name string 16 | eh Exthdr 17 | }{ 18 | { 19 | name: "Unmarshal Exthdr DestRegister case", 20 | eh: Exthdr{ 21 | DestRegister: 1, 22 | Type: 2, 23 | Offset: 3, 24 | Len: 4, 25 | Flags: 5, 26 | Op: ExthdrOpTcpopt, 27 | SourceRegister: 0, 28 | }, 29 | }, 30 | { 31 | name: "Unmarshal Exthdr SourceRegister case", 32 | eh: Exthdr{ 33 | SourceRegister: 1, 34 | Type: 2, 35 | Offset: 3, 36 | Len: 4, 37 | Op: ExthdrOpTcpopt, 38 | DestRegister: 0, 39 | Flags: 0, 40 | }, 41 | }, 42 | } 43 | 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | neh := Exthdr{} 47 | data, err := tt.eh.marshal(0 /* don't care in this test */) 48 | if err != nil { 49 | t.Fatalf("marshal error: %+v", err) 50 | 51 | } 52 | ad, err := netlink.NewAttributeDecoder(data) 53 | if err != nil { 54 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 55 | } 56 | ad.ByteOrder = binary.BigEndian 57 | for ad.Next() { 58 | if ad.Type() == unix.NFTA_EXPR_DATA { 59 | if err := neh.unmarshal(0, ad.Bytes()); err != nil { 60 | t.Errorf("unmarshal error: %+v", err) 61 | break 62 | } 63 | } 64 | } 65 | if !reflect.DeepEqual(tt.eh, neh) { 66 | t.Fatalf("original %+v and recovered %+v Exthdr structs are different", tt.eh, neh) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /expr/fib.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | // Fib defines fib expression structure 26 | type Fib struct { 27 | Register uint32 28 | ResultOIF bool 29 | ResultOIFNAME bool 30 | ResultADDRTYPE bool 31 | FlagSADDR bool 32 | FlagDADDR bool 33 | FlagMARK bool 34 | FlagIIF bool 35 | FlagOIF bool 36 | FlagPRESENT bool 37 | } 38 | 39 | func (e *Fib) marshal(fam byte) ([]byte, error) { 40 | data, err := e.marshalData(fam) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return netlink.MarshalAttributes([]netlink.Attribute{ 45 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("fib\x00")}, 46 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 47 | }) 48 | } 49 | 50 | func (e *Fib) marshalData(fam byte) ([]byte, error) { 51 | data := []byte{} 52 | reg, err := netlink.MarshalAttributes([]netlink.Attribute{ 53 | {Type: unix.NFTA_FIB_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, 54 | }) 55 | if err != nil { 56 | return nil, err 57 | } 58 | data = append(data, reg...) 59 | flags := uint32(0) 60 | if e.FlagSADDR { 61 | flags |= unix.NFTA_FIB_F_SADDR 62 | } 63 | if e.FlagDADDR { 64 | flags |= unix.NFTA_FIB_F_DADDR 65 | } 66 | if e.FlagMARK { 67 | flags |= unix.NFTA_FIB_F_MARK 68 | } 69 | if e.FlagIIF { 70 | flags |= unix.NFTA_FIB_F_IIF 71 | } 72 | if e.FlagOIF { 73 | flags |= unix.NFTA_FIB_F_OIF 74 | } 75 | if e.FlagPRESENT { 76 | flags |= unix.NFTA_FIB_F_PRESENT 77 | } 78 | if flags != 0 { 79 | flg, err := netlink.MarshalAttributes([]netlink.Attribute{ 80 | {Type: unix.NFTA_FIB_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, 81 | }) 82 | if err != nil { 83 | return nil, err 84 | } 85 | data = append(data, flg...) 86 | } 87 | results := uint32(0) 88 | if e.ResultOIF { 89 | results |= unix.NFT_FIB_RESULT_OIF 90 | } 91 | if e.ResultOIFNAME { 92 | results |= unix.NFT_FIB_RESULT_OIFNAME 93 | } 94 | if e.ResultADDRTYPE { 95 | results |= unix.NFT_FIB_RESULT_ADDRTYPE 96 | } 97 | if results != 0 { 98 | rslt, err := netlink.MarshalAttributes([]netlink.Attribute{ 99 | {Type: unix.NFTA_FIB_RESULT, Data: binaryutil.BigEndian.PutUint32(results)}, 100 | }) 101 | if err != nil { 102 | return nil, err 103 | } 104 | data = append(data, rslt...) 105 | } 106 | return data, nil 107 | } 108 | 109 | func (e *Fib) unmarshal(fam byte, data []byte) error { 110 | ad, err := netlink.NewAttributeDecoder(data) 111 | if err != nil { 112 | return err 113 | } 114 | ad.ByteOrder = binary.BigEndian 115 | for ad.Next() { 116 | switch ad.Type() { 117 | case unix.NFTA_FIB_DREG: 118 | e.Register = ad.Uint32() 119 | case unix.NFTA_FIB_RESULT: 120 | result := ad.Uint32() 121 | switch result { 122 | case unix.NFT_FIB_RESULT_OIF: 123 | e.ResultOIF = true 124 | case unix.NFT_FIB_RESULT_OIFNAME: 125 | e.ResultOIFNAME = true 126 | case unix.NFT_FIB_RESULT_ADDRTYPE: 127 | e.ResultADDRTYPE = true 128 | } 129 | case unix.NFTA_FIB_FLAGS: 130 | flags := ad.Uint32() 131 | e.FlagSADDR = (flags & unix.NFTA_FIB_F_SADDR) != 0 132 | e.FlagDADDR = (flags & unix.NFTA_FIB_F_DADDR) != 0 133 | e.FlagMARK = (flags & unix.NFTA_FIB_F_MARK) != 0 134 | e.FlagIIF = (flags & unix.NFTA_FIB_F_IIF) != 0 135 | e.FlagOIF = (flags & unix.NFTA_FIB_F_OIF) != 0 136 | e.FlagPRESENT = (flags & unix.NFTA_FIB_F_PRESENT) != 0 137 | } 138 | } 139 | return ad.Err() 140 | } 141 | -------------------------------------------------------------------------------- /expr/flow_offload.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/mdlayher/netlink" 21 | "golang.org/x/sys/unix" 22 | ) 23 | 24 | const NFTNL_EXPR_FLOW_TABLE_NAME = 1 25 | 26 | type FlowOffload struct { 27 | Name string 28 | } 29 | 30 | func (e *FlowOffload) marshal(fam byte) ([]byte, error) { 31 | data, err := e.marshalData(fam) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return netlink.MarshalAttributes([]netlink.Attribute{ 37 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("flow_offload\x00")}, 38 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 39 | }) 40 | } 41 | 42 | func (e *FlowOffload) marshalData(fam byte) ([]byte, error) { 43 | return netlink.MarshalAttributes([]netlink.Attribute{ 44 | {Type: NFTNL_EXPR_FLOW_TABLE_NAME, Data: []byte(e.Name)}, 45 | }) 46 | } 47 | 48 | func (e *FlowOffload) unmarshal(fam byte, data []byte) error { 49 | ad, err := netlink.NewAttributeDecoder(data) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | ad.ByteOrder = binary.BigEndian 55 | for ad.Next() { 56 | switch ad.Type() { 57 | case NFTNL_EXPR_FLOW_TABLE_NAME: 58 | e.Name = ad.String() 59 | } 60 | } 61 | 62 | return ad.Err() 63 | } 64 | -------------------------------------------------------------------------------- /expr/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type HashType uint32 26 | 27 | const ( 28 | HashTypeJenkins HashType = unix.NFT_HASH_JENKINS 29 | HashTypeSym HashType = unix.NFT_HASH_SYM 30 | ) 31 | 32 | // Hash defines type for nftables internal hashing functions 33 | type Hash struct { 34 | SourceRegister uint32 35 | DestRegister uint32 36 | Length uint32 37 | Modulus uint32 38 | Seed uint32 39 | Offset uint32 40 | Type HashType 41 | } 42 | 43 | func (e *Hash) marshal(fam byte) ([]byte, error) { 44 | data, err := e.marshalData(fam) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return netlink.MarshalAttributes([]netlink.Attribute{ 49 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("hash\x00")}, 50 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 51 | }) 52 | } 53 | 54 | func (e *Hash) marshalData(fam byte) ([]byte, error) { 55 | hashAttrs := []netlink.Attribute{ 56 | {Type: unix.NFTA_HASH_SREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.SourceRegister))}, 57 | {Type: unix.NFTA_HASH_DREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.DestRegister))}, 58 | {Type: unix.NFTA_HASH_LEN, Data: binaryutil.BigEndian.PutUint32(uint32(e.Length))}, 59 | {Type: unix.NFTA_HASH_MODULUS, Data: binaryutil.BigEndian.PutUint32(uint32(e.Modulus))}, 60 | } 61 | if e.Seed != 0 { 62 | hashAttrs = append(hashAttrs, netlink.Attribute{ 63 | Type: unix.NFTA_HASH_SEED, Data: binaryutil.BigEndian.PutUint32(uint32(e.Seed)), 64 | }) 65 | } 66 | hashAttrs = append(hashAttrs, []netlink.Attribute{ 67 | {Type: unix.NFTA_HASH_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.Offset))}, 68 | {Type: unix.NFTA_HASH_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, 69 | }...) 70 | return netlink.MarshalAttributes(hashAttrs) 71 | } 72 | 73 | func (e *Hash) unmarshal(fam byte, data []byte) error { 74 | ad, err := netlink.NewAttributeDecoder(data) 75 | if err != nil { 76 | return err 77 | } 78 | ad.ByteOrder = binary.BigEndian 79 | for ad.Next() { 80 | switch ad.Type() { 81 | case unix.NFTA_HASH_SREG: 82 | e.SourceRegister = ad.Uint32() 83 | case unix.NFTA_HASH_DREG: 84 | e.DestRegister = ad.Uint32() 85 | case unix.NFTA_HASH_LEN: 86 | e.Length = ad.Uint32() 87 | case unix.NFTA_HASH_MODULUS: 88 | e.Modulus = ad.Uint32() 89 | case unix.NFTA_HASH_SEED: 90 | e.Seed = ad.Uint32() 91 | case unix.NFTA_HASH_OFFSET: 92 | e.Offset = ad.Uint32() 93 | case unix.NFTA_HASH_TYPE: 94 | e.Type = HashType(ad.Uint32()) 95 | } 96 | } 97 | return ad.Err() 98 | } 99 | -------------------------------------------------------------------------------- /expr/immediate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | 21 | "github.com/google/nftables/binaryutil" 22 | "github.com/mdlayher/netlink" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | type Immediate struct { 27 | Register uint32 28 | Data []byte 29 | } 30 | 31 | func (e *Immediate) marshal(fam byte) ([]byte, error) { 32 | data, err := e.marshalData(fam) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return netlink.MarshalAttributes([]netlink.Attribute{ 37 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")}, 38 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 39 | }) 40 | } 41 | 42 | func (e *Immediate) marshalData(fam byte) ([]byte, error) { 43 | immData, err := netlink.MarshalAttributes([]netlink.Attribute{ 44 | {Type: unix.NFTA_DATA_VALUE, Data: e.Data}, 45 | }) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return netlink.MarshalAttributes([]netlink.Attribute{ 51 | {Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, 52 | {Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData}, 53 | }) 54 | } 55 | 56 | func (e *Immediate) unmarshal(fam byte, data []byte) error { 57 | ad, err := netlink.NewAttributeDecoder(data) 58 | if err != nil { 59 | return err 60 | } 61 | ad.ByteOrder = binary.BigEndian 62 | for ad.Next() { 63 | switch ad.Type() { 64 | case unix.NFTA_IMMEDIATE_DREG: 65 | e.Register = ad.Uint32() 66 | case unix.NFTA_IMMEDIATE_DATA: 67 | nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes()) 68 | if err != nil { 69 | return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err) 70 | } 71 | for nestedAD.Next() { 72 | switch nestedAD.Type() { 73 | case unix.NFTA_DATA_VALUE: 74 | e.Data = nestedAD.Bytes() 75 | } 76 | } 77 | if nestedAD.Err() != nil { 78 | return fmt.Errorf("decoding immediate: %v", nestedAD.Err()) 79 | } 80 | } 81 | } 82 | return ad.Err() 83 | } 84 | -------------------------------------------------------------------------------- /expr/limit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | "errors" 20 | "fmt" 21 | 22 | "github.com/google/nftables/binaryutil" 23 | "github.com/mdlayher/netlink" 24 | "golang.org/x/sys/unix" 25 | ) 26 | 27 | // LimitType represents the type of the limit expression. 28 | type LimitType uint32 29 | 30 | // Imported from the nft_limit_type enum in netfilter/nf_tables.h. 31 | const ( 32 | LimitTypePkts LimitType = unix.NFT_LIMIT_PKTS 33 | LimitTypePktBytes LimitType = unix.NFT_LIMIT_PKT_BYTES 34 | ) 35 | 36 | // LimitTime represents the limit unit. 37 | type LimitTime uint64 38 | 39 | // Possible limit unit values. 40 | const ( 41 | LimitTimeSecond LimitTime = 1 42 | LimitTimeMinute LimitTime = 60 43 | LimitTimeHour LimitTime = 60 * 60 44 | LimitTimeDay LimitTime = 60 * 60 * 24 45 | LimitTimeWeek LimitTime = 60 * 60 * 24 * 7 46 | ) 47 | 48 | func limitTime(value uint64) (LimitTime, error) { 49 | switch LimitTime(value) { 50 | case LimitTimeSecond: 51 | return LimitTimeSecond, nil 52 | case LimitTimeMinute: 53 | return LimitTimeMinute, nil 54 | case LimitTimeHour: 55 | return LimitTimeHour, nil 56 | case LimitTimeDay: 57 | return LimitTimeDay, nil 58 | case LimitTimeWeek: 59 | return LimitTimeWeek, nil 60 | default: 61 | return 0, fmt.Errorf("expr: invalid limit unit value %d", value) 62 | } 63 | } 64 | 65 | // Limit represents a rate limit expression. 66 | type Limit struct { 67 | Type LimitType 68 | Rate uint64 69 | Over bool 70 | Unit LimitTime 71 | Burst uint32 72 | } 73 | 74 | func (l *Limit) marshal(fam byte) ([]byte, error) { 75 | data, err := l.marshalData(fam) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return netlink.MarshalAttributes([]netlink.Attribute{ 81 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("limit\x00")}, 82 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 83 | }) 84 | } 85 | 86 | func (l *Limit) marshalData(fam byte) ([]byte, error) { 87 | var flags uint32 88 | if l.Over { 89 | flags = unix.NFT_LIMIT_F_INV 90 | } 91 | attrs := []netlink.Attribute{ 92 | {Type: unix.NFTA_LIMIT_RATE, Data: binaryutil.BigEndian.PutUint64(l.Rate)}, 93 | {Type: unix.NFTA_LIMIT_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))}, 94 | {Type: unix.NFTA_LIMIT_BURST, Data: binaryutil.BigEndian.PutUint32(l.Burst)}, 95 | {Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))}, 96 | {Type: unix.NFTA_LIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, 97 | } 98 | 99 | return netlink.MarshalAttributes(attrs) 100 | } 101 | 102 | func (l *Limit) unmarshal(fam byte, data []byte) error { 103 | ad, err := netlink.NewAttributeDecoder(data) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | ad.ByteOrder = binary.BigEndian 109 | for ad.Next() { 110 | switch ad.Type() { 111 | case unix.NFTA_LIMIT_RATE: 112 | l.Rate = ad.Uint64() 113 | case unix.NFTA_LIMIT_UNIT: 114 | l.Unit, err = limitTime(ad.Uint64()) 115 | if err != nil { 116 | return err 117 | } 118 | case unix.NFTA_LIMIT_BURST: 119 | l.Burst = ad.Uint32() 120 | case unix.NFTA_LIMIT_TYPE: 121 | l.Type = LimitType(ad.Uint32()) 122 | if l.Type != LimitTypePkts && l.Type != LimitTypePktBytes { 123 | return fmt.Errorf("expr: invalid limit type %d", l.Type) 124 | } 125 | case unix.NFTA_LIMIT_FLAGS: 126 | l.Over = (ad.Uint32() & unix.NFT_LIMIT_F_INV) != 0 127 | default: 128 | return errors.New("expr: unhandled limit netlink attribute") 129 | } 130 | } 131 | return ad.Err() 132 | } 133 | -------------------------------------------------------------------------------- /expr/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type LogLevel uint32 26 | 27 | const ( 28 | // See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca#n1226 29 | LogLevelEmerg LogLevel = iota 30 | LogLevelAlert 31 | LogLevelCrit 32 | LogLevelErr 33 | LogLevelWarning 34 | LogLevelNotice 35 | LogLevelInfo 36 | LogLevelDebug 37 | LogLevelAudit 38 | ) 39 | 40 | type LogFlags uint32 41 | 42 | const ( 43 | // See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_log.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca 44 | LogFlagsTCPSeq LogFlags = 0x01 << iota 45 | LogFlagsTCPOpt 46 | LogFlagsIPOpt 47 | LogFlagsUID 48 | LogFlagsNFLog 49 | LogFlagsMACDecode 50 | LogFlagsMask LogFlags = 0x2f 51 | ) 52 | 53 | // Log defines type for NFT logging 54 | // See https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n25 55 | type Log struct { 56 | Level LogLevel 57 | // Refers to log flags (flags all, flags ip options, ...) 58 | Flags LogFlags 59 | // Equivalent to expression flags. 60 | // Indicates that an option is set by setting a bit 61 | // on index referred by the NFTA_LOG_* value. 62 | // See https://cs.opensource.google/go/x/sys/+/3681064d:unix/ztypes_linux.go;l=2126;drc=3681064d51587c1db0324b3d5c23c2ddbcff6e8f 63 | Key uint32 64 | Snaplen uint32 65 | Group uint16 66 | QThreshold uint16 67 | // Log prefix string content 68 | Data []byte 69 | } 70 | 71 | func (e *Log) marshal(fam byte) ([]byte, error) { 72 | data, err := e.marshalData(fam) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | return netlink.MarshalAttributes([]netlink.Attribute{ 78 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("log\x00")}, 79 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 80 | }) 81 | } 82 | 83 | func (e *Log) marshalData(fam byte) ([]byte, error) { 84 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n129 85 | attrs := make([]netlink.Attribute, 0) 86 | if e.Key&(1<= /* sic! */ XTablesExtensionNameMaxLen { 37 | name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. 38 | } 39 | // Marshalling assumes that the correct Info type for the particular table 40 | // family and Match revision has been set. 41 | info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info) 42 | if err != nil { 43 | return nil, err 44 | } 45 | attrs := []netlink.Attribute{ 46 | {Type: unix.NFTA_MATCH_NAME, Data: []byte(name + "\x00")}, 47 | {Type: unix.NFTA_MATCH_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, 48 | {Type: unix.NFTA_MATCH_INFO, Data: info}, 49 | } 50 | return netlink.MarshalAttributes(attrs) 51 | } 52 | 53 | func (e *Match) unmarshal(fam byte, data []byte) error { 54 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 55 | ad, err := netlink.NewAttributeDecoder(data) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | var info []byte 61 | ad.ByteOrder = binary.BigEndian 62 | for ad.Next() { 63 | switch ad.Type() { 64 | case unix.NFTA_MATCH_NAME: 65 | // We are forgiving here, accepting any length and even missing terminating \x00. 66 | e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) 67 | case unix.NFTA_MATCH_REV: 68 | e.Rev = ad.Uint32() 69 | case unix.NFTA_MATCH_INFO: 70 | info = ad.Bytes() 71 | } 72 | } 73 | if err = ad.Err(); err != nil { 74 | return err 75 | } 76 | e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /expr/match_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/google/nftables/xt" 9 | "github.com/mdlayher/netlink" 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | func TestMatch(t *testing.T) { 14 | t.Parallel() 15 | payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) 16 | tests := []struct { 17 | name string 18 | mtch Match 19 | }{ 20 | { 21 | name: "Unmarshal Target case", 22 | mtch: Match{ 23 | Name: "foobar", 24 | Rev: 1234567890, 25 | Info: &payload, 26 | }, 27 | }, 28 | } 29 | 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | ntgt := Match{} 33 | data, err := tt.mtch.marshal(0 /* don't care in this test */) 34 | if err != nil { 35 | t.Fatalf("marshal error: %+v", err) 36 | 37 | } 38 | ad, err := netlink.NewAttributeDecoder(data) 39 | if err != nil { 40 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 41 | } 42 | ad.ByteOrder = binary.BigEndian 43 | for ad.Next() { 44 | if ad.Type() == unix.NFTA_EXPR_DATA { 45 | if err := ntgt.unmarshal(0 /* don't care in this test */, ad.Bytes()); err != nil { 46 | t.Errorf("unmarshal error: %+v", err) 47 | break 48 | } 49 | } 50 | } 51 | if !reflect.DeepEqual(tt.mtch, ntgt) { 52 | t.Fatalf("original %+v and recovered %+v Match structs are different", tt.mtch, ntgt) 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /expr/meta_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/netlink" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestMeta(t *testing.T) { 13 | t.Parallel() 14 | tests := []struct { 15 | name string 16 | meta Meta 17 | }{ 18 | { 19 | name: "Unmarshal Meta DestRegister case", 20 | meta: Meta{ 21 | Key: 1, 22 | SourceRegister: false, 23 | Register: 1, 24 | }, 25 | }, 26 | { 27 | name: "Unmarshal Meta SourceRegister case", 28 | meta: Meta{ 29 | Key: 1, 30 | SourceRegister: true, 31 | Register: 1, 32 | }, 33 | }, 34 | } 35 | 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | nMeta := Meta{} 39 | data, err := tt.meta.marshal(0 /* don't care in this test */) 40 | if err != nil { 41 | t.Fatalf("marshal error: %+v", err) 42 | 43 | } 44 | ad, err := netlink.NewAttributeDecoder(data) 45 | if err != nil { 46 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 47 | } 48 | ad.ByteOrder = binary.BigEndian 49 | for ad.Next() { 50 | if ad.Type() == unix.NFTA_EXPR_DATA { 51 | if err := nMeta.unmarshal(0, ad.Bytes()); err != nil { 52 | t.Errorf("unmarshal error: %+v", err) 53 | break 54 | } 55 | } 56 | } 57 | if !reflect.DeepEqual(tt.meta, nMeta) { 58 | t.Fatalf("original %+v and recovered %+v Exthdr structs are different", tt.meta, nMeta) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /expr/nat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type NATType uint32 26 | 27 | // Possible NATType values. 28 | const ( 29 | NATTypeSourceNAT NATType = unix.NFT_NAT_SNAT 30 | NATTypeDestNAT NATType = unix.NFT_NAT_DNAT 31 | ) 32 | 33 | type NAT struct { 34 | Type NATType 35 | Family uint32 // TODO: typed const 36 | RegAddrMin uint32 37 | RegAddrMax uint32 38 | RegProtoMin uint32 39 | RegProtoMax uint32 40 | Random bool 41 | FullyRandom bool 42 | Persistent bool 43 | Prefix bool 44 | Specified bool 45 | } 46 | 47 | // |00048|N-|00001| |len |flags| type| 48 | // |00008|--|00001| |len |flags| type| 49 | // | 6e 61 74 00 | | data | n a t 50 | // |00036|N-|00002| |len |flags| type| 51 | // |00008|--|00001| |len |flags| type| NFTA_NAT_TYPE 52 | // | 00 00 00 01 | | data | NFT_NAT_DNAT 53 | // |00008|--|00002| |len |flags| type| NFTA_NAT_FAMILY 54 | // | 00 00 00 02 | | data | NFPROTO_IPV4 55 | // |00008|--|00003| |len |flags| type| NFTA_NAT_REG_ADDR_MIN 56 | // | 00 00 00 01 | | data | reg 1 57 | // |00008|--|00005| |len |flags| type| NFTA_NAT_REG_PROTO_MIN 58 | // | 00 00 00 02 | | data | reg 2 59 | 60 | func (e *NAT) marshal(fam byte) ([]byte, error) { 61 | data, err := e.marshalData(fam) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return netlink.MarshalAttributes([]netlink.Attribute{ 66 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("nat\x00")}, 67 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 68 | }) 69 | } 70 | 71 | func (e *NAT) marshalData(fam byte) ([]byte, error) { 72 | attrs := []netlink.Attribute{ 73 | {Type: unix.NFTA_NAT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, 74 | {Type: unix.NFTA_NAT_FAMILY, Data: binaryutil.BigEndian.PutUint32(e.Family)}, 75 | } 76 | if e.RegAddrMin != 0 { 77 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMin)}) 78 | if e.RegAddrMax != 0 { 79 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMax)}) 80 | } 81 | } 82 | if e.RegProtoMin != 0 { 83 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}) 84 | if e.RegProtoMax != 0 { 85 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)}) 86 | } 87 | } 88 | flags := uint32(0) 89 | if e.Random { 90 | flags |= NF_NAT_RANGE_PROTO_RANDOM 91 | } 92 | if e.FullyRandom { 93 | flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY 94 | } 95 | if e.Persistent { 96 | flags |= NF_NAT_RANGE_PERSISTENT 97 | } 98 | if e.Prefix { 99 | flags |= NF_NAT_RANGE_PREFIX 100 | } 101 | if e.Specified { 102 | flags |= NF_NAT_RANGE_PROTO_SPECIFIED 103 | } 104 | if flags != 0 { 105 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}) 106 | } 107 | 108 | return netlink.MarshalAttributes(attrs) 109 | } 110 | 111 | func (e *NAT) unmarshal(fam byte, data []byte) error { 112 | ad, err := netlink.NewAttributeDecoder(data) 113 | if err != nil { 114 | return err 115 | } 116 | ad.ByteOrder = binary.BigEndian 117 | for ad.Next() { 118 | switch ad.Type() { 119 | case unix.NFTA_NAT_TYPE: 120 | e.Type = NATType(ad.Uint32()) 121 | case unix.NFTA_NAT_FAMILY: 122 | e.Family = ad.Uint32() 123 | case unix.NFTA_NAT_REG_ADDR_MIN: 124 | e.RegAddrMin = ad.Uint32() 125 | case unix.NFTA_NAT_REG_ADDR_MAX: 126 | e.RegAddrMax = ad.Uint32() 127 | case unix.NFTA_NAT_REG_PROTO_MIN: 128 | e.RegProtoMin = ad.Uint32() 129 | case unix.NFTA_NAT_REG_PROTO_MAX: 130 | e.RegProtoMax = ad.Uint32() 131 | case unix.NFTA_NAT_FLAGS: 132 | flags := ad.Uint32() 133 | e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0 134 | e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0 135 | e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0 136 | e.Prefix = (flags & NF_NAT_RANGE_PREFIX) != 0 137 | e.Specified = (flags & NF_NAT_RANGE_PROTO_SPECIFIED) != 0 138 | } 139 | } 140 | return ad.Err() 141 | } 142 | -------------------------------------------------------------------------------- /expr/nat_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/netlink" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestNat(t *testing.T) { 13 | t.Parallel() 14 | tests := []struct { 15 | name string 16 | nat NAT 17 | }{ 18 | { 19 | name: "Unmarshal DNAT specified case", 20 | nat: NAT{ 21 | Type: NATTypeDestNAT, 22 | Family: unix.NFPROTO_IPV4, 23 | RegAddrMin: 1, 24 | RegProtoMin: 2, 25 | Specified: true, 26 | }, 27 | }, 28 | { 29 | name: "Unmarshal SNAT persistent case", 30 | nat: NAT{ 31 | Type: NATTypeSourceNAT, 32 | Family: unix.NFPROTO_IPV4, 33 | RegAddrMin: 1, 34 | RegProtoMin: 2, 35 | Persistent: true, 36 | }, 37 | }, 38 | } 39 | 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | nnat := NAT{} 43 | data, err := tt.nat.marshal(0 /* don't care in this test */) 44 | if err != nil { 45 | t.Fatalf("marshal error: %+v", err) 46 | 47 | } 48 | ad, err := netlink.NewAttributeDecoder(data) 49 | if err != nil { 50 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 51 | } 52 | ad.ByteOrder = binary.BigEndian 53 | for ad.Next() { 54 | if ad.Type() == unix.NFTA_EXPR_DATA { 55 | if err := nnat.unmarshal(0, ad.Bytes()); err != nil { 56 | t.Errorf("unmarshal error: %+v", err) 57 | break 58 | } 59 | } 60 | } 61 | if !reflect.DeepEqual(tt.nat, nnat) { 62 | t.Fatalf("original %+v and recovered %+v Ct structs are different", tt.nat, nnat) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /expr/notrack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "github.com/mdlayher/netlink" 19 | "golang.org/x/sys/unix" 20 | ) 21 | 22 | type Notrack struct{} 23 | 24 | func (e *Notrack) marshal(fam byte) ([]byte, error) { 25 | return netlink.MarshalAttributes([]netlink.Attribute{ 26 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("notrack\x00")}, 27 | }) 28 | } 29 | 30 | func (e *Notrack) marshalData(fam byte) ([]byte, error) { 31 | return []byte("notrack\x00"), nil 32 | } 33 | 34 | func (e *Notrack) unmarshal(fam byte, data []byte) error { 35 | ad, err := netlink.NewAttributeDecoder(data) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return ad.Err() 42 | } 43 | -------------------------------------------------------------------------------- /expr/numgen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | 21 | "github.com/google/nftables/binaryutil" 22 | "github.com/mdlayher/netlink" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | // Numgen defines Numgen expression structure 27 | type Numgen struct { 28 | Register uint32 29 | Modulus uint32 30 | Type uint32 31 | Offset uint32 32 | } 33 | 34 | func (e *Numgen) marshal(fam byte) ([]byte, error) { 35 | data, err := e.marshalData(fam) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return netlink.MarshalAttributes([]netlink.Attribute{ 41 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("numgen\x00")}, 42 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 43 | }) 44 | } 45 | 46 | func (e *Numgen) marshalData(fam byte) ([]byte, error) { 47 | // Currently only two types are supported, failing if Type is not of two known types 48 | switch e.Type { 49 | case unix.NFT_NG_INCREMENTAL: 50 | case unix.NFT_NG_RANDOM: 51 | default: 52 | return nil, fmt.Errorf("unsupported numgen type %d", e.Type) 53 | } 54 | 55 | return netlink.MarshalAttributes([]netlink.Attribute{ 56 | {Type: unix.NFTA_NG_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, 57 | {Type: unix.NFTA_NG_MODULUS, Data: binaryutil.BigEndian.PutUint32(e.Modulus)}, 58 | {Type: unix.NFTA_NG_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)}, 59 | {Type: unix.NFTA_NG_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, 60 | }) 61 | } 62 | 63 | func (e *Numgen) unmarshal(fam byte, data []byte) error { 64 | ad, err := netlink.NewAttributeDecoder(data) 65 | if err != nil { 66 | return err 67 | } 68 | ad.ByteOrder = binary.BigEndian 69 | for ad.Next() { 70 | switch ad.Type() { 71 | case unix.NFTA_NG_DREG: 72 | e.Register = ad.Uint32() 73 | case unix.NFTA_NG_MODULUS: 74 | e.Modulus = ad.Uint32() 75 | case unix.NFTA_NG_TYPE: 76 | e.Type = ad.Uint32() 77 | case unix.NFTA_NG_OFFSET: 78 | e.Offset = ad.Uint32() 79 | } 80 | } 81 | return ad.Err() 82 | } 83 | -------------------------------------------------------------------------------- /expr/objref.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Objref struct { 26 | Type int // TODO: enum 27 | Name string 28 | } 29 | 30 | func (e *Objref) marshal(fam byte) ([]byte, error) { 31 | data, err := e.marshalData(fam) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return netlink.MarshalAttributes([]netlink.Attribute{ 37 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("objref\x00")}, 38 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 39 | }) 40 | } 41 | 42 | func (e *Objref) marshalData(fam byte) ([]byte, error) { 43 | return netlink.MarshalAttributes([]netlink.Attribute{ 44 | {Type: unix.NFTA_OBJREF_IMM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, 45 | {Type: unix.NFTA_OBJREF_IMM_NAME, Data: []byte(e.Name)}, // NOT \x00-terminated?! 46 | }) 47 | } 48 | 49 | func (e *Objref) unmarshal(fam byte, data []byte) error { 50 | ad, err := netlink.NewAttributeDecoder(data) 51 | if err != nil { 52 | return err 53 | } 54 | ad.ByteOrder = binary.BigEndian 55 | for ad.Next() { 56 | switch ad.Type() { 57 | case unix.NFTA_OBJREF_IMM_TYPE: 58 | e.Type = int(ad.Uint32()) 59 | case unix.NFTA_OBJREF_IMM_NAME: 60 | e.Name = ad.String() 61 | } 62 | } 63 | return ad.Err() 64 | } 65 | -------------------------------------------------------------------------------- /expr/payload.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type PayloadBase uint32 26 | type PayloadCsumType uint32 27 | type PayloadOperationType uint32 28 | 29 | // Possible PayloadBase values. 30 | const ( 31 | PayloadBaseLLHeader PayloadBase = unix.NFT_PAYLOAD_LL_HEADER 32 | PayloadBaseNetworkHeader PayloadBase = unix.NFT_PAYLOAD_NETWORK_HEADER 33 | PayloadBaseTransportHeader PayloadBase = unix.NFT_PAYLOAD_TRANSPORT_HEADER 34 | ) 35 | 36 | // Possible PayloadCsumType values. 37 | const ( 38 | CsumTypeNone PayloadCsumType = unix.NFT_PAYLOAD_CSUM_NONE 39 | CsumTypeInet PayloadCsumType = unix.NFT_PAYLOAD_CSUM_INET 40 | ) 41 | 42 | // Possible PayloadOperationType values. 43 | const ( 44 | PayloadLoad PayloadOperationType = iota 45 | PayloadWrite 46 | ) 47 | 48 | type Payload struct { 49 | OperationType PayloadOperationType 50 | DestRegister uint32 51 | SourceRegister uint32 52 | Base PayloadBase 53 | Offset uint32 54 | Len uint32 55 | CsumType PayloadCsumType 56 | CsumOffset uint32 57 | CsumFlags uint32 58 | } 59 | 60 | func (e *Payload) marshal(fam byte) ([]byte, error) { 61 | data, err := e.marshalData(fam) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return netlink.MarshalAttributes([]netlink.Attribute{ 67 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("payload\x00")}, 68 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 69 | }) 70 | } 71 | 72 | func (e *Payload) marshalData(fam byte) ([]byte, error) { 73 | var attrs []netlink.Attribute 74 | 75 | if e.OperationType == PayloadWrite { 76 | attrs = []netlink.Attribute{ 77 | {Type: unix.NFTA_PAYLOAD_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, 78 | } 79 | } else { 80 | attrs = []netlink.Attribute{ 81 | {Type: unix.NFTA_PAYLOAD_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, 82 | } 83 | } 84 | 85 | attrs = append(attrs, 86 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_BASE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Base))}, 87 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, 88 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, 89 | ) 90 | 91 | if e.CsumType > 0 { 92 | attrs = append(attrs, 93 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumType))}, 94 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumOffset))}, 95 | ) 96 | if e.CsumFlags > 0 { 97 | attrs = append(attrs, 98 | netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.CsumFlags)}, 99 | ) 100 | } 101 | } 102 | 103 | return netlink.MarshalAttributes(attrs) 104 | } 105 | 106 | func (e *Payload) unmarshal(fam byte, data []byte) error { 107 | ad, err := netlink.NewAttributeDecoder(data) 108 | if err != nil { 109 | return err 110 | } 111 | ad.ByteOrder = binary.BigEndian 112 | for ad.Next() { 113 | switch ad.Type() { 114 | case unix.NFTA_PAYLOAD_DREG: 115 | e.DestRegister = ad.Uint32() 116 | case unix.NFTA_PAYLOAD_SREG: 117 | e.SourceRegister = ad.Uint32() 118 | e.OperationType = PayloadWrite 119 | case unix.NFTA_PAYLOAD_BASE: 120 | e.Base = PayloadBase(ad.Uint32()) 121 | case unix.NFTA_PAYLOAD_OFFSET: 122 | e.Offset = ad.Uint32() 123 | case unix.NFTA_PAYLOAD_LEN: 124 | e.Len = ad.Uint32() 125 | case unix.NFTA_PAYLOAD_CSUM_TYPE: 126 | e.CsumType = PayloadCsumType(ad.Uint32()) 127 | case unix.NFTA_PAYLOAD_CSUM_OFFSET: 128 | e.CsumOffset = ad.Uint32() 129 | case unix.NFTA_PAYLOAD_CSUM_FLAGS: 130 | e.CsumFlags = ad.Uint32() 131 | } 132 | } 133 | return ad.Err() 134 | } 135 | -------------------------------------------------------------------------------- /expr/queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type QueueAttribute uint16 26 | 27 | type QueueFlag uint16 28 | 29 | // Possible QueueAttribute values 30 | const ( 31 | QueueNum QueueAttribute = unix.NFTA_QUEUE_NUM 32 | QueueTotal QueueAttribute = unix.NFTA_QUEUE_TOTAL 33 | QueueFlags QueueAttribute = unix.NFTA_QUEUE_FLAGS 34 | 35 | QueueFlagBypass QueueFlag = unix.NFT_QUEUE_FLAG_BYPASS 36 | QueueFlagFanout QueueFlag = unix.NFT_QUEUE_FLAG_CPU_FANOUT 37 | QueueFlagMask QueueFlag = unix.NFT_QUEUE_FLAG_MASK 38 | ) 39 | 40 | type Queue struct { 41 | Num uint16 42 | Total uint16 43 | Flag QueueFlag 44 | } 45 | 46 | func (e *Queue) marshal(fam byte) ([]byte, error) { 47 | data, err := e.marshalData(fam) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return netlink.MarshalAttributes([]netlink.Attribute{ 52 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("queue\x00")}, 53 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 54 | }) 55 | } 56 | 57 | func (e *Queue) marshalData(fam byte) ([]byte, error) { 58 | if e.Total == 0 { 59 | e.Total = 1 // The total default value is 1 60 | } 61 | return netlink.MarshalAttributes([]netlink.Attribute{ 62 | {Type: unix.NFTA_QUEUE_NUM, Data: binaryutil.BigEndian.PutUint16(e.Num)}, 63 | {Type: unix.NFTA_QUEUE_TOTAL, Data: binaryutil.BigEndian.PutUint16(e.Total)}, 64 | {Type: unix.NFTA_QUEUE_FLAGS, Data: binaryutil.BigEndian.PutUint16(uint16(e.Flag))}, 65 | }) 66 | } 67 | 68 | func (e *Queue) unmarshal(fam byte, data []byte) error { 69 | ad, err := netlink.NewAttributeDecoder(data) 70 | if err != nil { 71 | return err 72 | } 73 | ad.ByteOrder = binary.BigEndian 74 | for ad.Next() { 75 | switch ad.Type() { 76 | case unix.NFTA_QUEUE_NUM: 77 | e.Num = ad.Uint16() 78 | case unix.NFTA_QUEUE_TOTAL: 79 | e.Total = ad.Uint16() 80 | case unix.NFTA_QUEUE_FLAGS: 81 | e.Flag = QueueFlag(ad.Uint16()) 82 | } 83 | } 84 | return ad.Err() 85 | } 86 | -------------------------------------------------------------------------------- /expr/quota.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | // Quota defines a threshold against a number of bytes. 26 | type Quota struct { 27 | Bytes uint64 28 | Consumed uint64 29 | Over bool 30 | } 31 | 32 | func (q *Quota) marshal(fam byte) ([]byte, error) { 33 | data, err := q.marshalData(fam) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return netlink.MarshalAttributes([]netlink.Attribute{ 39 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("quota\x00")}, 40 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 41 | }) 42 | } 43 | 44 | func (q *Quota) marshalData(fam byte) ([]byte, error) { 45 | attrs := []netlink.Attribute{ 46 | {Type: unix.NFTA_QUOTA_BYTES, Data: binaryutil.BigEndian.PutUint64(q.Bytes)}, 47 | {Type: unix.NFTA_QUOTA_CONSUMED, Data: binaryutil.BigEndian.PutUint64(q.Consumed)}, 48 | } 49 | 50 | flags := uint32(0) 51 | if q.Over { 52 | flags = unix.NFT_QUOTA_F_INV 53 | } 54 | attrs = append(attrs, netlink.Attribute{ 55 | Type: unix.NFTA_QUOTA_FLAGS, 56 | Data: binaryutil.BigEndian.PutUint32(flags), 57 | }) 58 | 59 | return netlink.MarshalAttributes(attrs) 60 | } 61 | 62 | func (q *Quota) unmarshal(fam byte, data []byte) error { 63 | ad, err := netlink.NewAttributeDecoder(data) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | ad.ByteOrder = binary.BigEndian 69 | for ad.Next() { 70 | switch ad.Type() { 71 | case unix.NFTA_QUOTA_BYTES: 72 | q.Bytes = ad.Uint64() 73 | case unix.NFTA_QUOTA_CONSUMED: 74 | q.Consumed = ad.Uint64() 75 | case unix.NFTA_QUOTA_FLAGS: 76 | q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) != 0 77 | } 78 | } 79 | return ad.Err() 80 | } 81 | -------------------------------------------------------------------------------- /expr/range.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | // Range implements range expression 26 | type Range struct { 27 | Op CmpOp 28 | Register uint32 29 | FromData []byte 30 | ToData []byte 31 | } 32 | 33 | func (e *Range) marshal(fam byte) ([]byte, error) { 34 | data, err := e.marshalData(fam) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return netlink.MarshalAttributes([]netlink.Attribute{ 40 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("range\x00")}, 41 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 42 | }) 43 | } 44 | 45 | func (e *Range) marshalData(fam byte) ([]byte, error) { 46 | var attrs []netlink.Attribute 47 | var err error 48 | var rangeFromData, rangeToData []byte 49 | 50 | if e.Register > 0 { 51 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}) 52 | } 53 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}) 54 | if len(e.FromData) > 0 { 55 | rangeFromData, err = nestedAttr(e.FromData, unix.NFTA_RANGE_FROM_DATA) 56 | if err != nil { 57 | return nil, err 58 | } 59 | } 60 | if len(e.ToData) > 0 { 61 | rangeToData, err = nestedAttr(e.ToData, unix.NFTA_RANGE_TO_DATA) 62 | if err != nil { 63 | return nil, err 64 | } 65 | } 66 | data, err := netlink.MarshalAttributes(attrs) 67 | if err != nil { 68 | return nil, err 69 | } 70 | data = append(data, rangeFromData...) 71 | data = append(data, rangeToData...) 72 | return data, nil 73 | } 74 | 75 | func (e *Range) unmarshal(fam byte, data []byte) error { 76 | ad, err := netlink.NewAttributeDecoder(data) 77 | if err != nil { 78 | return err 79 | } 80 | ad.ByteOrder = binary.BigEndian 81 | for ad.Next() { 82 | switch ad.Type() { 83 | case unix.NFTA_RANGE_OP: 84 | e.Op = CmpOp(ad.Uint32()) 85 | case unix.NFTA_RANGE_SREG: 86 | e.Register = ad.Uint32() 87 | case unix.NFTA_RANGE_FROM_DATA: 88 | ad.Do(func(b []byte) error { 89 | ad, err := netlink.NewAttributeDecoder(b) 90 | if err != nil { 91 | return err 92 | } 93 | ad.ByteOrder = binary.BigEndian 94 | if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE { 95 | ad.Do(func(b []byte) error { 96 | e.FromData = b 97 | return nil 98 | }) 99 | } 100 | return ad.Err() 101 | }) 102 | case unix.NFTA_RANGE_TO_DATA: 103 | ad.Do(func(b []byte) error { 104 | ad, err := netlink.NewAttributeDecoder(b) 105 | if err != nil { 106 | return err 107 | } 108 | ad.ByteOrder = binary.BigEndian 109 | if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE { 110 | ad.Do(func(b []byte) error { 111 | e.ToData = b 112 | return nil 113 | }) 114 | } 115 | return ad.Err() 116 | }) 117 | } 118 | } 119 | return ad.Err() 120 | } 121 | 122 | func nestedAttr(data []byte, attrType uint16) ([]byte, error) { 123 | ae := netlink.NewAttributeEncoder() 124 | ae.Do(unix.NLA_F_NESTED|attrType, func() ([]byte, error) { 125 | nae := netlink.NewAttributeEncoder() 126 | nae.ByteOrder = binary.BigEndian 127 | nae.Bytes(unix.NFTA_DATA_VALUE, data) 128 | 129 | return nae.Encode() 130 | }) 131 | return ae.Encode() 132 | } 133 | -------------------------------------------------------------------------------- /expr/redirect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Redir struct { 26 | RegisterProtoMin uint32 27 | RegisterProtoMax uint32 28 | Flags uint32 29 | } 30 | 31 | func (e *Redir) marshal(fam byte) ([]byte, error) { 32 | data, err := e.marshalData(fam) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return netlink.MarshalAttributes([]netlink.Attribute{ 38 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("redir\x00")}, 39 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 40 | }) 41 | } 42 | 43 | func (e *Redir) marshalData(fam byte) ([]byte, error) { 44 | var attrs []netlink.Attribute 45 | if e.RegisterProtoMin > 0 { 46 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMin)}) 47 | } 48 | if e.RegisterProtoMax > 0 { 49 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMax)}) 50 | } 51 | if e.Flags > 0 { 52 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}) 53 | } 54 | 55 | return netlink.MarshalAttributes(attrs) 56 | } 57 | 58 | func (e *Redir) unmarshal(fam byte, data []byte) error { 59 | ad, err := netlink.NewAttributeDecoder(data) 60 | if err != nil { 61 | return err 62 | } 63 | ad.ByteOrder = binary.BigEndian 64 | for ad.Next() { 65 | switch ad.Type() { 66 | case unix.NFTA_REDIR_REG_PROTO_MIN: 67 | e.RegisterProtoMin = ad.Uint32() 68 | case unix.NFTA_REDIR_REG_PROTO_MAX: 69 | e.RegisterProtoMax = ad.Uint32() 70 | case unix.NFTA_REDIR_FLAGS: 71 | e.Flags = ad.Uint32() 72 | } 73 | } 74 | return ad.Err() 75 | } 76 | -------------------------------------------------------------------------------- /expr/reject.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type Reject struct { 26 | Type uint32 27 | Code uint8 28 | } 29 | 30 | func (e *Reject) marshal(fam byte) ([]byte, error) { 31 | data, err := e.marshalData(fam) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return netlink.MarshalAttributes([]netlink.Attribute{ 36 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("reject\x00")}, 37 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 38 | }) 39 | } 40 | 41 | func (e *Reject) marshalData(fam byte) ([]byte, error) { 42 | return netlink.MarshalAttributes([]netlink.Attribute{ 43 | {Type: unix.NFTA_REJECT_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)}, 44 | {Type: unix.NFTA_REJECT_ICMP_CODE, Data: []byte{e.Code}}, 45 | }) 46 | } 47 | 48 | func (e *Reject) unmarshal(fam byte, data []byte) error { 49 | ad, err := netlink.NewAttributeDecoder(data) 50 | if err != nil { 51 | return err 52 | } 53 | ad.ByteOrder = binary.BigEndian 54 | for ad.Next() { 55 | switch ad.Type() { 56 | case unix.NFTA_REJECT_TYPE: 57 | e.Type = ad.Uint32() 58 | case unix.NFTA_REJECT_ICMP_CODE: 59 | e.Code = ad.Uint8() 60 | } 61 | } 62 | return ad.Err() 63 | } 64 | -------------------------------------------------------------------------------- /expr/rt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type RtKey uint32 26 | 27 | const ( 28 | RtClassid RtKey = unix.NFT_RT_CLASSID 29 | RtNexthop4 RtKey = unix.NFT_RT_NEXTHOP4 30 | RtNexthop6 RtKey = unix.NFT_RT_NEXTHOP6 31 | RtTCPMSS RtKey = unix.NFT_RT_TCPMSS 32 | ) 33 | 34 | type Rt struct { 35 | Register uint32 36 | Key RtKey 37 | } 38 | 39 | func (e *Rt) marshal(fam byte) ([]byte, error) { 40 | data, err := e.marshalData(fam) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return netlink.MarshalAttributes([]netlink.Attribute{ 45 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("rt\x00")}, 46 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 47 | }) 48 | } 49 | 50 | func (e *Rt) marshalData(fam byte) ([]byte, error) { 51 | return netlink.MarshalAttributes([]netlink.Attribute{ 52 | {Type: unix.NFTA_RT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, 53 | {Type: unix.NFTA_RT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, 54 | }) 55 | } 56 | 57 | func (e *Rt) unmarshal(fam byte, data []byte) error { 58 | return fmt.Errorf("not yet implemented") 59 | } 60 | -------------------------------------------------------------------------------- /expr/secmark.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/mdlayher/netlink" 21 | "golang.org/x/sys/unix" 22 | ) 23 | 24 | // From https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=be0bae0ad31b0adb506f96de083f52a2bd0d4fbf#n1338 25 | const ( 26 | NFTA_SECMARK_CTX = 0x01 27 | ) 28 | 29 | type SecMark struct { 30 | Ctx string 31 | } 32 | 33 | func (e *SecMark) marshal(fam byte) ([]byte, error) { 34 | data, err := e.marshalData(fam) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return netlink.MarshalAttributes([]netlink.Attribute{ 39 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("secmark\x00")}, 40 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 41 | }) 42 | } 43 | 44 | func (e *SecMark) marshalData(fam byte) ([]byte, error) { 45 | attrs := []netlink.Attribute{ 46 | {Type: NFTA_SECMARK_CTX, Data: []byte(e.Ctx)}, 47 | } 48 | return netlink.MarshalAttributes(attrs) 49 | } 50 | 51 | func (e *SecMark) unmarshal(fam byte, data []byte) error { 52 | ad, err := netlink.NewAttributeDecoder(data) 53 | if err != nil { 54 | return err 55 | } 56 | ad.ByteOrder = binary.BigEndian 57 | for ad.Next() { 58 | switch ad.Type() { 59 | case NFTA_SECMARK_CTX: 60 | e.Ctx = ad.String() 61 | } 62 | } 63 | return ad.Err() 64 | } 65 | -------------------------------------------------------------------------------- /expr/socket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "golang.org/x/sys/unix" 21 | 22 | "github.com/google/nftables/binaryutil" 23 | "github.com/mdlayher/netlink" 24 | ) 25 | 26 | type Socket struct { 27 | Key SocketKey 28 | Level uint32 29 | Register uint32 30 | } 31 | 32 | type SocketKey uint32 33 | 34 | const ( 35 | // TODO, Once the constants below are available in golang.org/x/sys/unix, switch to use those. 36 | NFTA_SOCKET_KEY = 1 37 | NFTA_SOCKET_DREG = 2 38 | NFTA_SOCKET_LEVEL = 3 39 | 40 | NFT_SOCKET_TRANSPARENT = 0 41 | NFT_SOCKET_MARK = 1 42 | NFT_SOCKET_WILDCARD = 2 43 | NFT_SOCKET_CGROUPV2 = 3 44 | 45 | SocketKeyTransparent SocketKey = NFT_SOCKET_TRANSPARENT 46 | SocketKeyMark SocketKey = NFT_SOCKET_MARK 47 | SocketKeyWildcard SocketKey = NFT_SOCKET_WILDCARD 48 | SocketKeyCgroupv2 SocketKey = NFT_SOCKET_CGROUPV2 49 | ) 50 | 51 | func (e *Socket) marshal(fam byte) ([]byte, error) { 52 | exprData, err := e.marshalData(fam) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return netlink.MarshalAttributes([]netlink.Attribute{ 57 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("socket\x00")}, 58 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData}, 59 | }) 60 | } 61 | 62 | func (e *Socket) marshalData(fam byte) ([]byte, error) { 63 | // NOTE: Socket.Level is only used when Socket.Key == SocketKeyCgroupv2. But `nft` always encoding it. Check link below: 64 | // http://git.netfilter.org/nftables/tree/src/netlink_linearize.c?id=0583bac241ea18c9d7f61cb20ca04faa1e043b78#n319 65 | return netlink.MarshalAttributes( 66 | []netlink.Attribute{ 67 | {Type: NFTA_SOCKET_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, 68 | {Type: NFTA_SOCKET_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, 69 | {Type: NFTA_SOCKET_LEVEL, Data: binaryutil.BigEndian.PutUint32(uint32(e.Level))}, 70 | }, 71 | ) 72 | } 73 | 74 | func (e *Socket) unmarshal(fam byte, data []byte) error { 75 | ad, err := netlink.NewAttributeDecoder(data) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | ad.ByteOrder = binary.BigEndian 81 | for ad.Next() { 82 | switch ad.Type() { 83 | case NFTA_SOCKET_DREG: 84 | e.Register = ad.Uint32() 85 | case NFTA_SOCKET_KEY: 86 | e.Key = SocketKey(ad.Uint32()) 87 | case NFTA_SOCKET_LEVEL: 88 | e.Level = ad.Uint32() 89 | } 90 | } 91 | return ad.Err() 92 | } 93 | -------------------------------------------------------------------------------- /expr/socket_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/mdlayher/netlink" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | func TestSocket(t *testing.T) { 27 | t.Parallel() 28 | tests := []struct { 29 | name string 30 | socket Socket 31 | }{ 32 | { 33 | name: "Unmarshal `socket transparent`", 34 | socket: Socket{ 35 | Key: SocketKeyTransparent, 36 | Level: 0, 37 | Register: 1, 38 | }, 39 | }, 40 | { 41 | name: "Unmarshal `socket cgroup level 5`", 42 | socket: Socket{ 43 | Key: SocketKeyCgroupv2, 44 | Level: 5, 45 | Register: 1, 46 | }, 47 | }, 48 | { 49 | name: "Unmarshal `socket cgroup level 1`", 50 | socket: Socket{ 51 | Key: SocketKeyCgroupv2, 52 | Level: 1, 53 | Register: 1, 54 | }, 55 | }, 56 | { 57 | name: "Unmarshal `socket wildcard`", 58 | socket: Socket{ 59 | Key: SocketKeyWildcard, 60 | Level: 0, 61 | Register: 1, 62 | }, 63 | }, 64 | { 65 | name: "Unmarshal `socket mark`", 66 | socket: Socket{ 67 | Key: SocketKeyMark, 68 | Level: 0, 69 | Register: 1, 70 | }, 71 | }, 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | nSocket := Socket{} 77 | data, err := tt.socket.marshal(0 /* don't care in this test */) 78 | if err != nil { 79 | t.Fatalf("marshal error: %+v", err) 80 | 81 | } 82 | ad, err := netlink.NewAttributeDecoder(data) 83 | if err != nil { 84 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 85 | } 86 | ad.ByteOrder = binary.BigEndian 87 | for ad.Next() { 88 | if ad.Type() == unix.NFTA_EXPR_DATA { 89 | if err := nSocket.unmarshal(0, ad.Bytes()); err != nil { 90 | t.Errorf("unmarshal error: %+v", err) 91 | break 92 | } 93 | } 94 | } 95 | if !reflect.DeepEqual(tt.socket, nSocket) { 96 | t.Fatalf("original %+v and recovered %+v Socket structs are different", tt.socket, nSocket) 97 | } 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /expr/synproxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | type SynProxy struct { 26 | Mss uint16 27 | Wscale uint8 28 | Timestamp bool 29 | SackPerm bool 30 | // Probably not expected to be set by users 31 | // https://github.com/torvalds/linux/blob/521b1e7f4cf0b05a47995b103596978224b380a8/net/netfilter/nft_synproxy.c#L30-L31 32 | Ecn bool 33 | // True when Mss is set to a value or if 0 is an intended value of Mss 34 | MssValueSet bool 35 | // True when Wscale is set to a value or if 0 is an intended value of Wscale 36 | WscaleValueSet bool 37 | } 38 | 39 | // From https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=be0bae0ad31b0adb506f96de083f52a2bd0d4fbf#n1723 40 | // Currently not available in golang.org/x/sys/unix 41 | const ( 42 | NFTA_SYNPROXY_MSS = 0x01 43 | NFTA_SYNPROXY_WSCALE = 0x02 44 | NFTA_SYNPROXY_FLAGS = 0x03 45 | ) 46 | 47 | // From https://github.com/torvalds/linux/blob/521b1e7f4cf0b05a47995b103596978224b380a8/include/uapi/linux/netfilter/nf_synproxy.h#L7-L15 48 | // Currently not available in golang.org/x/sys/unix 49 | const ( 50 | NF_SYNPROXY_OPT_MSS = 0x01 51 | NF_SYNPROXY_OPT_WSCALE = 0x02 52 | NF_SYNPROXY_OPT_SACK_PERM = 0x04 53 | NF_SYNPROXY_OPT_TIMESTAMP = 0x08 54 | NF_SYNPROXY_OPT_ECN = 0x10 55 | ) 56 | 57 | func (e *SynProxy) marshal(fam byte) ([]byte, error) { 58 | data, err := e.marshalData(fam) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return netlink.MarshalAttributes([]netlink.Attribute{ 63 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("synproxy\x00")}, 64 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 65 | }) 66 | } 67 | 68 | func (e *SynProxy) marshalData(fam byte) ([]byte, error) { 69 | var flags uint32 70 | if e.Mss != 0 || e.MssValueSet { 71 | flags |= NF_SYNPROXY_OPT_MSS 72 | } 73 | if e.Wscale != 0 || e.WscaleValueSet { 74 | flags |= NF_SYNPROXY_OPT_WSCALE 75 | } 76 | if e.SackPerm { 77 | flags |= NF_SYNPROXY_OPT_SACK_PERM 78 | } 79 | if e.Timestamp { 80 | flags |= NF_SYNPROXY_OPT_TIMESTAMP 81 | } 82 | if e.Ecn { 83 | flags |= NF_SYNPROXY_OPT_ECN 84 | } 85 | attrs := []netlink.Attribute{ 86 | {Type: NFTA_SYNPROXY_MSS, Data: binaryutil.BigEndian.PutUint16(e.Mss)}, 87 | {Type: NFTA_SYNPROXY_WSCALE, Data: []byte{e.Wscale}}, 88 | {Type: NFTA_SYNPROXY_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, 89 | } 90 | return netlink.MarshalAttributes(attrs) 91 | } 92 | 93 | func (e *SynProxy) unmarshal(fam byte, data []byte) error { 94 | ad, err := netlink.NewAttributeDecoder(data) 95 | if err != nil { 96 | return err 97 | } 98 | ad.ByteOrder = binary.BigEndian 99 | for ad.Next() { 100 | switch ad.Type() { 101 | case NFTA_SYNPROXY_MSS: 102 | e.Mss = ad.Uint16() 103 | case NFTA_SYNPROXY_WSCALE: 104 | e.Wscale = ad.Uint8() 105 | case NFTA_SYNPROXY_FLAGS: 106 | flags := ad.Uint32() 107 | checkFlag := func(flag uint32) bool { 108 | return (flags & flag) == flag 109 | } 110 | e.MssValueSet = checkFlag(NF_SYNPROXY_OPT_MSS) 111 | e.WscaleValueSet = checkFlag(NF_SYNPROXY_OPT_WSCALE) 112 | e.SackPerm = checkFlag(NF_SYNPROXY_OPT_SACK_PERM) 113 | e.Timestamp = checkFlag(NF_SYNPROXY_OPT_TIMESTAMP) 114 | e.Ecn = checkFlag(NF_SYNPROXY_OPT_ECN) 115 | } 116 | } 117 | return ad.Err() 118 | } 119 | -------------------------------------------------------------------------------- /expr/target.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | 7 | "github.com/google/nftables/binaryutil" 8 | "github.com/google/nftables/xt" 9 | "github.com/mdlayher/netlink" 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | // See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n28 14 | const XTablesExtensionNameMaxLen = 29 15 | 16 | // See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30 17 | type Target struct { 18 | Name string 19 | Rev uint32 20 | Info xt.InfoAny 21 | } 22 | 23 | func (e *Target) marshal(fam byte) ([]byte, error) { 24 | data, err := e.marshalData(fam) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return netlink.MarshalAttributes([]netlink.Attribute{ 29 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("target\x00")}, 30 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 31 | }) 32 | } 33 | 34 | func (e *Target) marshalData(fam byte) ([]byte, error) { 35 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38 36 | name := e.Name 37 | // limit the extension name as (some) user-space tools do and leave room for 38 | // trailing \x00 39 | if len(name) >= /* sic! */ XTablesExtensionNameMaxLen { 40 | name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. 41 | } 42 | // Marshalling assumes that the correct Info type for the particular table 43 | // family and Match revision has been set. 44 | info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info) 45 | if err != nil { 46 | return nil, err 47 | } 48 | attrs := []netlink.Attribute{ 49 | {Type: unix.NFTA_TARGET_NAME, Data: []byte(name + "\x00")}, 50 | {Type: unix.NFTA_TARGET_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, 51 | {Type: unix.NFTA_TARGET_INFO, Data: info}, 52 | } 53 | 54 | return netlink.MarshalAttributes(attrs) 55 | } 56 | 57 | func (e *Target) unmarshal(fam byte, data []byte) error { 58 | // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 59 | ad, err := netlink.NewAttributeDecoder(data) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | var info []byte 65 | ad.ByteOrder = binary.BigEndian 66 | for ad.Next() { 67 | switch ad.Type() { 68 | case unix.NFTA_TARGET_NAME: 69 | // We are forgiving here, accepting any length and even missing terminating \x00. 70 | e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) 71 | case unix.NFTA_TARGET_REV: 72 | e.Rev = ad.Uint32() 73 | case unix.NFTA_TARGET_INFO: 74 | info = ad.Bytes() 75 | } 76 | } 77 | if err = ad.Err(); err != nil { 78 | return err 79 | } 80 | e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info) 81 | return err 82 | } 83 | -------------------------------------------------------------------------------- /expr/target_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/google/nftables/xt" 9 | "github.com/mdlayher/netlink" 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | func TestTarget(t *testing.T) { 14 | t.Parallel() 15 | payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) 16 | tests := []struct { 17 | name string 18 | tgt Target 19 | }{ 20 | { 21 | name: "Unmarshal Target case", 22 | tgt: Target{ 23 | Name: "foobar", 24 | Rev: 1234567890, 25 | Info: &payload, 26 | }, 27 | }, 28 | } 29 | 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | ntgt := Target{} 33 | data, err := tt.tgt.marshal(0 /* don't care in this test */) 34 | if err != nil { 35 | t.Fatalf("marshal error: %+v", err) 36 | 37 | } 38 | ad, err := netlink.NewAttributeDecoder(data) 39 | if err != nil { 40 | t.Fatalf("NewAttributeDecoder() error: %+v", err) 41 | } 42 | ad.ByteOrder = binary.BigEndian 43 | for ad.Next() { 44 | if ad.Type() == unix.NFTA_EXPR_DATA { 45 | if err := ntgt.unmarshal(0 /* don't care in this test */, ad.Bytes()); err != nil { 46 | t.Errorf("unmarshal error: %+v", err) 47 | break 48 | } 49 | } 50 | } 51 | if !reflect.DeepEqual(tt.tgt, ntgt) { 52 | t.Fatalf("original %+v and recovered %+v Target structs are different", tt.tgt, ntgt) 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /expr/tproxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/google/nftables/binaryutil" 21 | "github.com/mdlayher/netlink" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | const ( 26 | // NFTA_TPROXY_FAMILY defines attribute for a table family 27 | NFTA_TPROXY_FAMILY = 0x01 28 | // NFTA_TPROXY_REG_ADDR defines attribute for a register carrying redirection address value 29 | NFTA_TPROXY_REG_ADDR = 0x02 30 | // NFTA_TPROXY_REG_PORT defines attribute for a register carrying redirection port value 31 | NFTA_TPROXY_REG_PORT = 0x03 32 | ) 33 | 34 | // TProxy defines struct with parameters for the transparent proxy 35 | type TProxy struct { 36 | Family byte 37 | TableFamily byte 38 | RegAddr uint32 39 | RegPort uint32 40 | } 41 | 42 | func (e *TProxy) marshal(fam byte) ([]byte, error) { 43 | data, err := e.marshalData(fam) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return netlink.MarshalAttributes([]netlink.Attribute{ 48 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("tproxy\x00")}, 49 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 50 | }) 51 | } 52 | 53 | func (e *TProxy) marshalData(fam byte) ([]byte, error) { 54 | attrs := []netlink.Attribute{ 55 | {Type: NFTA_TPROXY_FAMILY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Family))}, 56 | {Type: NFTA_TPROXY_REG_PORT, Data: binaryutil.BigEndian.PutUint32(e.RegPort)}, 57 | } 58 | 59 | if e.RegAddr != 0 { 60 | attrs = append(attrs, netlink.Attribute{ 61 | Type: NFTA_TPROXY_REG_ADDR, 62 | Data: binaryutil.BigEndian.PutUint32(e.RegAddr), 63 | }) 64 | } 65 | 66 | return netlink.MarshalAttributes(attrs) 67 | } 68 | 69 | func (e *TProxy) unmarshal(fam byte, data []byte) error { 70 | ad, err := netlink.NewAttributeDecoder(data) 71 | if err != nil { 72 | return err 73 | } 74 | ad.ByteOrder = binary.BigEndian 75 | for ad.Next() { 76 | switch ad.Type() { 77 | case NFTA_TPROXY_FAMILY: 78 | e.Family = ad.Uint8() 79 | case NFTA_TPROXY_REG_PORT: 80 | e.RegPort = ad.Uint32() 81 | case NFTA_TPROXY_REG_ADDR: 82 | e.RegAddr = ad.Uint32() 83 | } 84 | } 85 | return ad.Err() 86 | } 87 | -------------------------------------------------------------------------------- /expr/verdict.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package expr 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | 22 | "github.com/google/nftables/binaryutil" 23 | "github.com/mdlayher/netlink" 24 | "golang.org/x/sys/unix" 25 | ) 26 | 27 | // This code assembles the verdict structure, as expected by the 28 | // nftables netlink API. 29 | // For further information, consult: 30 | // - netfilter.h (Linux kernel) 31 | // - net/netfilter/nf_tables_api.c (Linux kernel) 32 | // - src/expr/data_reg.c (linbnftnl) 33 | 34 | type Verdict struct { 35 | Kind VerdictKind 36 | Chain string 37 | } 38 | 39 | type VerdictKind int64 40 | 41 | // Verdicts, as per netfilter.h and netfilter/nf_tables.h. 42 | const ( 43 | VerdictReturn VerdictKind = iota - 5 44 | VerdictGoto 45 | VerdictJump 46 | VerdictBreak 47 | VerdictContinue 48 | VerdictDrop 49 | VerdictAccept 50 | VerdictStolen 51 | VerdictQueue 52 | VerdictRepeat 53 | VerdictStop 54 | ) 55 | 56 | func (e *Verdict) marshal(fam byte) ([]byte, error) { 57 | // A verdict is a tree of netlink attributes structured as follows: 58 | // NFTA_LIST_ELEM | NLA_F_NESTED { 59 | // NFTA_EXPR_NAME { "immediate\x00" } 60 | // NFTA_EXPR_DATA | NLA_F_NESTED { 61 | // NFTA_IMMEDIATE_DREG { NFT_REG_VERDICT } 62 | // NFTA_IMMEDIATE_DATA | NLA_F_NESTED { 63 | // the verdict code 64 | // } 65 | // } 66 | // } 67 | data, err := e.marshalData(fam) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return netlink.MarshalAttributes([]netlink.Attribute{ 72 | {Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")}, 73 | {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, 74 | }) 75 | } 76 | 77 | func (e *Verdict) marshalData(fam byte) ([]byte, error) { 78 | attrs := []netlink.Attribute{ 79 | {Type: unix.NFTA_VERDICT_CODE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Kind))}, 80 | } 81 | if e.Chain != "" { 82 | attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_VERDICT_CHAIN, Data: []byte(e.Chain + "\x00")}) 83 | } 84 | codeData, err := netlink.MarshalAttributes(attrs) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | immData, err := netlink.MarshalAttributes([]netlink.Attribute{ 90 | {Type: unix.NLA_F_NESTED | unix.NFTA_DATA_VERDICT, Data: codeData}, 91 | }) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | return netlink.MarshalAttributes([]netlink.Attribute{ 97 | {Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(unix.NFT_REG_VERDICT)}, 98 | {Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData}, 99 | }) 100 | } 101 | 102 | func (e *Verdict) unmarshal(fam byte, data []byte) error { 103 | ad, err := netlink.NewAttributeDecoder(data) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | ad.ByteOrder = binary.BigEndian 109 | for ad.Next() { 110 | switch ad.Type() { 111 | case unix.NFTA_IMMEDIATE_DATA: 112 | nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes()) 113 | if err != nil { 114 | return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err) 115 | } 116 | for nestedAD.Next() { 117 | switch nestedAD.Type() { 118 | case unix.NFTA_DATA_VERDICT: 119 | e.Kind = VerdictKind(int32(binaryutil.BigEndian.Uint32(nestedAD.Bytes()[4:8]))) 120 | if len(nestedAD.Bytes()) > 12 { 121 | e.Chain = string(bytes.Trim(nestedAD.Bytes()[12:], "\x00")) 122 | } 123 | } 124 | } 125 | if nestedAD.Err() != nil { 126 | return fmt.Errorf("decoding immediate: %v", nestedAD.Err()) 127 | } 128 | } 129 | } 130 | return ad.Err() 131 | } 132 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/mdlayher/netlink" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | type GenMsg struct { 12 | ID uint32 13 | ProcPID uint32 14 | ProcComm string // [16]byte - max 16bytes - kernel TASK_COMM_LEN 15 | } 16 | 17 | const genHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWGEN) 18 | 19 | func genFromMsg(msg netlink.Message) (*GenMsg, error) { 20 | if got, want := msg.Header.Type, genHeaderType; got != want { 21 | return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want) 22 | } 23 | ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) 24 | if err != nil { 25 | return nil, err 26 | } 27 | ad.ByteOrder = binary.BigEndian 28 | 29 | msgOut := &GenMsg{} 30 | for ad.Next() { 31 | switch ad.Type() { 32 | case unix.NFTA_GEN_ID: 33 | msgOut.ID = ad.Uint32() 34 | case unix.NFTA_GEN_PROC_PID: 35 | msgOut.ProcPID = ad.Uint32() 36 | case unix.NFTA_GEN_PROC_NAME: 37 | msgOut.ProcComm = ad.String() 38 | default: 39 | return nil, fmt.Errorf("Unknown attribute: %d %v\n", ad.Type(), ad.Bytes()) 40 | } 41 | } 42 | if err := ad.Err(); err != nil { 43 | return nil, err 44 | } 45 | return msgOut, nil 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/nftables 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/google/go-cmp v0.6.0 7 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 8 | github.com/vishvananda/netlink v1.3.0 9 | github.com/vishvananda/netns v0.0.4 10 | golang.org/x/sys v0.31.0 11 | ) 12 | 13 | require ( 14 | github.com/mdlayher/socket v0.5.0 // indirect 15 | golang.org/x/net v0.38.0 // indirect 16 | golang.org/x/sync v0.6.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= 4 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= 5 | github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= 6 | github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 7 | github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= 8 | github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= 9 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 10 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 11 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 12 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 13 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 14 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 15 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 18 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 19 | -------------------------------------------------------------------------------- /integration/testdata/add_chain.nft: -------------------------------------------------------------------------------- 1 | table inet test-table { 2 | chain test-chain { 3 | type nat hook output priority dstnat; policy accept; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integration/testdata/add_flowtables.nft: -------------------------------------------------------------------------------- 1 | table inet test-table { 2 | set test-set { 3 | type ifname 4 | elements = { "dummy0" } 5 | } 6 | 7 | flowtable test-flowtable { 8 | hook ingress priority filter + 5 9 | devices = { dummy0 } 10 | } 11 | 12 | chain test-chain { 13 | type filter hook forward priority mangle; policy accept; 14 | iifname != @test-set return 15 | oifname != @test-set return 16 | ct state established ct packets > 20 flow add @test-flowtable counter packets 0 bytes 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /integration/testdata/add_table.nft: -------------------------------------------------------------------------------- 1 | table inet test-table { 2 | } 3 | -------------------------------------------------------------------------------- /internal/nftest/nftest.go: -------------------------------------------------------------------------------- 1 | // Package nftest contains utility functions for nftables testing. 2 | package nftest 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/google/nftables" 11 | "github.com/google/nftables/binaryutil" 12 | "github.com/mdlayher/netlink" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | // Recorder provides an nftables connection that does not send to the Linux 17 | // kernel but instead records netlink messages into the recorder. The recorded 18 | // requests can later be obtained using Requests and compared using Diff. 19 | type Recorder struct { 20 | requests []netlink.Message 21 | } 22 | 23 | // Conn opens an nftables connection that records netlink messages into the 24 | // Recorder. 25 | func (r *Recorder) Conn() (*nftables.Conn, error) { 26 | nextHandle := uint64(1) 27 | return nftables.New(nftables.WithTestDial( 28 | func(req []netlink.Message) ([]netlink.Message, error) { 29 | r.requests = append(r.requests, req...) 30 | 31 | replies := make([]netlink.Message, 0, len(req)) 32 | // Generate replies. 33 | for _, msg := range req { 34 | if msg.Header.Flags&netlink.Echo != 0 { 35 | data := append([]byte{}, msg.Data...) 36 | switch msg.Header.Type { 37 | case netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWRULE): 38 | attrs, _ := netlink.MarshalAttributes([]netlink.Attribute{ 39 | {Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(nextHandle)}, 40 | }) 41 | nextHandle++ 42 | data = append(data, attrs...) 43 | } 44 | replies = append(replies, netlink.Message{ 45 | Header: msg.Header, 46 | Data: data, 47 | }) 48 | } 49 | } 50 | // Generate acknowledgements. 51 | for _, msg := range req { 52 | if msg.Header.Flags&netlink.Acknowledge != 0 { 53 | replies = append(replies, netlink.Message{ 54 | Header: netlink.Header{ 55 | Length: 4, 56 | Type: netlink.Error, 57 | Sequence: msg.Header.Sequence, 58 | PID: msg.Header.PID, 59 | }, 60 | Data: []byte{0, 0, 0, 0}, 61 | }) 62 | } 63 | } 64 | return replies, nil 65 | })) 66 | } 67 | 68 | // Requests returns the recorded netlink messages (typically nftables requests). 69 | func (r *Recorder) Requests() []netlink.Message { 70 | return r.requests 71 | } 72 | 73 | // NewRecorder returns a ready-to-use Recorder. 74 | func NewRecorder() *Recorder { 75 | return &Recorder{} 76 | } 77 | 78 | // Diff returns the first difference between the specified netlink messages and 79 | // the expected netlink message payloads. 80 | func Diff(got []netlink.Message, want [][]byte) string { 81 | for idx, msg := range got { 82 | b, err := msg.MarshalBinary() 83 | if err != nil { 84 | return fmt.Sprintf("msg.MarshalBinary: %v", err) 85 | } 86 | if len(b) < 16 { 87 | continue 88 | } 89 | b = b[16:] 90 | if len(want) == 0 { 91 | return fmt.Sprintf("no want entry for message %d: %x", idx, b) 92 | } 93 | if got, want := b, want[0]; !bytes.Equal(got, want) { 94 | return fmt.Sprintf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) 95 | } 96 | want = want[1:] 97 | } 98 | return "" 99 | } 100 | 101 | // MatchRulesetBytes is a test helper that ensures the fillRuleset modifications 102 | // correspond to the provided want netlink message payloads 103 | func MatchRulesetBytes(t *testing.T, fillRuleset func(c *nftables.Conn), want [][]byte) { 104 | t.Helper() 105 | 106 | rec := NewRecorder() 107 | 108 | c, err := rec.Conn() 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | c.FlushRuleset() 114 | 115 | fillRuleset(c) 116 | 117 | if err := c.Flush(); err != nil { 118 | t.Fatal(err) 119 | } 120 | 121 | if diff := Diff(rec.Requests(), want); diff != "" { 122 | t.Errorf("unexpected netlink messages: diff: %s", diff) 123 | } 124 | } 125 | 126 | // nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing 127 | // users to make sense of large byte literals more easily. 128 | func nfdump(b []byte) string { 129 | var buf bytes.Buffer 130 | i := 0 131 | for ; i < len(b); i += 4 { 132 | // TODO: show printable characters as ASCII 133 | fmt.Fprintf(&buf, "%02x %02x %02x %02x\n", 134 | b[i], 135 | b[i+1], 136 | b[i+2], 137 | b[i+3]) 138 | } 139 | for ; i < len(b); i++ { 140 | fmt.Fprintf(&buf, "%02x ", b[i]) 141 | } 142 | return buf.String() 143 | } 144 | 145 | // linediff returns a side-by-side diff of two nfdump() return values, flagging 146 | // lines which are not equal with an exclamation point prefix. 147 | func linediff(a, b string) string { 148 | var buf bytes.Buffer 149 | fmt.Fprintf(&buf, "got -- want\n") 150 | linesA := strings.Split(a, "\n") 151 | linesB := strings.Split(b, "\n") 152 | for idx, lineA := range linesA { 153 | if idx >= len(linesB) { 154 | break 155 | } 156 | lineB := linesB[idx] 157 | prefix := "! " 158 | if lineA == lineB { 159 | prefix = " " 160 | } 161 | fmt.Fprintf(&buf, "%s%s -- %s\n", prefix, lineA, lineB) 162 | } 163 | return buf.String() 164 | } 165 | -------------------------------------------------------------------------------- /internal/nftest/system_conn.go: -------------------------------------------------------------------------------- 1 | package nftest 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/google/nftables" 8 | "github.com/vishvananda/netns" 9 | ) 10 | 11 | // OpenSystemConn returns a netlink connection that tests against 12 | // the running kernel in a separate network namespace. 13 | // nftest.CleanupSystemConn() must be called from a defer to cleanup 14 | // created network namespace. 15 | func OpenSystemConn(t *testing.T, enableSysTests bool) (*nftables.Conn, netns.NsHandle) { 16 | t.Helper() 17 | if !enableSysTests { 18 | t.SkipNow() 19 | } 20 | // We lock the goroutine into the current thread, as namespace operations 21 | // such as those invoked by `netns.New()` are thread-local. This is undone 22 | // in nftest.CleanupSystemConn(). 23 | runtime.LockOSThread() 24 | 25 | ns, err := netns.New() 26 | if err != nil { 27 | t.Fatalf("netns.New() failed: %v", err) 28 | } 29 | c, err := nftables.New(nftables.WithNetNSFd(int(ns))) 30 | if err != nil { 31 | t.Fatalf("nftables.New() failed: %v", err) 32 | } 33 | return c, ns 34 | } 35 | 36 | func CleanupSystemConn(t *testing.T, newNS netns.NsHandle) { 37 | defer runtime.UnlockOSThread() 38 | 39 | if err := newNS.Close(); err != nil { 40 | t.Fatalf("newNS.Close() failed: %v", err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/parseexprfunc/parseexprfunc.go: -------------------------------------------------------------------------------- 1 | package parseexprfunc 2 | 3 | import ( 4 | "github.com/mdlayher/netlink" 5 | ) 6 | 7 | var ( 8 | ParseExprBytesFromNameFunc func(fam byte, ad *netlink.AttributeDecoder, exprName string) ([]interface{}, error) 9 | ParseExprBytesFunc func(fam byte, ad *netlink.AttributeDecoder) ([]interface{}, error) 10 | ParseExprMsgFunc func(fam byte, b []byte) ([]interface{}, error) 11 | ) 12 | -------------------------------------------------------------------------------- /monitor_test.go: -------------------------------------------------------------------------------- 1 | package nftables_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | 13 | "github.com/google/nftables" 14 | "github.com/google/nftables/expr" 15 | "github.com/google/nftables/internal/nftest" 16 | ) 17 | 18 | func ExampleNewMonitor() { 19 | conn, err := nftables.New() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | mon := nftables.NewMonitor() 25 | defer mon.Close() 26 | events, err := conn.AddGenerationalMonitor(mon) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | for ev := range events { 31 | log.Printf("ev: %+v, data = %T", ev, ev.Changes) 32 | 33 | for _, change := range ev.Changes { 34 | switch change.Type { 35 | case nftables.MonitorEventTypeNewTable: 36 | log.Printf("data = %+v", change.Data.(*nftables.Table)) 37 | // …more cases if needed… 38 | } 39 | } 40 | } 41 | } 42 | 43 | func TestMonitor(t *testing.T) { 44 | // Create a new network namespace to test these operations, 45 | // and tear down the namespace at test completion. 46 | c, newNS := nftest.OpenSystemConn(t, *enableSysTests) 47 | defer nftest.CleanupSystemConn(t, newNS) 48 | // Clear all rules at the beginning + end of the test. 49 | c.FlushRuleset() 50 | defer c.FlushRuleset() 51 | // default to monitor all 52 | monitor := nftables.NewMonitor() 53 | events, err := c.AddGenerationalMonitor(monitor) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | defer monitor.Close() 58 | 59 | var gotTable *nftables.Table 60 | var gotChain *nftables.Chain 61 | var gotRule *nftables.Rule 62 | wg := sync.WaitGroup{} 63 | wg.Add(1) 64 | var errMonitor error 65 | go func() { 66 | defer wg.Done() 67 | count := int32(0) 68 | for { 69 | event, ok := <-events 70 | if !ok { 71 | return 72 | } 73 | 74 | genMsg := event.GeneratedBy.Data.(*nftables.GenMsg) 75 | fileName := filepath.Base(os.Args[0]) 76 | 77 | if genMsg.ProcComm != fileName { 78 | errMonitor = fmt.Errorf("procComm: %s, want: %s", genMsg.ProcComm, fileName) 79 | return 80 | } 81 | 82 | for _, change := range event.Changes { 83 | if change.Error != nil { 84 | errMonitor = fmt.Errorf("monitor err: %s", change.Error) 85 | return 86 | } 87 | 88 | switch change.Type { 89 | case nftables.MonitorEventTypeNewTable: 90 | gotTable = change.Data.(*nftables.Table) 91 | atomic.AddInt32(&count, 1) 92 | case nftables.MonitorEventTypeNewChain: 93 | gotChain = change.Data.(*nftables.Chain) 94 | atomic.AddInt32(&count, 1) 95 | case nftables.MonitorEventTypeNewRule: 96 | gotRule = change.Data.(*nftables.Rule) 97 | atomic.AddInt32(&count, 1) 98 | } 99 | if atomic.LoadInt32(&count) == 3 { 100 | return 101 | } 102 | } 103 | } 104 | }() 105 | 106 | nat := c.AddTable(&nftables.Table{ 107 | Family: nftables.TableFamilyIPv4, 108 | Name: "nat", 109 | }) 110 | 111 | postrouting := c.AddChain(&nftables.Chain{ 112 | Name: "postrouting", 113 | Hooknum: nftables.ChainHookPostrouting, 114 | Priority: nftables.ChainPriorityNATSource, 115 | Table: nat, 116 | Type: nftables.ChainTypeNAT, 117 | }) 118 | 119 | rule := c.AddRule(&nftables.Rule{ 120 | Table: nat, 121 | Chain: postrouting, 122 | Exprs: []expr.Any{ 123 | // payload load 4b @ network header + 12 => reg 1 124 | &expr.Payload{ 125 | DestRegister: 1, 126 | Base: expr.PayloadBaseNetworkHeader, 127 | Offset: 12, 128 | Len: 4, 129 | }, 130 | // cmp eq reg 1 0x0245a8c0 131 | &expr.Cmp{ 132 | Op: expr.CmpOpEq, 133 | Register: 1, 134 | Data: net.ParseIP("192.168.69.2").To4(), 135 | }, 136 | 137 | // masq 138 | &expr.Masq{}, 139 | }, 140 | }) 141 | 142 | if err := c.Flush(); err != nil { 143 | t.Fatal(err) 144 | } 145 | 146 | wg.Wait() 147 | 148 | if errMonitor != nil { 149 | t.Fatal("monitor err", errMonitor) 150 | } 151 | 152 | if gotTable.Family != nat.Family || gotTable.Name != nat.Name { 153 | t.Fatal("no want table", gotTable.Family, gotTable.Name) 154 | } 155 | if gotChain.Type != postrouting.Type || gotChain.Name != postrouting.Name || 156 | *gotChain.Hooknum != *postrouting.Hooknum { 157 | t.Fatal("no want chain", gotChain.Type, gotChain.Name, gotChain.Hooknum) 158 | } 159 | if gotRule.Table.Family != nat.Family { 160 | t.Fatal("rule wrong family", gotRule.Table.Family, gotRule.Table.Name) 161 | } 162 | if len(gotRule.Exprs) != len(rule.Exprs) { 163 | t.Fatal("no want rule") 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /quota.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nftables 16 | 17 | import ( 18 | "github.com/google/nftables/expr" 19 | "github.com/mdlayher/netlink" 20 | "golang.org/x/sys/unix" 21 | ) 22 | 23 | type QuotaObj struct { 24 | Table *Table 25 | Name string 26 | Bytes uint64 27 | Consumed uint64 28 | Over bool 29 | } 30 | 31 | func (q *QuotaObj) unmarshal(ad *netlink.AttributeDecoder) error { 32 | for ad.Next() { 33 | switch ad.Type() { 34 | case unix.NFTA_QUOTA_BYTES: 35 | q.Bytes = ad.Uint64() 36 | case unix.NFTA_QUOTA_CONSUMED: 37 | q.Consumed = ad.Uint64() 38 | case unix.NFTA_QUOTA_FLAGS: 39 | q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) != 0 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | func (q *QuotaObj) table() *Table { 46 | return q.Table 47 | } 48 | 49 | func (q *QuotaObj) family() TableFamily { 50 | return q.Table.Family 51 | } 52 | 53 | func (q *QuotaObj) data() expr.Any { 54 | return &expr.Quota{ 55 | Bytes: q.Bytes, 56 | Consumed: q.Consumed, 57 | Over: q.Over, 58 | } 59 | } 60 | 61 | func (q *QuotaObj) name() string { 62 | return q.Name 63 | } 64 | 65 | func (q *QuotaObj) objType() ObjType { 66 | return ObjTypeQuota 67 | } 68 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nftables 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/mdlayher/netlink" 21 | "golang.org/x/sys/unix" 22 | ) 23 | 24 | const ( 25 | newTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE) 26 | delTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE) 27 | ) 28 | 29 | // TableFamily specifies the address family for this table. 30 | type TableFamily byte 31 | 32 | // Possible TableFamily values. 33 | const ( 34 | TableFamilyUnspecified TableFamily = unix.NFPROTO_UNSPEC 35 | TableFamilyINet TableFamily = unix.NFPROTO_INET 36 | TableFamilyIPv4 TableFamily = unix.NFPROTO_IPV4 37 | TableFamilyIPv6 TableFamily = unix.NFPROTO_IPV6 38 | TableFamilyARP TableFamily = unix.NFPROTO_ARP 39 | TableFamilyNetdev TableFamily = unix.NFPROTO_NETDEV 40 | TableFamilyBridge TableFamily = unix.NFPROTO_BRIDGE 41 | ) 42 | 43 | // A Table contains Chains. See also 44 | // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables 45 | type Table struct { 46 | Name string // NFTA_TABLE_NAME 47 | Use uint32 // NFTA_TABLE_USE (Number of chains in table) 48 | Flags uint32 // NFTA_TABLE_FLAGS 49 | Family TableFamily 50 | } 51 | 52 | // DelTable deletes a specific table, along with all chains/rules it contains. 53 | func (cc *Conn) DelTable(t *Table) { 54 | cc.mu.Lock() 55 | defer cc.mu.Unlock() 56 | data := cc.marshalAttr([]netlink.Attribute{ 57 | {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")}, 58 | {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}}, 59 | }) 60 | cc.messages = append(cc.messages, netlinkMessage{ 61 | Header: netlink.Header{ 62 | Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE), 63 | Flags: netlink.Request | netlink.Acknowledge, 64 | }, 65 | Data: append(extraHeader(uint8(t.Family), 0), data...), 66 | }) 67 | } 68 | 69 | func (cc *Conn) addTable(t *Table, flag netlink.HeaderFlags) *Table { 70 | cc.mu.Lock() 71 | defer cc.mu.Unlock() 72 | data := cc.marshalAttr([]netlink.Attribute{ 73 | {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")}, 74 | {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}}, 75 | }) 76 | cc.messages = append(cc.messages, netlinkMessage{ 77 | Header: netlink.Header{ 78 | Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE), 79 | Flags: netlink.Request | netlink.Acknowledge | flag, 80 | }, 81 | Data: append(extraHeader(uint8(t.Family), 0), data...), 82 | }) 83 | return t 84 | } 85 | 86 | // AddTable adds the specified Table, just like `nft add table ...`. 87 | // See also https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables 88 | func (cc *Conn) AddTable(t *Table) *Table { 89 | return cc.addTable(t, netlink.Create) 90 | } 91 | 92 | // CreateTable create the specified Table if it do not existed. 93 | // just like `nft create table ...`. 94 | func (cc *Conn) CreateTable(t *Table) *Table { 95 | return cc.addTable(t, netlink.Excl) 96 | } 97 | 98 | // FlushTable removes all rules in all chains within the specified Table. See also 99 | // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables#Flushing_tables 100 | func (cc *Conn) FlushTable(t *Table) { 101 | cc.mu.Lock() 102 | defer cc.mu.Unlock() 103 | data := cc.marshalAttr([]netlink.Attribute{ 104 | {Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")}, 105 | }) 106 | cc.messages = append(cc.messages, netlinkMessage{ 107 | Header: netlink.Header{ 108 | Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE), 109 | Flags: netlink.Request | netlink.Acknowledge, 110 | }, 111 | Data: append(extraHeader(uint8(t.Family), 0), data...), 112 | }) 113 | } 114 | 115 | // ListTable returns table found for the specified name. Searches for 116 | // the table under IPv4 family. As per nft man page: "When no address 117 | // family is specified, ip is used by default." 118 | func (cc *Conn) ListTable(name string) (*Table, error) { 119 | return cc.ListTableOfFamily(name, TableFamilyIPv4) 120 | } 121 | 122 | // ListTableOfFamily returns table found for the specified name and table family 123 | func (cc *Conn) ListTableOfFamily(name string, family TableFamily) (*Table, error) { 124 | t, err := cc.listTablesOfNameAndFamily(name, family) 125 | if err != nil { 126 | return nil, err 127 | } 128 | if got, want := len(t), 1; got != want { 129 | return nil, fmt.Errorf("expected table count %d, got %d", want, got) 130 | } 131 | return t[0], nil 132 | } 133 | 134 | // ListTables returns currently configured tables in the kernel 135 | func (cc *Conn) ListTables() ([]*Table, error) { 136 | return cc.ListTablesOfFamily(TableFamilyUnspecified) 137 | } 138 | 139 | // ListTablesOfFamily returns currently configured tables for the specified table family 140 | // in the kernel. It lists all tables if family is TableFamilyUnspecified. 141 | func (cc *Conn) ListTablesOfFamily(family TableFamily) ([]*Table, error) { 142 | return cc.listTablesOfNameAndFamily("", family) 143 | } 144 | 145 | func (cc *Conn) listTablesOfNameAndFamily(name string, family TableFamily) ([]*Table, error) { 146 | conn, closer, err := cc.netlinkConn() 147 | if err != nil { 148 | return nil, err 149 | } 150 | defer func() { _ = closer() }() 151 | 152 | data := extraHeader(uint8(family), 0) 153 | flags := netlink.Request | netlink.Dump 154 | if name != "" { 155 | data = append(data, cc.marshalAttr([]netlink.Attribute{ 156 | {Type: unix.NFTA_TABLE_NAME, Data: []byte(name + "\x00")}, 157 | })...) 158 | flags = netlink.Request 159 | } 160 | 161 | msg := netlink.Message{ 162 | Header: netlink.Header{ 163 | Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETTABLE), 164 | Flags: flags, 165 | }, 166 | Data: data, 167 | } 168 | 169 | response, err := conn.Execute(msg) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | var tables []*Table 175 | for _, m := range response { 176 | t, err := tableFromMsg(m) 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | tables = append(tables, t) 182 | } 183 | 184 | return tables, nil 185 | } 186 | 187 | func tableFromMsg(msg netlink.Message) (*Table, error) { 188 | if got, want1, want2 := msg.Header.Type, newTableHeaderType, delTableHeaderType; got != want1 && got != want2 { 189 | return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2) 190 | } 191 | 192 | var t Table 193 | t.Family = TableFamily(msg.Data[0]) 194 | 195 | ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) 196 | if err != nil { 197 | return nil, err 198 | } 199 | 200 | for ad.Next() { 201 | switch ad.Type() { 202 | case unix.NFTA_TABLE_NAME: 203 | t.Name = ad.String() 204 | case unix.NFTA_TABLE_USE: 205 | t.Use = ad.Uint32() 206 | case unix.NFTA_TABLE_FLAGS: 207 | t.Flags = ad.Uint32() 208 | } 209 | } 210 | 211 | return &t, nil 212 | } 213 | -------------------------------------------------------------------------------- /userdata/userdata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package userdata implements a TLV parser/serializer for libnftables-compatible comments 16 | package userdata 17 | 18 | import ( 19 | "bytes" 20 | "encoding/binary" 21 | ) 22 | 23 | type Type byte 24 | 25 | // TLV type values are defined in: 26 | // https://git.netfilter.org/iptables/tree/iptables/nft.c?id=73611d5582e72367a698faf1b5301c836e981465#n1659 27 | const ( 28 | TypeComment Type = iota 29 | TypeEbtablesPolicy 30 | 31 | TypesCount 32 | ) 33 | 34 | // TLV type values are defined in: 35 | // https://git.netfilter.org/libnftnl/tree/include/libnftnl/udata.h#n39 36 | const ( 37 | NFTNL_UDATA_SET_KEYBYTEORDER Type = iota 38 | NFTNL_UDATA_SET_DATABYTEORDER 39 | NFTNL_UDATA_SET_MERGE_ELEMENTS 40 | NFTNL_UDATA_SET_KEY_TYPEOF 41 | NFTNL_UDATA_SET_DATA_TYPEOF 42 | NFTNL_UDATA_SET_EXPR 43 | NFTNL_UDATA_SET_DATA_INTERVAL 44 | NFTNL_UDATA_SET_COMMENT 45 | 46 | NFTNL_UDATA_SET_MAX 47 | ) 48 | 49 | // Set element userdata types 50 | const ( 51 | NFTNL_UDATA_SET_ELEM_COMMENT Type = iota 52 | NFTNL_UDATA_SET_ELEM_FLAGS 53 | ) 54 | 55 | func Append(udata []byte, typ Type, data []byte) []byte { 56 | udata = append(udata, byte(typ), byte(len(data))) 57 | udata = append(udata, data...) 58 | 59 | return udata 60 | } 61 | 62 | func Get(udata []byte, styp Type) []byte { 63 | for { 64 | if len(udata) < 2 { 65 | break 66 | } 67 | 68 | typ := Type(udata[0]) 69 | length := int(udata[1]) 70 | data := udata[2 : 2+length] 71 | 72 | if styp == typ { 73 | return data 74 | } 75 | 76 | if len(udata) < 2+length { 77 | break 78 | } else { 79 | udata = udata[2+length:] 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func AppendUint32(udata []byte, typ Type, num uint32) []byte { 87 | data := binary.LittleEndian.AppendUint32(nil, num) 88 | 89 | return Append(udata, typ, data) 90 | } 91 | 92 | func GetUint32(udata []byte, typ Type) (uint32, bool) { 93 | data := Get(udata, typ) 94 | if data == nil { 95 | return 0, false 96 | } 97 | 98 | return binary.LittleEndian.Uint32(data), true 99 | } 100 | 101 | func AppendString(udata []byte, typ Type, str string) []byte { 102 | data := append([]byte(str), 0) 103 | return Append(udata, typ, data) 104 | } 105 | 106 | func GetString(udata []byte, typ Type) (string, bool) { 107 | data := Get(udata, typ) 108 | if data == nil { 109 | return "", false 110 | } 111 | 112 | data, _ = bytes.CutSuffix(data, []byte{0}) 113 | 114 | return string(data), true 115 | } 116 | -------------------------------------------------------------------------------- /userdata/userdata_cli_interop_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package userdata_test 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "flag" 21 | "os/exec" 22 | "testing" 23 | 24 | "github.com/google/nftables" 25 | "github.com/google/nftables/internal/nftest" 26 | "github.com/google/nftables/userdata" 27 | ) 28 | 29 | var enableSysTests = flag.Bool("run_system_tests", false, "Run tests that operate against the live kernel") 30 | 31 | type nftCliMetainfo struct { 32 | Version string `json:"version,omitempty"` 33 | ReleaseName string `json:"release_name,omitempty"` 34 | JSONSchemaVersion int `json:"json_schema_version,omitempty"` 35 | } 36 | 37 | type nftCliTable struct { 38 | Family string `json:"family,omitempty"` 39 | Name string `json:"name,omitempty"` 40 | Handle int `json:"handle,omitempty"` 41 | } 42 | 43 | type nftCliChain struct { 44 | Family string `json:"family,omitempty"` 45 | Table string `json:"table,omitempty"` 46 | Name string `json:"name,omitempty"` 47 | Handle int `json:"handle,omitempty"` 48 | } 49 | 50 | type nftCliExpr struct{} 51 | 52 | type nftCliRule struct { 53 | Family string `json:"family,omitempty"` 54 | Table string `json:"table,omitempty"` 55 | Chain string `json:"chain,omitempty"` 56 | Handle int `json:"handle,omitempty"` 57 | Comment string `json:"comment,omitempty"` 58 | Expr []nftCliExpr `json:"expr"` 59 | } 60 | 61 | type nftCommand struct { 62 | Ruleset interface{} `json:"ruleset"` 63 | Table *nftCliTable `json:"table,omitempty"` 64 | Chain *nftCliChain `json:"chain,omitempty"` 65 | Rule *nftCliRule `json:"rule,omitempty"` 66 | } 67 | 68 | type nftCliObject struct { 69 | Metainfo *nftCliMetainfo `json:"metainfo,omitempty"` 70 | Table *nftCliTable `json:"table,omitempty"` 71 | Chain *nftCliChain `json:"chain,omitempty"` 72 | Rule *nftCliRule `json:"rule,omitempty"` 73 | Add *nftCommand `json:"add,omitempty"` 74 | Flush *nftCommand `json:"flush,omitempty"` 75 | } 76 | 77 | type nftCli struct { 78 | Nftables []nftCliObject `json:"nftables"` 79 | } 80 | 81 | func TestCommentInteropGo2Cli(t *testing.T) { 82 | wantComment := "my comment" 83 | 84 | // Create a new network namespace to test these operations, 85 | // and tear down the namespace at test completion. 86 | c, newNS := nftest.OpenSystemConn(t, *enableSysTests) 87 | defer nftest.CleanupSystemConn(t, newNS) 88 | 89 | c.FlushRuleset() 90 | 91 | table := c.AddTable(&nftables.Table{ 92 | Name: "userdata-table", 93 | Family: nftables.TableFamilyIPv4, 94 | }) 95 | 96 | chain := c.AddChain(&nftables.Chain{ 97 | Name: "userdata-chain", 98 | Table: table, 99 | }) 100 | 101 | c.AddRule(&nftables.Rule{ 102 | Table: table, 103 | Chain: chain, 104 | UserData: userdata.AppendString(nil, userdata.TypeComment, wantComment), 105 | }) 106 | 107 | if err := c.Flush(); err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | out := bytes.NewBuffer(nil) 112 | d := exec.Command("nft", "-j", "list", "table", "userdata-table") 113 | d.Stdout = out 114 | if err := d.Run(); err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | var outJson nftCli 119 | if err := json.Unmarshal(out.Bytes(), &outJson); err != nil { 120 | t.Fatal() 121 | } 122 | 123 | found := 0 124 | for _, e := range outJson.Nftables { 125 | if e.Rule == nil || e.Rule.Handle == 0 { 126 | continue 127 | } 128 | 129 | if e.Rule.Comment != wantComment { 130 | t.Fatal() 131 | } 132 | 133 | found++ 134 | } 135 | 136 | if found != 1 { 137 | t.Fatalf("found %d rules", found) 138 | } 139 | 140 | c.DelTable(table) 141 | 142 | if err := c.Flush(); err != nil { 143 | t.Fatal(err) 144 | } 145 | } 146 | 147 | func TestCommentInteropCli2Go(t *testing.T) { 148 | wantComment := "my comment" 149 | 150 | inJson := nftCli{ 151 | Nftables: []nftCliObject{ 152 | { 153 | Metainfo: &nftCliMetainfo{ 154 | JSONSchemaVersion: 1, 155 | }, 156 | }, 157 | { 158 | Flush: &nftCommand{ 159 | Ruleset: nil, 160 | }, 161 | }, 162 | { 163 | Add: &nftCommand{ 164 | Table: &nftCliTable{ 165 | Family: "ip", 166 | Name: "userdata-table", 167 | }, 168 | }, 169 | }, 170 | { 171 | Add: &nftCommand{ 172 | Chain: &nftCliChain{ 173 | Family: "ip", 174 | Name: "userdata-chain", 175 | Table: "userdata-table", 176 | }, 177 | }, 178 | }, 179 | { 180 | Add: &nftCommand{ 181 | Rule: &nftCliRule{ 182 | Family: "ip", 183 | Table: "userdata-table", 184 | Chain: "userdata-chain", 185 | Comment: wantComment, 186 | Expr: []nftCliExpr{}, 187 | }, 188 | }, 189 | }, 190 | }, 191 | } 192 | 193 | in := bytes.NewBuffer(nil) 194 | if err := json.NewEncoder(in).Encode(inJson); err != nil { 195 | t.Fatal() 196 | } 197 | 198 | // Create a new network namespace to test these operations, 199 | // and tear down the namespace at test completion. 200 | c, newNS := nftest.OpenSystemConn(t, *enableSysTests) 201 | defer nftest.CleanupSystemConn(t, newNS) 202 | 203 | d := exec.Command("nft", "-j", "-f", "-") 204 | d.Stdin = in 205 | if err := d.Run(); err != nil { 206 | t.Fatal(err) 207 | } 208 | 209 | table := &nftables.Table{ 210 | Name: "userdata-table", 211 | Family: nftables.TableFamilyIPv4, 212 | } 213 | 214 | chain := &nftables.Chain{ 215 | Name: "userdata-chain", 216 | Table: table, 217 | } 218 | 219 | rules, err := c.GetRules(table, chain) 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | 224 | if len(rules) != 1 { 225 | t.Fatal() 226 | } 227 | 228 | if comment, ok := userdata.GetString(rules[0].UserData, userdata.TypeComment); !ok { 229 | t.Fatalf("failed to find comment") 230 | } else if comment != wantComment { 231 | t.Fatalf("comment mismatch %q != %q", comment, wantComment) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /userdata/userdata_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package userdata_test 16 | 17 | import ( 18 | "bytes" 19 | "encoding/hex" 20 | "testing" 21 | 22 | "github.com/google/nftables" 23 | "github.com/google/nftables/userdata" 24 | ) 25 | 26 | func TestUserDataComment(t *testing.T) { 27 | r := nftables.Rule{} 28 | 29 | wantComment := "this is my comment" 30 | want := []byte{ 31 | byte(userdata.TypeComment), // Type 32 | byte(len(wantComment) + 1), // Length (including terminating null byte) 33 | } 34 | want = append(want, []byte(wantComment)...) // Payload 35 | want = append(want, 0) // Terminating null byte 36 | 37 | r.UserData = userdata.AppendString(r.UserData, userdata.TypeComment, wantComment) 38 | 39 | if !bytes.Equal(r.UserData, want) { 40 | t.Fatalf("UserData mismatch: %s != %s", 41 | hex.EncodeToString(r.UserData), 42 | hex.EncodeToString(want)) 43 | } 44 | 45 | if comment, ok := userdata.GetString(r.UserData, userdata.TypeComment); !ok { 46 | t.Fatalf("failed to get comment") 47 | } else if comment != wantComment { 48 | t.Fatalf("comment does not match: %s != %s", comment, wantComment) 49 | } 50 | } 51 | 52 | func TestUint32(t *testing.T) { 53 | // Define a custom type for storing a rule ID 54 | const TypeRuleID = userdata.TypesCount 55 | 56 | r := nftables.Rule{} 57 | 58 | wantRuleID := uint32(1234) 59 | want := []byte{byte(TypeRuleID), 4, 210, 4, 0, 0} 60 | 61 | r.UserData = userdata.AppendUint32(r.UserData, TypeRuleID, wantRuleID) 62 | 63 | if !bytes.Equal(r.UserData, want) { 64 | t.Fatalf("UserData mismatch: %x != %x", r.UserData, want) 65 | } 66 | 67 | if ruleID, ok := userdata.GetUint32(r.UserData, TypeRuleID); !ok { 68 | t.Fatalf("failed to get id") 69 | } else if ruleID != wantRuleID { 70 | t.Fatalf("id mismatch") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nftables 16 | 17 | import ( 18 | "encoding/binary" 19 | "net" 20 | 21 | "github.com/google/nftables/binaryutil" 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | func extraHeader(family uint8, resID uint16) []byte { 26 | return append([]byte{ 27 | family, 28 | unix.NFNETLINK_V0, 29 | }, binaryutil.BigEndian.PutUint16(resID)...) 30 | } 31 | 32 | // General form of address family dependent message, see 33 | // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nfnetlink.h#29 34 | type NFGenMsg struct { 35 | NFGenFamily uint8 36 | Version uint8 37 | ResourceID uint16 38 | } 39 | 40 | func (genmsg *NFGenMsg) Decode(b []byte) { 41 | if len(b) < 4 { 42 | return 43 | } 44 | genmsg.NFGenFamily = b[0] 45 | genmsg.Version = b[1] 46 | genmsg.ResourceID = binary.BigEndian.Uint16(b[2:]) 47 | } 48 | 49 | // NetFirstAndLastIP takes the beginning address of an entire network in CIDR 50 | // notation (e.g. 192.168.1.0/24) and returns the first and last IP addresses 51 | // within the network (e.g. first 192.168.1.0, last 192.168.1.255). 52 | // 53 | // Note that these are the first and last IP addresses, not the first and last 54 | // *usable* IP addresses (which would be 192.168.1.1 and 192.168.1.254, 55 | // respectively, for 192.168.1.0/24). 56 | func NetFirstAndLastIP(networkCIDR string) (first, last net.IP, err error) { 57 | _, subnet, err := net.ParseCIDR(networkCIDR) 58 | if err != nil { 59 | return nil, nil, err 60 | } 61 | 62 | first = make(net.IP, len(subnet.IP)) 63 | last = make(net.IP, len(subnet.IP)) 64 | 65 | switch len(subnet.IP) { 66 | case net.IPv4len: 67 | mask := binary.BigEndian.Uint32(subnet.Mask) 68 | ip := binary.BigEndian.Uint32(subnet.IP) 69 | // To achieve the first IP address, we need to AND the IP with the mask. 70 | // The AND operation will set all bits in the host part to 0. 71 | binary.BigEndian.PutUint32(first, ip&mask) 72 | // To achieve the last IP address, we need to OR the IP network with the inverted mask. 73 | // The AND between the IP and the mask will set all bits in the host part to 0, keeping the network part. 74 | // The XOR between the mask and 0xffffffff will set all bits in the host part to 1, and the network part to 0. 75 | // The OR operation will keep the host part unchanged, and sets the host part to all 1. 76 | binary.BigEndian.PutUint32(last, (ip&mask)|(mask^0xffffffff)) 77 | case net.IPv6len: 78 | mask1 := binary.BigEndian.Uint64(subnet.Mask[:8]) 79 | mask2 := binary.BigEndian.Uint64(subnet.Mask[8:]) 80 | ip1 := binary.BigEndian.Uint64(subnet.IP[:8]) 81 | ip2 := binary.BigEndian.Uint64(subnet.IP[8:]) 82 | binary.BigEndian.PutUint64(first[:8], ip1&mask1) 83 | binary.BigEndian.PutUint64(first[8:], ip2&mask2) 84 | binary.BigEndian.PutUint64(last[:8], (ip1&mask1)|(mask1^0xffffffffffffffff)) 85 | binary.BigEndian.PutUint64(last[8:], (ip2&mask2)|(mask2^0xffffffffffffffff)) 86 | } 87 | 88 | return first, last, nil 89 | } 90 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestNetFirstAndLastIP(t *testing.T) { 10 | type args struct { 11 | cidr string 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | wantFirstIP net.IP 17 | wantLastIP net.IP 18 | wantErr bool 19 | }{ 20 | { 21 | name: "Test Fake", 22 | args: args{cidr: "fakecidr"}, 23 | wantFirstIP: nil, 24 | wantLastIP: nil, 25 | wantErr: true, 26 | }, 27 | { 28 | name: "Test IPV4 1", 29 | args: args{cidr: "10.0.0.0/24"}, 30 | wantFirstIP: net.IP{10, 0, 0, 0}, 31 | wantLastIP: net.IP{10, 0, 0, 255}, 32 | wantErr: false, 33 | }, 34 | { 35 | name: "Test IPV4 2", 36 | args: args{cidr: "10.0.0.20/24"}, 37 | wantFirstIP: net.IP{10, 0, 0, 0}, 38 | wantLastIP: net.IP{10, 0, 0, 255}, 39 | wantErr: false, 40 | }, 41 | { 42 | name: "Test IPV4 2", 43 | args: args{cidr: "10.0.0.0/19"}, 44 | wantFirstIP: net.IP{10, 0, 0, 0}, 45 | wantLastIP: net.IP{10, 0, 31, 255}, 46 | wantErr: false, 47 | }, 48 | { 49 | name: "Test IPV6 1", 50 | args: args{cidr: "ff00::/16"}, 51 | wantFirstIP: net.ParseIP("ff00::"), 52 | wantLastIP: net.ParseIP("ff00:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 53 | wantErr: false, 54 | }, 55 | { 56 | name: "Test IPV6 2", 57 | args: args{cidr: "2001:db8::/62"}, 58 | wantFirstIP: net.ParseIP("2001:db8::"), 59 | wantLastIP: net.ParseIP("2001:db8:0000:0003:ffff:ffff:ffff:ffff"), 60 | wantErr: false, 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | gotFirstIP, gotLastIP, err := NetFirstAndLastIP(tt.args.cidr) 66 | if (err != nil) != tt.wantErr { 67 | t.Errorf("GetFirstAndLastIPFromCIDR() error = %v, wantErr %v", err, tt.wantErr) 68 | return 69 | } 70 | if !reflect.DeepEqual(gotFirstIP, tt.wantFirstIP) { 71 | t.Errorf("GetFirstAndLastIPFromCIDR() gotFirstIP = %v, want %v", gotFirstIP, tt.wantFirstIP) 72 | } 73 | if !reflect.DeepEqual(gotLastIP, tt.wantLastIP) { 74 | t.Errorf("GetFirstAndLastIPFromCIDR() gotLastIP = %v, want %v", gotLastIP, tt.wantLastIP) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /xt/comment.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // CommentSize is the fixed size of a comment info xt blob, see: 9 | // https://elixir.bootlin.com/linux/v6.8.7/source/include/uapi/linux/netfilter/xt_comment.h#L5 10 | const CommentSize = 256 11 | 12 | // Comment gets marshalled and unmarshalled as a fixed-sized char array, filled 13 | // with zeros as necessary, see: 14 | // https://elixir.bootlin.com/linux/v6.8.7/source/include/uapi/linux/netfilter/xt_comment.h#L7 15 | type Comment string 16 | 17 | func (c *Comment) marshal(fam TableFamily, rev uint32) ([]byte, error) { 18 | if len(*c) >= CommentSize { 19 | return nil, fmt.Errorf("comment must be less than %d bytes, got %d bytes", 20 | CommentSize, len(*c)) 21 | } 22 | data := make([]byte, CommentSize) 23 | copy(data, []byte(*c)) 24 | return data, nil 25 | } 26 | 27 | func (c *Comment) unmarshal(fam TableFamily, rev uint32, data []byte) error { 28 | if len(data) != CommentSize { 29 | return fmt.Errorf("malformed comment: got %d bytes, expected exactly %d bytes", 30 | len(data), CommentSize) 31 | } 32 | *c = Comment(bytes.TrimRight(data, "\x00")) 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /xt/comment_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestComment(t *testing.T) { 10 | t.Parallel() 11 | payload := Comment("The quick brown fox jumps over the lazy dog.") 12 | oversized := Comment(strings.Repeat("foobar", 100)) 13 | tests := []struct { 14 | name string 15 | info InfoAny 16 | errmsg string 17 | }{ 18 | { 19 | name: "un/marshal Comment round-trip", 20 | info: &payload, 21 | }, 22 | { 23 | name: "marshal oversized Comment", 24 | info: &oversized, 25 | errmsg: "comment must be less than 256 bytes, got 600 bytes", 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | data, err := tt.info.marshal(0, 0) 32 | if err != nil { 33 | if tt.errmsg != "" && err.Error() == tt.errmsg { 34 | return 35 | } 36 | t.Fatalf("marshal error: %+v", err) 37 | 38 | } 39 | if len(data) != CommentSize { 40 | t.Fatalf("marshal error: invalid size %d", len(data)) 41 | } 42 | if data[len(data)-1] != 0 { 43 | t.Fatalf("marshal error: invalid termination") 44 | } 45 | var comment Comment 46 | var recoveredInfo InfoAny = &comment 47 | err = recoveredInfo.unmarshal(0, 0, data) 48 | if err != nil { 49 | t.Fatalf("unmarshal error: %+v", err) 50 | } 51 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 52 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 53 | } 54 | }) 55 | } 56 | 57 | oversizeddata := []byte(oversized) 58 | var comment Comment 59 | if err := (&comment).unmarshal(0, 0, oversizeddata); err == nil { 60 | t.Fatalf("unmarshal: expected error, but got nil") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /xt/info.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "golang.org/x/sys/unix" 5 | ) 6 | 7 | // TableFamily specifies the address family of the table Match or Target Info 8 | // data is contained in. On purpose, we don't import the expr package here in 9 | // order to keep the option open to import this package instead into expr. 10 | type TableFamily byte 11 | 12 | // InfoAny is a (un)marshaling implemented by any info type. 13 | type InfoAny interface { 14 | marshal(fam TableFamily, rev uint32) ([]byte, error) 15 | unmarshal(fam TableFamily, rev uint32, data []byte) error 16 | } 17 | 18 | // Marshal a Match or Target Info type into its binary representation. 19 | func Marshal(fam TableFamily, rev uint32, info InfoAny) ([]byte, error) { 20 | return info.marshal(fam, rev) 21 | } 22 | 23 | // Unmarshal Info binary payload into its corresponding dedicated type as 24 | // indicated by the name argument. In several cases, unmarshalling depends on 25 | // the specific table family the Target or Match expression with the info 26 | // payload belongs to, as well as the specific info structure revision. 27 | func Unmarshal(name string, fam TableFamily, rev uint32, data []byte) (InfoAny, error) { 28 | var i InfoAny 29 | switch name { 30 | case "addrtype": 31 | switch rev { 32 | case 0: 33 | i = &AddrType{} 34 | case 1: 35 | i = &AddrTypeV1{} 36 | } 37 | case "comment": 38 | var c Comment 39 | i = &c 40 | case "conntrack": 41 | switch rev { 42 | case 1: 43 | i = &ConntrackMtinfo1{} 44 | case 2: 45 | i = &ConntrackMtinfo2{} 46 | case 3: 47 | i = &ConntrackMtinfo3{} 48 | } 49 | case "tcp": 50 | i = &Tcp{} 51 | case "udp": 52 | i = &Udp{} 53 | case "SNAT": 54 | if fam == unix.NFPROTO_IPV4 { 55 | i = &NatIPv4MultiRangeCompat{} 56 | } 57 | case "DNAT": 58 | switch fam { 59 | case unix.NFPROTO_IPV4: 60 | if rev == 0 { 61 | i = &NatIPv4MultiRangeCompat{} 62 | break 63 | } 64 | fallthrough 65 | case unix.NFPROTO_IPV6: 66 | switch rev { 67 | case 1: 68 | i = &NatRange{} 69 | case 2: 70 | i = &NatRange2{} 71 | } 72 | } 73 | case "MASQUERADE": 74 | switch fam { 75 | case unix.NFPROTO_IPV4: 76 | i = &NatIPv4MultiRangeCompat{} 77 | } 78 | case "REDIRECT": 79 | switch fam { 80 | case unix.NFPROTO_IPV4: 81 | if rev == 0 { 82 | i = &NatIPv4MultiRangeCompat{} 83 | break 84 | } 85 | fallthrough 86 | case unix.NFPROTO_IPV6: 87 | i = &NatRange{} 88 | } 89 | } 90 | if i == nil { 91 | i = &Unknown{} 92 | } 93 | if err := i.unmarshal(fam, rev, data); err != nil { 94 | return nil, err 95 | } 96 | return i, nil 97 | } 98 | -------------------------------------------------------------------------------- /xt/match_addrtype.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "github.com/google/nftables/alignedbuff" 5 | ) 6 | 7 | // Rev. 0, see https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_addrtype.h#L38 8 | type AddrType struct { 9 | Source uint16 10 | Dest uint16 11 | InvertSource bool 12 | InvertDest bool 13 | } 14 | 15 | type AddrTypeFlags uint32 16 | 17 | const ( 18 | AddrTypeUnspec AddrTypeFlags = 1 << iota 19 | AddrTypeUnicast 20 | AddrTypeLocal 21 | AddrTypeBroadcast 22 | AddrTypeAnycast 23 | AddrTypeMulticast 24 | AddrTypeBlackhole 25 | AddrTypeUnreachable 26 | AddrTypeProhibit 27 | AddrTypeThrow 28 | AddrTypeNat 29 | AddrTypeXresolve 30 | ) 31 | 32 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_addrtype.h#L31 33 | type AddrTypeV1 struct { 34 | Source uint16 35 | Dest uint16 36 | Flags AddrTypeFlags 37 | } 38 | 39 | func (x *AddrType) marshal(fam TableFamily, rev uint32) ([]byte, error) { 40 | ab := alignedbuff.New() 41 | ab.PutUint16(x.Source) 42 | ab.PutUint16(x.Dest) 43 | putBool32(&ab, x.InvertSource) 44 | putBool32(&ab, x.InvertDest) 45 | return ab.Data(), nil 46 | } 47 | 48 | func (x *AddrType) unmarshal(fam TableFamily, rev uint32, data []byte) error { 49 | ab := alignedbuff.NewWithData(data) 50 | var err error 51 | if x.Source, err = ab.Uint16(); err != nil { 52 | return nil 53 | } 54 | if x.Dest, err = ab.Uint16(); err != nil { 55 | return nil 56 | } 57 | if x.InvertSource, err = bool32(&ab); err != nil { 58 | return nil 59 | } 60 | if x.InvertDest, err = bool32(&ab); err != nil { 61 | return nil 62 | } 63 | return nil 64 | } 65 | 66 | func (x *AddrTypeV1) marshal(fam TableFamily, rev uint32) ([]byte, error) { 67 | ab := alignedbuff.New() 68 | ab.PutUint16(x.Source) 69 | ab.PutUint16(x.Dest) 70 | ab.PutUint32(uint32(x.Flags)) 71 | return ab.Data(), nil 72 | } 73 | 74 | func (x *AddrTypeV1) unmarshal(fam TableFamily, rev uint32, data []byte) error { 75 | ab := alignedbuff.NewWithData(data) 76 | var err error 77 | if x.Source, err = ab.Uint16(); err != nil { 78 | return nil 79 | } 80 | if x.Dest, err = ab.Uint16(); err != nil { 81 | return nil 82 | } 83 | var flags uint32 84 | if flags, err = ab.Uint32(); err != nil { 85 | return nil 86 | } 87 | x.Flags = AddrTypeFlags(flags) 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /xt/match_addrtype_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTargetAddrType(t *testing.T) { 9 | t.Parallel() 10 | tests := []struct { 11 | name string 12 | fam byte 13 | rev uint32 14 | info InfoAny 15 | empty InfoAny 16 | }{ 17 | { 18 | name: "un/marshal AddrType Rev 0 round-trip", 19 | fam: 0, 20 | rev: 0, 21 | info: &AddrType{ 22 | Source: 0x1234, 23 | Dest: 0x5678, 24 | InvertSource: true, 25 | InvertDest: false, 26 | }, 27 | empty: &AddrType{}, 28 | }, 29 | { 30 | name: "un/marshal AddrType Rev 0 round-trip", 31 | fam: 0, 32 | rev: 0, 33 | info: &AddrType{ 34 | Source: 0x1234, 35 | Dest: 0x5678, 36 | InvertSource: false, 37 | InvertDest: true, 38 | }, 39 | empty: &AddrType{}, 40 | }, 41 | { 42 | name: "un/marshal AddrType Rev 1 round-trip", 43 | fam: 0, 44 | rev: 0, 45 | info: &AddrTypeV1{ 46 | Source: 0x1234, 47 | Dest: 0x5678, 48 | Flags: 0xb00f, 49 | }, 50 | empty: &AddrTypeV1{}, 51 | }, 52 | } 53 | 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev) 57 | if err != nil { 58 | t.Fatalf("marshal error: %+v", err) 59 | 60 | } 61 | var recoveredInfo InfoAny = tt.empty 62 | err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data) 63 | if err != nil { 64 | t.Fatalf("unmarshal error: %+v", err) 65 | } 66 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 67 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 68 | } 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /xt/match_conntrack.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/google/nftables/alignedbuff" 7 | ) 8 | 9 | type ConntrackFlags uint16 10 | 11 | const ( 12 | ConntrackState ConntrackFlags = 1 << iota 13 | ConntrackProto 14 | ConntrackOrigSrc 15 | ConntrackOrigDst 16 | ConntrackReplSrc 17 | ConntrackReplDst 18 | ConntrackStatus 19 | ConntrackExpires 20 | ConntrackOrigSrcPort 21 | ConntrackOrigDstPort 22 | ConntrackReplSrcPort 23 | ConntrackReplDstPrt 24 | ConntrackDirection 25 | ConntrackStateAlias 26 | ) 27 | 28 | type ConntrackMtinfoBase struct { 29 | OrigSrcAddr net.IP 30 | OrigSrcMask net.IPMask 31 | OrigDstAddr net.IP 32 | OrigDstMask net.IPMask 33 | ReplSrcAddr net.IP 34 | ReplSrcMask net.IPMask 35 | ReplDstAddr net.IP 36 | ReplDstMask net.IPMask 37 | ExpiresMin uint32 38 | ExpiresMax uint32 39 | L4Proto uint16 40 | OrigSrcPort uint16 41 | OrigDstPort uint16 42 | ReplSrcPort uint16 43 | ReplDstPort uint16 44 | MatchFlags uint16 45 | InvertFlags uint16 46 | } 47 | 48 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L38 49 | type ConntrackMtinfo1 struct { 50 | ConntrackMtinfoBase 51 | StateMask uint8 52 | StatusMask uint8 53 | } 54 | 55 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L51 56 | type ConntrackMtinfo2 struct { 57 | ConntrackMtinfoBase 58 | StateMask uint16 59 | StatusMask uint16 60 | } 61 | 62 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L64 63 | type ConntrackMtinfo3 struct { 64 | ConntrackMtinfo2 65 | OrigSrcPortHigh uint16 66 | OrigDstPortHigh uint16 67 | ReplSrcPortHigh uint16 68 | ReplDstPortHigh uint16 69 | } 70 | 71 | func (x *ConntrackMtinfoBase) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 72 | if err := putIPv46(ab, fam, x.OrigSrcAddr); err != nil { 73 | return err 74 | } 75 | if err := putIPv46Mask(ab, fam, x.OrigSrcMask); err != nil { 76 | return err 77 | } 78 | if err := putIPv46(ab, fam, x.OrigDstAddr); err != nil { 79 | return err 80 | } 81 | if err := putIPv46Mask(ab, fam, x.OrigDstMask); err != nil { 82 | return err 83 | } 84 | if err := putIPv46(ab, fam, x.ReplSrcAddr); err != nil { 85 | return err 86 | } 87 | if err := putIPv46Mask(ab, fam, x.ReplSrcMask); err != nil { 88 | return err 89 | } 90 | if err := putIPv46(ab, fam, x.ReplDstAddr); err != nil { 91 | return err 92 | } 93 | if err := putIPv46Mask(ab, fam, x.ReplDstMask); err != nil { 94 | return err 95 | } 96 | ab.PutUint32(x.ExpiresMin) 97 | ab.PutUint32(x.ExpiresMax) 98 | ab.PutUint16(x.L4Proto) 99 | ab.PutUint16(x.OrigSrcPort) 100 | ab.PutUint16(x.OrigDstPort) 101 | ab.PutUint16(x.ReplSrcPort) 102 | ab.PutUint16(x.ReplDstPort) 103 | ab.PutUint16(x.MatchFlags) 104 | ab.PutUint16(x.InvertFlags) 105 | return nil 106 | } 107 | 108 | func (x *ConntrackMtinfoBase) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 109 | var err error 110 | if x.OrigSrcAddr, err = iPv46(ab, fam); err != nil { 111 | return err 112 | } 113 | if x.OrigSrcMask, err = iPv46Mask(ab, fam); err != nil { 114 | return err 115 | } 116 | if x.OrigDstAddr, err = iPv46(ab, fam); err != nil { 117 | return err 118 | } 119 | if x.OrigDstMask, err = iPv46Mask(ab, fam); err != nil { 120 | return err 121 | } 122 | if x.ReplSrcAddr, err = iPv46(ab, fam); err != nil { 123 | return err 124 | } 125 | if x.ReplSrcMask, err = iPv46Mask(ab, fam); err != nil { 126 | return err 127 | } 128 | if x.ReplDstAddr, err = iPv46(ab, fam); err != nil { 129 | return err 130 | } 131 | if x.ReplDstMask, err = iPv46Mask(ab, fam); err != nil { 132 | return err 133 | } 134 | if x.ExpiresMin, err = ab.Uint32(); err != nil { 135 | return err 136 | } 137 | if x.ExpiresMax, err = ab.Uint32(); err != nil { 138 | return err 139 | } 140 | if x.L4Proto, err = ab.Uint16(); err != nil { 141 | return err 142 | } 143 | if x.OrigSrcPort, err = ab.Uint16(); err != nil { 144 | return err 145 | } 146 | if x.OrigDstPort, err = ab.Uint16(); err != nil { 147 | return err 148 | } 149 | if x.ReplSrcPort, err = ab.Uint16(); err != nil { 150 | return err 151 | } 152 | if x.ReplDstPort, err = ab.Uint16(); err != nil { 153 | return err 154 | } 155 | if x.MatchFlags, err = ab.Uint16(); err != nil { 156 | return err 157 | } 158 | if x.InvertFlags, err = ab.Uint16(); err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | 164 | func (x *ConntrackMtinfo1) marshal(fam TableFamily, rev uint32) ([]byte, error) { 165 | ab := alignedbuff.New() 166 | if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, &ab); err != nil { 167 | return nil, err 168 | } 169 | ab.PutUint8(x.StateMask) 170 | ab.PutUint8(x.StatusMask) 171 | return ab.Data(), nil 172 | } 173 | 174 | func (x *ConntrackMtinfo1) unmarshal(fam TableFamily, rev uint32, data []byte) error { 175 | ab := alignedbuff.NewWithData(data) 176 | var err error 177 | if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, &ab); err != nil { 178 | return err 179 | } 180 | if x.StateMask, err = ab.Uint8(); err != nil { 181 | return err 182 | } 183 | if x.StatusMask, err = ab.Uint8(); err != nil { 184 | return err 185 | } 186 | return nil 187 | } 188 | 189 | func (x *ConntrackMtinfo2) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 190 | if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, ab); err != nil { 191 | return err 192 | } 193 | ab.PutUint16(x.StateMask) 194 | ab.PutUint16(x.StatusMask) 195 | return nil 196 | } 197 | 198 | func (x *ConntrackMtinfo2) marshal(fam TableFamily, rev uint32) ([]byte, error) { 199 | ab := alignedbuff.New() 200 | if err := x.marshalAB(fam, rev, &ab); err != nil { 201 | return nil, err 202 | } 203 | return ab.Data(), nil 204 | } 205 | 206 | func (x *ConntrackMtinfo2) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 207 | var err error 208 | if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, ab); err != nil { 209 | return err 210 | } 211 | if x.StateMask, err = ab.Uint16(); err != nil { 212 | return err 213 | } 214 | if x.StatusMask, err = ab.Uint16(); err != nil { 215 | return err 216 | } 217 | return nil 218 | } 219 | 220 | func (x *ConntrackMtinfo2) unmarshal(fam TableFamily, rev uint32, data []byte) error { 221 | ab := alignedbuff.NewWithData(data) 222 | var err error 223 | if err = x.unmarshalAB(fam, rev, &ab); err != nil { 224 | return err 225 | } 226 | return nil 227 | } 228 | 229 | func (x *ConntrackMtinfo3) marshal(fam TableFamily, rev uint32) ([]byte, error) { 230 | ab := alignedbuff.New() 231 | if err := x.ConntrackMtinfo2.marshalAB(fam, rev, &ab); err != nil { 232 | return nil, err 233 | } 234 | ab.PutUint16(x.OrigSrcPortHigh) 235 | ab.PutUint16(x.OrigDstPortHigh) 236 | ab.PutUint16(x.ReplSrcPortHigh) 237 | ab.PutUint16(x.ReplDstPortHigh) 238 | return ab.Data(), nil 239 | } 240 | 241 | func (x *ConntrackMtinfo3) unmarshal(fam TableFamily, rev uint32, data []byte) error { 242 | ab := alignedbuff.NewWithData(data) 243 | var err error 244 | if err = x.ConntrackMtinfo2.unmarshalAB(fam, rev, &ab); err != nil { 245 | return err 246 | } 247 | if x.OrigSrcPortHigh, err = ab.Uint16(); err != nil { 248 | return err 249 | } 250 | if x.OrigDstPortHigh, err = ab.Uint16(); err != nil { 251 | return err 252 | } 253 | if x.ReplSrcPortHigh, err = ab.Uint16(); err != nil { 254 | return err 255 | } 256 | if x.ReplDstPortHigh, err = ab.Uint16(); err != nil { 257 | return err 258 | } 259 | return nil 260 | } 261 | -------------------------------------------------------------------------------- /xt/match_tcp.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "github.com/google/nftables/alignedbuff" 5 | ) 6 | 7 | // Tcp is the Match.Info payload for the tcp xtables extension 8 | // (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp). 9 | // 10 | // See 11 | // https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_tcpudp.h#L8 12 | type Tcp struct { 13 | SrcPorts [2]uint16 // min, max source port range 14 | DstPorts [2]uint16 // min, max destination port range 15 | Option uint8 // TCP option if non-zero 16 | FlagsMask uint8 // TCP flags mask 17 | FlagsCmp uint8 // TCP flags compare 18 | InvFlags TcpInvFlagset // Inverse flags 19 | } 20 | 21 | type TcpInvFlagset uint8 22 | 23 | const ( 24 | TcpInvSrcPorts TcpInvFlagset = 1 << iota 25 | TcpInvDestPorts 26 | TcpInvFlags 27 | TcpInvOption 28 | TcpInvMask TcpInvFlagset = (1 << iota) - 1 29 | ) 30 | 31 | func (x *Tcp) marshal(fam TableFamily, rev uint32) ([]byte, error) { 32 | ab := alignedbuff.New() 33 | ab.PutUint16(x.SrcPorts[0]) 34 | ab.PutUint16(x.SrcPorts[1]) 35 | ab.PutUint16(x.DstPorts[0]) 36 | ab.PutUint16(x.DstPorts[1]) 37 | ab.PutUint8(x.Option) 38 | ab.PutUint8(x.FlagsMask) 39 | ab.PutUint8(x.FlagsCmp) 40 | ab.PutUint8(byte(x.InvFlags)) 41 | return ab.Data(), nil 42 | } 43 | 44 | func (x *Tcp) unmarshal(fam TableFamily, rev uint32, data []byte) error { 45 | ab := alignedbuff.NewWithData(data) 46 | var err error 47 | if x.SrcPorts[0], err = ab.Uint16(); err != nil { 48 | return err 49 | } 50 | if x.SrcPorts[1], err = ab.Uint16(); err != nil { 51 | return err 52 | } 53 | if x.DstPorts[0], err = ab.Uint16(); err != nil { 54 | return err 55 | } 56 | if x.DstPorts[1], err = ab.Uint16(); err != nil { 57 | return err 58 | } 59 | if x.Option, err = ab.Uint8(); err != nil { 60 | return err 61 | } 62 | if x.FlagsMask, err = ab.Uint8(); err != nil { 63 | return err 64 | } 65 | if x.FlagsCmp, err = ab.Uint8(); err != nil { 66 | return err 67 | } 68 | var invFlags uint8 69 | if invFlags, err = ab.Uint8(); err != nil { 70 | return err 71 | } 72 | x.InvFlags = TcpInvFlagset(invFlags) 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /xt/match_tcp_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMatchTcp(t *testing.T) { 9 | t.Parallel() 10 | tests := []struct { 11 | name string 12 | info InfoAny 13 | }{ 14 | { 15 | name: "un/marshal Tcp round-trip", 16 | info: &Tcp{ 17 | SrcPorts: [2]uint16{0x1234, 0x5678}, 18 | DstPorts: [2]uint16{0x2345, 0x6789}, 19 | Option: 0x12, 20 | FlagsMask: 0x34, 21 | FlagsCmp: 0x56, 22 | InvFlags: 0x78, 23 | }, 24 | }, 25 | } 26 | 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | data, err := tt.info.marshal(0, 0) 30 | if err != nil { 31 | t.Fatalf("marshal error: %+v", err) 32 | 33 | } 34 | var recoveredInfo InfoAny = &Tcp{} 35 | err = recoveredInfo.unmarshal(0, 0, data) 36 | if err != nil { 37 | t.Fatalf("unmarshal error: %+v", err) 38 | } 39 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 40 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /xt/match_udp.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "github.com/google/nftables/alignedbuff" 5 | ) 6 | 7 | // Tcp is the Match.Info payload for the tcp xtables extension 8 | // (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp). 9 | // 10 | // See 11 | // https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_tcpudp.h#L25 12 | type Udp struct { 13 | SrcPorts [2]uint16 // min, max source port range 14 | DstPorts [2]uint16 // min, max destination port range 15 | InvFlags UdpInvFlagset // Inverse flags 16 | } 17 | 18 | type UdpInvFlagset uint8 19 | 20 | const ( 21 | UdpInvSrcPorts UdpInvFlagset = 1 << iota 22 | UdpInvDestPorts 23 | UdpInvMask UdpInvFlagset = (1 << iota) - 1 24 | ) 25 | 26 | func (x *Udp) marshal(fam TableFamily, rev uint32) ([]byte, error) { 27 | ab := alignedbuff.New() 28 | ab.PutUint16(x.SrcPorts[0]) 29 | ab.PutUint16(x.SrcPorts[1]) 30 | ab.PutUint16(x.DstPorts[0]) 31 | ab.PutUint16(x.DstPorts[1]) 32 | ab.PutUint8(byte(x.InvFlags)) 33 | return ab.Data(), nil 34 | } 35 | 36 | func (x *Udp) unmarshal(fam TableFamily, rev uint32, data []byte) error { 37 | ab := alignedbuff.NewWithData(data) 38 | var err error 39 | if x.SrcPorts[0], err = ab.Uint16(); err != nil { 40 | return err 41 | } 42 | if x.SrcPorts[1], err = ab.Uint16(); err != nil { 43 | return err 44 | } 45 | if x.DstPorts[0], err = ab.Uint16(); err != nil { 46 | return err 47 | } 48 | if x.DstPorts[1], err = ab.Uint16(); err != nil { 49 | return err 50 | } 51 | var invFlags uint8 52 | if invFlags, err = ab.Uint8(); err != nil { 53 | return err 54 | } 55 | x.InvFlags = UdpInvFlagset(invFlags) 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /xt/match_udp_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMatchUdp(t *testing.T) { 9 | t.Parallel() 10 | tests := []struct { 11 | name string 12 | info InfoAny 13 | }{ 14 | { 15 | name: "un/marshal Udp round-trip", 16 | info: &Udp{ 17 | SrcPorts: [2]uint16{0x1234, 0x5678}, 18 | DstPorts: [2]uint16{0x2345, 0x6789}, 19 | InvFlags: 0x78, 20 | }, 21 | }, 22 | } 23 | 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | data, err := tt.info.marshal(0, 0) 27 | if err != nil { 28 | t.Fatalf("marshal error: %+v", err) 29 | 30 | } 31 | var recoveredInfo InfoAny = &Udp{} 32 | err = recoveredInfo.unmarshal(0, 0, data) 33 | if err != nil { 34 | t.Fatalf("unmarshal error: %+v", err) 35 | } 36 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 37 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /xt/target_dnat.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/google/nftables/alignedbuff" 7 | ) 8 | 9 | type NatRangeFlags uint 10 | 11 | // See: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L8 12 | const ( 13 | NatRangeMapIPs NatRangeFlags = (1 << iota) 14 | NatRangeProtoSpecified 15 | NatRangeProtoRandom 16 | NatRangePersistent 17 | NatRangeProtoRandomFully 18 | NatRangeProtoOffset 19 | NatRangeNetmap 20 | 21 | NatRangeMask NatRangeFlags = (1 << iota) - 1 22 | 23 | NatRangeProtoRandomAll = NatRangeProtoRandom | NatRangeProtoRandomFully 24 | ) 25 | 26 | // see: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L38 27 | type NatRange struct { 28 | Flags uint // sic! platform/arch/compiler-dependent uint size 29 | MinIP net.IP // always taking up space for an IPv6 address 30 | MaxIP net.IP // dito 31 | MinPort uint16 32 | MaxPort uint16 33 | } 34 | 35 | // see: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L46 36 | type NatRange2 struct { 37 | NatRange 38 | BasePort uint16 39 | } 40 | 41 | func (x *NatRange) marshal(fam TableFamily, rev uint32) ([]byte, error) { 42 | ab := alignedbuff.New() 43 | if err := x.marshalAB(fam, rev, &ab); err != nil { 44 | return nil, err 45 | } 46 | return ab.Data(), nil 47 | } 48 | 49 | func (x *NatRange) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 50 | ab.PutUint(x.Flags) 51 | if err := putIPv46(ab, fam, x.MinIP); err != nil { 52 | return err 53 | } 54 | if err := putIPv46(ab, fam, x.MaxIP); err != nil { 55 | return err 56 | } 57 | ab.PutUint16BE(x.MinPort) 58 | ab.PutUint16BE(x.MaxPort) 59 | return nil 60 | } 61 | 62 | func (x *NatRange) unmarshal(fam TableFamily, rev uint32, data []byte) error { 63 | ab := alignedbuff.NewWithData(data) 64 | return x.unmarshalAB(fam, rev, &ab) 65 | } 66 | 67 | func (x *NatRange) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 68 | var err error 69 | if x.Flags, err = ab.Uint(); err != nil { 70 | return err 71 | } 72 | if x.MinIP, err = iPv46(ab, fam); err != nil { 73 | return err 74 | } 75 | if x.MaxIP, err = iPv46(ab, fam); err != nil { 76 | return err 77 | } 78 | if x.MinPort, err = ab.Uint16BE(); err != nil { 79 | return err 80 | } 81 | if x.MaxPort, err = ab.Uint16BE(); err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | 87 | func (x *NatRange2) marshal(fam TableFamily, rev uint32) ([]byte, error) { 88 | ab := alignedbuff.New() 89 | if err := x.NatRange.marshalAB(fam, rev, &ab); err != nil { 90 | return nil, err 91 | } 92 | ab.PutUint16BE(x.BasePort) 93 | return ab.Data(), nil 94 | } 95 | 96 | func (x *NatRange2) unmarshal(fam TableFamily, rev uint32, data []byte) error { 97 | ab := alignedbuff.NewWithData(data) 98 | var err error 99 | if err = x.NatRange.unmarshalAB(fam, rev, &ab); err != nil { 100 | return err 101 | } 102 | if x.BasePort, err = ab.Uint16BE(); err != nil { 103 | return err 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /xt/target_dnat_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "testing" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func TestTargetDNAT(t *testing.T) { 12 | t.Parallel() 13 | tests := []struct { 14 | name string 15 | fam byte 16 | rev uint32 17 | info InfoAny 18 | empty InfoAny 19 | }{ 20 | { 21 | name: "un/marshal NatRange IPv4 round-trip", 22 | fam: unix.NFPROTO_IPV4, 23 | rev: 0, 24 | info: &NatRange{ 25 | Flags: 0x1234, 26 | MinIP: net.ParseIP("12.23.34.45").To4(), 27 | MaxIP: net.ParseIP("21.32.43.54").To4(), 28 | MinPort: 0x5678, 29 | MaxPort: 0xabcd, 30 | }, 31 | empty: &NatRange{}, 32 | }, 33 | { 34 | name: "un/marshal NatRange IPv6 round-trip", 35 | fam: unix.NFPROTO_IPV6, 36 | rev: 0, 37 | info: &NatRange{ 38 | Flags: 0x1234, 39 | MinIP: net.ParseIP("fe80::dead:beef"), 40 | MaxIP: net.ParseIP("fe80::c001:cafe"), 41 | MinPort: 0x5678, 42 | MaxPort: 0xabcd, 43 | }, 44 | empty: &NatRange{}, 45 | }, 46 | { 47 | name: "un/marshal NatRange2 IPv4 round-trip", 48 | fam: unix.NFPROTO_IPV4, 49 | rev: 0, 50 | info: &NatRange2{ 51 | NatRange: NatRange{ 52 | Flags: 0x1234, 53 | MinIP: net.ParseIP("12.23.34.45").To4(), 54 | MaxIP: net.ParseIP("21.32.43.54").To4(), 55 | MinPort: 0x5678, 56 | MaxPort: 0xabcd, 57 | }, 58 | BasePort: 0xfedc, 59 | }, 60 | empty: &NatRange2{}, 61 | }, 62 | { 63 | name: "un/marshal NatRange2 IPv6 round-trip", 64 | fam: unix.NFPROTO_IPV6, 65 | rev: 0, 66 | info: &NatRange2{ 67 | NatRange: NatRange{ 68 | Flags: 0x1234, 69 | MinIP: net.ParseIP("fe80::dead:beef"), 70 | MaxIP: net.ParseIP("fe80::c001:cafe"), 71 | MinPort: 0x5678, 72 | MaxPort: 0xabcd, 73 | }, 74 | BasePort: 0xfedc, 75 | }, 76 | empty: &NatRange2{}, 77 | }, 78 | } 79 | 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev) 83 | if err != nil { 84 | t.Fatalf("marshal error: %+v", err) 85 | 86 | } 87 | var recoveredInfo InfoAny = tt.empty 88 | err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data) 89 | if err != nil { 90 | t.Fatalf("unmarshal error: %+v", err) 91 | } 92 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 93 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /xt/target_masquerade_ip.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/google/nftables/alignedbuff" 8 | ) 9 | 10 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L25 11 | type NatIPv4Range struct { 12 | Flags uint // sic! 13 | MinIP net.IP 14 | MaxIP net.IP 15 | MinPort uint16 16 | MaxPort uint16 17 | } 18 | 19 | // NatIPv4MultiRangeCompat despite being a slice of NAT IPv4 ranges is currently allowed to 20 | // only hold exactly one element. 21 | // 22 | // See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L33 23 | type NatIPv4MultiRangeCompat []NatIPv4Range 24 | 25 | func (x *NatIPv4MultiRangeCompat) marshal(fam TableFamily, rev uint32) ([]byte, error) { 26 | ab := alignedbuff.New() 27 | if len(*x) != 1 { 28 | return nil, errors.New("MasqueradeIp must contain exactly one NatIPv4Range") 29 | } 30 | ab.PutUint(uint(len(*x))) 31 | for _, nat := range *x { 32 | if err := nat.marshalAB(fam, rev, &ab); err != nil { 33 | return nil, err 34 | } 35 | } 36 | return ab.Data(), nil 37 | } 38 | 39 | func (x *NatIPv4MultiRangeCompat) unmarshal(fam TableFamily, rev uint32, data []byte) error { 40 | ab := alignedbuff.NewWithData(data) 41 | l, err := ab.Uint() 42 | if err != nil { 43 | return err 44 | } 45 | nats := make(NatIPv4MultiRangeCompat, l) 46 | for l > 0 { 47 | l-- 48 | if err := nats[l].unmarshalAB(fam, rev, &ab); err != nil { 49 | return err 50 | } 51 | } 52 | *x = nats 53 | return nil 54 | } 55 | 56 | func (x *NatIPv4Range) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 57 | ab.PutUint(x.Flags) 58 | ab.PutBytesAligned32(x.MinIP.To4(), 4) 59 | ab.PutBytesAligned32(x.MaxIP.To4(), 4) 60 | ab.PutUint16BE(x.MinPort) 61 | ab.PutUint16BE(x.MaxPort) 62 | return nil 63 | } 64 | 65 | func (x *NatIPv4Range) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { 66 | var err error 67 | if x.Flags, err = ab.Uint(); err != nil { 68 | return err 69 | } 70 | var ip []byte 71 | if ip, err = ab.BytesAligned32(4); err != nil { 72 | return err 73 | } 74 | x.MinIP = net.IP(ip) 75 | if ip, err = ab.BytesAligned32(4); err != nil { 76 | return err 77 | } 78 | x.MaxIP = net.IP(ip) 79 | if x.MinPort, err = ab.Uint16BE(); err != nil { 80 | return err 81 | } 82 | if x.MaxPort, err = ab.Uint16BE(); err != nil { 83 | return err 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /xt/target_masquerade_ip_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "testing" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func TestTargetMasqueradeIP(t *testing.T) { 12 | t.Parallel() 13 | tests := []struct { 14 | name string 15 | fam byte 16 | rev uint32 17 | info InfoAny 18 | empty InfoAny 19 | }{ 20 | { 21 | name: "un/marshal NatIPv4Range round-trip", 22 | fam: unix.NFPROTO_IPV4, 23 | rev: 0, 24 | info: &NatIPv4MultiRangeCompat{ 25 | NatIPv4Range{ 26 | Flags: 0x1234, 27 | MinIP: net.ParseIP("12.23.34.45").To4(), 28 | MaxIP: net.ParseIP("21.32.43.54").To4(), 29 | MinPort: 0x5678, 30 | MaxPort: 0xabcd, 31 | }, 32 | }, 33 | empty: new(NatIPv4MultiRangeCompat), 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev) 40 | if err != nil { 41 | t.Fatalf("marshal error: %+v", err) 42 | 43 | } 44 | var recoveredInfo InfoAny = tt.empty 45 | err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data) 46 | if err != nil { 47 | t.Fatalf("unmarshal error: %+v", err) 48 | } 49 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 50 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /xt/unknown.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | // Unknown represents the bytes Info payload for unknown Info types where no 4 | // dedicated match/target info type has (yet) been defined. 5 | type Unknown []byte 6 | 7 | func (x *Unknown) marshal(fam TableFamily, rev uint32) ([]byte, error) { 8 | // In case of unknown payload we assume its creator knows what she/he does 9 | // and thus we don't do any alignment padding. Just take the payload "as 10 | // is". 11 | return *x, nil 12 | } 13 | 14 | func (x *Unknown) unmarshal(fam TableFamily, rev uint32, data []byte) error { 15 | *x = data 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /xt/unknown_test.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestUnknown(t *testing.T) { 9 | t.Parallel() 10 | payload := Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) 11 | tests := []struct { 12 | name string 13 | info InfoAny 14 | }{ 15 | { 16 | name: "un/marshal Unknown round-trip", 17 | info: &payload, 18 | }, 19 | } 20 | 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | data, err := tt.info.marshal(0, 0) 24 | if err != nil { 25 | t.Fatalf("marshal error: %+v", err) 26 | 27 | } 28 | var recoveredInfo InfoAny = &Unknown{} 29 | err = recoveredInfo.unmarshal(0, 0, data) 30 | if err != nil { 31 | t.Fatalf("unmarshal error: %+v", err) 32 | } 33 | if !reflect.DeepEqual(tt.info, recoveredInfo) { 34 | t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /xt/util.go: -------------------------------------------------------------------------------- 1 | package xt 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/google/nftables/alignedbuff" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func bool32(ab *alignedbuff.AlignedBuff) (bool, error) { 12 | v, err := ab.Uint32() 13 | if err != nil { 14 | return false, err 15 | } 16 | if v != 0 { 17 | return true, nil 18 | } 19 | return false, nil 20 | } 21 | 22 | func putBool32(ab *alignedbuff.AlignedBuff, b bool) { 23 | if b { 24 | ab.PutUint32(1) 25 | return 26 | } 27 | ab.PutUint32(0) 28 | } 29 | 30 | func iPv46(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IP, error) { 31 | ip, err := ab.BytesAligned32(16) 32 | if err != nil { 33 | return nil, err 34 | } 35 | switch fam { 36 | case unix.NFPROTO_IPV4: 37 | return net.IP(ip[:4]), nil 38 | case unix.NFPROTO_IPV6: 39 | return net.IP(ip), nil 40 | default: 41 | return nil, fmt.Errorf("unmarshal IP: unsupported table family %d", fam) 42 | } 43 | } 44 | 45 | func iPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IPMask, error) { 46 | v, err := iPv46(ab, fam) 47 | return net.IPMask(v), err 48 | } 49 | 50 | func putIPv46(ab *alignedbuff.AlignedBuff, fam TableFamily, ip net.IP) error { 51 | switch fam { 52 | case unix.NFPROTO_IPV4: 53 | ab.PutBytesAligned32(ip.To4(), 16) 54 | case unix.NFPROTO_IPV6: 55 | ab.PutBytesAligned32(ip.To16(), 16) 56 | default: 57 | return fmt.Errorf("marshal IP: unsupported table family %d", fam) 58 | } 59 | return nil 60 | } 61 | 62 | func putIPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily, mask net.IPMask) error { 63 | return putIPv46(ab, fam, net.IP(mask)) 64 | } 65 | -------------------------------------------------------------------------------- /xt/xt.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package xt implements dedicated types for (some) of the "Info" payload in Match 3 | and Target expressions that bridge between the nftables and xtables worlds. 4 | 5 | Bridging between the more unified world of nftables and the slightly 6 | heterogenous world of xtables comes with some caveats. Unmarshalling the 7 | extension/translation information in Match and Target expressions requires 8 | information about the table family the information belongs to, as well as type 9 | and type revision information. In consequence, unmarshalling the Match and 10 | Target Info field payloads often (but not necessarily always) require the table 11 | family and revision information, so it gets passed to the type-specific 12 | unmarshallers. 13 | 14 | To complicate things more, even marshalling requires knowledge about the 15 | enclosing table family. The NatRange/NatRange2 types are an example, where it is 16 | necessary to differentiate between IPv4 and IPv6 address marshalling. Due to 17 | Go's net.IP habit to normally store IPv4 addresses as IPv4-compatible IPv6 18 | addresses (see also RFC 4291, section 2.5.5.1) marshalling must be handled 19 | differently in the context of an IPv6 table compared to an IPv4 table. In an 20 | IPv4 table, an IPv4-compatible IPv6 address must be marshalled as a 32bit 21 | address, whereas in an IPv6 table the IPv4 address must be marshalled as an 22 | 128bit IPv4-compatible IPv6 address. Not relying on heuristics here we avoid 23 | behavior unexpected and most probably unknown to our API users. The net.IP habit 24 | of storing IPv4 addresses in two different storage formats is already a source 25 | for trouble, especially when comparing net.IPs from different Go module sources. 26 | We won't add to this confusion. (...or maybe we can, because of it?) 27 | 28 | An important property of all types of Info extension/translation payloads is 29 | that their marshalling and unmarshalling doesn't follow netlink's TLV 30 | (tag-length-value) architecture. Instead, Info payloads a basically plain binary 31 | blobs of their respective type-specific data structures, so host 32 | platform/architecture alignment and data type sizes apply. The alignedbuff 33 | package implements the different required data types alignments. 34 | 35 | Please note that Info payloads are always padded at their end to the next uint64 36 | alignment. Kernel code is checking for the padded payload size and will reject 37 | payloads not correctly padded at their ends. 38 | 39 | Most of the time, we find explifcitly sized (unsigned integer) data types. 40 | However, there are notable exceptions where "unsigned int" is used: on 64bit 41 | platforms this mostly translates into 32bit(!). This differs from Go mapping 42 | uint to uint64 instead. This package currently clamps its mapping of C's 43 | "unsigned int" to Go's uint32 for marshalling and unmarshalling. If in the 44 | future 128bit platforms with a differently sized C unsigned int should come into 45 | production, then the alignedbuff package will need to be adapted accordingly, as 46 | it abstracts away this data type handling. 47 | */ 48 | package xt 49 | --------------------------------------------------------------------------------