├── .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 | [](https://github.com/google/nftables/actions/workflows/push.yml)
2 | [](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 |
--------------------------------------------------------------------------------