pax_global_header00006660000000000000000000000064145733213730014522gustar00rootroot0000000000000052 comment=5494b7c55f2ec41dd187ff0b94a2505f6d248a34 nftables-0.2.0/000077500000000000000000000000001457332137300133175ustar00rootroot00000000000000nftables-0.2.0/.github/000077500000000000000000000000001457332137300146575ustar00rootroot00000000000000nftables-0.2.0/.github/workflows/000077500000000000000000000000001457332137300167145ustar00rootroot00000000000000nftables-0.2.0/.github/workflows/push.yml000066400000000000000000000012221457332137300204130ustar00rootroot00000000000000name: Push on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: name: CI runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go 1.x uses: actions/setup-go@v4 with: # Run on the latest minor release of Go 1.20: go-version: ^1.20 id: go - name: Ensure all files were formatted as per gofmt run: | [ "$(gofmt -l $(find . -name '*.go') 2>&1)" = "" ] - name: Run tests run: | go vet . go test ./... go test -c github.com/google/nftables sudo ./nftables.test -test.v -run_system_tests nftables-0.2.0/CONTRIBUTING.md000066400000000000000000000017111457332137300155500ustar00rootroot00000000000000# How to Contribute We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution, this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. nftables-0.2.0/LICENSE000066400000000000000000000261361457332137300143340ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. nftables-0.2.0/README.md000066400000000000000000000017251457332137300146030ustar00rootroot00000000000000[![Build Status](https://github.com/google/nftables/actions/workflows/push.yml/badge.svg)](https://github.com/google/nftables/actions/workflows/push.yml) [![GoDoc](https://godoc.org/github.com/google/nftables?status.svg)](https://godoc.org/github.com/google/nftables) **This is not the correct repository for issues with the Linux nftables project!** This repository contains a third-party Go package to programmatically interact with nftables. Find the official nftables website at https://wiki.nftables.org/ This package manipulates Linux nftables (the iptables successor). It is implemented in pure Go, i.e. does not wrap libnftnl. This is not an official Google product. ## Breaking changes This package is in very early stages, and only contains enough data types and functions to install very basic nftables rules. It is likely that mistakes with the data types/API will be identified as more functionality is added. ## Contributions Contributions are very welcome! nftables-0.2.0/alignedbuff/000077500000000000000000000000001457332137300155655ustar00rootroot00000000000000nftables-0.2.0/alignedbuff/alignedbuff.go000066400000000000000000000222101457332137300203570ustar00rootroot00000000000000// Package alignedbuff implements encoding and decoding aligned data elements // to/from buffers in native endianess. // // # Note // // The alignment/padding as implemented in this package must match that of // kernel's and user space C implementations for a particular architecture (bit // size). Please see also the "dummy structure" _xt_align // (https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/x_tables.h#L93) // as well as the associated XT_ALIGN C preprocessor macro. // // In particular, we rely on the Go compiler to follow the same architecture // alignments as the C compiler(s) on Linux. package alignedbuff import ( "bytes" "errors" "fmt" "unsafe" "github.com/google/nftables/binaryutil" ) // ErrEOF signals trying to read beyond the available payload information. var ErrEOF = errors.New("not enough data left") // AlignedBuff implements marshalling and unmarshalling information in // platform/architecture native endianess and data type alignment. It // additionally covers some of the nftables-xtables translation-specific // idiosyncracies to the extend needed in order to properly marshal and // unmarshal Match and Target expressions, and their Info payload in particular. type AlignedBuff struct { data []byte pos int } // New returns a new AlignedBuff for marshalling aligned data in native // endianess. func New() AlignedBuff { return AlignedBuff{} } // NewWithData returns a new AlignedBuff for unmarshalling the passed data in // native endianess. func NewWithData(data []byte) AlignedBuff { return AlignedBuff{data: data} } // Data returns the properly padded info payload data written before by calling // the various Uint8, Uint16, ... marshalling functions. func (a *AlignedBuff) Data() []byte { // The Linux kernel expects payloads to be padded to the next uint64 // alignment. a.alignWrite(uint64AlignMask) return a.data } // BytesAligned32 unmarshals the given amount of bytes starting with the native // alignment for uint32 data types. It returns ErrEOF when trying to read beyond // the payload. // // BytesAligned32 is used to unmarshal IP addresses for different IP versions, // which are always aligned the same way as the native alignment for uint32. func (a *AlignedBuff) BytesAligned32(size int) ([]byte, error) { if err := a.alignCheckedRead(uint32AlignMask); err != nil { return nil, err } if a.pos > len(a.data)-size { return nil, ErrEOF } data := a.data[a.pos : a.pos+size] a.pos += size return data, nil } // Uint8 unmarshals an uint8 in native endianess and alignment. It returns // ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Uint8() (uint8, error) { if a.pos >= len(a.data) { return 0, ErrEOF } v := a.data[a.pos] a.pos++ return v, nil } // Uint16 unmarshals an uint16 in native endianess and alignment. It returns // ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Uint16() (uint16, error) { if err := a.alignCheckedRead(uint16AlignMask); err != nil { return 0, err } v := binaryutil.NativeEndian.Uint16(a.data[a.pos : a.pos+2]) a.pos += 2 return v, nil } // Uint16BE unmarshals an uint16 in "network" (=big endian) endianess and native // uint16 alignment. It returns ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Uint16BE() (uint16, error) { if err := a.alignCheckedRead(uint16AlignMask); err != nil { return 0, err } v := binaryutil.BigEndian.Uint16(a.data[a.pos : a.pos+2]) a.pos += 2 return v, nil } // Uint32 unmarshals an uint32 in native endianess and alignment. It returns // ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Uint32() (uint32, error) { if err := a.alignCheckedRead(uint32AlignMask); err != nil { return 0, err } v := binaryutil.NativeEndian.Uint32(a.data[a.pos : a.pos+4]) a.pos += 4 return v, nil } // Uint64 unmarshals an uint64 in native endianess and alignment. It returns // ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Uint64() (uint64, error) { if err := a.alignCheckedRead(uint64AlignMask); err != nil { return 0, err } v := binaryutil.NativeEndian.Uint64(a.data[a.pos : a.pos+8]) a.pos += 8 return v, nil } // Int32 unmarshals an int32 in native endianess and alignment. It returns // ErrEOF when trying to read beyond the payload. func (a *AlignedBuff) Int32() (int32, error) { if err := a.alignCheckedRead(int32AlignMask); err != nil { return 0, err } v := binaryutil.Int32(a.data[a.pos : a.pos+4]) a.pos += 4 return v, nil } // String unmarshals a null terminated string func (a *AlignedBuff) String() (string, error) { len := 0 for { if a.data[a.pos+len] == 0x00 { break } len++ } v := binaryutil.String(a.data[a.pos : a.pos+len]) a.pos += len return v, nil } // StringWithLength unmarshals a string of a given length (for non-null // terminated strings) func (a *AlignedBuff) StringWithLength(len int) (string, error) { v := binaryutil.String(a.data[a.pos : a.pos+len]) a.pos += len return v, nil } // Uint unmarshals an uint in native endianess and alignment for the C "unsigned // int" type. It returns ErrEOF when trying to read beyond the payload. Please // note that on 64bit platforms, the size and alignment of C's and Go's unsigned // integer data types differ, so we encapsulate this difference here. func (a *AlignedBuff) Uint() (uint, error) { switch uintSize { case 2: v, err := a.Uint16() return uint(v), err case 4: v, err := a.Uint32() return uint(v), err case 8: v, err := a.Uint64() return uint(v), err default: panic(fmt.Sprintf("unsupported uint size %d", uintSize)) } } // PutBytesAligned32 marshals the given bytes starting with the native alignment // for uint32 data types. It additionaly adds padding to reach the specified // size. // // PutBytesAligned32 is used to marshal IP addresses for different IP versions, // which are always aligned the same way as the native alignment for uint32. func (a *AlignedBuff) PutBytesAligned32(data []byte, size int) { a.alignWrite(uint32AlignMask) a.data = append(a.data, data...) a.pos += len(data) if len(data) < size { padding := size - len(data) a.data = append(a.data, bytes.Repeat([]byte{0}, padding)...) a.pos += padding } } // PutUint8 marshals an uint8 in native endianess and alignment. func (a *AlignedBuff) PutUint8(v uint8) { a.data = append(a.data, v) a.pos++ } // PutUint16 marshals an uint16 in native endianess and alignment. func (a *AlignedBuff) PutUint16(v uint16) { a.alignWrite(uint16AlignMask) a.data = append(a.data, binaryutil.NativeEndian.PutUint16(v)...) a.pos += 2 } // PutUint16BE marshals an uint16 in "network" (=big endian) endianess and // native uint16 alignment. func (a *AlignedBuff) PutUint16BE(v uint16) { a.alignWrite(uint16AlignMask) a.data = append(a.data, binaryutil.BigEndian.PutUint16(v)...) a.pos += 2 } // PutUint32 marshals an uint32 in native endianess and alignment. func (a *AlignedBuff) PutUint32(v uint32) { a.alignWrite(uint32AlignMask) a.data = append(a.data, binaryutil.NativeEndian.PutUint32(v)...) a.pos += 4 } // PutUint64 marshals an uint64 in native endianess and alignment. func (a *AlignedBuff) PutUint64(v uint64) { a.alignWrite(uint64AlignMask) a.data = append(a.data, binaryutil.NativeEndian.PutUint64(v)...) a.pos += 8 } // PutInt32 marshals an int32 in native endianess and alignment. func (a *AlignedBuff) PutInt32(v int32) { a.alignWrite(int32AlignMask) a.data = append(a.data, binaryutil.PutInt32(v)...) a.pos += 4 } // PutString marshals a string. func (a *AlignedBuff) PutString(v string) { a.data = append(a.data, binaryutil.PutString(v)...) a.pos += len(v) } // PutUint marshals an uint in native endianess and alignment for the C // "unsigned int" type. Please note that on 64bit platforms, the size and // alignment of C's and Go's unsigned integer data types differ, so we // encapsulate this difference here. func (a *AlignedBuff) PutUint(v uint) { switch uintSize { case 2: a.PutUint16(uint16(v)) case 4: a.PutUint32(uint32(v)) case 8: a.PutUint64(uint64(v)) default: panic(fmt.Sprintf("unsupported uint size %d", uintSize)) } } // alignCheckedRead aligns the (read) position if necessary and suitable // according to the specified alignment mask. alignCheckedRead returns an error // if after any necessary alignment there isn't enough data left to be read into // a value of the size corresponding to the specified alignment mask. func (a *AlignedBuff) alignCheckedRead(m int) error { a.pos = (a.pos + m) & ^m if a.pos > len(a.data)-(m+1) { return ErrEOF } return nil } // alignWrite aligns the (write) position if necessary and suitable according to // the specified alignment mask. It doubles as final payload padding helpmate in // order to keep the kernel happy. func (a *AlignedBuff) alignWrite(m int) { pos := (a.pos + m) & ^m if pos != a.pos { a.data = append(a.data, padding[:pos-a.pos]...) a.pos = pos } } // This is ... ugly. var uint16AlignMask = int(unsafe.Alignof(uint16(0)) - 1) var uint32AlignMask = int(unsafe.Alignof(uint32(0)) - 1) var uint64AlignMask = int(unsafe.Alignof(uint64(0)) - 1) var padding = bytes.Repeat([]byte{0}, uint64AlignMask) var int32AlignMask = int(unsafe.Alignof(int32(0)) - 1) // And this even worse. var uintSize = unsafe.Sizeof(uint32(0)) nftables-0.2.0/alignedbuff/alignedbuff_test.go000066400000000000000000000142741457332137300214310ustar00rootroot00000000000000package alignedbuff import ( "testing" ) func TestAlignmentData(t *testing.T) { if uint16AlignMask == 0 { t.Fatal("zero uint16 alignment mask") } if uint32AlignMask == 0 { t.Fatal("zero uint32 alignment mask") } if uint64AlignMask == 0 { t.Fatal("zero uint64 alignment mask") } if len(padding) == 0 { t.Fatal("zero alignment padding sequence") } if uintSize == 0 { t.Fatal("zero uint size") } if int32AlignMask == 0 { t.Fatal("zero uint32 alignment mask") } } func TestAlignedBuff8(t *testing.T) { b := NewWithData([]byte{0x42}) tests := []struct { name string v uint8 err error }{ { name: "first read", v: 0x42, err: nil, }, { name: "end of buffer", v: 0, err: ErrEOF, }, } for _, tt := range tests { v, err := b.Uint8() if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } func TestAlignedBuff16(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutUint16(0x1234) b0.PutUint16(0x5678) b := NewWithData(b0.data) v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v uint16 err error }{ { name: "first read", v: 0x1234, err: nil, }, { name: "second read", v: 0x5678, err: nil, }, { name: "end of buffer", v: 0, err: ErrEOF, }, } for _, tt := range tests { v, err := b.Uint16() if v != tt.v || err != tt.err { t.Errorf("%s failed, expected: %#v %#v, got: %#v, %#v", tt.name, tt.v, tt.err, v, err) } } } func TestAlignedBuff32(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutUint32(0x12345678) b0.PutUint32(0x01cecafe) b := NewWithData(b0.data) // Sigh. The Linux kernel expects certain nftables payloads to be padded to // the uint64 next alignment. Now, on 64bit platforms this will be a 64bit // alignment, yet on 32bit platforms this will be a 32bit alignment. So, we // should calculate the expected data length here separately from our // implementation to be fail safe! However, this might be rather a recipe // for a safe fail... expectedlen := 2*(uint32AlignMask+1) + (uint64AlignMask + 1) if len(b0.Data()) != expectedlen { t.Fatalf("alignment padding failed") } v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v uint32 err error }{ { name: "first read", v: 0x12345678, err: nil, }, { name: "second read", v: 0x01cecafe, err: nil, }, { name: "end of buffer", v: 0, err: ErrEOF, }, } for _, tt := range tests { v, err := b.Uint32() if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } func TestAlignedBuff64(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutUint64(0x1234567823456789) b0.PutUint64(0x01cecafec001beef) b := NewWithData(b0.data) v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v uint64 err error }{ { name: "first read", v: 0x1234567823456789, err: nil, }, { name: "second read", v: 0x01cecafec001beef, err: nil, }, { name: "end of buffer", v: 0, err: ErrEOF, }, } for _, tt := range tests { v, err := b.Uint64() if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } func TestAlignedUint(t *testing.T) { expectedv := uint(^uint32(0) - 1) b0 := New() b0.PutUint8(0x55) b0.PutUint(expectedv) b0.PutUint8(0xAA) b := NewWithData(b0.data) v, err := b.Uint8() if v != 0x55 || err != nil { t.Fatalf("sentinel read failed") } uiv, err := b.Uint() if uiv != expectedv || err != nil { t.Fatalf("uint read failed, expected: %d, got: %d", expectedv, uiv) } v, err = b.Uint8() if v != 0xAA || err != nil { t.Fatalf("sentinel read failed") } } func TestAlignedBuffInt32(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutInt32(0x12345678) b0.PutInt32(0x01cecafe) b := NewWithData(b0.data) // Sigh. The Linux kernel expects certain nftables payloads to be padded to // the uint64 next alignment. Now, on 64bit platforms this will be a 64bit // alignment, yet on 32bit platforms this will be a 32bit alignment. So, we // should calculate the expected data length here separately from our // implementation to be fail safe! However, this might be rather a recipe // for a safe fail... expectedlen := 2*(uint32AlignMask+1) + (uint64AlignMask + 1) if len(b0.Data()) != expectedlen { t.Fatalf("alignment padding failed") } v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v int32 err error }{ { name: "first read", v: 0x12345678, err: nil, }, { name: "second read", v: 0x01cecafe, err: nil, }, { name: "end of buffer", v: 0, err: ErrEOF, }, } for _, tt := range tests { v, err := b.Int32() if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } func TestAlignedBuffPutNullTerminatedString(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutString("test" + "\x00") b := NewWithData(b0.data) v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v string err error }{ { name: "first read", v: "test", err: nil, }, } for _, tt := range tests { v, err := b.String() if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } func TestAlignedBuffPutString(t *testing.T) { b0 := New() b0.PutUint8(0x42) b0.PutString("test") b := NewWithData(b0.data) v, err := b.Uint8() if v != 0x42 || err != nil { t.Fatalf("unaligment read failed") } tests := []struct { name string v string err error }{ { name: "first read", v: "test", err: nil, }, } for _, tt := range tests { v, err := b.StringWithLength(len("test")) if v != tt.v || err != tt.err { t.Errorf("expected: %#v %#v, got: %#v, %#v", tt.v, tt.err, v, err) } } } nftables-0.2.0/binaryutil/000077500000000000000000000000001457332137300155015ustar00rootroot00000000000000nftables-0.2.0/binaryutil/binaryutil.go000066400000000000000000000060431457332137300202150ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package binaryutil contains convenience wrappers around encoding/binary. package binaryutil import ( "bytes" "encoding/binary" "unsafe" ) // ByteOrder is like binary.ByteOrder, but allocates memory and returns byte // slices, for convenience. type ByteOrder interface { PutUint16(v uint16) []byte PutUint32(v uint32) []byte PutUint64(v uint64) []byte Uint16(b []byte) uint16 Uint32(b []byte) uint32 Uint64(b []byte) uint64 } // NativeEndian is either little endian or big endian, depending on the native // endian-ness, and allocates memory and returns byte slices, for convenience. var NativeEndian ByteOrder = &nativeEndian{} type nativeEndian struct{} func (nativeEndian) PutUint16(v uint16) []byte { buf := make([]byte, 2) *(*uint16)(unsafe.Pointer(&buf[0])) = v return buf } func (nativeEndian) PutUint32(v uint32) []byte { buf := make([]byte, 4) *(*uint32)(unsafe.Pointer(&buf[0])) = v return buf } func (nativeEndian) PutUint64(v uint64) []byte { buf := make([]byte, 8) *(*uint64)(unsafe.Pointer(&buf[0])) = v return buf } func (nativeEndian) Uint16(b []byte) uint16 { return *(*uint16)(unsafe.Pointer(&b[0])) } func (nativeEndian) Uint32(b []byte) uint32 { return *(*uint32)(unsafe.Pointer(&b[0])) } func (nativeEndian) Uint64(b []byte) uint64 { return *(*uint64)(unsafe.Pointer(&b[0])) } // BigEndian is like binary.BigEndian, but allocates memory and returns byte // slices, for convenience. var BigEndian ByteOrder = &bigEndian{} type bigEndian struct{} func (bigEndian) PutUint16(v uint16) []byte { buf := make([]byte, 2) binary.BigEndian.PutUint16(buf, v) return buf } func (bigEndian) PutUint32(v uint32) []byte { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, v) return buf } func (bigEndian) PutUint64(v uint64) []byte { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, v) return buf } func (bigEndian) Uint16(b []byte) uint16 { return binary.BigEndian.Uint16(b) } func (bigEndian) Uint32(b []byte) uint32 { return binary.BigEndian.Uint32(b) } func (bigEndian) Uint64(b []byte) uint64 { return binary.BigEndian.Uint64(b) } // For dealing with types not supported by the encoding/binary interface func PutInt32(v int32) []byte { buf := make([]byte, 4) *(*int32)(unsafe.Pointer(&buf[0])) = v return buf } func Int32(b []byte) int32 { return *(*int32)(unsafe.Pointer(&b[0])) } func PutString(s string) []byte { return []byte(s) } func String(b []byte) string { return string(bytes.TrimRight(b, "\x00")) } nftables-0.2.0/binaryutil/binaryutil_test.go000066400000000000000000000103351457332137300212530ustar00rootroot00000000000000package binaryutil import ( "bytes" "encoding/binary" "reflect" "testing" "unsafe" ) func TestNativeByteOrder(t *testing.T) { // See https://stackoverflow.com/a/53286786 var natEnd binary.ByteOrder canary := [2]byte{} *(*uint16)(unsafe.Pointer(&canary[0])) = uint16(0xABCD) switch canary { case [2]byte{0xCD, 0xAB}: natEnd = binary.LittleEndian case [2]byte{0xAB, 0xCD}: natEnd = binary.BigEndian default: t.Fatalf("unsupported \"mixed\" native endianness") } tests := []struct { name string expectedv interface{} marshal func(v interface{}) []byte unmarshal func(b []byte) interface{} reference func(v interface{}, b []byte) }{ { name: "Uint16", expectedv: uint16(0x1234), marshal: func(v interface{}) []byte { return NativeEndian.PutUint16(v.(uint16)) }, unmarshal: func(b []byte) interface{} { return NativeEndian.Uint16(b) }, reference: func(v interface{}, b []byte) { natEnd.PutUint16(b, v.(uint16)) }, }, { name: "Uint32", expectedv: uint32(0x12345678), marshal: func(v interface{}) []byte { return NativeEndian.PutUint32(v.(uint32)) }, unmarshal: func(b []byte) interface{} { return NativeEndian.Uint32(b) }, reference: func(v interface{}, b []byte) { natEnd.PutUint32(b, v.(uint32)) }, }, { name: "Uint64", expectedv: uint64(0x1234567801020304), marshal: func(v interface{}) []byte { return NativeEndian.PutUint64(v.(uint64)) }, unmarshal: func(b []byte) interface{} { return NativeEndian.Uint64(b) }, reference: func(v interface{}, b []byte) { natEnd.PutUint64(b, v.(uint64)) }, }, } for _, tt := range tests { expectedb := make([]byte, reflect.TypeOf(tt.expectedv).Size()) tt.reference(tt.expectedv, expectedb) actualb := tt.marshal(tt.expectedv) if !bytes.Equal(actualb, expectedb) { t.Errorf("NativeEndian.Put%s failure, expected: %#v, got: %#v", tt.name, expectedb, actualb) } actualv := tt.unmarshal(actualb) if !reflect.DeepEqual(tt.expectedv, actualv) { t.Errorf("NativeEndian.%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actualv) } } } func TestBigEndian(t *testing.T) { tests := []struct { name string expected []byte expectedv interface{} actual []byte unmarshal func(b []byte) interface{} }{ { name: "Uint16", expected: []byte{0x12, 0x34}, expectedv: uint16(0x1234), actual: BigEndian.PutUint16(0x1234), unmarshal: func(b []byte) interface{} { return BigEndian.Uint16(b) }, }, { name: "Uint32", expected: []byte{0x12, 0x34, 0x56, 0x78}, expectedv: uint32(0x12345678), actual: BigEndian.PutUint32(0x12345678), unmarshal: func(b []byte) interface{} { return BigEndian.Uint32(b) }, }, { name: "Uint64", expected: []byte{0x12, 0x34, 0x56, 0x78, 0x01, 0x02, 0x03, 0x04}, expectedv: uint64(0x1234567801020304), actual: BigEndian.PutUint64(0x1234567801020304), unmarshal: func(b []byte) interface{} { return BigEndian.Uint64(b) }, }, } for _, tt := range tests { if bytes.Compare(tt.actual, tt.expected) != 0 { t.Errorf("BigEndian.Put%s failure, expected: %#v, got: %#v", tt.name, tt.expected, tt.actual) } if actual := tt.unmarshal(tt.actual); !reflect.DeepEqual(actual, tt.expectedv) { t.Errorf("BigEndian.%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actual) } } } func TestOtherTypes(t *testing.T) { tests := []struct { name string expected []byte expectedv interface{} actual []byte unmarshal func(b []byte) interface{} }{ { name: "Int32", expected: []byte{0x78, 0x56, 0x34, 0x12}, expectedv: int32(0x12345678), actual: PutInt32(0x12345678), unmarshal: func(b []byte) interface{} { return Int32(b) }, }, { name: "String", expected: []byte{0x74, 0x65, 0x73, 0x74}, expectedv: "test", actual: PutString("test"), unmarshal: func(b []byte) interface{} { return String(b) }, }, } for _, tt := range tests { if bytes.Compare(tt.actual, tt.expected) != 0 { t.Errorf("Put%s failure, expected: %#v, got: %#v", tt.name, tt.expected, tt.actual) } if actual := tt.unmarshal(tt.actual); !reflect.DeepEqual(actual, tt.expectedv) { t.Errorf("%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actual) } } } nftables-0.2.0/chain.go000066400000000000000000000221721457332137300147340ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "encoding/binary" "fmt" "math" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // ChainHook specifies at which step in packet processing the Chain should be // executed. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_hooks type ChainHook uint32 // Possible ChainHook values. var ( ChainHookPrerouting *ChainHook = ChainHookRef(unix.NF_INET_PRE_ROUTING) ChainHookInput *ChainHook = ChainHookRef(unix.NF_INET_LOCAL_IN) ChainHookForward *ChainHook = ChainHookRef(unix.NF_INET_FORWARD) ChainHookOutput *ChainHook = ChainHookRef(unix.NF_INET_LOCAL_OUT) ChainHookPostrouting *ChainHook = ChainHookRef(unix.NF_INET_POST_ROUTING) ChainHookIngress *ChainHook = ChainHookRef(unix.NF_NETDEV_INGRESS) ) // ChainHookRef returns a pointer to a ChainHookRef value. func ChainHookRef(h ChainHook) *ChainHook { return &h } // ChainPriority orders the chain relative to Netfilter internal operations. See // also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority type ChainPriority int32 // Possible ChainPriority values. var ( // from /usr/include/linux/netfilter_ipv4.h ChainPriorityFirst *ChainPriority = ChainPriorityRef(math.MinInt32) ChainPriorityConntrackDefrag *ChainPriority = ChainPriorityRef(-400) ChainPriorityRaw *ChainPriority = ChainPriorityRef(-300) ChainPrioritySELinuxFirst *ChainPriority = ChainPriorityRef(-225) ChainPriorityConntrack *ChainPriority = ChainPriorityRef(-200) ChainPriorityMangle *ChainPriority = ChainPriorityRef(-150) ChainPriorityNATDest *ChainPriority = ChainPriorityRef(-100) ChainPriorityFilter *ChainPriority = ChainPriorityRef(0) ChainPrioritySecurity *ChainPriority = ChainPriorityRef(50) ChainPriorityNATSource *ChainPriority = ChainPriorityRef(100) ChainPrioritySELinuxLast *ChainPriority = ChainPriorityRef(225) ChainPriorityConntrackHelper *ChainPriority = ChainPriorityRef(300) ChainPriorityConntrackConfirm *ChainPriority = ChainPriorityRef(math.MaxInt32) ChainPriorityLast *ChainPriority = ChainPriorityRef(math.MaxInt32) ) // ChainPriorityRef returns a pointer to a ChainPriority value. func ChainPriorityRef(p ChainPriority) *ChainPriority { return &p } // ChainType defines what this chain will be used for. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types type ChainType string // Possible ChainType values. const ( ChainTypeFilter ChainType = "filter" ChainTypeRoute ChainType = "route" ChainTypeNAT ChainType = "nat" ) // ChainPolicy defines what this chain default policy will be. type ChainPolicy uint32 // Possible ChainPolicy values. const ( ChainPolicyDrop ChainPolicy = iota ChainPolicyAccept ) // A Chain contains Rules. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains type Chain struct { Name string Table *Table Hooknum *ChainHook Priority *ChainPriority Type ChainType Policy *ChainPolicy } // AddChain adds the specified Chain. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Adding_base_chains func (cc *Conn) AddChain(c *Chain) *Chain { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_CHAIN_TABLE, Data: []byte(c.Table.Name + "\x00")}, {Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")}, }) if c.Hooknum != nil && c.Priority != nil { hookAttr := []netlink.Attribute{ {Type: unix.NFTA_HOOK_HOOKNUM, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Hooknum))}, {Type: unix.NFTA_HOOK_PRIORITY, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Priority))}, } data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NLA_F_NESTED | unix.NFTA_CHAIN_HOOK, Data: cc.marshalAttr(hookAttr)}, })...) } if c.Policy != nil { data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_CHAIN_POLICY, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Policy))}, })...) } if c.Type != "" { data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_CHAIN_TYPE, Data: []byte(c.Type + "\x00")}, })...) } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(c.Table.Family), 0), data...), }) return c } // DelChain deletes the specified Chain. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Deleting_chains func (cc *Conn) DelChain(c *Chain) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_CHAIN_TABLE, Data: []byte(c.Table.Name + "\x00")}, {Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")}, }) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(c.Table.Family), 0), data...), }) } // FlushChain removes all rules within the specified Chain. See also // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Flushing_chain func (cc *Conn) FlushChain(c *Chain) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_RULE_TABLE, Data: []byte(c.Table.Name + "\x00")}, {Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")}, }) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(c.Table.Family), 0), data...), }) } // ListChains returns currently configured chains in the kernel func (cc *Conn) ListChains() ([]*Chain, error) { return cc.ListChainsOfTableFamily(TableFamilyUnspecified) } // ListChainsOfTableFamily returns currently configured chains for the specified // family in the kernel. It lists all chains ins all tables if family is // TableFamilyUnspecified. func (cc *Conn) ListChainsOfTableFamily(family TableFamily) ([]*Chain, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() msg := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETCHAIN), Flags: netlink.Request | netlink.Dump, }, Data: extraHeader(uint8(family), 0), } response, err := conn.Execute(msg) if err != nil { return nil, err } var chains []*Chain for _, m := range response { c, err := chainFromMsg(m) if err != nil { return nil, err } chains = append(chains, c) } return chains, nil } func chainFromMsg(msg netlink.Message) (*Chain, error) { newChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN) delChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN) if got, want1, want2 := msg.Header.Type, newChainHeaderType, delChainHeaderType; got != want1 && got != want2 { return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2) } var c Chain ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) if err != nil { return nil, err } for ad.Next() { switch ad.Type() { case unix.NFTA_CHAIN_NAME: c.Name = ad.String() case unix.NFTA_TABLE_NAME: c.Table = &Table{Name: ad.String()} // msg[0] carries TableFamily byte indicating whether it is IPv4, IPv6 or something else c.Table.Family = TableFamily(msg.Data[0]) case unix.NFTA_CHAIN_TYPE: c.Type = ChainType(ad.String()) case unix.NFTA_CHAIN_POLICY: policy := ChainPolicy(binaryutil.BigEndian.Uint32(ad.Bytes())) c.Policy = &policy case unix.NFTA_CHAIN_HOOK: ad.Do(func(b []byte) error { c.Hooknum, c.Priority, err = hookFromMsg(b) return err }) } } return &c, nil } func hookFromMsg(b []byte) (*ChainHook, *ChainPriority, error) { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return nil, nil, err } ad.ByteOrder = binary.BigEndian var hooknum ChainHook var prio ChainPriority for ad.Next() { switch ad.Type() { case unix.NFTA_HOOK_HOOKNUM: hooknum = ChainHook(ad.Uint32()) case unix.NFTA_HOOK_PRIORITY: prio = ChainPriority(ad.Uint32()) } } return &hooknum, &prio, nil } nftables-0.2.0/compat_policy.go000066400000000000000000000034701457332137300165140ustar00rootroot00000000000000package nftables import ( "fmt" "github.com/google/nftables/expr" "golang.org/x/sys/unix" ) const nft_RULE_COMPAT_F_INV uint32 = (1 << 1) const nft_RULE_COMPAT_F_MASK uint32 = nft_RULE_COMPAT_F_INV // Used by xt match or target like xt_tcpudp to set compat policy between xtables and nftables // https://elixir.bootlin.com/linux/v5.12/source/net/netfilter/nft_compat.c#L187 type compatPolicy struct { Proto uint32 Flag uint32 } var xtMatchCompatMap map[string]*compatPolicy = map[string]*compatPolicy{ "tcp": { Proto: unix.IPPROTO_TCP, }, "udp": { Proto: unix.IPPROTO_UDP, }, "udplite": { Proto: unix.IPPROTO_UDPLITE, }, "tcpmss": { Proto: unix.IPPROTO_TCP, }, "sctp": { Proto: unix.IPPROTO_SCTP, }, "osf": { Proto: unix.IPPROTO_TCP, }, "ipcomp": { Proto: unix.IPPROTO_COMP, }, "esp": { Proto: unix.IPPROTO_ESP, }, } var xtTargetCompatMap map[string]*compatPolicy = map[string]*compatPolicy{ "TCPOPTSTRIP": { Proto: unix.IPPROTO_TCP, }, "TCPMSS": { Proto: unix.IPPROTO_TCP, }, } func getCompatPolicy(exprs []expr.Any) (*compatPolicy, error) { var exprItem expr.Any var compat *compatPolicy for _, iter := range exprs { var tmpExprItem expr.Any var tmpCompat *compatPolicy switch item := iter.(type) { case *expr.Match: if compat, ok := xtMatchCompatMap[item.Name]; ok { tmpCompat = compat tmpExprItem = item } else { continue } case *expr.Target: if compat, ok := xtTargetCompatMap[item.Name]; ok { tmpCompat = compat tmpExprItem = item } else { continue } default: continue } if compat == nil { compat = tmpCompat exprItem = tmpExprItem } else if *compat != *tmpCompat { return nil, fmt.Errorf("%#v and %#v has conflict compat policy %#v vs %#v", exprItem, tmpExprItem, compat, tmpCompat) } } return compat, nil } nftables-0.2.0/compat_policy_test.go000066400000000000000000000032011457332137300175430ustar00rootroot00000000000000package nftables import ( "testing" "github.com/google/nftables/expr" "github.com/google/nftables/xt" "golang.org/x/sys/unix" ) func TestGetCompatPolicy(t *testing.T) { // -tcp --dport 0:65534 --sport 0:65534 tcpMatch := &expr.Match{ Name: "tcp", Info: &xt.Tcp{ SrcPorts: [2]uint16{0, 65534}, DstPorts: [2]uint16{0, 65534}, }, } // -udp --dport 0:65534 --sport 0:65534 udpMatch := &expr.Match{ Name: "udp", Info: &xt.Udp{ SrcPorts: [2]uint16{0, 65534}, DstPorts: [2]uint16{0, 65534}, }, } // -j TCPMSS --set-mss 1460 mess := xt.Unknown([]byte{1460 & 0xff, (1460 >> 8) & 0xff}) tcpMessTarget := &expr.Target{ Name: "TCPMESS", Info: &mess, } // -m state --state ESTABLISHED ctMatch := &expr.Match{ Name: "conntrack", Rev: 1, Info: &xt.ConntrackMtinfo1{ ConntrackMtinfoBase: xt.ConntrackMtinfoBase{ MatchFlags: 0x2001, }, StateMask: 0x02, }, } // compatPolicy.Proto should be tcp if compatPolicy, err := getCompatPolicy([]expr.Any{ tcpMatch, tcpMessTarget, ctMatch, }); err != nil { t.Fatalf("getCompatPolicy fail %#v", err) } else if compatPolicy.Proto != unix.IPPROTO_TCP { t.Fatalf("getCompatPolicy wrong %#v", compatPolicy) } // should conflict if _, err := getCompatPolicy([]expr.Any{ udpMatch, tcpMatch, }, ); err == nil { t.Fatalf("getCompatPolicy fail err should not be nil") } // compatPolicy should be nil if compatPolicy, err := getCompatPolicy([]expr.Any{ ctMatch, }); err != nil { t.Fatalf("getCompatPolicy fail %#v", err) } else if compatPolicy != nil { t.Fatalf("getCompatPolicy fail compat policy of conntrack match should be nil") } } nftables-0.2.0/conn.go000066400000000000000000000236251457332137300146130ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "errors" "fmt" "os" "sync" "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nltest" "golang.org/x/sys/unix" ) // A Conn represents a netlink connection of the nftables family. // // All methods return their input, so that variables can be defined from string // literals when desired. // // Commands are buffered. Flush sends all buffered commands in a single batch. type Conn struct { TestDial nltest.Func // for testing only; passed to nltest.Dial NetNS int // fd referencing the network namespace netlink will interact with. lasting bool // establish a lasting connection to be used across multiple netlink operations. mu sync.Mutex // protects the following state messages []netlink.Message err error nlconn *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol. } // ConnOption is an option to change the behavior of the nftables Conn returned by Open. type ConnOption func(*Conn) // New returns a netlink connection for querying and modifying nftables. Some // aspects of the new netlink connection can be configured using the options // WithNetNSFd, WithTestDial, and AsLasting. // // A lasting netlink connection should be closed by calling CloseLasting() to // close the underlying lasting netlink connection, cancelling all pending // operations using this connection. func New(opts ...ConnOption) (*Conn, error) { cc := &Conn{} for _, opt := range opts { opt(cc) } if !cc.lasting { return cc, nil } nlconn, err := cc.dialNetlink() if err != nil { return nil, err } cc.nlconn = nlconn return cc, nil } // AsLasting creates the new netlink connection as a lasting connection that is // reused across multiple netlink operations, instead of opening and closing the // underlying netlink connection only for the duration of a single netlink // operation. func AsLasting() ConnOption { return func(cc *Conn) { // We cannot create the underlying connection yet, as we are called // anywhere in the option processing chain and there might be later // options still modifying connection behavior. cc.lasting = true } } // WithNetNSFd sets the network namespace to create a new netlink connection to: // the fd must reference a network namespace. func WithNetNSFd(fd int) ConnOption { return func(cc *Conn) { cc.NetNS = fd } } // WithTestDial sets the specified nltest.Func when creating a new netlink // connection. func WithTestDial(f nltest.Func) ConnOption { return func(cc *Conn) { cc.TestDial = f } } // netlinkCloser is returned by netlinkConn(UnderLock) and must be called after // being done with the returned netlink connection in order to properly close // this connection, if necessary. type netlinkCloser func() error // netlinkConn returns a netlink connection together with a netlinkCloser that // later must be called by the caller when it doesn't need the returned netlink // connection anymore. The netlinkCloser will close the netlink connection when // necessary. If New has been told to create a lasting connection, then this // lasting netlink connection will be returned, otherwise a new "transient" // netlink connection will be opened and returned instead. netlinkConn must not // be called while the Conn.mu lock is currently helt (this will cause a // deadlock). Use netlinkConnUnderLock instead in such situations. func (cc *Conn) netlinkConn() (*netlink.Conn, netlinkCloser, error) { cc.mu.Lock() defer cc.mu.Unlock() return cc.netlinkConnUnderLock() } // netlinkConnUnderLock works like netlinkConn but must be called while holding // the Conn.mu lock. func (cc *Conn) netlinkConnUnderLock() (*netlink.Conn, netlinkCloser, error) { if cc.nlconn != nil { return cc.nlconn, func() error { return nil }, nil } nlconn, err := cc.dialNetlink() if err != nil { return nil, nil, err } return nlconn, func() error { return nlconn.Close() }, nil } func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]netlink.Message, error) { if nlconn == nil { return nil, errors.New("netlink conn is not initialized") } // first receive will be the message that we expect reply, err := nlconn.Receive() if err != nil { return nil, err } if (sentMsgFlags & netlink.Acknowledge) == 0 { // we did not request an ack return reply, nil } if (sentMsgFlags & netlink.Dump) == netlink.Dump { // sent message has Dump flag set, there will be no acks // https://github.com/torvalds/linux/blob/7e062cda7d90543ac8c7700fc7c5527d0c0f22ad/net/netlink/af_netlink.c#L2387-L2390 return reply, nil } if len(reply) != 0 { last := reply[len(reply)-1] for re := last.Header.Type; (re&netlink.Overrun) == netlink.Overrun && (re&netlink.Done) != netlink.Done; re = last.Header.Type { // we are not finished, the message is overrun r, err := nlconn.Receive() if err != nil { return nil, err } reply = append(reply, r...) last = reply[len(reply)-1] } if last.Header.Type == netlink.Error && binaryutil.BigEndian.Uint32(last.Data[:4]) == 0 { // we have already collected an ack return reply, nil } } // Now we expect an ack ack, err := nlconn.Receive() if err != nil { return nil, err } if len(ack) == 0 { // received an empty ack? return reply, nil } msg := ack[0] if msg.Header.Type != netlink.Error { // acks should be delivered as NLMSG_ERROR return nil, fmt.Errorf("expected header %v, but got %v", netlink.Error, msg.Header.Type) } if binaryutil.BigEndian.Uint32(msg.Data[:4]) != 0 { // if errno field is not set to 0 (success), this is an error return nil, fmt.Errorf("error delivered in message: %v", msg.Data) } return reply, nil } // CloseLasting closes the lasting netlink connection that has been opened using // AsLasting option when creating this connection. If either no lasting netlink // connection has been opened or the lasting connection is already in the // process of closing or has been closed, CloseLasting will immediately return // without any error. // // CloseLasting will terminate all pending netlink operations using the lasting // connection. // // After closing a lasting connection, the connection will revert to using // on-demand transient netlink connections when calling further netlink // operations (such as GetTables). func (cc *Conn) CloseLasting() error { // Don't acquire the lock for the whole duration of the CloseLasting // operation, but instead only so long as to make sure to only run the // netlink socket close on the first time with a lasting netlink socket. As // there is only the New() constructor, but no Open() method, it's // impossible to reopen a lasting connection. cc.mu.Lock() nlconn := cc.nlconn cc.nlconn = nil cc.mu.Unlock() if nlconn != nil { return nlconn.Close() } return nil } // Flush sends all buffered commands in a single batch to nftables. func (cc *Conn) Flush() error { cc.mu.Lock() defer func() { cc.messages = nil cc.mu.Unlock() }() if len(cc.messages) == 0 { // Messages were already programmed, returning nil return nil } if cc.err != nil { return cc.err // serialization error } conn, closer, err := cc.netlinkConnUnderLock() if err != nil { return err } defer func() { _ = closer() }() if _, err := conn.SendMessages(batch(cc.messages)); err != nil { return fmt.Errorf("SendMessages: %w", err) } var errs error // Fetch the requested acknowledgement for each message we sent. for _, msg := range cc.messages { if _, err := receiveAckAware(conn, msg.Header.Flags); err != nil { if errors.Is(err, os.ErrPermission) { // Kernel will only send one permission error to user space. return err } errs = errors.Join(errs, err) } } if errs != nil { return fmt.Errorf("conn.Receive: %w", errs) } return nil } // FlushRuleset flushes the entire ruleset. See also // https://wiki.nftables.org/wiki-nftables/index.php/Operations_at_ruleset_level func (cc *Conn) FlushRuleset() { cc.mu.Lock() defer cc.mu.Unlock() cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: extraHeader(0, 0), }) } func (cc *Conn) dialNetlink() (*netlink.Conn, error) { if cc.TestDial != nil { return nltest.Dial(cc.TestDial), nil } return netlink.Dial(unix.NETLINK_NETFILTER, &netlink.Config{NetNS: cc.NetNS}) } func (cc *Conn) setErr(err error) { if cc.err != nil { return } cc.err = err } func (cc *Conn) marshalAttr(attrs []netlink.Attribute) []byte { b, err := netlink.MarshalAttributes(attrs) if err != nil { cc.setErr(err) return nil } return b } func (cc *Conn) marshalExpr(fam byte, e expr.Any) []byte { b, err := expr.Marshal(fam, e) if err != nil { cc.setErr(err) return nil } return b } func batch(messages []netlink.Message) []netlink.Message { batch := []netlink.Message{ { Header: netlink.Header{ Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN), Flags: netlink.Request, }, Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES), }, } batch = append(batch, messages...) batch = append(batch, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_END), Flags: netlink.Request, }, Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES), }) return batch } nftables-0.2.0/counter.go000066400000000000000000000040021457332137300153210ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // CounterObj implements Obj. type CounterObj struct { Table *Table Name string // e.g. “fwded” Bytes uint64 Packets uint64 } func (c *CounterObj) unmarshal(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.NFTA_COUNTER_BYTES: c.Bytes = ad.Uint64() case unix.NFTA_COUNTER_PACKETS: c.Packets = ad.Uint64() } } return ad.Err() } func (c *CounterObj) table() *Table { return c.Table } func (c *CounterObj) family() TableFamily { return c.Table.Family } func (c *CounterObj) marshal(data bool) ([]byte, error) { obj, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_COUNTER_BYTES, Data: binaryutil.BigEndian.PutUint64(c.Bytes)}, {Type: unix.NFTA_COUNTER_PACKETS, Data: binaryutil.BigEndian.PutUint64(c.Packets)}, }) if err != nil { return nil, err } const NFT_OBJECT_COUNTER = 1 // TODO: get into x/sys/unix attrs := []netlink.Attribute{ {Type: unix.NFTA_OBJ_TABLE, Data: []byte(c.Table.Name + "\x00")}, {Type: unix.NFTA_OBJ_NAME, Data: []byte(c.Name + "\x00")}, {Type: unix.NFTA_OBJ_TYPE, Data: binaryutil.BigEndian.PutUint32(NFT_OBJECT_COUNTER)}, } if data { attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: obj}) } return netlink.MarshalAttributes(attrs) } nftables-0.2.0/doc.go000066400000000000000000000012741457332137300144170ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package nftables manipulates Linux nftables (the iptables successor). package nftables nftables-0.2.0/expr/000077500000000000000000000000001457332137300142755ustar00rootroot00000000000000nftables-0.2.0/expr/bitwise.go000066400000000000000000000055631457332137300163030ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Bitwise struct { SourceRegister uint32 DestRegister uint32 Len uint32 Mask []byte Xor []byte } func (e *Bitwise) marshal(fam byte) ([]byte, error) { mask, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: e.Mask}, }) if err != nil { return nil, err } xor, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: e.Xor}, }) if err != nil { return nil, err } data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_BITWISE_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, {Type: unix.NFTA_BITWISE_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, {Type: unix.NFTA_BITWISE_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, {Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_MASK, Data: mask}, {Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_XOR, Data: xor}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("bitwise\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Bitwise) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_BITWISE_SREG: e.SourceRegister = ad.Uint32() case unix.NFTA_BITWISE_DREG: e.DestRegister = ad.Uint32() case unix.NFTA_BITWISE_LEN: e.Len = ad.Uint32() case unix.NFTA_BITWISE_MASK: // Since NFTA_BITWISE_MASK is nested, it requires additional decoding ad.Nested(func(nad *netlink.AttributeDecoder) error { for nad.Next() { switch nad.Type() { case unix.NFTA_DATA_VALUE: e.Mask = nad.Bytes() } } return nil }) case unix.NFTA_BITWISE_XOR: // Since NFTA_BITWISE_XOR is nested, it requires additional decoding ad.Nested(func(nad *netlink.AttributeDecoder) error { for nad.Next() { switch nad.Type() { case unix.NFTA_DATA_VALUE: e.Xor = nad.Bytes() } } return nil }) } } return ad.Err() } nftables-0.2.0/expr/bitwise_test.go000066400000000000000000000024021457332137300173270ustar00rootroot00000000000000package expr import ( "encoding/binary" "reflect" "testing" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestBitwise(t *testing.T) { t.Parallel() tests := []struct { name string bw Bitwise }{ { name: "Unmarshal Bitwise IPv4 case", bw: Bitwise{ SourceRegister: 1, DestRegister: 2, Len: 4, // By specifying Xor to 0x0,0x0,0x0,0x0 and Mask to 0xff,0xff,0x0,0x0 // an expression will match /16 IPv4 address. Xor: []byte{0x0, 0x0, 0x0, 0x0}, Mask: []byte{0xff, 0xff, 0x0, 0x0}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nbw := Bitwise{} data, err := tt.bw.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := nbw.unmarshal(0, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.bw, nbw) { t.Fatalf("original %+v and recovered %+v Bitwise structs are different", tt.bw, nbw) } }) } } nftables-0.2.0/expr/byteorder.go000066400000000000000000000035671457332137300166360ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type ByteorderOp uint32 const ( ByteorderNtoh ByteorderOp = unix.NFT_BYTEORDER_NTOH ByteorderHton ByteorderOp = unix.NFT_BYTEORDER_HTON ) type Byteorder struct { SourceRegister uint32 DestRegister uint32 Op ByteorderOp Len uint32 Size uint32 } func (e *Byteorder) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_BYTEORDER_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, {Type: unix.NFTA_BYTEORDER_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, {Type: unix.NFTA_BYTEORDER_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}, {Type: unix.NFTA_BYTEORDER_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, {Type: unix.NFTA_BYTEORDER_SIZE, Data: binaryutil.BigEndian.PutUint32(e.Size)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("byteorder\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Byteorder) unmarshal(fam byte, data []byte) error { return fmt.Errorf("not yet implemented") } nftables-0.2.0/expr/connlimit.go000066400000000000000000000040271457332137300166230ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) const ( // Per https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1167 NFTA_CONNLIMIT_UNSPEC = iota NFTA_CONNLIMIT_COUNT NFTA_CONNLIMIT_FLAGS NFT_CONNLIMIT_F_INV = 1 ) // Per https://git.netfilter.org/libnftnl/tree/src/expr/connlimit.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c type Connlimit struct { Count uint32 Flags uint32 } func (e *Connlimit) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: NFTA_CONNLIMIT_COUNT, Data: binaryutil.BigEndian.PutUint32(e.Count)}, {Type: NFTA_CONNLIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("connlimit\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Connlimit) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case NFTA_CONNLIMIT_COUNT: e.Count = binaryutil.BigEndian.Uint32(ad.Bytes()) case NFTA_CONNLIMIT_FLAGS: e.Flags = binaryutil.BigEndian.Uint32(ad.Bytes()) } } return ad.Err() } nftables-0.2.0/expr/counter.go000066400000000000000000000032121457332137300163010ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Counter struct { Bytes uint64 Packets uint64 } func (e *Counter) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_COUNTER_BYTES, Data: binaryutil.BigEndian.PutUint64(e.Bytes)}, {Type: unix.NFTA_COUNTER_PACKETS, Data: binaryutil.BigEndian.PutUint64(e.Packets)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("counter\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Counter) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_COUNTER_BYTES: e.Bytes = ad.Uint64() case unix.NFTA_COUNTER_PACKETS: e.Packets = ad.Uint64() } } return ad.Err() } nftables-0.2.0/expr/ct.go000066400000000000000000000065651457332137300152460ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // CtKey specifies which piece of conntrack information should be loaded. See // also https://wiki.nftables.org/wiki-nftables/index.php/Matching_connection_tracking_stateful_metainformation type CtKey uint32 // Possible CtKey values. const ( CtKeySTATE CtKey = unix.NFT_CT_STATE CtKeyDIRECTION CtKey = unix.NFT_CT_DIRECTION CtKeySTATUS CtKey = unix.NFT_CT_STATUS CtKeyMARK CtKey = unix.NFT_CT_MARK CtKeySECMARK CtKey = unix.NFT_CT_SECMARK CtKeyEXPIRATION CtKey = unix.NFT_CT_EXPIRATION CtKeyHELPER CtKey = unix.NFT_CT_HELPER CtKeyL3PROTOCOL CtKey = unix.NFT_CT_L3PROTOCOL CtKeySRC CtKey = unix.NFT_CT_SRC CtKeyDST CtKey = unix.NFT_CT_DST CtKeyPROTOCOL CtKey = unix.NFT_CT_PROTOCOL CtKeyPROTOSRC CtKey = unix.NFT_CT_PROTO_SRC CtKeyPROTODST CtKey = unix.NFT_CT_PROTO_DST CtKeyLABELS CtKey = unix.NFT_CT_LABELS CtKeyPKTS CtKey = unix.NFT_CT_PKTS CtKeyBYTES CtKey = unix.NFT_CT_BYTES CtKeyAVGPKT CtKey = unix.NFT_CT_AVGPKT CtKeyZONE CtKey = unix.NFT_CT_ZONE CtKeyEVENTMASK CtKey = unix.NFT_CT_EVENTMASK // https://sources.debian.org/src//nftables/0.9.8-3/src/ct.c/?hl=39#L39 CtStateBitINVALID uint32 = 1 CtStateBitESTABLISHED uint32 = 2 CtStateBitRELATED uint32 = 4 CtStateBitNEW uint32 = 8 CtStateBitUNTRACKED uint32 = 64 ) // Ct defines type for NFT connection tracking type Ct struct { Register uint32 SourceRegister bool Key CtKey } func (e *Ct) marshal(fam byte) ([]byte, error) { regData := []byte{} exprData, err := netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_CT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, }, ) if err != nil { return nil, err } if e.SourceRegister { regData, err = netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_CT_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }, ) } else { regData, err = netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_CT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }, ) } if err != nil { return nil, err } exprData = append(exprData, regData...) return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("ct\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData}, }) } func (e *Ct) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_CT_KEY: e.Key = CtKey(ad.Uint32()) case unix.NFTA_CT_DREG: e.Register = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/dup.go000066400000000000000000000033411457332137300154150ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Dup struct { RegAddr uint32 RegDev uint32 IsRegDevSet bool } func (e *Dup) marshal(fam byte) ([]byte, error) { attrs := []netlink.Attribute{ {Type: unix.NFTA_DUP_SREG_ADDR, Data: binaryutil.BigEndian.PutUint32(e.RegAddr)}, } if e.IsRegDevSet { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_DUP_SREG_DEV, Data: binaryutil.BigEndian.PutUint32(e.RegDev)}) } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("dup\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Dup) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_DUP_SREG_ADDR: e.RegAddr = ad.Uint32() case unix.NFTA_DUP_SREG_DEV: e.RegDev = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/dynset.go000066400000000000000000000112741457332137300161370ustar00rootroot00000000000000// Copyright 2020 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "time" "github.com/google/nftables/binaryutil" "github.com/google/nftables/internal/parseexprfunc" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // Not yet supported by unix package // https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=2027-2036 const ( NFTA_DYNSET_EXPRESSIONS = 0xa NFT_DYNSET_F_EXPR = (1 << 1) ) // Dynset represent a rule dynamically adding or updating a set or a map based on an incoming packet. type Dynset struct { SrcRegKey uint32 SrcRegData uint32 SetID uint32 SetName string Operation uint32 Timeout time.Duration Invert bool Exprs []Any } func (e *Dynset) marshal(fam byte) ([]byte, error) { // See: https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c var opAttrs []netlink.Attribute opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_KEY, Data: binaryutil.BigEndian.PutUint32(e.SrcRegKey)}) if e.SrcRegData != 0 { opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_DATA, Data: binaryutil.BigEndian.PutUint32(e.SrcRegData)}) } opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_OP, Data: binaryutil.BigEndian.PutUint32(e.Operation)}) if e.Timeout != 0 { opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))}) } var flags uint32 if e.Invert { flags |= unix.NFT_DYNSET_F_INV } opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SET_NAME, Data: []byte(e.SetName + "\x00")}, netlink.Attribute{Type: unix.NFTA_DYNSET_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)}) // Per https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n170 if len(e.Exprs) > 0 { flags |= NFT_DYNSET_F_EXPR switch len(e.Exprs) { case 1: exprData, err := Marshal(fam, e.Exprs[0]) if err != nil { return nil, err } opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_EXPR, Data: exprData}) default: var elemAttrs []netlink.Attribute for _, ex := range e.Exprs { exprData, err := Marshal(fam, ex) if err != nil { return nil, err } elemAttrs = append(elemAttrs, netlink.Attribute{Type: unix.NFTA_LIST_ELEM, Data: exprData}) } elemData, err := netlink.MarshalAttributes(elemAttrs) if err != nil { return nil, err } opAttrs = append(opAttrs, netlink.Attribute{Type: NFTA_DYNSET_EXPRESSIONS, Data: elemData}) } } opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}) opData, err := netlink.MarshalAttributes(opAttrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("dynset\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: opData}, }) } func (e *Dynset) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_DYNSET_SET_NAME: e.SetName = ad.String() case unix.NFTA_DYNSET_SET_ID: e.SetID = ad.Uint32() case unix.NFTA_DYNSET_SREG_KEY: e.SrcRegKey = ad.Uint32() case unix.NFTA_DYNSET_SREG_DATA: e.SrcRegData = ad.Uint32() case unix.NFTA_DYNSET_OP: e.Operation = ad.Uint32() case unix.NFTA_DYNSET_TIMEOUT: e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64())) case unix.NFTA_DYNSET_FLAGS: e.Invert = (ad.Uint32() & unix.NFT_DYNSET_F_INV) != 0 case unix.NFTA_DYNSET_EXPR: exprs, err := parseexprfunc.ParseExprBytesFunc(fam, ad, ad.Bytes()) if err != nil { return err } e.setInterfaceExprs(exprs) case NFTA_DYNSET_EXPRESSIONS: exprs, err := parseexprfunc.ParseExprMsgFunc(fam, ad.Bytes()) if err != nil { return err } e.setInterfaceExprs(exprs) } } return ad.Err() } func (e *Dynset) setInterfaceExprs(exprs []interface{}) { e.Exprs = make([]Any, len(exprs)) for i := range exprs { e.Exprs[i] = exprs[i].(Any) } } nftables-0.2.0/expr/expr.go000066400000000000000000000275431457332137300156150ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package expr provides nftables rule expressions. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/google/nftables/internal/parseexprfunc" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func init() { parseexprfunc.ParseExprBytesFunc = func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error) { exprs, err := exprsFromBytes(fam, ad, b) if err != nil { return nil, err } result := make([]interface{}, len(exprs)) for idx, expr := range exprs { result[idx] = expr } return result, nil } parseexprfunc.ParseExprMsgFunc = func(fam byte, b []byte) ([]interface{}, error) { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian var exprs []interface{} for ad.Next() { e, err := parseexprfunc.ParseExprBytesFunc(fam, ad, b) if err != nil { return e, err } exprs = append(exprs, e...) } return exprs, ad.Err() } } // Marshal serializes the specified expression into a byte slice. func Marshal(fam byte, e Any) ([]byte, error) { return e.marshal(fam) } // Unmarshal fills an expression from the specified byte slice. func Unmarshal(fam byte, data []byte, e Any) error { return e.unmarshal(fam, data) } // exprsFromBytes parses nested raw expressions bytes // to construct nftables expressions func exprsFromBytes(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]Any, error) { var exprs []Any ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian var name string for ad.Next() { switch ad.Type() { case unix.NFTA_EXPR_NAME: name = ad.String() if name == "notrack" { e := &Notrack{} exprs = append(exprs, e) } case unix.NFTA_EXPR_DATA: var e Any switch name { case "ct": e = &Ct{} case "range": e = &Range{} case "meta": e = &Meta{} case "cmp": e = &Cmp{} case "counter": e = &Counter{} case "objref": e = &Objref{} case "payload": e = &Payload{} case "lookup": e = &Lookup{} case "immediate": e = &Immediate{} case "bitwise": e = &Bitwise{} case "redir": e = &Redir{} case "nat": e = &NAT{} case "limit": e = &Limit{} case "quota": e = &Quota{} case "dynset": e = &Dynset{} case "log": e = &Log{} case "exthdr": e = &Exthdr{} case "match": e = &Match{} case "target": e = &Target{} case "connlimit": e = &Connlimit{} case "queue": e = &Queue{} case "flow_offload": e = &FlowOffload{} case "reject": e = &Reject{} case "masq": e = &Masq{} case "hash": e = &Hash{} } if e == nil { // TODO: introduce an opaque expression type so that users know // something is here. continue // unsupported expression type } ad.Do(func(b []byte) error { if err := Unmarshal(fam, b, e); err != nil { return err } // Verdict expressions are a special-case of immediate expressions, so // if the expression is an immediate writing nothing into the verdict // register (invalid), re-parse it as a verdict expression. if imm, isImmediate := e.(*Immediate); isImmediate && imm.Register == unix.NFT_REG_VERDICT && len(imm.Data) == 0 { e = &Verdict{} if err := Unmarshal(fam, b, e); err != nil { return err } } exprs = append(exprs, e) return nil }) } } return ad.Err() }) return exprs, ad.Err() } // Any is an interface implemented by any expression type. type Any interface { marshal(fam byte) ([]byte, error) unmarshal(fam byte, data []byte) error } // MetaKey specifies which piece of meta information should be loaded. See also // https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation type MetaKey uint32 // Possible MetaKey values. const ( MetaKeyLEN MetaKey = unix.NFT_META_LEN MetaKeyPROTOCOL MetaKey = unix.NFT_META_PROTOCOL MetaKeyPRIORITY MetaKey = unix.NFT_META_PRIORITY MetaKeyMARK MetaKey = unix.NFT_META_MARK MetaKeyIIF MetaKey = unix.NFT_META_IIF MetaKeyOIF MetaKey = unix.NFT_META_OIF MetaKeyIIFNAME MetaKey = unix.NFT_META_IIFNAME MetaKeyOIFNAME MetaKey = unix.NFT_META_OIFNAME MetaKeyIIFTYPE MetaKey = unix.NFT_META_IIFTYPE MetaKeyOIFTYPE MetaKey = unix.NFT_META_OIFTYPE MetaKeySKUID MetaKey = unix.NFT_META_SKUID MetaKeySKGID MetaKey = unix.NFT_META_SKGID MetaKeyNFTRACE MetaKey = unix.NFT_META_NFTRACE MetaKeyRTCLASSID MetaKey = unix.NFT_META_RTCLASSID MetaKeySECMARK MetaKey = unix.NFT_META_SECMARK MetaKeyNFPROTO MetaKey = unix.NFT_META_NFPROTO MetaKeyL4PROTO MetaKey = unix.NFT_META_L4PROTO MetaKeyBRIIIFNAME MetaKey = unix.NFT_META_BRI_IIFNAME MetaKeyBRIOIFNAME MetaKey = unix.NFT_META_BRI_OIFNAME MetaKeyPKTTYPE MetaKey = unix.NFT_META_PKTTYPE MetaKeyCPU MetaKey = unix.NFT_META_CPU MetaKeyIIFGROUP MetaKey = unix.NFT_META_IIFGROUP MetaKeyOIFGROUP MetaKey = unix.NFT_META_OIFGROUP MetaKeyCGROUP MetaKey = unix.NFT_META_CGROUP MetaKeyPRANDOM MetaKey = unix.NFT_META_PRANDOM ) // Meta loads packet meta information for later comparisons. See also // https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation type Meta struct { Key MetaKey SourceRegister bool Register uint32 } func (e *Meta) marshal(fam byte) ([]byte, error) { regData := []byte{} exprData, err := netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_META_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, }, ) if err != nil { return nil, err } if e.SourceRegister { regData, err = netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_META_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }, ) } else { regData, err = netlink.MarshalAttributes( []netlink.Attribute{ {Type: unix.NFTA_META_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }, ) } if err != nil { return nil, err } exprData = append(exprData, regData...) return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("meta\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData}, }) } func (e *Meta) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_META_SREG: e.Register = ad.Uint32() e.SourceRegister = true case unix.NFTA_META_DREG: e.Register = ad.Uint32() case unix.NFTA_META_KEY: e.Key = MetaKey(ad.Uint32()) } } return ad.Err() } // Masq (Masquerade) is a special case of SNAT, where the source address is // automagically set to the address of the output interface. See also // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)#Masquerading type Masq struct { Random bool FullyRandom bool Persistent bool ToPorts bool RegProtoMin uint32 RegProtoMax uint32 } // TODO, Once the constants below are available in golang.org/x/sys/unix, switch to use those. const ( // NF_NAT_RANGE_PROTO_RANDOM defines flag for a random masquerade NF_NAT_RANGE_PROTO_RANDOM = 0x4 // NF_NAT_RANGE_PROTO_RANDOM_FULLY defines flag for a fully random masquerade NF_NAT_RANGE_PROTO_RANDOM_FULLY = 0x10 // NF_NAT_RANGE_PERSISTENT defines flag for a persistent masquerade NF_NAT_RANGE_PERSISTENT = 0x8 // NF_NAT_RANGE_PREFIX defines flag for a prefix masquerade NF_NAT_RANGE_PREFIX = 0x40 ) func (e *Masq) marshal(fam byte) ([]byte, error) { msgData := []byte{} if !e.ToPorts { flags := uint32(0) if e.Random { flags |= NF_NAT_RANGE_PROTO_RANDOM } if e.FullyRandom { flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY } if e.Persistent { flags |= NF_NAT_RANGE_PERSISTENT } if flags != 0 { flagsData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_MASQ_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}}) if err != nil { return nil, err } msgData = append(msgData, flagsData...) } } else { regsData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_MASQ_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}}) if err != nil { return nil, err } msgData = append(msgData, regsData...) if e.RegProtoMax != 0 { regsData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_MASQ_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)}}) if err != nil { return nil, err } msgData = append(msgData, regsData...) } } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("masq\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: msgData}, }) } func (e *Masq) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_MASQ_REG_PROTO_MIN: e.ToPorts = true e.RegProtoMin = ad.Uint32() case unix.NFTA_MASQ_REG_PROTO_MAX: e.RegProtoMax = ad.Uint32() case unix.NFTA_MASQ_FLAGS: flags := ad.Uint32() e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0 e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0 e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0 } } return ad.Err() } // CmpOp specifies which type of comparison should be performed. type CmpOp uint32 // Possible CmpOp values. const ( CmpOpEq CmpOp = unix.NFT_CMP_EQ CmpOpNeq CmpOp = unix.NFT_CMP_NEQ CmpOpLt CmpOp = unix.NFT_CMP_LT CmpOpLte CmpOp = unix.NFT_CMP_LTE CmpOpGt CmpOp = unix.NFT_CMP_GT CmpOpGte CmpOp = unix.NFT_CMP_GTE ) // Cmp compares a register with the specified data. type Cmp struct { Op CmpOp Register uint32 Data []byte } func (e *Cmp) marshal(fam byte) ([]byte, error) { cmpData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: e.Data}, }) if err != nil { return nil, err } exprData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_CMP_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, {Type: unix.NFTA_CMP_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}, {Type: unix.NLA_F_NESTED | unix.NFTA_CMP_DATA, Data: cmpData}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("cmp\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData}, }) } func (e *Cmp) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_CMP_SREG: e.Register = ad.Uint32() case unix.NFTA_CMP_OP: e.Op = CmpOp(ad.Uint32()) case unix.NFTA_CMP_DATA: ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE { ad.Do(func(b []byte) error { e.Data = b return nil }) } return ad.Err() }) } } return ad.Err() } nftables-0.2.0/expr/exthdr.go000066400000000000000000000057651457332137300161370ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type ExthdrOp uint32 const ( ExthdrOpIpv6 ExthdrOp = unix.NFT_EXTHDR_OP_IPV6 ExthdrOpTcpopt ExthdrOp = unix.NFT_EXTHDR_OP_TCPOPT ) type Exthdr struct { DestRegister uint32 Type uint8 Offset uint32 Len uint32 Flags uint32 Op ExthdrOp SourceRegister uint32 } func (e *Exthdr) marshal(fam byte) ([]byte, error) { var attr []netlink.Attribute // Operations are differentiated by the Op and whether the SourceRegister // or DestRegister is set. Mixing them results in EOPNOTSUPP. if e.SourceRegister != 0 { attr = []netlink.Attribute{ {Type: unix.NFTA_EXTHDR_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}} } else { attr = []netlink.Attribute{ {Type: unix.NFTA_EXTHDR_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}} } attr = append(attr, netlink.Attribute{Type: unix.NFTA_EXTHDR_TYPE, Data: []byte{e.Type}}, netlink.Attribute{Type: unix.NFTA_EXTHDR_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, netlink.Attribute{Type: unix.NFTA_EXTHDR_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, netlink.Attribute{Type: unix.NFTA_EXTHDR_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}) // Flags is only set if DREG is set if e.DestRegister != 0 { attr = append(attr, netlink.Attribute{Type: unix.NFTA_EXTHDR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}) } data, err := netlink.MarshalAttributes(attr) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("exthdr\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Exthdr) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_EXTHDR_DREG: e.DestRegister = ad.Uint32() case unix.NFTA_EXTHDR_TYPE: e.Type = ad.Uint8() case unix.NFTA_EXTHDR_OFFSET: e.Offset = ad.Uint32() case unix.NFTA_EXTHDR_LEN: e.Len = ad.Uint32() case unix.NFTA_EXTHDR_FLAGS: e.Flags = ad.Uint32() case unix.NFTA_EXTHDR_OP: e.Op = ExthdrOp(ad.Uint32()) case unix.NFTA_EXTHDR_SREG: e.SourceRegister = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/exthdr_test.go000066400000000000000000000026401457332137300171630ustar00rootroot00000000000000package expr import ( "encoding/binary" "reflect" "testing" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestExthdr(t *testing.T) { t.Parallel() tests := []struct { name string eh Exthdr }{ { name: "Unmarshal Exthdr DestRegister case", eh: Exthdr{ DestRegister: 1, Type: 2, Offset: 3, Len: 4, Flags: 5, Op: ExthdrOpTcpopt, SourceRegister: 0, }, }, { name: "Unmarshal Exthdr SourceRegister case", eh: Exthdr{ SourceRegister: 1, Type: 2, Offset: 3, Len: 4, Op: ExthdrOpTcpopt, DestRegister: 0, Flags: 0, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { neh := Exthdr{} data, err := tt.eh.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := neh.unmarshal(0, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.eh, neh) { t.Fatalf("original %+v and recovered %+v Exthdr structs are different", tt.eh, neh) } }) } } nftables-0.2.0/expr/fib.go000066400000000000000000000065251457332137300153740ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // Fib defines fib expression structure type Fib struct { Register uint32 ResultOIF bool ResultOIFNAME bool ResultADDRTYPE bool FlagSADDR bool FlagDADDR bool FlagMARK bool FlagIIF bool FlagOIF bool FlagPRESENT bool } func (e *Fib) marshal(fam byte) ([]byte, error) { data := []byte{} reg, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_FIB_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }) if err != nil { return nil, err } data = append(data, reg...) flags := uint32(0) if e.FlagSADDR { flags |= unix.NFTA_FIB_F_SADDR } if e.FlagDADDR { flags |= unix.NFTA_FIB_F_DADDR } if e.FlagMARK { flags |= unix.NFTA_FIB_F_MARK } if e.FlagIIF { flags |= unix.NFTA_FIB_F_IIF } if e.FlagOIF { flags |= unix.NFTA_FIB_F_OIF } if e.FlagPRESENT { flags |= unix.NFTA_FIB_F_PRESENT } if flags != 0 { flg, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_FIB_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, }) if err != nil { return nil, err } data = append(data, flg...) } results := uint32(0) if e.ResultOIF { results |= unix.NFT_FIB_RESULT_OIF } if e.ResultOIFNAME { results |= unix.NFT_FIB_RESULT_OIFNAME } if e.ResultADDRTYPE { results |= unix.NFT_FIB_RESULT_ADDRTYPE } if results != 0 { rslt, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_FIB_RESULT, Data: binaryutil.BigEndian.PutUint32(results)}, }) if err != nil { return nil, err } data = append(data, rslt...) } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("fib\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Fib) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_FIB_DREG: e.Register = ad.Uint32() case unix.NFTA_FIB_RESULT: result := ad.Uint32() e.ResultOIF = (result & unix.NFT_FIB_RESULT_OIF) == 1 e.ResultOIFNAME = (result & unix.NFT_FIB_RESULT_OIFNAME) == 1 e.ResultADDRTYPE = (result & unix.NFT_FIB_RESULT_ADDRTYPE) == 1 case unix.NFTA_FIB_FLAGS: flags := ad.Uint32() e.FlagSADDR = (flags & unix.NFTA_FIB_F_SADDR) == 1 e.FlagDADDR = (flags & unix.NFTA_FIB_F_DADDR) == 1 e.FlagMARK = (flags & unix.NFTA_FIB_F_MARK) == 1 e.FlagIIF = (flags & unix.NFTA_FIB_F_IIF) == 1 e.FlagOIF = (flags & unix.NFTA_FIB_F_OIF) == 1 e.FlagPRESENT = (flags & unix.NFTA_FIB_F_PRESENT) == 1 } } return ad.Err() } nftables-0.2.0/expr/flow_offload.go000066400000000000000000000027401457332137300172700ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) const NFTNL_EXPR_FLOW_TABLE_NAME = 1 type FlowOffload struct { Name string } func (e *FlowOffload) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: NFTNL_EXPR_FLOW_TABLE_NAME, Data: []byte(e.Name)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("flow_offload\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *FlowOffload) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case NFTNL_EXPR_FLOW_TABLE_NAME: e.Name = ad.String() } } return ad.Err() } nftables-0.2.0/expr/hash.go000066400000000000000000000055021457332137300155510ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type HashType uint32 const ( HashTypeJenkins HashType = unix.NFT_HASH_JENKINS HashTypeSym HashType = unix.NFT_HASH_SYM ) // Hash defines type for nftables internal hashing functions type Hash struct { SourceRegister uint32 DestRegister uint32 Length uint32 Modulus uint32 Seed uint32 Offset uint32 Type HashType } func (e *Hash) marshal(fam byte) ([]byte, error) { hashAttrs := []netlink.Attribute{ {Type: unix.NFTA_HASH_SREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.SourceRegister))}, {Type: unix.NFTA_HASH_DREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.DestRegister))}, {Type: unix.NFTA_HASH_LEN, Data: binaryutil.BigEndian.PutUint32(uint32(e.Length))}, {Type: unix.NFTA_HASH_MODULUS, Data: binaryutil.BigEndian.PutUint32(uint32(e.Modulus))}, } if e.Seed != 0 { hashAttrs = append(hashAttrs, netlink.Attribute{ Type: unix.NFTA_HASH_SEED, Data: binaryutil.BigEndian.PutUint32(uint32(e.Seed)), }) } hashAttrs = append(hashAttrs, []netlink.Attribute{ {Type: unix.NFTA_HASH_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.Offset))}, {Type: unix.NFTA_HASH_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, }...) data, err := netlink.MarshalAttributes(hashAttrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("hash\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Hash) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_HASH_SREG: e.SourceRegister = ad.Uint32() case unix.NFTA_HASH_DREG: e.DestRegister = ad.Uint32() case unix.NFTA_HASH_LEN: e.Length = ad.Uint32() case unix.NFTA_HASH_MODULUS: e.Modulus = ad.Uint32() case unix.NFTA_HASH_SEED: e.Seed = ad.Uint32() case unix.NFTA_HASH_OFFSET: e.Offset = ad.Uint32() case unix.NFTA_HASH_TYPE: e.Type = HashType(ad.Uint32()) } } return ad.Err() } nftables-0.2.0/expr/immediate.go000066400000000000000000000042141457332137300165630ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Immediate struct { Register uint32 Data []byte } func (e *Immediate) marshal(fam byte) ([]byte, error) { immData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: e.Data}, }) if err != nil { return nil, err } data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, {Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Immediate) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_IMMEDIATE_DREG: e.Register = ad.Uint32() case unix.NFTA_IMMEDIATE_DATA: nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes()) if err != nil { return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err) } for nestedAD.Next() { switch nestedAD.Type() { case unix.NFTA_DATA_VALUE: e.Data = nestedAD.Bytes() } } if nestedAD.Err() != nil { return fmt.Errorf("decoding immediate: %v", nestedAD.Err()) } } } return ad.Err() } nftables-0.2.0/expr/limit.go000066400000000000000000000066711457332137300157540ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "errors" "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // LimitType represents the type of the limit expression. type LimitType uint32 // Imported from the nft_limit_type enum in netfilter/nf_tables.h. const ( LimitTypePkts LimitType = unix.NFT_LIMIT_PKTS LimitTypePktBytes LimitType = unix.NFT_LIMIT_PKT_BYTES ) // LimitTime represents the limit unit. type LimitTime uint64 // Possible limit unit values. const ( LimitTimeSecond LimitTime = 1 LimitTimeMinute LimitTime = 60 LimitTimeHour LimitTime = 60 * 60 LimitTimeDay LimitTime = 60 * 60 * 24 LimitTimeWeek LimitTime = 60 * 60 * 24 * 7 ) func limitTime(value uint64) (LimitTime, error) { switch LimitTime(value) { case LimitTimeSecond: return LimitTimeSecond, nil case LimitTimeMinute: return LimitTimeMinute, nil case LimitTimeHour: return LimitTimeHour, nil case LimitTimeDay: return LimitTimeDay, nil case LimitTimeWeek: return LimitTimeWeek, nil default: return 0, fmt.Errorf("expr: invalid limit unit value %d", value) } } // Limit represents a rate limit expression. type Limit struct { Type LimitType Rate uint64 Over bool Unit LimitTime Burst uint32 } func (l *Limit) marshal(fam byte) ([]byte, error) { var flags uint32 if l.Over { flags = unix.NFT_LIMIT_F_INV } attrs := []netlink.Attribute{ {Type: unix.NFTA_LIMIT_RATE, Data: binaryutil.BigEndian.PutUint64(l.Rate)}, {Type: unix.NFTA_LIMIT_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))}, {Type: unix.NFTA_LIMIT_BURST, Data: binaryutil.BigEndian.PutUint32(l.Burst)}, {Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))}, {Type: unix.NFTA_LIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("limit\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (l *Limit) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_LIMIT_RATE: l.Rate = ad.Uint64() case unix.NFTA_LIMIT_UNIT: l.Unit, err = limitTime(ad.Uint64()) if err != nil { return err } case unix.NFTA_LIMIT_BURST: l.Burst = ad.Uint32() case unix.NFTA_LIMIT_TYPE: l.Type = LimitType(ad.Uint32()) if l.Type != LimitTypePkts && l.Type != LimitTypePktBytes { return fmt.Errorf("expr: invalid limit type %d", l.Type) } case unix.NFTA_LIMIT_FLAGS: l.Over = (ad.Uint32() & unix.NFT_LIMIT_F_INV) == 1 default: return errors.New("expr: unhandled limit netlink attribute") } } return ad.Err() } nftables-0.2.0/expr/log.go000066400000000000000000000104221457332137300154040ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type LogLevel uint32 const ( // See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca#n1226 LogLevelEmerg LogLevel = iota LogLevelAlert LogLevelCrit LogLevelErr LogLevelWarning LogLevelNotice LogLevelInfo LogLevelDebug LogLevelAudit ) type LogFlags uint32 const ( // See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_log.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca LogFlagsTCPSeq LogFlags = 0x01 << iota LogFlagsTCPOpt LogFlagsIPOpt LogFlagsUID LogFlagsNFLog LogFlagsMACDecode LogFlagsMask LogFlags = 0x2f ) // Log defines type for NFT logging // See https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n25 type Log struct { Level LogLevel // Refers to log flags (flags all, flags ip options, ...) Flags LogFlags // Equivalent to expression flags. // Indicates that an option is set by setting a bit // on index referred by the NFTA_LOG_* value. // See https://cs.opensource.google/go/x/sys/+/3681064d:unix/ztypes_linux.go;l=2126;drc=3681064d51587c1db0324b3d5c23c2ddbcff6e8f Key uint32 Snaplen uint32 Group uint16 QThreshold uint16 // Log prefix string content Data []byte } func (e *Log) marshal(fam byte) ([]byte, error) { // Per https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n129 attrs := make([]netlink.Attribute, 0) if e.Key&(1<= /* sic! */ XTablesExtensionNameMaxLen { name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. } // Marshalling assumes that the correct Info type for the particular table // family and Match revision has been set. info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info) if err != nil { return nil, err } attrs := []netlink.Attribute{ {Type: unix.NFTA_MATCH_NAME, Data: []byte(name + "\x00")}, {Type: unix.NFTA_MATCH_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, {Type: unix.NFTA_MATCH_INFO, Data: info}, } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("match\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Match) unmarshal(fam byte, data []byte) error { // Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } var info []byte ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_MATCH_NAME: // We are forgiving here, accepting any length and even missing terminating \x00. e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) case unix.NFTA_MATCH_REV: e.Rev = ad.Uint32() case unix.NFTA_MATCH_INFO: info = ad.Bytes() } } if err = ad.Err(); err != nil { return err } e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info) return err } nftables-0.2.0/expr/match_test.go000066400000000000000000000022511457332137300167570ustar00rootroot00000000000000package expr import ( "encoding/binary" "reflect" "testing" "github.com/google/nftables/xt" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestMatch(t *testing.T) { t.Parallel() payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) tests := []struct { name string mtch Match }{ { name: "Unmarshal Target case", mtch: Match{ Name: "foobar", Rev: 1234567890, Info: &payload, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ntgt := Match{} data, err := tt.mtch.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := ntgt.unmarshal(0 /* don't care in this test */, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.mtch, ntgt) { t.Fatalf("original %+v and recovered %+v Match structs are different", tt.mtch, ntgt) } }) } } nftables-0.2.0/expr/meta_test.go000066400000000000000000000023311457332137300166100ustar00rootroot00000000000000package expr import ( "encoding/binary" "reflect" "testing" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestMeta(t *testing.T) { t.Parallel() tests := []struct { name string meta Meta }{ { name: "Unmarshal Meta DestRegister case", meta: Meta{ Key: 1, SourceRegister: false, Register: 1, }, }, { name: "Unmarshal Meta SourceRegister case", meta: Meta{ Key: 1, SourceRegister: true, Register: 1, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nMeta := Meta{} data, err := tt.meta.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := nMeta.unmarshal(0, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.meta, nMeta) { t.Fatalf("original %+v and recovered %+v Exthdr structs are different", tt.meta, nMeta) } }) } } nftables-0.2.0/expr/nat.go000066400000000000000000000100741457332137300154100ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type NATType uint32 // Possible NATType values. const ( NATTypeSourceNAT NATType = unix.NFT_NAT_SNAT NATTypeDestNAT NATType = unix.NFT_NAT_DNAT ) type NAT struct { Type NATType Family uint32 // TODO: typed const RegAddrMin uint32 RegAddrMax uint32 RegProtoMin uint32 RegProtoMax uint32 Random bool FullyRandom bool Persistent bool Prefix bool } // |00048|N-|00001| |len |flags| type| // |00008|--|00001| |len |flags| type| // | 6e 61 74 00 | | data | n a t // |00036|N-|00002| |len |flags| type| // |00008|--|00001| |len |flags| type| NFTA_NAT_TYPE // | 00 00 00 01 | | data | NFT_NAT_DNAT // |00008|--|00002| |len |flags| type| NFTA_NAT_FAMILY // | 00 00 00 02 | | data | NFPROTO_IPV4 // |00008|--|00003| |len |flags| type| NFTA_NAT_REG_ADDR_MIN // | 00 00 00 01 | | data | reg 1 // |00008|--|00005| |len |flags| type| NFTA_NAT_REG_PROTO_MIN // | 00 00 00 02 | | data | reg 2 func (e *NAT) marshal(fam byte) ([]byte, error) { attrs := []netlink.Attribute{ {Type: unix.NFTA_NAT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, {Type: unix.NFTA_NAT_FAMILY, Data: binaryutil.BigEndian.PutUint32(e.Family)}, } if e.RegAddrMin != 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMin)}) if e.RegAddrMax != 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMax)}) } } if e.RegProtoMin != 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}) if e.RegProtoMax != 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)}) } } flags := uint32(0) if e.Random { flags |= NF_NAT_RANGE_PROTO_RANDOM } if e.FullyRandom { flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY } if e.Persistent { flags |= NF_NAT_RANGE_PERSISTENT } if e.Prefix { flags |= NF_NAT_RANGE_PREFIX } if flags != 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}) } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("nat\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *NAT) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_NAT_TYPE: e.Type = NATType(ad.Uint32()) case unix.NFTA_NAT_FAMILY: e.Family = ad.Uint32() case unix.NFTA_NAT_REG_ADDR_MIN: e.RegAddrMin = ad.Uint32() case unix.NFTA_NAT_REG_ADDR_MAX: e.RegAddrMax = ad.Uint32() case unix.NFTA_NAT_REG_PROTO_MIN: e.RegProtoMin = ad.Uint32() case unix.NFTA_NAT_REG_PROTO_MAX: e.RegProtoMax = ad.Uint32() case unix.NFTA_NAT_FLAGS: flags := ad.Uint32() e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0 e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0 e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0 e.Prefix = (flags & NF_NAT_RANGE_PREFIX) != 0 } } return ad.Err() } nftables-0.2.0/expr/notrack.go000066400000000000000000000020321457332137300162620ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Notrack struct{} func (e *Notrack) marshal(fam byte) ([]byte, error) { return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("notrack\x00")}, }) } func (e *Notrack) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } return ad.Err() } nftables-0.2.0/expr/numgen.go000066400000000000000000000042761457332137300161260ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // Numgen defines Numgen expression structure type Numgen struct { Register uint32 Modulus uint32 Type uint32 Offset uint32 } func (e *Numgen) marshal(fam byte) ([]byte, error) { // Currently only two types are supported, failing if Type is not of two known types switch e.Type { case unix.NFT_NG_INCREMENTAL: case unix.NFT_NG_RANDOM: default: return nil, fmt.Errorf("unsupported numgen type %d", e.Type) } data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_NG_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, {Type: unix.NFTA_NG_MODULUS, Data: binaryutil.BigEndian.PutUint32(e.Modulus)}, {Type: unix.NFTA_NG_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)}, {Type: unix.NFTA_NG_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("numgen\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Numgen) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_NG_DREG: e.Register = ad.Uint32() case unix.NFTA_NG_MODULUS: e.Modulus = ad.Uint32() case unix.NFTA_NG_TYPE: e.Type = ad.Uint32() case unix.NFTA_NG_OFFSET: e.Offset = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/objref.go000066400000000000000000000032251457332137300160750ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Objref struct { Type int // TODO: enum Name string } func (e *Objref) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_OBJREF_IMM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, {Type: unix.NFTA_OBJREF_IMM_NAME, Data: []byte(e.Name)}, // NOT \x00-terminated?! }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("objref\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Objref) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_OBJREF_IMM_TYPE: e.Type = int(ad.Uint32()) case unix.NFTA_OBJREF_IMM_NAME: e.Name = ad.String() } } return ad.Err() } nftables-0.2.0/expr/payload.go000066400000000000000000000074331457332137300162640ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type PayloadBase uint32 type PayloadCsumType uint32 type PayloadOperationType uint32 // Possible PayloadBase values. const ( PayloadBaseLLHeader PayloadBase = unix.NFT_PAYLOAD_LL_HEADER PayloadBaseNetworkHeader PayloadBase = unix.NFT_PAYLOAD_NETWORK_HEADER PayloadBaseTransportHeader PayloadBase = unix.NFT_PAYLOAD_TRANSPORT_HEADER ) // Possible PayloadCsumType values. const ( CsumTypeNone PayloadCsumType = unix.NFT_PAYLOAD_CSUM_NONE CsumTypeInet PayloadCsumType = unix.NFT_PAYLOAD_CSUM_INET ) // Possible PayloadOperationType values. const ( PayloadLoad PayloadOperationType = iota PayloadWrite ) type Payload struct { OperationType PayloadOperationType DestRegister uint32 SourceRegister uint32 Base PayloadBase Offset uint32 Len uint32 CsumType PayloadCsumType CsumOffset uint32 CsumFlags uint32 } func (e *Payload) marshal(fam byte) ([]byte, error) { var attrs []netlink.Attribute if e.OperationType == PayloadWrite { attrs = []netlink.Attribute{ {Type: unix.NFTA_PAYLOAD_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}, } } else { attrs = []netlink.Attribute{ {Type: unix.NFTA_PAYLOAD_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, } } attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_PAYLOAD_BASE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Base))}, netlink.Attribute{Type: unix.NFTA_PAYLOAD_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, netlink.Attribute{Type: unix.NFTA_PAYLOAD_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, ) if e.CsumType > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumType))}, netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumOffset))}, ) if e.CsumFlags > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.CsumFlags)}, ) } } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("payload\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Payload) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_PAYLOAD_DREG: e.DestRegister = ad.Uint32() case unix.NFTA_PAYLOAD_SREG: e.SourceRegister = ad.Uint32() e.OperationType = PayloadWrite case unix.NFTA_PAYLOAD_BASE: e.Base = PayloadBase(ad.Uint32()) case unix.NFTA_PAYLOAD_OFFSET: e.Offset = ad.Uint32() case unix.NFTA_PAYLOAD_LEN: e.Len = ad.Uint32() case unix.NFTA_PAYLOAD_CSUM_TYPE: e.CsumType = PayloadCsumType(ad.Uint32()) case unix.NFTA_PAYLOAD_CSUM_OFFSET: e.CsumOffset = ad.Uint32() case unix.NFTA_PAYLOAD_CSUM_FLAGS: e.CsumFlags = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/queue.go000066400000000000000000000043161457332137300157540ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type QueueAttribute uint16 type QueueFlag uint16 // Possible QueueAttribute values const ( QueueNum QueueAttribute = unix.NFTA_QUEUE_NUM QueueTotal QueueAttribute = unix.NFTA_QUEUE_TOTAL QueueFlags QueueAttribute = unix.NFTA_QUEUE_FLAGS // TODO: get into x/sys/unix QueueFlagBypass QueueFlag = 0x01 QueueFlagFanout QueueFlag = 0x02 QueueFlagMask QueueFlag = 0x03 ) type Queue struct { Num uint16 Total uint16 Flag QueueFlag } func (e *Queue) marshal(fam byte) ([]byte, error) { if e.Total == 0 { e.Total = 1 // The total default value is 1 } data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_QUEUE_NUM, Data: binaryutil.BigEndian.PutUint16(e.Num)}, {Type: unix.NFTA_QUEUE_TOTAL, Data: binaryutil.BigEndian.PutUint16(e.Total)}, {Type: unix.NFTA_QUEUE_FLAGS, Data: binaryutil.BigEndian.PutUint16(uint16(e.Flag))}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("queue\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Queue) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_QUEUE_NUM: e.Num = ad.Uint16() case unix.NFTA_QUEUE_TOTAL: e.Total = ad.Uint16() case unix.NFTA_QUEUE_FLAGS: e.Flag = QueueFlag(ad.Uint16()) } } return ad.Err() } nftables-0.2.0/expr/quota.go000066400000000000000000000037551457332137300157670ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // Quota defines a threshold against a number of bytes. type Quota struct { Bytes uint64 Consumed uint64 Over bool } func (q *Quota) marshal(fam byte) ([]byte, error) { attrs := []netlink.Attribute{ {Type: unix.NFTA_QUOTA_BYTES, Data: binaryutil.BigEndian.PutUint64(q.Bytes)}, {Type: unix.NFTA_QUOTA_CONSUMED, Data: binaryutil.BigEndian.PutUint64(q.Consumed)}, } flags := uint32(0) if q.Over { flags = unix.NFT_QUOTA_F_INV } attrs = append(attrs, netlink.Attribute{ Type: unix.NFTA_QUOTA_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags), }) data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("quota\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (q *Quota) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_QUOTA_BYTES: q.Bytes = ad.Uint64() case unix.NFTA_QUOTA_CONSUMED: q.Consumed = ad.Uint64() case unix.NFTA_QUOTA_FLAGS: q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1 } } return ad.Err() } nftables-0.2.0/expr/range.go000066400000000000000000000063311457332137300157230ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // Range implements range expression type Range struct { Op CmpOp Register uint32 FromData []byte ToData []byte } func (e *Range) marshal(fam byte) ([]byte, error) { var attrs []netlink.Attribute var err error var rangeFromData, rangeToData []byte if e.Register > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}) } attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))}) if len(e.FromData) > 0 { rangeFromData, err = nestedAttr(e.FromData, unix.NFTA_RANGE_FROM_DATA) if err != nil { return nil, err } } if len(e.ToData) > 0 { rangeToData, err = nestedAttr(e.ToData, unix.NFTA_RANGE_TO_DATA) if err != nil { return nil, err } } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } data = append(data, rangeFromData...) data = append(data, rangeToData...) return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("range\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Range) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_RANGE_OP: e.Op = CmpOp(ad.Uint32()) case unix.NFTA_RANGE_SREG: e.Register = ad.Uint32() case unix.NFTA_RANGE_FROM_DATA: ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE { ad.Do(func(b []byte) error { e.FromData = b return nil }) } return ad.Err() }) case unix.NFTA_RANGE_TO_DATA: ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE { ad.Do(func(b []byte) error { e.ToData = b return nil }) } return ad.Err() }) } } return ad.Err() } func nestedAttr(data []byte, attrType uint16) ([]byte, error) { ae := netlink.NewAttributeEncoder() ae.Do(unix.NLA_F_NESTED|attrType, func() ([]byte, error) { nae := netlink.NewAttributeEncoder() nae.ByteOrder = binary.BigEndian nae.Bytes(unix.NFTA_DATA_VALUE, data) return nae.Encode() }) return ae.Encode() } nftables-0.2.0/expr/redirect.go000066400000000000000000000041101457332137300164210ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Redir struct { RegisterProtoMin uint32 RegisterProtoMax uint32 Flags uint32 } func (e *Redir) marshal(fam byte) ([]byte, error) { var attrs []netlink.Attribute if e.RegisterProtoMin > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMin)}) } if e.RegisterProtoMax > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMax)}) } if e.Flags > 0 { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}) } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("redir\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Redir) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_REDIR_REG_PROTO_MIN: e.RegisterProtoMin = ad.Uint32() case unix.NFTA_REDIR_REG_PROTO_MAX: e.RegisterProtoMax = ad.Uint32() case unix.NFTA_REDIR_FLAGS: e.Flags = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/reject.go000066400000000000000000000031331457332137300161000ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type Reject struct { Type uint32 Code uint8 } func (e *Reject) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_REJECT_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)}, {Type: unix.NFTA_REJECT_ICMP_CODE, Data: []byte{e.Code}}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("reject\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Reject) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_REJECT_TYPE: e.Type = ad.Uint32() case unix.NFTA_REJECT_ICMP_CODE: e.Code = ad.Uint8() } } return ad.Err() } nftables-0.2.0/expr/rt.go000066400000000000000000000030471457332137300152550ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type RtKey uint32 const ( RtClassid RtKey = unix.NFT_RT_CLASSID RtNexthop4 RtKey = unix.NFT_RT_NEXTHOP4 RtNexthop6 RtKey = unix.NFT_RT_NEXTHOP6 RtTCPMSS RtKey = unix.NFT_RT_TCPMSS ) type Rt struct { Register uint32 Key RtKey } func (e *Rt) marshal(fam byte) ([]byte, error) { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_RT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, {Type: unix.NFTA_RT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("rt\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Rt) unmarshal(fam byte, data []byte) error { return fmt.Errorf("not yet implemented") } nftables-0.2.0/expr/socket.go000066400000000000000000000050441457332137300161170ustar00rootroot00000000000000// Copyright 2023 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "golang.org/x/sys/unix" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" ) type Socket struct { Key SocketKey Level uint32 Register uint32 } type SocketKey uint32 const ( // TODO, Once the constants below are available in golang.org/x/sys/unix, switch to use those. NFTA_SOCKET_KEY = 1 NFTA_SOCKET_DREG = 2 NFTA_SOCKET_LEVEL = 3 NFT_SOCKET_TRANSPARENT = 0 NFT_SOCKET_MARK = 1 NFT_SOCKET_WILDCARD = 2 NFT_SOCKET_CGROUPV2 = 3 SocketKeyTransparent SocketKey = NFT_SOCKET_TRANSPARENT SocketKeyMark SocketKey = NFT_SOCKET_MARK SocketKeyWildcard SocketKey = NFT_SOCKET_WILDCARD SocketKeyCgroupv2 SocketKey = NFT_SOCKET_CGROUPV2 ) func (e *Socket) marshal(fam byte) ([]byte, error) { // NOTE: Socket.Level is only used when Socket.Key == SocketKeyCgroupv2. But `nft` always encoding it. Check link below: // http://git.netfilter.org/nftables/tree/src/netlink_linearize.c?id=0583bac241ea18c9d7f61cb20ca04faa1e043b78#n319 exprData, err := netlink.MarshalAttributes( []netlink.Attribute{ {Type: NFTA_SOCKET_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, {Type: NFTA_SOCKET_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))}, {Type: NFTA_SOCKET_LEVEL, Data: binaryutil.BigEndian.PutUint32(uint32(e.Level))}, }, ) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("socket\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData}, }) } func (e *Socket) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case NFTA_SOCKET_DREG: e.Register = ad.Uint32() case NFTA_SOCKET_KEY: e.Key = SocketKey(ad.Uint32()) case NFTA_SOCKET_LEVEL: e.Level = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/socket_test.go000066400000000000000000000043651457332137300171630ustar00rootroot00000000000000// Copyright 2023 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "reflect" "testing" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestSocket(t *testing.T) { t.Parallel() tests := []struct { name string socket Socket }{ { name: "Unmarshal `socket transparent`", socket: Socket{ Key: SocketKeyTransparent, Level: 0, Register: 1, }, }, { name: "Unmarshal `socket cgroup level 5`", socket: Socket{ Key: SocketKeyCgroupv2, Level: 5, Register: 1, }, }, { name: "Unmarshal `socket cgroup level 1`", socket: Socket{ Key: SocketKeyCgroupv2, Level: 1, Register: 1, }, }, { name: "Unmarshal `socket wildcard`", socket: Socket{ Key: SocketKeyWildcard, Level: 0, Register: 1, }, }, { name: "Unmarshal `socket mark`", socket: Socket{ Key: SocketKeyMark, Level: 0, Register: 1, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nSocket := Socket{} data, err := tt.socket.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := nSocket.unmarshal(0, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.socket, nSocket) { t.Fatalf("original %+v and recovered %+v Socket structs are different", tt.socket, nSocket) } }) } } nftables-0.2.0/expr/target.go000066400000000000000000000045341457332137300161200ustar00rootroot00000000000000package expr import ( "bytes" "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/google/nftables/xt" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n28 const XTablesExtensionNameMaxLen = 29 // See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30 type Target struct { Name string Rev uint32 Info xt.InfoAny } func (e *Target) marshal(fam byte) ([]byte, error) { // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38 name := e.Name // limit the extension name as (some) user-space tools do and leave room for // trailing \x00 if len(name) >= /* sic! */ XTablesExtensionNameMaxLen { name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. } // Marshalling assumes that the correct Info type for the particular table // family and Match revision has been set. info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info) if err != nil { return nil, err } attrs := []netlink.Attribute{ {Type: unix.NFTA_TARGET_NAME, Data: []byte(name + "\x00")}, {Type: unix.NFTA_TARGET_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, {Type: unix.NFTA_TARGET_INFO, Data: info}, } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("target\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Target) unmarshal(fam byte, data []byte) error { // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } var info []byte ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_TARGET_NAME: // We are forgiving here, accepting any length and even missing terminating \x00. e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) case unix.NFTA_TARGET_REV: e.Rev = ad.Uint32() case unix.NFTA_TARGET_INFO: info = ad.Bytes() } } if err = ad.Err(); err != nil { return err } e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info) return err } nftables-0.2.0/expr/target_test.go000066400000000000000000000022521457332137300171520ustar00rootroot00000000000000package expr import ( "encoding/binary" "reflect" "testing" "github.com/google/nftables/xt" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestTarget(t *testing.T) { t.Parallel() payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) tests := []struct { name string tgt Target }{ { name: "Unmarshal Target case", tgt: Target{ Name: "foobar", Rev: 1234567890, Info: &payload, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ntgt := Target{} data, err := tt.tgt.marshal(0 /* don't care in this test */) if err != nil { t.Fatalf("marshal error: %+v", err) } ad, err := netlink.NewAttributeDecoder(data) if err != nil { t.Fatalf("NewAttributeDecoder() error: %+v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { if ad.Type() == unix.NFTA_EXPR_DATA { if err := ntgt.unmarshal(0 /* don't care in this test */, ad.Bytes()); err != nil { t.Errorf("unmarshal error: %+v", err) break } } } if !reflect.DeepEqual(tt.tgt, ntgt) { t.Fatalf("original %+v and recovered %+v Target structs are different", tt.tgt, ntgt) } }) } } nftables-0.2.0/expr/tproxy.go000066400000000000000000000044411457332137300161740ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "encoding/binary" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) const ( // NFTA_TPROXY_FAMILY defines attribute for a table family NFTA_TPROXY_FAMILY = 0x01 // NFTA_TPROXY_REG_ADDR defines attribute for a register carrying redirection address value NFTA_TPROXY_REG_ADDR = 0x02 // NFTA_TPROXY_REG_PORT defines attribute for a register carrying redirection port value NFTA_TPROXY_REG_PORT = 0x03 ) // TProxy defines struct with parameters for the transparent proxy type TProxy struct { Family byte TableFamily byte RegAddr uint32 RegPort uint32 } func (e *TProxy) marshal(fam byte) ([]byte, error) { attrs := []netlink.Attribute{ {Type: NFTA_TPROXY_FAMILY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Family))}, {Type: NFTA_TPROXY_REG_PORT, Data: binaryutil.BigEndian.PutUint32(e.RegPort)}, } if e.RegAddr != 0 { attrs = append(attrs, netlink.Attribute{ Type: NFTA_TPROXY_REG_ADDR, Data: binaryutil.BigEndian.PutUint32(e.RegAddr), }) } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("tproxy\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *TProxy) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case NFTA_TPROXY_FAMILY: e.Family = ad.Uint8() case NFTA_TPROXY_REG_PORT: e.RegPort = ad.Uint32() case NFTA_TPROXY_REG_ADDR: e.RegAddr = ad.Uint32() } } return ad.Err() } nftables-0.2.0/expr/verdict.go000066400000000000000000000067041457332137300162730ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package expr import ( "bytes" "encoding/binary" "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // This code assembles the verdict structure, as expected by the // nftables netlink API. // For further information, consult: // - netfilter.h (Linux kernel) // - net/netfilter/nf_tables_api.c (Linux kernel) // - src/expr/data_reg.c (linbnftnl) type Verdict struct { Kind VerdictKind Chain string } type VerdictKind int64 // Verdicts, as per netfilter.h and netfilter/nf_tables.h. const ( VerdictReturn VerdictKind = iota - 5 VerdictGoto VerdictJump VerdictBreak VerdictContinue VerdictDrop VerdictAccept VerdictStolen VerdictQueue VerdictRepeat VerdictStop ) func (e *Verdict) marshal(fam byte) ([]byte, error) { // A verdict is a tree of netlink attributes structured as follows: // NFTA_LIST_ELEM | NLA_F_NESTED { // NFTA_EXPR_NAME { "immediate\x00" } // NFTA_EXPR_DATA | NLA_F_NESTED { // NFTA_IMMEDIATE_DREG { NFT_REG_VERDICT } // NFTA_IMMEDIATE_DATA | NLA_F_NESTED { // the verdict code // } // } // } attrs := []netlink.Attribute{ {Type: unix.NFTA_VERDICT_CODE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Kind))}, } if e.Chain != "" { attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_VERDICT_CHAIN, Data: []byte(e.Chain + "\x00")}) } codeData, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } immData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NLA_F_NESTED | unix.NFTA_DATA_VERDICT, Data: codeData}, }) if err != nil { return nil, err } data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(unix.NFT_REG_VERDICT)}, {Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData}, }) if err != nil { return nil, err } return netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")}, {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, }) } func (e *Verdict) unmarshal(fam byte, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_IMMEDIATE_DATA: nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes()) if err != nil { return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err) } for nestedAD.Next() { switch nestedAD.Type() { case unix.NFTA_DATA_VERDICT: e.Kind = VerdictKind(int32(binaryutil.BigEndian.Uint32(nestedAD.Bytes()[4:8]))) if len(nestedAD.Bytes()) > 12 { e.Chain = string(bytes.Trim(nestedAD.Bytes()[12:], "\x00")) } } } if nestedAD.Err() != nil { return fmt.Errorf("decoding immediate: %v", nestedAD.Err()) } } } return ad.Err() } nftables-0.2.0/flowtable.go000066400000000000000000000201551457332137300156300ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "encoding/binary" "fmt" "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) const ( // not in ztypes_linux.go, added here // https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=1870-1892 NFT_MSG_NEWFLOWTABLE = 0x16 NFT_MSG_GETFLOWTABLE = 0x17 NFT_MSG_DELFLOWTABLE = 0x18 ) const ( // not in ztypes_linux.go, added here // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1634 _ = iota NFTA_FLOWTABLE_TABLE NFTA_FLOWTABLE_NAME NFTA_FLOWTABLE_HOOK NFTA_FLOWTABLE_USE NFTA_FLOWTABLE_HANDLE NFTA_FLOWTABLE_PAD NFTA_FLOWTABLE_FLAGS ) const ( // not in ztypes_linux.go, added here // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1657 _ = iota NFTA_FLOWTABLE_HOOK_NUM NFTA_FLOWTABLE_PRIORITY NFTA_FLOWTABLE_DEVS ) const ( // not in ztypes_linux.go, added here, used for flowtable device name specification // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1709 NFTA_DEVICE_NAME = 1 ) type FlowtableFlags uint32 const ( _ FlowtableFlags = iota FlowtableFlagsHWOffload FlowtableFlagsCounter FlowtableFlagsMask = (FlowtableFlagsHWOffload | FlowtableFlagsCounter) ) type FlowtableHook uint32 func FlowtableHookRef(h FlowtableHook) *FlowtableHook { return &h } var ( // Only ingress is supported // https://github.com/torvalds/linux/blob/b72018ab8236c3ae427068adeb94bdd3f20454ec/net/netfilter/nf_tables_api.c#L7378-L7379 FlowtableHookIngress *FlowtableHook = FlowtableHookRef(unix.NF_NETDEV_INGRESS) ) type FlowtablePriority int32 func FlowtablePriorityRef(p FlowtablePriority) *FlowtablePriority { return &p } var ( // As per man page: // The priority can be a signed integer or filter which stands for 0. Addition and subtraction can be used to set relative priority, e.g. filter + 5 equals to 5. // https://git.netfilter.org/nftables/tree/doc/nft.txt?id=8c600a843b7c0c1cc275ecc0603bd1fc57773e98#n712 FlowtablePriorityFilter *FlowtablePriority = FlowtablePriorityRef(0) ) type Flowtable struct { Table *Table Name string Hooknum *FlowtableHook Priority *FlowtablePriority Devices []string Use uint32 // Bitmask flags, can be HW_OFFLOAD or COUNTER // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1621 Flags FlowtableFlags Handle uint64 } func (cc *Conn) AddFlowtable(f *Flowtable) *Flowtable { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)}, {Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)}, {Type: NFTA_FLOWTABLE_FLAGS, Data: binaryutil.BigEndian.PutUint32(uint32(f.Flags))}, }) if f.Hooknum == nil { f.Hooknum = FlowtableHookIngress } if f.Priority == nil { f.Priority = FlowtablePriorityFilter } hookAttr := []netlink.Attribute{ {Type: NFTA_FLOWTABLE_HOOK_NUM, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Hooknum))}, {Type: NFTA_FLOWTABLE_PRIORITY, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Priority))}, } if len(f.Devices) > 0 { devs := make([]netlink.Attribute, len(f.Devices)) for i, d := range f.Devices { devs[i] = netlink.Attribute{Type: NFTA_DEVICE_NAME, Data: []byte(d)} } hookAttr = append(hookAttr, netlink.Attribute{ Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_DEVS, Data: cc.marshalAttr(devs), }) } data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_HOOK, Data: cc.marshalAttr(hookAttr)}, })...) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(f.Table.Family), 0), data...), }) return f } func (cc *Conn) DelFlowtable(f *Flowtable) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)}, {Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)}, }) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELFLOWTABLE), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(f.Table.Family), 0), data...), }) } func (cc *Conn) ListFlowtables(t *Table) ([]*Flowtable, error) { reply, err := cc.getFlowtables(t) if err != nil { return nil, err } var fts []*Flowtable for _, msg := range reply { f, err := ftsFromMsg(msg) if err != nil { return nil, err } f.Table = t fts = append(fts, f) } return fts, nil } func (cc *Conn) getFlowtables(t *Table) ([]netlink.Message, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() attrs := []netlink.Attribute{ {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(t.Name + "\x00")}, } data, err := netlink.MarshalAttributes(attrs) if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_GETFLOWTABLE), Flags: netlink.Request | netlink.Acknowledge | netlink.Dump, }, Data: append(extraHeader(uint8(t.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %v", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %v", err) } return reply, nil } func ftsFromMsg(msg netlink.Message) (*Flowtable, error) { flowHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE) if got, want := msg.Header.Type, flowHeaderType; got != want { return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want) } ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian var ft Flowtable for ad.Next() { switch ad.Type() { case NFTA_FLOWTABLE_NAME: ft.Name = ad.String() case NFTA_FLOWTABLE_USE: ft.Use = ad.Uint32() case NFTA_FLOWTABLE_HANDLE: ft.Handle = ad.Uint64() case NFTA_FLOWTABLE_FLAGS: ft.Flags = FlowtableFlags(ad.Uint32()) case NFTA_FLOWTABLE_HOOK: ad.Do(func(b []byte) error { ft.Hooknum, ft.Priority, ft.Devices, err = ftsHookFromMsg(b) return err }) } } return &ft, nil } func ftsHookFromMsg(b []byte) (*FlowtableHook, *FlowtablePriority, []string, error) { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return nil, nil, nil, err } ad.ByteOrder = binary.BigEndian var hooknum FlowtableHook var prio FlowtablePriority var devices []string for ad.Next() { switch ad.Type() { case NFTA_FLOWTABLE_HOOK_NUM: hooknum = FlowtableHook(ad.Uint32()) case NFTA_FLOWTABLE_PRIORITY: prio = FlowtablePriority(ad.Uint32()) case NFTA_FLOWTABLE_DEVS: ad.Do(func(b []byte) error { devices, err = devsFromMsg(b) return err }) } } return &hooknum, &prio, devices, nil } func devsFromMsg(b []byte) ([]string, error) { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian devs := make([]string, 0) for ad.Next() { switch ad.Type() { case NFTA_DEVICE_NAME: devs = append(devs, ad.String()) } } return devs, nil } nftables-0.2.0/go.mod000066400000000000000000000006341457332137300144300ustar00rootroot00000000000000module github.com/google/nftables go 1.21 require ( github.com/mdlayher/netlink v1.7.2 github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc golang.org/x/sys v0.18.0 ) require ( github.com/google/go-cmp v0.6.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/mdlayher/socket v0.5.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sync v0.6.0 // indirect ) nftables-0.2.0/go.sum000066400000000000000000000025361457332137300144600ustar00rootroot00000000000000github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= nftables-0.2.0/internal/000077500000000000000000000000001457332137300151335ustar00rootroot00000000000000nftables-0.2.0/internal/nftest/000077500000000000000000000000001457332137300164365ustar00rootroot00000000000000nftables-0.2.0/internal/nftest/nftest.go000066400000000000000000000070111457332137300202670ustar00rootroot00000000000000// Package nftest contains utility functions for nftables testing. package nftest import ( "bytes" "fmt" "strings" "testing" "github.com/google/nftables" "github.com/mdlayher/netlink" ) // Recorder provides an nftables connection that does not send to the Linux // kernel but instead records netlink messages into the recorder. The recorded // requests can later be obtained using Requests and compared using Diff. type Recorder struct { requests []netlink.Message } // Conn opens an nftables connection that records netlink messages into the // Recorder. func (r *Recorder) Conn() (*nftables.Conn, error) { return nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { r.requests = append(r.requests, req...) acks := make([]netlink.Message, 0, len(req)) for _, msg := range req { if msg.Header.Flags&netlink.Acknowledge != 0 { acks = append(acks, netlink.Message{ Header: netlink.Header{ Length: 4, Type: netlink.Error, Sequence: msg.Header.Sequence, PID: msg.Header.PID, }, Data: []byte{0, 0, 0, 0}, }) } } return acks, nil })) } // Requests returns the recorded netlink messages (typically nftables requests). func (r *Recorder) Requests() []netlink.Message { return r.requests } // NewRecorder returns a ready-to-use Recorder. func NewRecorder() *Recorder { return &Recorder{} } // Diff returns the first difference between the specified netlink messages and // the expected netlink message payloads. func Diff(got []netlink.Message, want [][]byte) string { for idx, msg := range got { b, err := msg.MarshalBinary() if err != nil { return fmt.Sprintf("msg.MarshalBinary: %v", err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { return fmt.Sprintf("no want entry for message %d: %x", idx, b) } if got, want := b, want[0]; !bytes.Equal(got, want) { return fmt.Sprintf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return "" } // MatchRulesetBytes is a test helper that ensures the fillRuleset modifications // correspond to the provided want netlink message payloads func MatchRulesetBytes(t *testing.T, fillRuleset func(c *nftables.Conn), want [][]byte) { t.Helper() rec := NewRecorder() c, err := rec.Conn() if err != nil { t.Fatal(err) } c.FlushRuleset() fillRuleset(c) if err := c.Flush(); err != nil { t.Fatal(err) } if diff := Diff(rec.Requests(), want); diff != "" { t.Errorf("unexpected netlink messages: diff: %s", diff) } } // nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing // users to make sense of large byte literals more easily. func nfdump(b []byte) string { var buf bytes.Buffer i := 0 for ; i < len(b); i += 4 { // TODO: show printable characters as ASCII fmt.Fprintf(&buf, "%02x %02x %02x %02x\n", b[i], b[i+1], b[i+2], b[i+3]) } for ; i < len(b); i++ { fmt.Fprintf(&buf, "%02x ", b[i]) } return buf.String() } // linediff returns a side-by-side diff of two nfdump() return values, flagging // lines which are not equal with an exclamation point prefix. func linediff(a, b string) string { var buf bytes.Buffer fmt.Fprintf(&buf, "got -- want\n") linesA := strings.Split(a, "\n") linesB := strings.Split(b, "\n") for idx, lineA := range linesA { if idx >= len(linesB) { break } lineB := linesB[idx] prefix := "! " if lineA == lineB { prefix = " " } fmt.Fprintf(&buf, "%s%s -- %s\n", prefix, lineA, lineB) } return buf.String() } nftables-0.2.0/internal/nftest/system_conn.go000066400000000000000000000021021457332137300213210ustar00rootroot00000000000000package nftest import ( "runtime" "testing" "github.com/google/nftables" "github.com/vishvananda/netns" ) // OpenSystemConn returns a netlink connection that tests against // the running kernel in a separate network namespace. // nftest.CleanupSystemConn() must be called from a defer to cleanup // created network namespace. func OpenSystemConn(t *testing.T, enableSysTests bool) (*nftables.Conn, netns.NsHandle) { t.Helper() if !enableSysTests { t.SkipNow() } // We lock the goroutine into the current thread, as namespace operations // such as those invoked by `netns.New()` are thread-local. This is undone // in nftest.CleanupSystemConn(). runtime.LockOSThread() ns, err := netns.New() if err != nil { t.Fatalf("netns.New() failed: %v", err) } c, err := nftables.New(nftables.WithNetNSFd(int(ns))) if err != nil { t.Fatalf("nftables.New() failed: %v", err) } return c, ns } func CleanupSystemConn(t *testing.T, newNS netns.NsHandle) { defer runtime.UnlockOSThread() if err := newNS.Close(); err != nil { t.Fatalf("newNS.Close() failed: %v", err) } } nftables-0.2.0/internal/parseexprfunc/000077500000000000000000000000001457332137300200205ustar00rootroot00000000000000nftables-0.2.0/internal/parseexprfunc/parseexprfunc.go000066400000000000000000000003601457332137300232330ustar00rootroot00000000000000package parseexprfunc import ( "github.com/mdlayher/netlink" ) var ( ParseExprBytesFunc func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error) ParseExprMsgFunc func(fam byte, b []byte) ([]interface{}, error) ) nftables-0.2.0/monitor.go000066400000000000000000000220421457332137300153350ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "math" "strings" "sync" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) type MonitorAction uint8 // Possible MonitorAction values. const ( MonitorActionNew MonitorAction = 1 << iota MonitorActionDel MonitorActionMask MonitorAction = (1 << iota) - 1 MonitorActionAny MonitorAction = MonitorActionMask ) type MonitorObject uint32 // Possible MonitorObject values. const ( MonitorObjectTables MonitorObject = 1 << iota MonitorObjectChains MonitorObjectSets MonitorObjectRules MonitorObjectElements MonitorObjectRuleset MonitorObjectMask MonitorObject = (1 << iota) - 1 MonitorObjectAny MonitorObject = MonitorObjectMask ) var ( monitorFlags = map[MonitorAction]map[MonitorObject]uint32{ MonitorActionAny: { MonitorObjectAny: 0xffffffff, MonitorObjectTables: 1<>8 != netlink.HeaderType(unix.NFNL_SUBSYS_NFTABLES) { continue } msgType := msg.Header.Type & 0x00ff if monitor.monitorFlags&1< reg 1 &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 12, Len: 4, }, // cmp eq reg 1 0x0245a8c0 &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: net.ParseIP("192.168.69.2").To4(), }, // masq &expr.Masq{}, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } wg.Wait() if gotTable.Family != nat.Family || gotTable.Name != nat.Name { t.Fatal("no want table", gotTable.Family, gotTable.Name) } if gotChain.Type != postrouting.Type || gotChain.Name != postrouting.Name || *gotChain.Hooknum != *postrouting.Hooknum { t.Fatal("no want chain", gotChain.Type, gotChain.Name, gotChain.Hooknum) } if len(gotRule.Exprs) != len(rule.Exprs) { t.Fatal("no want rule") } } nftables-0.2.0/nftables_test.go000066400000000000000000006300541457332137300165130ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables_test import ( "bytes" "errors" "flag" "fmt" "net" "os" "reflect" "strings" "testing" "time" "github.com/google/nftables" "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "github.com/google/nftables/internal/nftest" "github.com/google/nftables/xt" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) var enableSysTests = flag.Bool("run_system_tests", false, "Run tests that operate against the live kernel") // nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing // users to make sense of large byte literals more easily. func nfdump(b []byte) string { var buf bytes.Buffer i := 0 for ; i < len(b); i += 4 { // TODO: show printable characters as ASCII fmt.Fprintf(&buf, "%02x %02x %02x %02x\n", b[i], b[i+1], b[i+2], b[i+3]) } for ; i < len(b); i++ { fmt.Fprintf(&buf, "%02x ", b[i]) } return buf.String() } // linediff returns a side-by-side diff of two nfdump() return values, flagging // lines which are not equal with an exclamation point prefix. func linediff(a, b string) string { var buf bytes.Buffer fmt.Fprintf(&buf, "got -- want\n") linesA := strings.Split(a, "\n") linesB := strings.Split(b, "\n") for idx, lineA := range linesA { if idx >= len(linesB) { break } lineB := linesB[idx] prefix := "! " if lineA == lineB { prefix = " " } fmt.Fprintf(&buf, "%s%s -- %s\n", prefix, lineA, lineB) } return buf.String() } func ifname(n string) []byte { b := make([]byte, 16) copy(b, []byte(n+"\x00")) return b } func TestRuleOperations(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) prerouting := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: prerouting, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 drop ] Kind: expr.VerdictDrop, }, }, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: prerouting, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 drop ] Kind: expr.VerdictDrop, }, }, }) c.InsertRule(&nftables.Rule{ Table: filter, Chain: prerouting, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 accept ] Kind: expr.VerdictAccept, }, }, }) c.InsertRule(&nftables.Rule{ Table: filter, Chain: prerouting, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 queue ] Kind: expr.VerdictQueue, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } rules, _ := c.GetRules(filter, prerouting) want := []expr.VerdictKind{ expr.VerdictQueue, expr.VerdictAccept, expr.VerdictDrop, expr.VerdictDrop, } for i, r := range rules { rr, _ := r.Exprs[0].(*expr.Verdict) if rr.Kind != want[i] { t.Fatalf("bad verdict kind at %d", i) } } c.ReplaceRule(&nftables.Rule{ Table: filter, Chain: prerouting, Handle: rules[2].Handle, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 accept ] Kind: expr.VerdictAccept, }, }, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: prerouting, Position: rules[2].Handle, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 drop ] Kind: expr.VerdictDrop, }, }, }) c.InsertRule(&nftables.Rule{ Table: filter, Chain: prerouting, Position: rules[2].Handle, Exprs: []expr.Any{ &expr.Verdict{ // [ immediate reg 0 queue ] Kind: expr.VerdictQueue, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } rules, _ = c.GetRules(filter, prerouting) want = []expr.VerdictKind{ expr.VerdictQueue, expr.VerdictAccept, expr.VerdictQueue, expr.VerdictAccept, expr.VerdictDrop, expr.VerdictDrop, } for i, r := range rules { rr, _ := r.Exprs[0].(*expr.Verdict) if rr.Kind != want[i] { t.Fatalf("bad verdict kind at %d", i) } } } func TestConfigureNAT(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip nat []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add chain nat postrouting '{' type nat hook postrouting priority 100 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x03\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x04\x08\x00\x02\x00\x00\x00\x00\x64\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add rule nat postrouting oifname uplink0 masquerade []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x74\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"), // nft add rule nat prerouting iif uplink0 tcp dport 4070 dnat 192.168.23.2:4080 []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x98\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xe6\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x17\x02\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xf0\x00\x00\x30\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x02"), // nft add rule nat prerouting iifname uplink0 udp dport 4070-4090 dnat 192.168.23.2:4070-4090 []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\xf8\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x11\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x05\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xe6\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x03\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xfa\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x17\x02\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xe6\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x03\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xfa\x00\x00\x38\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x06\x00\x00\x00\x00\x03"), // nft add rule nat prerouting ip daddr 10.0.0.0/24 dnat prefix to 20.0.0.0/24 []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x38\x01\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x10\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xff\xff\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x0a\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x14\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x08\x00\x01\x00\x14\x00\x00\xff\x38\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x07\x00\x00\x00\x00\x40"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, Table: nat, Type: nftables.ChainTypeNAT, }) postrouting := c.AddChain(&nftables.Chain{ Name: "postrouting", Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityNATSource, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: postrouting, Exprs: []expr.Any{ // meta load oifname => reg 1 &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, // cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: ifname("uplink0"), }, // masq &expr.Masq{}, }, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load iifname => reg 1 ] &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: ifname("uplink0"), }, // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO }, // [ cmp eq reg 1 0x0000e60f ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: binaryutil.BigEndian.PutUint16(4070), }, // [ immediate reg 1 0x0217a8c0 ] &expr.Immediate{ Register: 1, Data: net.ParseIP("192.168.23.2").To4(), }, // [ immediate reg 2 0x0000f00f ] &expr.Immediate{ Register: 2, Data: binaryutil.BigEndian.PutUint16(4080), }, // [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ] &expr.NAT{ Type: expr.NATTypeDestNAT, Family: unix.NFPROTO_IPV4, RegAddrMin: 1, RegProtoMin: 2, }, }, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load iifname => reg 1 ] &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: ifname("uplink0"), }, // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_UDP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO }, // [ cmp gte reg 1 0x0000e60f ] &expr.Cmp{ Op: expr.CmpOpGte, Register: 1, Data: binaryutil.BigEndian.PutUint16(4070), }, // [ cmp lte reg 1 0x0000fa0f ] &expr.Cmp{ Op: expr.CmpOpLte, Register: 1, Data: binaryutil.BigEndian.PutUint16(4090), }, // [ immediate reg 1 0x0217a8c0 ] &expr.Immediate{ Register: 1, Data: net.ParseIP("192.168.23.2").To4(), }, // [ immediate reg 2 0x0000f00f ] &expr.Immediate{ Register: 2, Data: binaryutil.BigEndian.PutUint16(4070), }, // [ immediate reg 3 0x0000fa0f ] &expr.Immediate{ Register: 3, Data: binaryutil.BigEndian.PutUint16(4090), }, // [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 3 ] &expr.NAT{ Type: expr.NATTypeDestNAT, Family: unix.NFPROTO_IPV4, RegAddrMin: 1, RegProtoMin: 2, RegProtoMax: 3, }, }, }) dstipmatch, dstcidrmatch, err := net.ParseCIDR("10.0.0.0/24") if err != nil { t.Fatal(err) } dnatfirstip, dnatlastip, err := nftables.NetFirstAndLastIP("20.0.0.0/24") if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 16, // destination addr offset Len: 4, }, &expr.Bitwise{ SourceRegister: 1, DestRegister: 1, Len: 4, // By specifying Xor to 0x0,0x0,0x0,0x0 and Mask to the CIDR mask, // the rule will match the CIDR of the IP (e.g in this case 10.0.0.0/24). Xor: []byte{0x0, 0x0, 0x0, 0x0}, Mask: dstcidrmatch.Mask, }, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: dstipmatch.To4(), }, &expr.Immediate{ Register: 1, Data: dnatfirstip, }, &expr.Immediate{ Register: 2, Data: dnatlastip, }, &expr.NAT{ Type: expr.NATTypeDestNAT, RegAddrMin: 1, RegAddrMax: 2, Prefix: true, Family: uint32(nftables.TableFamilyIPv4), }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureNATSourceAddress(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip nat []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain nat postrouting '{' type nat hook postrouting priority 100 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x03\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x04\x08\x00\x02\x00\x00\x00\x00\x64\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add rule nat postrouting ip saddr 192.168.69.2 masquerade []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x78\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\xc0\xa8\x45\x02\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) postrouting := c.AddChain(&nftables.Chain{ Name: "postrouting", Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityNATSource, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: postrouting, Exprs: []expr.Any{ // payload load 4b @ network header + 12 => reg 1 &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 12, Len: 4, }, // cmp eq reg 1 0x0245a8c0 &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: net.ParseIP("192.168.69.2").To4(), }, // masq &expr.Masq{}, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestMasqMarshalUnmarshal(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyINet, Name: "filter", }) postrouting := c.AddChain(&nftables.Chain{ Name: "postrouting", Table: filter, Type: nftables.ChainTypeNAT, Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityFilter, }) min := uint32(1) max := uint32(3) c.AddRule(&nftables.Rule{ Table: filter, Chain: postrouting, Exprs: []expr.Any{ &expr.Masq{ ToPorts: true, RegProtoMin: min, RegProtoMax: max, }, }, }) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyINet, Name: "filter", }, &nftables.Chain{ Name: "postrouting", }, ) if err != nil { t.Fatalf("c.GetRules() failed: %v", err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected rule count: got %d, want %d", got, want) } rule := rules[0] if got, want := len(rule.Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } me, ok := rule.Exprs[0].(*expr.Masq) if !ok { t.Fatalf("unexpected expression type: got %T, want *expr.Masq", rule.Exprs[0]) } if got, want := me.ToPorts, true; got != want { t.Errorf("unexpected masq random flag: got %v, want %v", got, want) } if got, want := me.RegProtoMin, min; got != want { t.Errorf("unexpected reg proto min: got %d, want %d", got, want) } if got, want := me.RegProtoMax, max; got != want { t.Errorf("unexpected reg proto max: got %d, want %d", got, want) } } func TestExprLogOptions(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) input := c.AddChain(&nftables.Chain{ Name: "input", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, }) keyGQ := uint32((1 << unix.NFTA_LOG_GROUP) | (1 << unix.NFTA_LOG_QTHRESHOLD) | (1 << unix.NFTA_LOG_SNAPLEN)) c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ &expr.Log{ Key: keyGQ, QThreshold: uint16(20), Group: uint16(1), Snaplen: uint32(132), }, }, }) keyPL := uint32((1 << unix.NFTA_LOG_PREFIX) | (1 << unix.NFTA_LOG_LEVEL) | (1 << unix.NFTA_LOG_FLAGS)) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ &expr.Log{ Key: keyPL, Data: []byte("LOG FORWARD"), Level: expr.LogLevelDebug, Flags: expr.LogFlagsTCPOpt | expr.LogFlagsIPOpt, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "input", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } rule := rules[0] if got, want := len(rule.Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } le, ok := rule.Exprs[0].(*expr.Log) if !ok { t.Fatalf("unexpected expression type: got %T, want *expr.Log", rule.Exprs[0]) } if got, want := le.Key, keyGQ; got != want { t.Fatalf("unexpected log key: got %d, want %d", got, want) } if got, want := le.Group, uint16(1); got != want { t.Fatalf("unexpected group: got %d, want %d", got, want) } if got, want := le.QThreshold, uint16(20); got != want { t.Fatalf("unexpected queue-threshold: got %d, want %d", got, want) } if got, want := le.Snaplen, uint32(132); got != want { t.Fatalf("unexpected snaplen: got %d, want %d", got, want) } rules, err = c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "forward", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } rule = rules[0] if got, want := len(rule.Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } le, ok = rule.Exprs[0].(*expr.Log) if !ok { t.Fatalf("unexpected expression type: got %T, want *expr.Log", rule.Exprs[0]) } if got, want := le.Key, keyPL; got != want { t.Fatalf("unexpected log key: got %d, want %d", got, want) } if got, want := string(le.Data), "LOG FORWARD"; got != want { t.Fatalf("unexpected prefix data: got %s, want %s", got, want) } if got, want := le.Level, expr.LogLevelDebug; got != want { t.Fatalf("unexpected log level: got %d, want %d", got, want) } if got, want := le.Flags, expr.LogFlagsTCPOpt|expr.LogFlagsIPOpt; got != want { t.Fatalf("unexpected log flags: got %d, want %d", got, want) } } func TestExprLogPrefix(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) input := c.AddChain(&nftables.Chain{ Name: "input", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ &expr.Log{ Key: 1 << unix.NFTA_LOG_PREFIX, Data: []byte("LOG INPUT"), }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "input", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } logExpr, ok := rules[0].Exprs[0].(*expr.Log) if !ok { t.Fatalf("Exprs[0] is type %T, want *expr.Log", rules[0].Exprs[0]) } // nftables defaults to warn log level when no level is specified and group is not defined // see https://wiki.nftables.org/wiki-nftables/index.php/Logging_traffic if got, want := logExpr.Key, uint32((1< reg 1 ] &expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}}, // [ immediate reg 1 0x0000a0c3 ] &expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint16(50080)}, // [ tproxy ip port reg 1 ] &expr.TProxy{ Family: byte(nftables.TableFamilyIPv4), TableFamily: byte(nftables.TableFamilyIPv4), RegPort: 1, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestTProxyWithAddrField(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add rule filter divert ip protocol tcp tproxy to 10.10.72.1:50080 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0b\x00\x02\x00\x64\x69\x76\x65\x72\x74\x00\x00\xe8\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x04\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x0a\x0a\x48\x01\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\xc3\xa0\x00\x00\x2c\x00\x01\x80\x0b\x00\x01\x00\x74\x70\x72\x6f\x78\x79\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: &nftables.Table{Name: "filter", Family: nftables.TableFamilyIPv4}, Chain: &nftables.Chain{ Name: "divert", Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityRef(-150), }, Exprs: []expr.Any{ // [ payload load 1b @ network header + 9 => reg 1 ] &expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}}, // [ immediate reg 1 0x01480a0a ] &expr.Immediate{Register: 1, Data: []byte("\x0a\x0a\x48\x01")}, // [ immediate reg 2 0x0000a0c3 ] &expr.Immediate{Register: 2, Data: binaryutil.BigEndian.PutUint16(50080)}, // [ tproxy ip addr reg 1 port reg 2 ] &expr.TProxy{ Family: byte(nftables.TableFamilyIPv4), TableFamily: byte(nftables.TableFamilyIPv4), RegAddr: 1, RegPort: 2, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestCt(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // sudo nft add rule ipv4table ipv4chain-5 ct mark 123 counter []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x10\x00\x02\x00\x69\x70\x76\x34\x63\x68\x61\x69\x6e\x2d\x35\x00\x24\x00\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: &nftables.Table{Name: "ipv4table", Family: nftables.TableFamilyIPv4}, Chain: &nftables.Chain{ Name: "ipv4chain-5", }, Exprs: []expr.Any{ // [ ct load mark => reg 1 ] &expr.Ct{ Key: unix.NFT_CT_MARK, Register: 1, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestCtSet(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // sudo nft add rule filter forward ct mark set 1 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x50\x00\x04\x80\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x01\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x04\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: &nftables.Table{Name: "filter", Family: nftables.TableFamilyIPv4}, Chain: &nftables.Chain{ Name: "forward", }, Exprs: []expr.Any{ // [ immediate reg 1 0x00000001 ] &expr.Immediate{ Register: 1, Data: binaryutil.NativeEndian.PutUint32(1), }, // [ ct set mark with reg 1 ] &expr.Ct{ Key: expr.CtKeyMARK, Register: 1, SourceRegister: true, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestCtStat(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // ct state established,related accept []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0b\x00\x02\x00\x6f\x75\x74\x70\x75\x74\x00\x00\xc4\x00\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x06\x00\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: &nftables.Table{Name: "filter", Family: nftables.TableFamilyIPv4}, Chain: &nftables.Chain{ Name: "output", }, Exprs: []expr.Any{ &expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE}, &expr.Bitwise{ SourceRegister: 1, DestRegister: 1, Len: 4, Mask: binaryutil.NativeEndian.PutUint32(expr.CtStateBitESTABLISHED | expr.CtStateBitRELATED), Xor: binaryutil.NativeEndian.PutUint32(0), }, &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}}, &expr.Verdict{Kind: expr.VerdictAccept}, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestAddRuleWithPosition(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add rule ip ipv4table ipv4chain-1 position 2 ip version 6 []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x10\x00\x02\x00\x69\x70\x76\x34\x63\x68\x61\x69\x6e\x2d\x31\x00\xa8\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x0c\x00\x04\x80\x05\x00\x01\x00\xf0\x00\x00\x00\x0c\x00\x05\x80\x05\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x60\x00\x00\x00\x0c\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x02"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Position: 2, Table: &nftables.Table{Name: "ipv4table", Family: nftables.TableFamilyIPv4}, Chain: &nftables.Chain{ Name: "ipv4chain-1", Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityRef(0), }, Exprs: []expr.Any{ // [ payload load 1b @ network header + 0 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 0, // Offset for a transport protocol header Len: 1, // 1 bytes for port }, // [ bitwise reg 1 = (reg=1 & 0x000000f0 ) ^ 0x00000000 ] &expr.Bitwise{ SourceRegister: 1, DestRegister: 1, Len: 1, Mask: []byte{0xf0}, Xor: []byte{0x0}, }, // [ cmp eq reg 1 0x00000060 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{(0x6 << 4)}, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestLastingConnection(t *testing.T) { testdialerr := errors.New("test dial sentinel error") dialCount := 0 c, err := nftables.New( nftables.AsLasting(), nftables.WithTestDial(func(req []netlink.Message) ([]netlink.Message, error) { dialCount++ return nil, testdialerr })) if err != nil { t.Errorf("creating lasting netlink connection failed %v", err) return } defer func() { if err := c.CloseLasting(); err != nil { t.Errorf("closing lasting netlink connection failed %v", err) } }() _, err = c.ListTables() if !errors.Is(err, testdialerr) { t.Errorf("non-testdialerr error returned from TestDial %v", err) return } if dialCount != 1 { t.Errorf("internal test error with TestDial invocations %v", dialCount) return } // While a lasting netlink connection is open, replacing TestDial must be // ineffective as there is no need to dial again and activating a new // TestDial function. The newly set TestDial function must be getting // ignored. c.TestDial = func(req []netlink.Message) ([]netlink.Message, error) { dialCount-- return nil, errors.New("transient netlink connection error") } _, err = c.ListTables() if !errors.Is(err, testdialerr) { t.Errorf("non-testdialerr error returned from TestDial %v", err) return } if dialCount != 2 { t.Errorf("internal test error with TestDial invocations %v", dialCount) return } for i := 0; i < 2; i++ { err = c.CloseLasting() if err != nil { t.Errorf("closing lasting netlink connection failed in attempt no. %d: %v", i, err) return } } _, err = c.ListTables() if errors.Is(err, testdialerr) { t.Error("testdialerr error returned from TestDial when expecting different error") return } if dialCount != 1 { t.Errorf("internal test error with TestDial invocations %v", dialCount) return } // fall into defer'ed second CloseLasting which must not cause any errors. } func TestListChains(t *testing.T) { polDrop := nftables.ChainPolicyDrop polAcpt := nftables.ChainPolicyAccept reply := [][]byte{ // chain input { type filter hook input priority filter; policy accept; } []byte("\x70\x00\x00\x00\x03\x0a\x02\x00\x00\x00\x00\x00\xb8\x76\x02\x00\x01\x00\x00\xc3\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x0a\x00\x03\x00\x69\x6e\x70\x75\x74\x00\x00\x00\x14\x00\x04\x00\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x05\x00\x00\x00\x00\x01\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\x00\x00\x00\x00"), // chain forward { type filter hook forward priority filter; policy drop; } []byte("\x70\x00\x00\x00\x03\x0a\x02\x00\x00\x00\x00\x01\xb8\x76\x02\x00\x01\x00\x00\xc3\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x02\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x00\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x05\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\x00\x00\x00\x00"), // chain output { type filter hook output priority filter; policy accept; } []byte("\x70\x00\x00\x00\x03\x0a\x02\x00\x00\x00\x00\x02\xb8\x76\x02\x00\x01\x00\x00\xc3\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x03\x0b\x00\x03\x00\x6f\x75\x74\x70\x75\x74\x00\x00\x14\x00\x04\x00\x08\x00\x01\x00\x00\x00\x00\x03\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x05\x00\x00\x00\x00\x01\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\x00\x00\x00\x00"), // chain undef { counter packets 56235 bytes 175436495 return } []byte("\x40\x00\x00\x00\x03\x0a\x02\x00\x00\x00\x00\x03\xb8\x76\x02\x00\x01\x00\x00\xc3\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x04\x0a\x00\x03\x00\x75\x6e\x64\x65\x66\x00\x00\x00\x08\x00\x06\x00\x00\x00\x00\x01"), []byte("\x14\x00\x00\x00\x03\x00\x02\x00\x00\x00\x00\x04\xb8\x76\x02\x00\x00\x00\x00\x00"), } want := []*nftables.Chain{ { Name: "input", Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter, Policy: &polAcpt, }, { Name: "forward", Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter, Policy: &polDrop, }, { Name: "output", Hooknum: nftables.ChainHookOutput, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter, Policy: &polAcpt, }, { Name: "undef", Hooknum: nil, Priority: nil, Policy: nil, }, } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { msgReply := make([]netlink.Message, len(reply)) for i, r := range reply { nm := &netlink.Message{} nm.UnmarshalBinary(r) nm.Header.Sequence = req[0].Header.Sequence nm.Header.PID = req[0].Header.PID msgReply[i] = *nm } return msgReply, nil })) if err != nil { t.Fatal(err) } chains, err := c.ListChains() if err != nil { t.Errorf("error returned from TestDial %v", err) return } if len(chains) != len(want) { t.Errorf("number of chains %d != number of want %d", len(chains), len(want)) return } validate := func(got interface{}, want interface{}, name string, index int) { if got != want { t.Errorf("chain %d: chain %s mismatch, got %v want %v", index, name, got, want) } } for i, chain := range chains { validate(chain.Name, want[i].Name, "name", i) if want[i].Hooknum != nil && chain.Hooknum != nil { validate(*chain.Hooknum, *want[i].Hooknum, "hooknum value", i) } else { validate(chain.Hooknum, want[i].Hooknum, "hooknum pointer", i) } if want[i].Priority != nil && chain.Priority != nil { validate(*chain.Priority, *want[i].Priority, "priority value", i) } else { validate(chain.Priority, want[i].Priority, "priority pointer", i) } validate(chain.Type, want[i].Type, "type", i) if want[i].Policy != nil && chain.Policy != nil { validate(*chain.Policy, *want[i].Policy, "policy value", i) } else { validate(chain.Policy, want[i].Policy, "policy pointer", i) } } } func TestAddChain(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte }{ { name: "Base chain", chain: &nftables.Chain{ Name: "base-chain", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityRef(0), Type: nftables.ChainTypeFilter, }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, }, { name: "Regular chain", chain: &nftables.Chain{ Name: "regular-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter regular-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x12\x00\x03\x00\x72\x65\x67\x75\x6c\x61\x72\x2d\x63\x68\x61\x69\x6e\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter c.AddChain(tt.chain) if err := c.Flush(); err != nil { t.Fatal(err) } } } func TestDelChain(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte }{ { name: "Base chain", chain: &nftables.Chain{ Name: "base-chain", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityRef(0), Type: nftables.ChainTypeFilter, }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft delete chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, }, { name: "Regular chain", chain: &nftables.Chain{ Name: "regular-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft delete chain ip filter regular-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x12\x00\x03\x00\x72\x65\x67\x75\x6c\x61\x72\x2d\x63\x68\x61\x69\x6e\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } tt.chain.Table = &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", } c.DelChain(tt.chain) if err := c.Flush(); err != nil { t.Fatal(err) } } } func TestGetObjReset(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft list chain ip filter forward want := [][]byte{ {0x2, 0x0, 0x0, 0x0, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x66, 0x77, 0x64, 0x65, 0x64, 0x0, 0x0, 0x0, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x1}, } // The reply messages come from adding log.Printf("msgs: %#v", msgs) to // (*github.com/mdlayher/netlink/Conn).receive reply := [][]netlink.Message{ nil, {{Header: netlink.Header{Length: 0x64, Type: 0xa12, Flags: 0x802, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0x2, 0x0, 0x0, 0x10, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x66, 0x77, 0x64, 0x65, 0x64, 0x0, 0x0, 0x0, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1c, 0x0, 0x4, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x61, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xc, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}}}, {{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}}, {{Header: netlink.Header{Length: 36, Type: netlink.Error, Flags: 0x100, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0, 0, 0, 0, 88, 0, 0, 0, 12, 10, 5, 4, 143, 109, 199, 146, 236, 9, 0, 0}}}, } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } rep := reply[0] reply = reply[1:] return rep, nil })) if err != nil { t.Fatal(err) } filter := &nftables.Table{Name: "filter", Family: nftables.TableFamilyIPv4} obj, err := c.ResetObject(&nftables.CounterObj{ Table: filter, Name: "fwded", }) if err != nil { t.Fatal(err) } co, ok := obj.(*nftables.CounterObj) if !ok { t.Fatalf("unexpected type: got %T, want *nftables.CounterObj", obj) } if got, want := co.Table.Name, filter.Name; got != want { t.Errorf("unexpected table name: got %q, want %q", got, want) } if got, want := co.Table.Family, filter.Family; got != want { t.Errorf("unexpected table family: got %d, want %d", got, want) } if got, want := co.Packets, uint64(9); got != want { t.Errorf("unexpected number of packets: got %d, want %d", got, want) } if got, want := co.Bytes, uint64(1121); got != want { t.Errorf("unexpected number of bytes: got %d, want %d", got, want) } } func TestObjAPI(t *testing.T) { if os.Getenv("TRAVIS") == "true" { t.SkipNow() } // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() table := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tableOther := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "foo", }) chain := c.AddChain(&nftables.Chain{ Name: "chain", Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityFilter, }) counter1 := c.AddObj(&nftables.CounterObj{ Table: table, Name: "fwded1", Bytes: 1, Packets: 1, }) counter2 := c.AddObj(&nftables.CounterObj{ Table: table, Name: "fwded2", Bytes: 1, Packets: 1, }) c.AddObj(&nftables.CounterObj{ Table: tableOther, Name: "fwdedOther", Bytes: 0, Packets: 0, }) c.AddRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Objref{ Type: 1, Name: "fwded1", }, }, }) if err := c.Flush(); err != nil { t.Fatalf(err.Error()) } objs, err := c.GetObjects(table) if err != nil { t.Errorf("c.GetObjects(table) failed: %v failed", err) } if got := len(objs); got != 2 { t.Fatalf("unexpected number of objects: got %d, want %d", got, 2) } objsOther, err := c.GetObjects(tableOther) if err != nil { t.Errorf("c.GetObjects(tableOther) failed: %v failed", err) } if got := len(objsOther); got != 1 { t.Fatalf("unexpected number of objects: got %d, want %d", got, 1) } obj1, err := c.GetObject(counter1) if err != nil { t.Errorf("c.GetObject(counter1) failed: %v failed", err) } rcounter1, ok := obj1.(*nftables.CounterObj) if !ok { t.Fatalf("unexpected type: got %T, want *nftables.CounterObj", rcounter1) } if rcounter1.Name != "fwded1" { t.Fatalf("unexpected counter name: got %s, want %s", rcounter1.Name, "fwded1") } obj2, err := c.GetObject(counter2) if err != nil { t.Errorf("c.GetObject(counter2) failed: %v failed", err) } rcounter2, ok := obj2.(*nftables.CounterObj) if !ok { t.Fatalf("unexpected type: got %T, want *nftables.CounterObj", rcounter2) } if rcounter2.Name != "fwded2" { t.Fatalf("unexpected counter name: got %s, want %s", rcounter2.Name, "fwded2") } _, err = c.ResetObject(counter1) if err != nil { t.Errorf("c.ResetObjects(table) failed: %v failed", err) } obj1, err = c.GetObject(counter1) if err != nil { t.Errorf("c.GetObject(counter1) failed: %v failed", err) } if counter1 := obj1.(*nftables.CounterObj); counter1.Packets > 0 { t.Errorf("unexpected packets number: got %d, want %d", counter1.Packets, 0) } obj2, err = c.GetObject(counter2) if err != nil { t.Errorf("c.GetObject(counter2) failed: %v failed", err) } if counter2 := obj2.(*nftables.CounterObj); counter2.Packets != 1 { t.Errorf("unexpected packets number: got %d, want %d", counter2.Packets, 1) } legacy, err := c.GetObj(counter1) if err != nil { t.Errorf("c.GetObj(counter1) failed: %v failed", err) } if len(legacy) != 2 { t.Errorf("unexpected number of objects: got %d, want %d", len(legacy), 2) } legacyReset, err := c.GetObjReset(counter1) if err != nil { t.Errorf("c.GetObjReset(counter1) failed: %v failed", err) } if len(legacyReset) != 2 { t.Errorf("unexpected number of objects: got %d, want %d", len(legacyReset), 2) } } func TestConfigureClamping(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain filter forward '{' type filter hook forward priority 0 \; '}' []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule ip filter forward oifname uplink0 tcp flags syn tcp option maxseg size set rt mtu []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xf0\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x0d\x08\x00\x04\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x0c\x00\x04\x80\x05\x00\x01\x00\x02\x00\x00\x00\x0c\x00\x05\x80\x05\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x05\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x72\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x40\x00\x01\x80\x0e\x00\x01\x00\x62\x79\x74\x65\x6f\x72\x64\x65\x72\x00\x00\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x05\x00\x00\x00\x00\x02\x3c\x00\x01\x80\x0b\x00\x01\x00\x65\x78\x74\x68\x64\x72\x00\x00\x2c\x00\x02\x80\x08\x00\x07\x00\x00\x00\x00\x01\x05\x00\x02\x00\x02\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x06\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load oifname => reg 1 ] &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, // [ cmp eq reg 1 0x30707070 0x00000000 0x00000000 0x00000000 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: ifname("uplink0"), }, // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 1b @ transport header + 13 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 13, // TODO Len: 1, // TODO }, // [ bitwise reg 1 = (reg=1 & 0x00000002 ) ^ 0x00000000 ] &expr.Bitwise{ DestRegister: 1, SourceRegister: 1, Len: 1, Mask: []byte{0x02}, Xor: []byte{0x00}, }, // [ cmp neq reg 1 0x00000000 ] &expr.Cmp{ Op: expr.CmpOpNeq, Register: 1, Data: []byte{0x00}, }, // [ rt load tcpmss => reg 1 ] &expr.Rt{ Register: 1, Key: expr.RtTCPMSS, }, // [ byteorder reg 1 = hton(reg 1, 2, 2) ] &expr.Byteorder{ DestRegister: 1, SourceRegister: 1, Op: expr.ByteorderHton, Len: 2, Size: 2, }, // [ exthdr write tcpopt reg 1 => 2b @ 2 + 2 ] &expr.Exthdr{ SourceRegister: 1, Type: 2, // TODO Offset: 2, Len: 2, Op: expr.ExthdrOpTcpopt, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestMatchPacketHeader(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was adopted from: // https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_headers want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain filter input '{' type filter hook forward priority filter \; '}' []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0a\x00\x03\x00\x69\x6e\x70\x75\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule ip filter input tcp flags syn tcp option maxseg size 1-500 drop []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0a\x00\x02\x00\x69\x6e\x70\x75\x74\x00\x00\x00\xc4\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x0d\x08\x00\x04\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x0c\x00\x04\x80\x05\x00\x01\x00\x02\x00\x00\x00\x0c\x00\x05\x80\x05\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x05\x00\x01\x00\x00\x00\x00\x00\x44\x00\x01\x80\x0b\x00\x01\x00\x65\x78\x74\x68\x64\x72\x00\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x05\x00\x02\x00\x02\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x06\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x05\x0c\x00\x03\x80\x06\x00\x01\x00\x00\x01\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x03\x0c\x00\x03\x80\x06\x00\x01\x00\x01\xf4\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) input := c.AddChain(&nftables.Chain{ Name: "input", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 1b @ transport header + 13 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 13, // TODO Len: 1, // TODO }, // [ bitwise reg 1 = (reg=1 & 0x00000002 ) ^ 0x00000000 ] &expr.Bitwise{ DestRegister: 1, SourceRegister: 1, Len: 1, Mask: []byte{0x02}, Xor: []byte{0x00}, }, // [ cmp neq reg 1 0x00000000 ] &expr.Cmp{ Op: expr.CmpOpNeq, Register: 1, Data: []byte{0x00}, }, // [ exthdr load tcpopt 2b @ 2 + 2 => reg 1 ] &expr.Exthdr{ DestRegister: 1, Type: 2, // TODO Offset: 2, Len: 2, Op: expr.ExthdrOpTcpopt, }, // [ cmp gte reg 1 0x00000100 ] &expr.Cmp{ Op: expr.CmpOpGte, Register: 1, Data: binaryutil.BigEndian.PutUint16(uint16(1)), }, // [ cmp lte reg 1 0x0000f401 ] &expr.Cmp{ Op: expr.CmpOpLte, Register: 1, Data: binaryutil.BigEndian.PutUint16(uint16(500)), }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestDropVerdict(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain filter forward '{' type filter hook forward priority 0 \; '}' []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule filter forward tcp dport 1234 drop []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xe4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x04\xd2\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x0000d204 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x04, 0xd2}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestCreateUseAnonymousSet(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // Create anonymous set with key len of 2 bytes and data len of 0 bytes []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x5f\x5f\x73\x65\x74\x25\x64\x00\x08\x00\x03\x00\x00\x00\x00\x03\x08\x00\x04\x00\x00\x00\x00\x0d\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x0a\x00\x00\x00\x00\x01\x0c\x00\x09\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0a\x00\x0d\x00\x00\x04\x02\x00\x00\x00\x00\x00"), // Assign the two values to the aforementioned anonymous set []byte("\x02\x00\x00\x00\x0c\x00\x02\x00\x5f\x5f\x73\x65\x74\x25\x64\x00\x08\x00\x04\x00\x00\x00\x00\x01\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x24\x00\x03\x80\x10\x00\x01\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x00\x45\x00\x00\x10\x00\x02\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x04\x8b\x00\x00"), // nft add rule filter forward tcp dport {69, 1163} drop []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xe8\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x30\x00\x01\x80\x0b\x00\x01\x00\x6c\x6f\x6f\x6b\x75\x70\x00\x00\x20\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x01\x00\x5f\x5f\x73\x65\x74\x25\x64\x00\x08\x00\x04\x00\x00\x00\x00\x01\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) set := &nftables.Set{ Anonymous: true, Constant: true, Table: filter, KeyType: nftables.TypeInetService, } if err := c.AddSet(set, []nftables.SetElement{ {Key: binaryutil.BigEndian.PutUint16(69)}, {Key: binaryutil.BigEndian.PutUint16(1163)}, }); err != nil { t.Errorf("c.AddSet() failed: %v", err) } c.AddRule(&nftables.Rule{ Table: filter, Chain: &nftables.Chain{Name: "forward", Type: nftables.ChainTypeFilter}, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ lookup reg 1 set __set%d ] &expr.Lookup{ SourceRegister: 1, SetName: set.Name, SetID: set.ID, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestCappedErrMsgOnSets(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) c, err := nftables.New(nftables.WithNetNSFd(int(newNS)), nftables.AsLasting()) if err != nil { t.Fatalf("nftables.New() failed: %v", err) } defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) if err := c.Flush(); err != nil { t.Errorf("failed adding table: %v", err) } tables, err := c.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { t.Errorf("failed to list IPv4 tables: %v", err) } for _, t := range tables { if t.Name == "filter" { filter = t break } } ifSet := &nftables.Set{ Table: filter, Name: "if_set", KeyType: nftables.TypeIFName, } if err := c.AddSet(ifSet, nil); err != nil { t.Errorf("c.AddSet(ifSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("failed adding set ifSet: %v", err) } ifSet, err = c.GetSetByName(filter, "if_set") if err != nil { t.Fatalf("failed getting set by name: %v", err) } elems, err := c.GetSetElements(ifSet) if err != nil { t.Errorf("failed getting set elements (ifSet): %v", err) } if got, want := len(elems), 0; got != want { t.Errorf("first GetSetElements(ifSet) call len not equal: got %d, want %d", got, want) } elements := []nftables.SetElement{ {Key: []byte("012345678912345\x00")}, } if err := c.SetAddElements(ifSet, elements); err != nil { t.Errorf("adding SetElements(ifSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("failed adding set elements ifSet: %v", err) } elems, err = c.GetSetElements(ifSet) if err != nil { t.Fatalf("failed getting set elements (ifSet): %v", err) } if got, want := len(elems), 1; got != want { t.Fatalf("second GetSetElements(ifSet) call len not equal: got %d, want %d", got, want) } if got, want := elems, elements; !reflect.DeepEqual(elems, elements) { t.Errorf("SetElements(ifSet) not equal: got %v, want %v", got, want) } } func TestCreateUseNamedSet(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, } if err := c.AddSet(portSet, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.SetAddElements(portSet, []nftables.SetElement{{Key: binaryutil.BigEndian.PutUint16(22)}}); err != nil { t.Errorf("c.SetVal(portSet) failed: %v", err) } ipSet := &nftables.Set{ Table: filter, Name: "IPs_4_dayz", KeyType: nftables.TypeIPAddr, } if err := c.AddSet(ipSet, []nftables.SetElement{{Key: []byte(net.ParseIP("192.168.1.64").To4())}}); err != nil { t.Errorf("c.AddSet(ipSet) failed: %v", err) } if err := c.SetAddElements(ipSet, []nftables.SetElement{{Key: []byte(net.ParseIP("192.168.1.42").To4())}}); err != nil { t.Errorf("c.SetVal(ipSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } sets, err := c.GetSets(filter) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(sets) != 2 { t.Fatalf("len(sets) = %d, want 2", len(sets)) } if sets[0].Name != "test" { t.Errorf("set[0].Name = %q, want test", sets[0].Name) } if sets[1].Name != "IPs_4_dayz" { t.Errorf("set[1].Name = %q, want IPs_4_dayz", sets[1].Name) } } func TestIP6SetAddElements(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv6, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "ports", KeyType: nftables.TypeInetService, } if err := c.AddSet(portSet, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.SetAddElements(portSet, []nftables.SetElement{ {Key: binaryutil.BigEndian.PutUint16(22)}, {Key: binaryutil.BigEndian.PutUint16(80)}, }); err != nil { t.Errorf("c.SetVal(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } sets, err := c.GetSets(filter) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(sets) != 1 { t.Fatalf("len(sets) = %d, want 1", len(sets)) } elements, err := c.GetSetElements(sets[0]) if err != nil { t.Errorf("c.GetSetElements(portSet) failed: %v", err) } if len(elements) != 2 { t.Fatalf("len(portSetElements) = %d, want 2", len(sets)) } } func TestCreateUseCounterSet(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, Counter: true, } if err := c.AddSet(portSet, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.SetAddElements(portSet, []nftables.SetElement{{Key: binaryutil.BigEndian.PutUint16(22)}}); err != nil { t.Errorf("c.SetVal(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } sets, err := c.GetSets(filter) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(sets) != 1 { t.Fatalf("len(sets) = %d, want 1", len(sets)) } if sets[0].Name != "test" { t.Errorf("set[0].Name = %q, want test", sets[0].Name) } } func TestCreateDeleteNamedSet(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, } if err := c.AddSet(portSet, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.DelSet(portSet) if err := c.Flush(); err != nil { t.Errorf("Second c.Flush() failed: %v", err) } sets, err := c.GetSets(filter) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(sets) != 0 { t.Fatalf("len(sets) = %d, want 0", len(sets)) } } func TestDeleteElementNamedSet(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, } if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}}); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.SetDeleteElements(portSet, []nftables.SetElement{{Key: []byte{0, 23}}}) if err := c.Flush(); err != nil { t.Errorf("Second c.Flush() failed: %v", err) } elems, err := c.GetSetElements(portSet) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(elems) != 1 { t.Fatalf("len(elems) = %d, want 1", len(elems)) } if !bytes.Equal(elems[0].Key, []byte{0, 22}) { t.Errorf("elems[0].Key = %v, want 22", elems[0].Key) } } func TestFlushNamedSet(t *testing.T) { if os.Getenv("TRAVIS") == "true" { t.SkipNow() } // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, } if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}}); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.FlushSet(portSet) if err := c.Flush(); err != nil { t.Errorf("Second c.Flush() failed: %v", err) } elems, err := c.GetSetElements(portSet) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(elems) != 0 { t.Fatalf("len(elems) = %d, want 0", len(elems)) } } func TestSetElementsInterval(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv6, Name: "filter", }) portSet := &nftables.Set{ Table: filter, Name: "ports", KeyType: nftables.MustConcatSetType(nftables.TypeIP6Addr, nftables.TypeInetService, nftables.TypeIP6Addr), Interval: true, Concatenation: true, } if err := c.AddSet(portSet, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } // { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 50000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb } keyBytes := []byte{119, 124, 171, 75, 133, 240, 22, 20, 73, 229, 210, 155, 170, 123, 204, 144, 195, 80, 0, 0, 135, 9, 28, 185, 22, 62, 155, 85, 53, 127, 239, 100, 112, 138, 237, 203} // { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 60000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb } keyEndBytes := []byte{119, 124, 171, 75, 133, 240, 22, 20, 73, 229, 210, 155, 170, 123, 204, 144, 234, 96, 0, 0, 135, 9, 28, 185, 22, 62, 155, 85, 53, 127, 239, 100, 112, 138, 237, 203} // elements = { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 50000-60000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb } if err := c.SetAddElements(portSet, []nftables.SetElement{ {Key: keyBytes, KeyEnd: keyEndBytes}, }); err != nil { t.Errorf("c.SetVal(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } sets, err := c.GetSets(filter) if err != nil { t.Errorf("c.GetSets() failed: %v", err) } if len(sets) != 1 { t.Fatalf("len(sets) = %d, want 1", len(sets)) } elements, err := c.GetSetElements(sets[0]) if err != nil { t.Errorf("c.GetSetElements(portSet) failed: %v", err) } if len(elements) != 1 { t.Fatalf("len(portSetElements) = %d, want 1", len(sets)) } element := elements[0] if len(element.Key) == 0 { t.Fatal("len(portSetElements.Key) = 0") } if len(element.KeyEnd) == 0 { t.Fatal("len(portSetElements.KeyEnd) = 0") } if !bytes.Equal(element.Key, keyBytes) { t.Fatal("element.Key != keyBytes") } if !bytes.Equal(element.KeyEnd, keyEndBytes) { t.Fatal("element.KeyEnd != keyEndBytes") } } func TestCreateListFlowtable(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", } flowtable := &nftables.Flowtable{ Table: filter, Name: "flowtable_test", } c.AddTable(filter) c.AddFlowtable(flowtable) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } flowtables, err := c.ListFlowtables(filter) if err != nil { t.Fatalf("c.ListFlowtables() failed: %v", err) } if got, want := len(flowtables), 1; got != want { t.Fatalf("flowtable entry length mismatch: got %d, want %d", got, want) } } func TestCreateListFlowtableWithDevices(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", } lo, err := net.InterfaceByName("lo") if err != nil { t.Fatalf("net.InterfaceByName() failed: %v", err) } flowtable := &nftables.Flowtable{ Table: filter, Name: "flowtable_test", Devices: []string{lo.Name}, Hooknum: nftables.FlowtableHookIngress, Priority: nftables.FlowtablePriorityRef(5), } c.AddTable(filter) c.AddFlowtable(flowtable) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } flowtables, err := c.ListFlowtables(filter) if err != nil { t.Fatalf("c.ListFlowtables() failed: %v", err) } if got, want := len(flowtables), 1; got != want { t.Fatalf("flowtable entry length mismatch: got %d, want %d", got, want) } sysFlowtable := flowtables[0] if got, want := sysFlowtable.Table, flowtable.Table; got != want { t.Errorf("flowtables table mismatch: got %v, want %v", got, want) } if got, want := sysFlowtable.Name, flowtable.Name; got != want { t.Errorf("flowtables name mismatch: got %s, want %s", got, want) } if len(sysFlowtable.Devices) != 1 { t.Fatalf("expected 1 device in flowtable, got %d", len(sysFlowtable.Devices)) } if got, want := sysFlowtable.Devices, flowtable.Devices; !reflect.DeepEqual(got, want) { t.Errorf("flowtables device mismatch: got %v, want %v", got, want) } if got, want := *sysFlowtable.Hooknum, *flowtable.Hooknum; got != want { t.Errorf("flowtables hook mismatch: got %v, want %v", got, want) } if got, want := *sysFlowtable.Priority, *flowtable.Priority; got != want { t.Errorf("flowtables prio mismatch: got %v, want %v", got, want) } } func TestCreateDeleteFlowtable(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", } flowtable := &nftables.Flowtable{ Table: filter, Name: "flowtable_test", } c.AddTable(filter) c.AddFlowtable(flowtable) flowtable.Name = "flowtable_test_to_delete" c.AddFlowtable(flowtable) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } flowtables, err := c.ListFlowtables(filter) if err != nil { t.Fatalf("c.ListFlowtables() failed: %v", err) } if got, want := len(flowtables), 2; got != want { t.Fatalf("flowtable entry length mismatch: got %d, want %d", got, want) } c.DelFlowtable(flowtable) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } flowtables, err = c.ListFlowtables(filter) if err != nil { t.Fatalf("c.ListFlowtables() after deletion failed: %v", err) } if got, want := len(flowtables), 1; got != want { t.Errorf("flowtable entry length mismatch: got %d, want %d", got, want) } if got, removed := flowtables[0].Name, flowtable.Name; got == removed { t.Errorf("wrong flowtable entry deleted") } } func TestOffload(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", } accept := nftables.ChainPolicyAccept forward := &nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Priority: nftables.ChainPriorityFilter, Hooknum: nftables.ChainHookForward, Policy: &accept, } flowtable := &nftables.Flowtable{ Table: filter, Name: "flowtable_test", } c.AddTable(filter) c.AddChain(forward) c.AddFlowtable(flowtable) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() failed: %v", err) } rule := &nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ &expr.Payload{ OperationType: expr.PayloadLoad, Base: expr.PayloadBaseNetworkHeader, Len: 1, Offset: 9, DestRegister: 1, }, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x06, 0x00, 0x00, 0x00}, }, &expr.FlowOffload{ Name: flowtable.Name, }, }, } c.AddRule(rule) if err := c.Flush(); err != nil { t.Fatalf("c.Flush() offload failed: %v", err) } rules, err := c.GetRule(filter, forward) if err != nil { t.Fatalf("c.GetRule() failed: %v", err) } if got, want := len(rules), 1; got != want { t.Fatalf("rule count mismatch: got %d, want %d", got, want) } sysRule := rules[0] if got, want := sysRule.Exprs, rule.Exprs; !reflect.DeepEqual(got, want) { t.Errorf("rule content mismatch: got %v, want %v", got, want) } } func TestFlushChain(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Table: filter, Name: "forward", }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x0000d204 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x04, 0xd2}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x000010e1 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0xe1, 0x10}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(filter, forward) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 2 { t.Fatalf("len(rules) = %d, want 2", len(rules)) } c.FlushChain(forward) if err := c.Flush(); err != nil { t.Errorf("Second c.Flush() failed: %v", err) } rules, err = c.GetRules(filter, forward) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 0 { t.Fatalf("len(rules) = %d, want 0", len(rules)) } } func TestFlushTable(t *testing.T) { if os.Getenv("TRAVIS") == "true" { t.SkipNow() } // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) forward := c.AddChain(&nftables.Chain{ Table: filter, Name: "forward", }) input := c.AddChain(&nftables.Chain{ Table: filter, Name: "input", }) prerouting := c.AddChain(&nftables.Chain{ Table: nat, Name: "prerouting", }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x0000d204 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x04, 0xd2}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x000010e1 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0xe1, 0x10}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ cmp eq reg 1 0x0000162e ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x2e, 0x16}, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO }, // [ cmp eq reg 1 0x00001600 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x00, 0x16}, }, // [ immediate reg 1 0x0000ae08 ] &expr.Immediate{ Register: 1, Data: binaryutil.BigEndian.PutUint16(2222), }, // [ redir proto_min reg 1 ] &expr.Redir{ RegisterProtoMin: 1, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(filter, forward) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 2 { t.Fatalf("len(rules) = %d, want 2", len(rules)) } rules, err = c.GetRules(filter, input) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 1 { t.Fatalf("len(rules) = %d, want 1", len(rules)) } rules, err = c.GetRules(nat, prerouting) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 1 { t.Fatalf("len(rules) = %d, want 1", len(rules)) } c.FlushTable(filter) if err := c.Flush(); err != nil { t.Errorf("Second c.Flush() failed: %v", err) } rules, err = c.GetRules(filter, forward) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 0 { t.Fatalf("len(rules) = %d, want 0", len(rules)) } rules, err = c.GetRules(filter, input) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 0 { t.Fatalf("len(rules) = %d, want 0", len(rules)) } rules, err = c.GetRules(nat, prerouting) if err != nil { t.Errorf("c.GetRules() failed: %v", err) } if len(rules) != 1 { t.Fatalf("len(rules) = %d, want 1", len(rules)) } } func TestGetLookupExprDestSet(t *testing.T) { c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) set := &nftables.Set{ Table: filter, Name: "test", IsMap: true, KeyType: nftables.TypeInetService, DataType: nftables.TypeVerdict, } if err := c.AddSet(set, nil); err != nil { t.Errorf("c.AddSet(set) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, &expr.Lookup{ SourceRegister: 1, SetName: set.Name, SetID: set.ID, DestRegister: 0, IsDestRegSet: true, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "forward", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 4; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } lookup, lookupOk := rules[0].Exprs[3].(*expr.Lookup) if !lookupOk { t.Fatalf("Exprs[3] is type %T, want *expr.Lookup", rules[0].Exprs[3]) } if want := (&expr.Lookup{ SourceRegister: 1, SetName: set.Name, DestRegister: 0, IsDestRegSet: true, }); !reflect.DeepEqual(lookup, want) { t.Errorf("lookup expr = %+v, wanted %+v", lookup, want) } } func TestGetRuleLookupVerdictImmediate(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) set := &nftables.Set{ Table: filter, Name: "test", KeyType: nftables.TypeInetService, } if err := c.AddSet(set, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2, }, // [ lookup reg 1 set __set%d ] &expr.Lookup{ SourceRegister: 1, SetName: set.Name, SetID: set.ID, }, // [ immediate reg 0 drop ] &expr.Verdict{ Kind: expr.VerdictAccept, }, // [ immediate reg 2 test ] &expr.Immediate{ Register: 2, Data: []byte("test"), }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "forward", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 6; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } lookup, lookupOk := rules[0].Exprs[3].(*expr.Lookup) if !lookupOk { t.Fatalf("Exprs[3] is type %T, want *expr.Lookup", rules[0].Exprs[3]) } if want := (&expr.Lookup{ SourceRegister: 1, SetName: set.Name, }); !reflect.DeepEqual(lookup, want) { t.Errorf("lookup expr = %+v, wanted %+v", lookup, want) } verdict, verdictOk := rules[0].Exprs[4].(*expr.Verdict) if !verdictOk { t.Fatalf("Exprs[4] is type %T, want *expr.Verdict", rules[0].Exprs[4]) } if want := (&expr.Verdict{ Kind: expr.VerdictAccept, }); !reflect.DeepEqual(verdict, want) { t.Errorf("verdict expr = %+v, wanted %+v", verdict, want) } imm, immOk := rules[0].Exprs[5].(*expr.Immediate) if !immOk { t.Fatalf("Exprs[4] is type %T, want *expr.Immediate", rules[0].Exprs[5]) } if want := (&expr.Immediate{ Register: 2, Data: []byte("test"), }); !reflect.DeepEqual(imm, want) { t.Errorf("verdict expr = %+v, wanted %+v", imm, want) } } func TestDynset(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) set := &nftables.Set{ Table: filter, Name: "dynamic-set", KeyType: nftables.TypeIPAddr, HasTimeout: true, Timeout: time.Duration(600 * time.Second), } if err := c.AddSet(set, nil); err != nil { t.Errorf("c.AddSet(portSet) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: uint32(12), Len: uint32(4), }, &expr.Dynset{ SrcRegKey: 1, SetName: set.Name, SetID: set.ID, Operation: uint32(unix.NFT_DYNSET_OP_UPDATE), }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules( &nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }, &nftables.Chain{ Name: "forward", }, ) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 2; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset) if !dynsetOk { t.Fatalf("Exprs[0] is type %T, want *expr.Dynset", rules[0].Exprs[1]) } if want := (&expr.Dynset{ SrcRegKey: 1, SetName: set.Name, Operation: uint32(unix.NFT_DYNSET_OP_UPDATE), }); !reflect.DeepEqual(dynset, want) { t.Errorf("dynset expr = %+v, wanted %+v", dynset, want) } } func TestDynsetWithOneExpression(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() table := &nftables.Table{ Name: "filter", Family: nftables.TableFamilyIPv4, } chain := &nftables.Chain{ Name: "forward", Hooknum: nftables.ChainHookForward, Table: table, Priority: nftables.ChainPriorityRef(0), Type: nftables.ChainTypeFilter, } set := &nftables.Set{ Table: table, Name: "myMeter", KeyType: nftables.TypeIPAddr, Dynamic: true, } c.AddTable(table) c.AddChain(chain) if err := c.AddSet(set, nil); err != nil { t.Errorf("c.AddSet(myMeter) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rule := &nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: uint32(12), Len: uint32(4), }, &expr.Dynset{ SrcRegKey: 1, SetName: set.Name, Operation: uint32(unix.NFT_DYNSET_OP_ADD), Exprs: []expr.Any{ &expr.Limit{ Type: expr.LimitTypePkts, Rate: 200, Unit: expr.LimitTimeSecond, Burst: 5, }, }, }, &expr.Verdict{ Kind: expr.VerdictDrop, }, }, } c.AddRule(rule) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(table, chain) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 3; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset) if !dynsetOk { t.Fatalf("Exprs[0] is type %T, want *expr.Dynset", rules[0].Exprs[1]) } if got, want := len(dynset.Exprs), 1; got != want { t.Fatalf("unexpected number of dynset.Exprs: got %d, want %d", got, want) } if got, want := dynset.SetName, set.Name; got != want { t.Fatalf("dynset.SetName is %s, want %s", got, want) } if want := (&expr.Limit{ Type: expr.LimitTypePkts, Rate: 200, Unit: expr.LimitTimeSecond, Burst: 5, }); !reflect.DeepEqual(dynset.Exprs[0], want) { t.Errorf("dynset.Exprs[0] expr = %+v, wanted %+v", dynset.Exprs[0], want) } } func TestDynsetWithMultipleExpressions(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() table := &nftables.Table{ Name: "filter", Family: nftables.TableFamilyIPv4, } chain := &nftables.Chain{ Name: "forward", Hooknum: nftables.ChainHookForward, Table: table, Priority: nftables.ChainPriorityRef(0), Type: nftables.ChainTypeFilter, } set := &nftables.Set{ Table: table, Name: "myMeter", KeyType: nftables.TypeIPAddr, Dynamic: true, } c.AddTable(table) c.AddChain(chain) if err := c.AddSet(set, nil); err != nil { t.Errorf("c.AddSet(myMeter) failed: %v", err) } if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rule := &nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: uint32(12), Len: uint32(4), }, &expr.Dynset{ SrcRegKey: 1, SetName: set.Name, Operation: uint32(unix.NFT_DYNSET_OP_ADD), Exprs: []expr.Any{ &expr.Connlimit{ Count: 20, Flags: 1, }, &expr.Limit{ Type: expr.LimitTypePkts, Rate: 10, Unit: expr.LimitTimeSecond, Burst: 2, }, }, }, &expr.Verdict{ Kind: expr.VerdictDrop, }, }, } c.AddRule(rule) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(table, chain) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 3; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset) if !dynsetOk { t.Fatalf("Exprs[0] is type %T, want *expr.Dynset", rules[0].Exprs[1]) } if got, want := len(dynset.Exprs), 2; got != want { t.Fatalf("unexpected number of dynset.Exprs: got %d, want %d", got, want) } if got, want := dynset.SetName, set.Name; got != want { t.Fatalf("dynset.SetName is %s, want %s", got, want) } if want := (&expr.Connlimit{ Count: 20, Flags: 1, }); !reflect.DeepEqual(dynset.Exprs[0], want) { t.Errorf("dynset.Exprs[0] expr = %+v, wanted %+v", dynset.Exprs[0], want) } if want := (&expr.Limit{ Type: expr.LimitTypePkts, Rate: 10, Unit: expr.LimitTimeSecond, Burst: 2, }); !reflect.DeepEqual(dynset.Exprs[1], want) { t.Errorf("dynset.Exprs[1] expr = %+v, wanted %+v", dynset.Exprs[1], want) } } func TestConfigureNATRedirect(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip nat []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add rule nat prerouting tcp dport 22 redirect to 2222 []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\xfc\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x00\x16\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x06\x00\x01\x00\x08\xae\x00\x00\x1c\x00\x01\x80\x0a\x00\x01\x00\x72\x65\x64\x69\x72\x00\x00\x00\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO }, // [ cmp eq reg 1 0x00001600 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x00, 0x16}, }, // [ immediate reg 1 0x0000ae08 ] &expr.Immediate{ Register: 1, Data: binaryutil.BigEndian.PutUint16(2222), }, // [ redir proto_min reg 1 ] &expr.Redir{ RegisterProtoMin: 1, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureJumpVerdict(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip nat []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add rule nat prerouting tcp dport 1-65535 jump istio_redirect []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x24\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x05\x0c\x00\x03\x80\x06\x00\x01\x00\x00\x01\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x03\x0c\x00\x03\x80\x06\x00\x01\x00\xff\xff\x00\x00\x44\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x30\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x02\x80\x20\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfd\x13\x00\x02\x00\x69\x73\x74\x69\x6f\x5f\x72\x65\x64\x69\x72\x65\x63\x74\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO }, // [ cmp gte reg 1 0x00000100 ] &expr.Cmp{ Op: expr.CmpOpGte, Register: 1, Data: []byte{0x00, 0x01}, }, // [ cmp lte reg 1 0x0000ffff ] &expr.Cmp{ Op: expr.CmpOpLte, Register: 1, Data: []byte{0xff, 0xff}, }, // [ immediate reg 0 jump -> istio_redirect ] &expr.Verdict{ Kind: expr.VerdictKind(unix.NFT_JUMP), Chain: "istio_redirect", }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureReturnVerdict(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip nat []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}' []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add rule nat prerouting meta skgid 1337 return []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x84\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x0b\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x39\x05\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() nat := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "nat", }) prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, Exprs: []expr.Any{ // [ meta load skgid => reg 1 ] &expr.Meta{Key: expr.MetaKeySKGID, Register: 1}, // [ cmp eq reg 1 0x00000539 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{0x39, 0x05, 0x00, 0x00}, }, // [ immediate reg 0 return ] &expr.Verdict{ Kind: expr.VerdictKind(unix.NFT_RETURN), }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureRangePort(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter forward tcp sport != 2024-2030 return // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain filter forward '{' type filter hook forward priority 0 \; '}' []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule filter forward tcp sport != 2024-2030 return []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xf4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x02\x3c\x00\x01\x80\x0a\x00\x01\x00\x72\x61\x6e\x67\x65\x00\x00\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x06\x00\x01\x00\x07\xe8\x00\x00\x0c\x00\x04\x80\x06\x00\x01\x00\x07\xee\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, // [ payload load 2b @ transport header + 0 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 0, // TODO Len: 2, // TODO }, // [ range neq reg 1 0x0000e807 0x0000ee07 ] &expr.Range{ Op: expr.CmpOpNeq, Register: 1, FromData: binaryutil.BigEndian.PutUint16(uint16(2024)), ToData: binaryutil.BigEndian.PutUint16(uint16(2030)), }, // [ immediate reg 0 return ] &expr.Verdict{ Kind: expr.VerdictKind(unix.NFT_RETURN), }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureRangeIPv4(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter forward ip saddr != 192.168.1.0-192.168.2.0 return // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain filter forward '{' type filter hook forward priority 0 \; '}' []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule filter forward ip saddr != 192.168.1.0-192.168.2.0 return []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xa4\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x3c\x00\x01\x80\x0a\x00\x01\x00\x72\x61\x6e\x67\x65\x00\x00\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\xc0\xa8\x01\x00\x0c\x00\x04\x80\x08\x00\x01\x00\xc0\xa8\x02\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ payload load 4b @ network header + 12 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 12, // TODO Len: 4, // TODO }, // [ range neq reg 1 0x0000e807 0x0000ee07 ] &expr.Range{ Op: expr.CmpOpNeq, Register: 1, FromData: net.ParseIP("192.168.1.0").To4(), ToData: net.ParseIP("192.168.2.0").To4(), }, // [ immediate reg 0 return ] &expr.Verdict{ Kind: expr.VerdictKind(unix.NFT_RETURN), }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestConfigureRangeIPv6(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule ip6 filter forward ip6 saddr != 2001:0001::1-2001:0002::1 return // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip6 filter []byte("\x0a\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip6 filter forward '{' type filter hook forward priority 0 \; '}' []byte("\x0a\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x03\x00\x66\x6f\x72\x77\x61\x72\x64\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule ip6 filter forward ip6 saddr != 2001:0001::1-2001:0002::1 return []byte("\x0a\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0c\x00\x02\x00\x66\x6f\x72\x77\x61\x72\x64\x00\xbc\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x10\x54\x00\x01\x80\x0a\x00\x01\x00\x72\x61\x6e\x67\x65\x00\x00\x00\x44\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x18\x00\x03\x80\x14\x00\x01\x00\x20\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x18\x00\x04\x80\x14\x00\x01\x00\x20\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv6, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "forward", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) ip1 := net.ParseIP("2001:0001::1").To16() ip2 := net.ParseIP("2001:0002::1").To16() c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ payload load 16b @ network header + 8 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 8, // TODO Len: 16, // TODO }, // [ range neq reg 1 0x01000120 0x00000000 0x00000000 0x01000000 0x02000120 0x00000000 0x00000000 0x01000000 ] &expr.Range{ Op: expr.CmpOpNeq, Register: 1, FromData: ip1, ToData: ip2, }, // [ immediate reg 0 return ] &expr.Verdict{ Kind: expr.VerdictKind(unix.NFT_RETURN), }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestSet4(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace-4.21 -f -v -x -s 2048 -etrace=sendto nft add table ip nat // // Until https://github.com/strace/strace/issues/100 is resolved, // you need to use strace 4.21 or apply the patch in the issue. // // Additional details can be obtained by specifying the --debug=all option // when calling nft(8). want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // table ip ipv4table { // set test-set { // type inet_service // flags constant // elements = { 12000, 12001, 12345, 12346 } // } // // chain ipv4chain-2 { // type nat hook prerouting priority dstnat; policy accept; // tcp dport @test-set // } // } []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x10\x00\x03\x00\x69\x70\x76\x34\x63\x68\x61\x69\x6e\x2d\x32\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\xff\xff\xff\x9c\x08\x00\x05\x00\x00\x00\x00\x01\x08\x00\x07\x00\x6e\x61\x74\x00"), []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x0d\x00\x02\x00\x74\x65\x73\x74\x2d\x73\x65\x74\x00\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x0d\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x0a\x00\x00\x00\x00\x01\x0c\x00\x09\x80\x08\x00\x01\x00\x00\x00\x00\x04\x0a\x00\x0d\x00\x00\x04\x02\x00\x00\x00\x00\x00"), []byte("\x02\x00\x00\x00\x0d\x00\x02\x00\x74\x65\x73\x74\x2d\x73\x65\x74\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x44\x00\x03\x80\x10\x00\x01\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x2e\xe0\x00\x00\x10\x00\x02\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x2e\xe1\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x30\x39\x00\x00\x10\x00\x04\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x30\x3a\x00\x00"), []byte("\x02\x00\x00\x00\x0e\x00\x01\x00\x69\x70\x76\x34\x74\x61\x62\x6c\x65\x00\x00\x00\x10\x00\x02\x00\x69\x70\x76\x34\x63\x68\x61\x69\x6e\x2d\x32\x00\xbc\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x34\x00\x01\x80\x0b\x00\x01\x00\x6c\x6f\x6f\x6b\x75\x70\x00\x00\x24\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x01\x0d\x00\x01\x00\x74\x65\x73\x74\x2d\x73\x65\x74\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } tbl := &nftables.Table{ Name: "ipv4table", Family: nftables.TableFamilyIPv4, } defPol := nftables.ChainPolicyAccept ch := &nftables.Chain{ Name: "ipv4chain-2", Table: tbl, Type: nftables.ChainTypeNAT, Priority: nftables.ChainPriorityNATDest, Hooknum: nftables.ChainHookPrerouting, Policy: &defPol, } set := nftables.Set{ Anonymous: false, Constant: true, Name: "test-set", ID: uint32(1), // rand.Intn(0xffff)), Table: tbl, KeyType: nftables.TypeInetService, } c.AddTable(tbl) c.AddChain(ch) re := []expr.Any{} re = append(re, &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}) re = append(re, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }) re = append(re, &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, // Offset for a transport protocol header Len: 2, // 2 bytes for port }) re = append(re, &expr.Lookup{ SourceRegister: 1, Invert: false, SetID: set.ID, SetName: set.Name, }) ports := []uint16{12000, 12001, 12345, 12346} setElements := make([]nftables.SetElement, len(ports)) for i := 0; i < len(ports); i++ { setElements[i].Key = binaryutil.BigEndian.PutUint16(ports[i]) } if err := c.AddSet(&set, setElements); err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: tbl, Chain: ch, Exprs: re, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestMasq(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte masqExprs []expr.Any }{ { name: "Masquerada", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain ip protocol tcp masquerade []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x78\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x04\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"), // batch end []byte("\x00\x00\x00\x0a"), }, masqExprs: []expr.Any{ &expr.Masq{}, }, }, { name: "Masquerada with flags", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain ip protocol tcp masquerade random,fully-random,persistent []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x80\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x04\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x1c\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x1c"), // batch end []byte("\x00\x00\x00\x0a"), }, masqExprs: []expr.Any{ &expr.Masq{Random: true, FullyRandom: true, Persistent: true, ToPorts: false}, }, }, { name: "Masquerada with 1 port", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain ip protocol tcp masquerade to :1024 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\xac\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x04\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x04\x00\x00\x00\x1c\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x0c\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), }, masqExprs: []expr.Any{ &expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint32(uint32(1024) << 16)}, &expr.Masq{ToPorts: true, RegProtoMin: 1}, }, }, { name: "Masquerada with port range", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain ip protocol tcp masquerade to :1024-2044 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\xe0\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x04\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x04\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x08\x00\x01\x00\x07\xfc\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x02"), // batch end []byte("\x00\x00\x00\x0a"), }, masqExprs: []expr.Any{ &expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint32(uint32(1024) << 16)}, &expr.Immediate{Register: 2, Data: binaryutil.BigEndian.PutUint32(uint32(2044) << 16)}, &expr.Masq{ToPorts: true, RegProtoMin: 1, RegProtoMax: 2}, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter chain := c.AddChain(tt.chain) exprs := []expr.Any{ // [ payload load 1b @ network header + 9 => reg 1 ] &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1, }, // [ cmp eq reg 1 0x00000006 ] &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, } exprs = append(exprs, tt.masqExprs...) c.AddRule(&nftables.Rule{ Table: filter, Chain: chain, Exprs: exprs, }) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestReject(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte rejectExprs []expr.Any }{ { name: "Reject", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain reject []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x28\x00\x04\x80\x24\x00\x01\x80\x0b\x00\x01\x00\x72\x65\x6a\x65\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, rejectExprs: []expr.Any{ &expr.Reject{}, }, }, { name: "Reject with tcp reset", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain reject with tcp reset []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x28\x00\x04\x80\x24\x00\x01\x80\x0b\x00\x01\x00\x72\x65\x6a\x65\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x05\x00\x02\x00\x01\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, rejectExprs: []expr.Any{ &expr.Reject{Type: unix.NFT_REJECT_TCP_RST, Code: unix.NFT_REJECT_TCP_RST}, }, }, { name: "Reject with icmp type host-unreachable", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain reject with icmp type host-unreachable []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x28\x00\x04\x80\x24\x00\x01\x80\x0b\x00\x01\x00\x72\x65\x6a\x65\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, rejectExprs: []expr.Any{ &expr.Reject{Type: unix.NFT_REJECT_ICMP_UNREACH, Code: unix.NFT_REJECT_ICMP_UNREACH}, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter chain := c.AddChain(tt.chain) c.AddRule(&nftables.Rule{ Table: filter, Chain: chain, Exprs: tt.rejectExprs, }) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestFib(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte fibExprs []expr.Any }{ { name: "fib saddr type local", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain fib saddr type local []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x2c\x00\x04\x80\x28\x00\x01\x80\x08\x00\x01\x00\x66\x69\x62\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x03"), // batch end []byte("\x00\x00\x00\x0a"), }, fibExprs: []expr.Any{ &expr.Fib{ Register: 1, FlagSADDR: true, ResultADDRTYPE: true, }, }, }, { name: "fib daddr type broadcast", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain fib daddr type broadcast []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x2c\x00\x04\x80\x28\x00\x01\x80\x08\x00\x01\x00\x66\x69\x62\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x03"), // batch end []byte("\x00\x00\x00\x0a"), }, fibExprs: []expr.Any{ &expr.Fib{ Register: 1, FlagDADDR: true, ResultADDRTYPE: true, }, }, }, { name: "fib saddr . iif oif missing", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain fib saddr . iif oif missing []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x2c\x00\x04\x80\x28\x00\x01\x80\x08\x00\x01\x00\x66\x69\x62\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x09\x08\x00\x02\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), }, fibExprs: []expr.Any{ &expr.Fib{ Register: 1, FlagSADDR: true, FlagIIF: true, ResultOIF: true, }, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter chain := c.AddChain(tt.chain) c.AddRule(&nftables.Rule{ Table: filter, Chain: chain, Exprs: tt.fibExprs, }) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestNumgen(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte numgenExprs []expr.Any }{ { name: "numgen random", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain numgen random mod 1 offset 0 vmap { 0 : drop } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x38\x00\x04\x80\x34\x00\x01\x80\x0b\x00\x01\x00\x6e\x75\x6d\x67\x65\x6e\x00\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x04\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, numgenExprs: []expr.Any{ &expr.Numgen{ Register: 1, Type: unix.NFT_NG_RANDOM, Modulus: 0x1, Offset: 0, }, }, }, { name: "numgen incremental", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add rule ip filter base-chain numgen inc mod 1 offset 0 vmap { 0 : drop } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x38\x00\x04\x80\x34\x00\x01\x80\x0b\x00\x01\x00\x6e\x75\x6d\x67\x65\x6e\x00\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, numgenExprs: []expr.Any{ &expr.Numgen{ Register: 1, Type: unix.NFT_NG_INCREMENTAL, Modulus: 0x1, Offset: 0, }, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter chain := c.AddChain(tt.chain) c.AddRule(&nftables.Rule{ Table: filter, Chain: chain, Exprs: tt.numgenExprs, }) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestMap(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte set nftables.Set element []nftables.SetElement }{ { name: "map inet_service: inet_service 1 element", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add map ip filter test-map { type inet_service: inet_service\; elements={ 22: 1024 } \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0d\x00\x02\x00\x74\x65\x73\x74\x2d\x6d\x61\x70\x00\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x0d\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\x00\x00\x00\x0d\x08\x00\x07\x00\x00\x00\x00\x02"), []byte("\x02\x00\x00\x00\x0d\x00\x02\x00\x74\x65\x73\x74\x2d\x6d\x61\x70\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x20\x00\x03\x80\x1c\x00\x01\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x00\x16\x00\x00\x0c\x00\x02\x80\x06\x00\x01\x00\x04\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, set: nftables.Set{ Name: "test-map", ID: uint32(1), KeyType: nftables.TypeInetService, DataType: nftables.TypeInetService, IsMap: true, }, element: []nftables.SetElement{ { Key: binaryutil.BigEndian.PutUint16(uint16(22)), Val: binaryutil.BigEndian.PutUint16(uint16(1024)), }, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter c.AddChain(tt.chain) tt.set.Table = filter c.AddSet(&tt.set, tt.element) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestVmap(t *testing.T) { tests := []struct { name string chain *nftables.Chain want [][]byte set nftables.Set element []nftables.SetElement }{ { name: "map inet_service: drop verdict", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add map ip filter test-vmap { type inet_service: verdict\; elements={ 22: drop } \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0e\x00\x02\x00\x74\x65\x73\x74\x2d\x76\x6d\x61\x70\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x0d\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\xff\xff\xff\x00\x08\x00\x07\x00\x00\x00\x00\x00"), []byte("\x02\x00\x00\x00\x0e\x00\x02\x00\x74\x65\x73\x74\x2d\x76\x6d\x61\x70\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x24\x00\x03\x80\x20\x00\x01\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x00\x16\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, set: nftables.Set{ Name: "test-vmap", ID: uint32(1), KeyType: nftables.TypeInetService, DataType: nftables.TypeVerdict, IsMap: true, }, element: []nftables.SetElement{ { Key: binaryutil.BigEndian.PutUint16(uint16(22)), VerdictData: &expr.Verdict{ Kind: expr.VerdictDrop, }, }, }, }, { name: "map inet_service: jump to chain verdict", chain: &nftables.Chain{ Name: "base-chain", }, want: [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // nft add map ip filter test-vmap { type inet_service: verdict\; elements={ 22: jump fake-chain } \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0e\x00\x02\x00\x74\x65\x73\x74\x2d\x76\x6d\x61\x70\x00\x00\x00\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x0d\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x0a\x00\x00\x00\x00\x01\x08\x00\x06\x00\xff\xff\xff\x00\x08\x00\x07\x00\x00\x00\x00\x00"), []byte("\x02\x00\x00\x00\x0e\x00\x02\x00\x74\x65\x73\x74\x2d\x76\x6d\x61\x70\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x01\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x34\x00\x03\x80\x30\x00\x01\x80\x0c\x00\x01\x80\x06\x00\x01\x00\x00\x16\x00\x00\x20\x00\x02\x80\x1c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfd\x0f\x00\x02\x00\x66\x61\x6b\x65\x2d\x63\x68\x61\x69\x6e\x00\x00"), // batch end []byte("\x00\x00\x00\x0a"), }, set: nftables.Set{ Name: "test-vmap", ID: uint32(1), KeyType: nftables.TypeInetService, DataType: nftables.TypeVerdict, IsMap: true, }, element: []nftables.SetElement{ { Key: binaryutil.BigEndian.PutUint16(uint16(22)), VerdictData: &expr.Verdict{ Kind: unix.NFT_JUMP, Chain: "fake-chain", }, }, }, }, } for _, tt := range tests { c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(tt.want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, tt.want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(tt.want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) tt.chain.Table = filter c.AddChain(tt.chain) tt.set.Table = filter c.AddSet(&tt.set, tt.element) if err := c.Flush(); err != nil { t.Fatalf("Test \"%s\" failed with error: %+v", tt.name, err) } } } func TestJHash(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2 // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule filter base_chain mark set jhash ip saddr mod 2 seed 0xfeedcafe offset 1 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\xa8\x00\x04\x80\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x4c\x00\x01\x80\x09\x00\x01\x00\x68\x61\x73\x68\x00\x00\x00\x00\x3c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x05\x00\xfe\xed\xca\xfe\x08\x00\x06\x00\x00\x00\x00\x01\x08\x00\x07\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x03\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ // [ payload load 4b @ network header + 12 => reg 2 ] &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, Offset: 12, Len: 4, }, // [ hash reg 1 = jhash(reg 2, 4, 0xfeedcafe) % mod 2 offset 1 ] &expr.Hash{ SourceRegister: 2, DestRegister: 1, Length: 4, Modulus: 2, Seed: 4276996862, Offset: 1, Type: expr.HashTypeJenkins, }, // [ meta set mark with reg 1 ] &expr.Meta{ Key: expr.MetaKeyMARK, SourceRegister: true, Register: 1, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestDup(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2 // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip mangle []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip mangle base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule mangle base-chain dup to 127.0.0.50 device lo []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x7c\x00\x04\x80\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x7f\x00\x00\x32\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x08\x00\x01\x00\x01\x00\x00\x00\x20\x00\x01\x80\x08\x00\x01\x00\x64\x75\x70\x00\x14\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() mangle := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "mangle", }) prerouting := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: mangle, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) lo, err := net.InterfaceByName("lo") if err != nil { t.Fatal(err) } c.AddRule(&nftables.Rule{ Table: mangle, Chain: prerouting, Exprs: []expr.Any{ // [ immediate reg 1 0x3200007f ] &expr.Immediate{ Register: 1, Data: net.ParseIP("127.0.0.50").To4(), }, // [ immediate reg 2 0x00000001 ] &expr.Immediate{ Register: 2, Data: binaryutil.NativeEndian.PutUint32(uint32(lo.Index)), }, // [ dup sreg_addr 1 sreg_dev 2 ] &expr.Dup{ RegAddr: 1, RegDev: 2, IsRegDevSet: true, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestDupWoDev(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2 // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip mangle []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip mangle base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule mangle base-chain dup to 127.0.0.50 []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x48\x00\x04\x80\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x7f\x00\x00\x32\x18\x00\x01\x80\x08\x00\x01\x00\x64\x75\x70\x00\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() mangle := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "mangle", }) prerouting := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: mangle, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: mangle, Chain: prerouting, Exprs: []expr.Any{ // [ immediate reg 1 0x3200007f ] &expr.Immediate{ Register: 1, Data: net.ParseIP("127.0.0.50").To4(), }, // [ dup sreg_addr 1 sreg_dev 2 ] &expr.Dup{ RegAddr: 1, IsRegDevSet: false, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestNotrack(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2 // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule filter base_chain notrack []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x10\x00\x01\x80\x0c\x00\x01\x00\x6e\x6f\x74\x72\x61\x63\x6b\x00"), // batch end []byte("\x00\x00\x00\x0a"), } nftest.MatchRulesetBytes(t, func(c *nftables.Conn) { filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) prerouting := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: prerouting, Exprs: []expr.Any{ // [ notrack ] &expr.Notrack{}, }, }) }, want) } func TestQuota(t *testing.T) { want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter regular-chain []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x12\x00\x03\x00\x72\x65\x67\x75\x6c\x61\x72\x2d\x63\x68\x61\x69\x6e\x00\x00\x00"), // nft add rule ip filter regular-chain quota over 6 bytes []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x12\x00\x02\x00\x72\x65\x67\x75\x6c\x61\x72\x2d\x63\x68\x61\x69\x6e\x00\x00\x00\x38\x00\x04\x80\x34\x00\x01\x80\x0a\x00\x01\x00\x71\x75\x6f\x74\x61\x00\x00\x00\x24\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x06\x0c\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x01"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want[idx]) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } got := b if !bytes.Equal(got, want[idx]) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want[idx]))) } } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) output := c.AddChain(&nftables.Chain{ Name: "regular-chain", Table: filter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: output, Exprs: []expr.Any{ // [ quota bytes 6 consumed 0 flags 1 ] &expr.Quota{ Bytes: 6, Consumed: 0, Over: true, }, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestStatelessNAT(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2 // // The nft(8) command sequence was taken from: // https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp want := [][]byte{ // batch begin []byte("\x00\x00\x00\x0a"), // nft flush ruleset []byte("\x00\x00\x00\x00"), // nft add table ip filter []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), // nft add chain ip filter base-chain { type filter hook prerouting priority 0 \; } []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x03\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), // nft add rule ip filter base-chain ip daddr set 192.168.1.1 notrack []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x66\x69\x6c\x74\x65\x72\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x63\x68\x61\x69\x6e\x00\x00\x8c\x00\x04\x80\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x01\x01\x4c\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x3c\x00\x02\x80\x08\x00\x05\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x10\x08\x00\x04\x00\x00\x00\x00\x04\x08\x00\x06\x00\x00\x00\x00\x01\x08\x00\x07\x00\x00\x00\x00\x0a\x08\x00\x08\x00\x00\x00\x00\x01\x10\x00\x01\x80\x0c\x00\x01\x00\x6e\x6f\x74\x72\x61\x63\x6b\x00"), // batch end []byte("\x00\x00\x00\x0a"), } c, err := nftables.New(nftables.WithTestDial( func(req []netlink.Message) ([]netlink.Message, error) { for idx, msg := range req { b, err := msg.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) < 16 { continue } b = b[16:] if len(want) == 0 { t.Errorf("no want entry for message %d: %x", idx, b) continue } if got, want := b, want[0]; !bytes.Equal(got, want) { t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) } want = want[1:] } return req, nil })) if err != nil { t.Fatal(err) } c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) forward := c.AddChain(&nftables.Chain{ Name: "base-chain", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, }) c.AddRule(&nftables.Rule{ Table: filter, Chain: forward, Exprs: []expr.Any{ &expr.Immediate{ Register: 1, Data: net.ParseIP("192.168.1.1").To4(), }, // [ payload write reg 1 => 4b @ network header + 16 csum_type 1 csum_off 10 csum_flags 0x1 ] &expr.Payload{ OperationType: expr.PayloadWrite, SourceRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 16, Len: 4, CsumType: expr.CsumTypeInet, CsumOffset: 10, CsumFlags: unix.NFT_PAYLOAD_L4CSUM_PSEUDOHDR, }, // [ notrack ] &expr.Notrack{}, }, }) if err := c.Flush(); err != nil { t.Fatal(err) } } func TestGetRulesObjref(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() table := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) chain := c.AddChain(&nftables.Chain{ Name: "forward", Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) counterName := "fwded1" c.AddObj(&nftables.CounterObj{ Table: table, Name: counterName, Bytes: 1, Packets: 1, }) counterRule := c.AddRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Objref{ Type: 1, Name: counterName, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(table, chain) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } objref, objrefOk := rules[0].Exprs[0].(*expr.Objref) if !objrefOk { t.Fatalf("Exprs[0] is type %T, want *expr.Objref", rules[0].Exprs[0]) } if want := counterRule.Exprs[0]; !reflect.DeepEqual(objref, want) { t.Errorf("objref expr = %+v, wanted %+v", objref, want) } } func TestAddQuotaObj(t *testing.T) { conn, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) conn.FlushRuleset() defer conn.FlushRuleset() table := &nftables.Table{ Name: "quota_demo", Family: nftables.TableFamilyIPv4, } tr := conn.AddTable(table) c := &nftables.Chain{ Name: "filter", Table: table, } conn.AddChain(c) o := &nftables.QuotaObj{ Table: tr, Name: "q_test", Bytes: 0x06400000, Consumed: 0, Over: true, } conn.AddObj(o) if err := conn.Flush(); err != nil { t.Errorf("conn.Flush() failed: %v", err) } obj, err := conn.GetObj(&nftables.QuotaObj{ Table: table, Name: "q_test", }) if err != nil { t.Fatalf("conn.GetObj() failed: %v", err) } if got, want := len(obj), 1; got != want { t.Fatalf("unexpected object list length: got %d, want %d", got, want) } o1, ok := obj[0].(*nftables.QuotaObj) if !ok { t.Fatalf("unexpected type: got %T, want *QuotaObj", obj[0]) } if got, want := o1.Name, o.Name; got != want { t.Fatalf("quota name mismatch: got %s, want %s", got, want) } if got, want := o1.Bytes, o.Bytes; got != want { t.Fatalf("quota bytes mismatch: got %d, want %d", got, want) } if got, want := o1.Consumed, o.Consumed; got != want { t.Fatalf("quota consumed mismatch: got %d, want %d", got, want) } if got, want := o1.Over, o.Over; got != want { t.Fatalf("quota over mismatch: got %v, want %v", got, want) } } func TestAddQuotaObjRef(t *testing.T) { conn, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) conn.FlushRuleset() defer conn.FlushRuleset() table := &nftables.Table{ Name: "quota_demo", Family: nftables.TableFamilyIPv4, } tr := conn.AddTable(table) c := &nftables.Chain{ Name: "filter", Table: table, } conn.AddChain(c) o := &nftables.QuotaObj{ Table: tr, Name: "q_test", Bytes: 0x06400000, Consumed: 0, Over: true, } conn.AddObj(o) r := &nftables.Rule{ Table: table, Chain: c, Exprs: []expr.Any{ &expr.Objref{ Type: 2, Name: "q_test", }, }, } conn.AddRule(r) if err := conn.Flush(); err != nil { t.Fatalf("failed to flush: %v", err) } rules, err := conn.GetRules(table, c) if err != nil { t.Fatalf("failed to get rules: %v", err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } objref, ok := rules[0].Exprs[0].(*expr.Objref) if !ok { t.Fatalf("Exprs[0] is type %T, want *expr.Objref", rules[0].Exprs[0]) } if want := r.Exprs[0]; !reflect.DeepEqual(objref, want) { t.Errorf("objref expr = %+v, wanted %+v", objref, want) } } func TestDeleteQuotaObj(t *testing.T) { conn, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) conn.FlushRuleset() defer conn.FlushRuleset() table := &nftables.Table{ Name: "quota_demo", Family: nftables.TableFamilyIPv4, } tr := conn.AddTable(table) c := &nftables.Chain{ Name: "filter", Table: table, } conn.AddChain(c) o := &nftables.QuotaObj{ Table: tr, Name: "q_test", Bytes: 0x06400000, Consumed: 0, Over: true, } conn.AddObj(o) if err := conn.Flush(); err != nil { t.Fatalf("conn.Flush() failed: %v", err) } obj, err := conn.GetObj(&nftables.QuotaObj{ Table: table, Name: "q_test", }) if err != nil { t.Fatalf("conn.GetObj() failed: %v", err) } if got, want := len(obj), 1; got != want { t.Fatalf("unexpected number of objects: got %d, want %d", got, want) } if got, want := obj[0], o; !reflect.DeepEqual(got, want) { t.Errorf("got = %+v, want = %+v", got, want) } conn.DeleteObject(&nftables.QuotaObj{ Table: tr, Name: "q_test", }) if err := conn.Flush(); err != nil { t.Fatalf("conn.Flush() failed: %v", err) } obj, err = conn.GetObj(&nftables.QuotaObj{ Table: table, Name: "q_test", }) if err != nil { t.Fatalf("conn.GetObj() failed: %v", err) } if got, want := len(obj), 0; got != want { t.Fatalf("unexpected object list length: got %d, want %d", got, want) } } func TestGetRulesQueue(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() table := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) chain := c.AddChain(&nftables.Chain{ Name: "forward", Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, }) queueRule := c.AddRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Queue{ Num: 1000, Flag: expr.QueueFlagBypass, }, }, }) if err := c.Flush(); err != nil { t.Errorf("c.Flush() failed: %v", err) } rules, err := c.GetRules(table, chain) if err != nil { t.Fatal(err) } if got, want := len(rules), 1; got != want { t.Fatalf("unexpected number of rules: got %d, want %d", got, want) } if got, want := len(rules[0].Exprs), 1; got != want { t.Fatalf("unexpected number of exprs: got %d, want %d", got, want) } queueExpr, ok := rules[0].Exprs[0].(*expr.Queue) if !ok { t.Fatalf("Exprs[0] is type %T, want *expr.Queue", rules[0].Exprs[0]) } if want := queueRule.Exprs[0]; !reflect.DeepEqual(queueExpr, want) { t.Errorf("queue expr = %+v, wanted %+v", queueExpr, want) } } func TestNftablesCompat(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. c, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) // Clear all rules at the beginning + end of the test. c.FlushRuleset() defer c.FlushRuleset() filter := c.AddTable(&nftables.Table{ Family: nftables.TableFamilyIPv4, Name: "filter", }) input := c.AddChain(&nftables.Chain{ Name: "input", Table: filter, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, }) // -tcp --dport 0:65534 --sport 0:65534 tcpMatch := &expr.Match{ Name: "tcp", Info: &xt.Tcp{ SrcPorts: [2]uint16{0, 65534}, DstPorts: [2]uint16{0, 65534}, }, } // -udp --dport 0:65534 --sport 0:65534 udpMatch := &expr.Match{ Name: "udp", Info: &xt.Udp{ SrcPorts: [2]uint16{0, 65534}, DstPorts: [2]uint16{0, 65534}, }, } // - j TCPMSS --set-mss 1460 mess := xt.Unknown([]byte{1460 & 0xff, (1460 >> 8) & 0xff}) tcpMessTarget := &expr.Target{ Name: "TCPMSS", Info: &mess, } // -m state --state ESTABLISHED ctMatch := &expr.Match{ Name: "conntrack", Rev: 1, Info: &xt.ConntrackMtinfo1{ ConntrackMtinfoBase: xt.ConntrackMtinfoBase{ MatchFlags: 0x2001, }, StateMask: 0x02, }, } // -p tcp --dport --dport 0:65534 --sport 0:65534 -m state --state ESTABLISHED -j TCPMSS --set-mss 1460 c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ tcpMatch, ctMatch, tcpMessTarget, }, }) if err := c.Flush(); err != nil { t.Fatalf("add rule fail %#v", err) } c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ udpMatch, &expr.Verdict{ Kind: expr.VerdictAccept, }, }, }) if err := c.Flush(); err != nil { t.Fatalf("add rule %#v fail", err) } // -m state --state ESTABLISHED -j ACCEPT c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ ctMatch, &expr.Verdict{ Kind: expr.VerdictAccept, }, }, }) if err := c.Flush(); err != nil { t.Fatalf("add rule %#v fail", err) } // -p udp --dport --dport 0:65534 --sport 0:65534 -m state --state ESTABLISHED -j ACCEPT c.AddRule(&nftables.Rule{ Table: filter, Chain: input, Exprs: []expr.Any{ tcpMatch, udpMatch, ctMatch, &expr.Verdict{ Kind: expr.VerdictAccept, }, }, }) if err := c.Flush(); err == nil { t.Fatalf("compat policy should conflict and err should not be err") } } nftables-0.2.0/obj.go000066400000000000000000000143451457332137300144270ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "encoding/binary" "fmt" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) var ( newObjHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ) delObjHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ) ) // Obj represents a netfilter stateful object. See also // https://wiki.nftables.org/wiki-nftables/index.php/Stateful_objects type Obj interface { table() *Table family() TableFamily unmarshal(*netlink.AttributeDecoder) error marshal(data bool) ([]byte, error) } // AddObject adds the specified Obj. Alias of AddObj. func (cc *Conn) AddObject(o Obj) Obj { return cc.AddObj(o) } // AddObj adds the specified Obj. See also // https://wiki.nftables.org/wiki-nftables/index.php/Stateful_objects func (cc *Conn) AddObj(o Obj) Obj { cc.mu.Lock() defer cc.mu.Unlock() data, err := o.marshal(true) if err != nil { cc.setErr(err) return nil } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(o.family()), 0), data...), }) return o } // DeleteObject deletes the specified Obj func (cc *Conn) DeleteObject(o Obj) { cc.mu.Lock() defer cc.mu.Unlock() data, err := o.marshal(false) if err != nil { cc.setErr(err) return } data = append(data, cc.marshalAttr([]netlink.Attribute{{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA}})...) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(o.family()), 0), data...), }) } // GetObj is a legacy method that return all Obj that belongs // to the same table as the given one func (cc *Conn) GetObj(o Obj) ([]Obj, error) { return cc.getObj(nil, o.table(), unix.NFT_MSG_GETOBJ) } // GetObjReset is a legacy method that reset all Obj that belongs // the same table as the given one func (cc *Conn) GetObjReset(o Obj) ([]Obj, error) { return cc.getObj(nil, o.table(), unix.NFT_MSG_GETOBJ_RESET) } // GetObject gets the specified Object func (cc *Conn) GetObject(o Obj) (Obj, error) { objs, err := cc.getObj(o, o.table(), unix.NFT_MSG_GETOBJ) if len(objs) == 0 { return nil, err } return objs[0], err } // GetObjects get all the Obj that belongs to the given table func (cc *Conn) GetObjects(t *Table) ([]Obj, error) { return cc.getObj(nil, t, unix.NFT_MSG_GETOBJ) } // ResetObject reset the given Obj func (cc *Conn) ResetObject(o Obj) (Obj, error) { objs, err := cc.getObj(o, o.table(), unix.NFT_MSG_GETOBJ_RESET) if len(objs) == 0 { return nil, err } return objs[0], err } // ResetObjects reset all the Obj that belongs to the given table func (cc *Conn) ResetObjects(t *Table) ([]Obj, error) { return cc.getObj(nil, t, unix.NFT_MSG_GETOBJ_RESET) } func objFromMsg(msg netlink.Message) (Obj, error) { if got, want1, want2 := msg.Header.Type, newObjHeaderType, delObjHeaderType; got != want1 && got != want2 { return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2) } ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian var ( table *Table name string objectType uint32 ) const NFT_OBJECT_COUNTER = 1 // TODO: get into x/sys/unix for ad.Next() { switch ad.Type() { case unix.NFTA_OBJ_TABLE: table = &Table{Name: ad.String(), Family: TableFamily(msg.Data[0])} case unix.NFTA_OBJ_NAME: name = ad.String() case unix.NFTA_OBJ_TYPE: objectType = ad.Uint32() case unix.NFTA_OBJ_DATA: switch objectType { case NFT_OBJECT_COUNTER: o := CounterObj{ Table: table, Name: name, } ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian return o.unmarshal(ad) }) return &o, ad.Err() case NFT_OBJECT_QUOTA: o := QuotaObj{ Table: table, Name: name, } ad.Do(func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return err } ad.ByteOrder = binary.BigEndian return o.unmarshal(ad) }) return &o, ad.Err() } } } if err := ad.Err(); err != nil { return nil, err } return nil, fmt.Errorf("malformed stateful object") } func (cc *Conn) getObj(o Obj, t *Table, msgType uint16) ([]Obj, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() var data []byte var flags netlink.HeaderFlags if o != nil { data, err = o.marshal(false) } else { flags = netlink.Dump data, err = netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")}, }) } if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | msgType), Flags: netlink.Request | netlink.Acknowledge | flags, }, Data: append(extraHeader(uint8(t.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %v", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %v", err) } var objs []Obj for _, msg := range reply { o, err := objFromMsg(msg) if err != nil { return nil, err } objs = append(objs, o) } return objs, nil } nftables-0.2.0/quota.go000066400000000000000000000042641457332137300150050ustar00rootroot00000000000000// Copyright 2023 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) const ( NFTA_OBJ_USERDATA = 8 NFT_OBJECT_QUOTA = 2 ) type QuotaObj struct { Table *Table Name string Bytes uint64 Consumed uint64 Over bool } func (q *QuotaObj) unmarshal(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.NFTA_QUOTA_BYTES: q.Bytes = ad.Uint64() case unix.NFTA_QUOTA_CONSUMED: q.Consumed = ad.Uint64() case unix.NFTA_QUOTA_FLAGS: q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1 } } return nil } func (q *QuotaObj) marshal(data bool) ([]byte, error) { flags := uint32(0) if q.Over { flags = unix.NFT_QUOTA_F_INV } obj, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_QUOTA_BYTES, Data: binaryutil.BigEndian.PutUint64(q.Bytes)}, {Type: unix.NFTA_QUOTA_CONSUMED, Data: binaryutil.BigEndian.PutUint64(q.Consumed)}, {Type: unix.NFTA_QUOTA_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, }) if err != nil { return nil, err } attrs := []netlink.Attribute{ {Type: unix.NFTA_OBJ_TABLE, Data: []byte(q.Table.Name + "\x00")}, {Type: unix.NFTA_OBJ_NAME, Data: []byte(q.Name + "\x00")}, {Type: unix.NFTA_OBJ_TYPE, Data: binaryutil.BigEndian.PutUint32(NFT_OBJECT_QUOTA)}, } if data { attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: obj}) } return netlink.MarshalAttributes(attrs) } func (q *QuotaObj) table() *Table { return q.Table } func (q *QuotaObj) family() TableFamily { return q.Table.Family } nftables-0.2.0/rule.go000066400000000000000000000173501457332137300146230ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nftables import ( "encoding/binary" "fmt" "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "github.com/google/nftables/internal/parseexprfunc" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) var ( newRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWRULE) delRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE) ) type ruleOperation uint32 // Possible PayloadOperationType values. const ( operationAdd ruleOperation = iota operationInsert operationReplace ) // A Rule does something with a packet. See also // https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management type Rule struct { Table *Table Chain *Chain Position uint64 Handle uint64 // The list of possible flags are specified by nftnl_rule_attr, see // https://git.netfilter.org/libnftnl/tree/include/libnftnl/rule.h#n21 // Current nftables go implementation supports only // NFTNL_RULE_POSITION flag for setting rule at position 0 Flags uint32 Exprs []expr.Any UserData []byte } // GetRule returns the rules in the specified table and chain. // // Deprecated: use GetRules instead. func (cc *Conn) GetRule(t *Table, c *Chain) ([]*Rule, error) { return cc.GetRules(t, c) } // GetRules returns the rules in the specified table and chain. func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")}, {Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")}, }) if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETRULE), Flags: netlink.Request | netlink.Acknowledge | netlink.Dump | unix.NLM_F_ECHO, }, Data: append(extraHeader(uint8(t.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %v", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %v", err) } var rules []*Rule for _, msg := range reply { r, err := ruleFromMsg(t.Family, msg) if err != nil { return nil, err } rules = append(rules, r) } return rules, nil } // AddRule adds the specified Rule func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule { cc.mu.Lock() defer cc.mu.Unlock() exprAttrs := make([]netlink.Attribute, len(r.Exprs)) for idx, expr := range r.Exprs { exprAttrs[idx] = netlink.Attribute{ Type: unix.NLA_F_NESTED | unix.NFTA_LIST_ELEM, Data: cc.marshalExpr(byte(r.Table.Family), expr), } } data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_RULE_TABLE, Data: []byte(r.Table.Name + "\x00")}, {Type: unix.NFTA_RULE_CHAIN, Data: []byte(r.Chain.Name + "\x00")}, }) if r.Handle != 0 { data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)}, })...) } data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NLA_F_NESTED | unix.NFTA_RULE_EXPRESSIONS, Data: cc.marshalAttr(exprAttrs)}, })...) if compatPolicy, err := getCompatPolicy(r.Exprs); err != nil { cc.setErr(err) } else if compatPolicy != nil { data = append(data, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NLA_F_NESTED | unix.NFTA_RULE_COMPAT, Data: cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_RULE_COMPAT_PROTO, Data: binaryutil.BigEndian.PutUint32(compatPolicy.Proto)}, {Type: unix.NFTA_RULE_COMPAT_FLAGS, Data: binaryutil.BigEndian.PutUint32(compatPolicy.Flag & nft_RULE_COMPAT_F_MASK)}, })}, })...) } msgData := []byte{} msgData = append(msgData, data...) var flags netlink.HeaderFlags if r.UserData != nil { msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_RULE_USERDATA, Data: r.UserData}, })...) } switch op { case operationAdd: flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO | unix.NLM_F_APPEND case operationInsert: flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO case operationReplace: flags = netlink.Request | netlink.Acknowledge | netlink.Replace | unix.NLM_F_ECHO | unix.NLM_F_REPLACE } if r.Position != 0 || (r.Flags&(1< 32/SetConcatTypeBits { return SetDatatype{}, ErrTooManyTypes } var magic, bytes uint32 names := make([]string, len(types)) for i, t := range types { bytes += t.Bytes // concatenated types pad the length to multiples of the register size (4 bytes) // see https://git.netfilter.org/nftables/tree/src/datatype.c?id=488356b895024d0944b20feb1f930558726e0877#n1162 if t.Bytes%4 != 0 { bytes += 4 - (t.Bytes % 4) } names[i] = t.Name magic <<= SetConcatTypeBits magic |= t.nftMagic & SetConcatTypeMask } return SetDatatype{Name: strings.Join(names, " . "), Bytes: bytes, nftMagic: magic}, nil } // ConcatSetTypeElements uses the ConcatSetType name to calculate and return // a list of base types which were used to construct the concatenated type func ConcatSetTypeElements(t SetDatatype) []SetDatatype { names := strings.Split(t.Name, " . ") types := make([]SetDatatype, len(names)) for i, n := range names { types[i] = nftDatatypesByName[n] } return types } // Set represents an nftables set. Anonymous sets are only valid within the // context of a single batch. type Set struct { Table *Table ID uint32 Name string Anonymous bool Constant bool Interval bool IsMap bool HasTimeout bool Counter bool // Can be updated per evaluation path, per `nft list ruleset` // indicates that set contains "flags dynamic" // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n298 Dynamic bool // Indicates that the set contains a concatenation // https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306 Concatenation bool Timeout time.Duration KeyType SetDatatype DataType SetDatatype // Either host (binaryutil.NativeEndian) or big (binaryutil.BigEndian) endian as per // https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109 KeyByteOrder binaryutil.ByteOrder } // SetElement represents a data point within a set. type SetElement struct { Key []byte Val []byte // Field used for definition of ending interval value in concatenated types // https://git.netfilter.org/libnftnl/tree/include/set_elem.h?id=e2514c0eff4da7e8e0aabd410f7b7d0b7564c880#n11 KeyEnd []byte IntervalEnd bool // To support vmap, a caller must be able to pass Verdict type of data. // If IsMap is true and VerdictData is not nil, then Val of SetElement will be ignored // and VerdictData will be wrapped into Attribute data. VerdictData *expr.Verdict // To support aging of set elements Timeout time.Duration // Life left of the "timeout" elements Expires time.Duration Counter *expr.Counter } func (s *SetElement) decode(fam byte) func(b []byte) error { return func(b []byte) error { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return fmt.Errorf("failed to create nested attribute decoder: %v", err) } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case unix.NFTA_SET_ELEM_KEY: s.Key, err = decodeElement(ad.Bytes()) if err != nil { return err } case NFTA_SET_ELEM_KEY_END: s.KeyEnd, err = decodeElement(ad.Bytes()) if err != nil { return err } case unix.NFTA_SET_ELEM_DATA: s.Val, err = decodeElement(ad.Bytes()) if err != nil { return err } case unix.NFTA_SET_ELEM_FLAGS: flags := ad.Uint32() s.IntervalEnd = (flags & unix.NFT_SET_ELEM_INTERVAL_END) != 0 case unix.NFTA_SET_ELEM_TIMEOUT: s.Timeout = time.Millisecond * time.Duration(ad.Uint64()) case unix.NFTA_SET_ELEM_EXPIRATION: s.Expires = time.Millisecond * time.Duration(ad.Uint64()) case unix.NFTA_SET_ELEM_EXPR: elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad, ad.Bytes()) if err != nil { return err } for _, elem := range elems { switch item := elem.(type) { case *expr.Counter: s.Counter = item } } } } return ad.Err() } } func decodeElement(d []byte) ([]byte, error) { ad, err := netlink.NewAttributeDecoder(d) if err != nil { return nil, fmt.Errorf("failed to create nested attribute decoder: %v", err) } ad.ByteOrder = binary.BigEndian var b []byte for ad.Next() { switch ad.Type() { case unix.NFTA_SET_ELEM_KEY: fallthrough case unix.NFTA_SET_ELEM_DATA: b = ad.Bytes() } } if err := ad.Err(); err != nil { return nil, err } return b, nil } // SetAddElements applies data points to an nftables set. func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error { cc.mu.Lock() defer cc.mu.Unlock() if s.Anonymous { return errors.New("anonymous sets cannot be updated") } elements, err := s.makeElemList(vals, s.ID) if err != nil { return err } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...), }) return nil } func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, error) { var elements []netlink.Attribute for i, v := range vals { item := make([]netlink.Attribute, 0) var flags uint32 if v.IntervalEnd { flags |= unix.NFT_SET_ELEM_INTERVAL_END item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_FLAGS | unix.NLA_F_NESTED, Data: binaryutil.BigEndian.PutUint32(flags)}) } encodedKey, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Key}}) if err != nil { return nil, fmt.Errorf("marshal key %d: %v", i, err) } item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_KEY | unix.NLA_F_NESTED, Data: encodedKey}) if len(v.KeyEnd) > 0 { encodedKeyEnd, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.KeyEnd}}) if err != nil { return nil, fmt.Errorf("marshal key end %d: %v", i, err) } item = append(item, netlink.Attribute{Type: NFTA_SET_ELEM_KEY_END | unix.NLA_F_NESTED, Data: encodedKeyEnd}) } if s.HasTimeout && v.Timeout != 0 { // Set has Timeout flag set, which means an individual element can specify its own timeout. item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(v.Timeout.Milliseconds()))}) } // The following switch statement deal with 3 different types of elements. // 1. v is an element of vmap // 2. v is an element of a regular map // 3. v is an element of a regular set (default) switch { case v.VerdictData != nil: // Since VerdictData is not nil, v is vmap element, need to add to the attributes encodedVal := []byte{} encodedKind, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(v.VerdictData.Kind))}, }) if err != nil { return nil, fmt.Errorf("marshal item %d: %v", i, err) } encodedVal = append(encodedVal, encodedKind...) if len(v.VerdictData.Chain) != 0 { encodedChain, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_ELEM_DATA, Data: []byte(v.VerdictData.Chain + "\x00")}, }) if err != nil { return nil, fmt.Errorf("marshal item %d: %v", i, err) } encodedVal = append(encodedVal, encodedChain...) } encodedVerdict, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal}}) if err != nil { return nil, fmt.Errorf("marshal item %d: %v", i, err) } item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVerdict}) case len(v.Val) > 0: // Since v.Val's length is not 0 then, v is a regular map element, need to add to the attributes encodedVal, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Val}}) if err != nil { return nil, fmt.Errorf("marshal item %d: %v", i, err) } item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal}) default: // If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes } encodedItem, err := netlink.MarshalAttributes(item) if err != nil { return nil, fmt.Errorf("marshal item %d: %v", i, err) } elements = append(elements, netlink.Attribute{Type: uint16(i+1) | unix.NLA_F_NESTED, Data: encodedItem}) } encodedElem, err := netlink.MarshalAttributes(elements) if err != nil { return nil, fmt.Errorf("marshal elements: %v", err) } return []netlink.Attribute{ {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")}, {Type: unix.NFTA_LOOKUP_SET_ID, Data: binaryutil.BigEndian.PutUint32(id)}, {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")}, {Type: unix.NFTA_SET_ELEM_LIST_ELEMENTS | unix.NLA_F_NESTED, Data: encodedElem}, }, nil } // AddSet adds the specified Set. func (cc *Conn) AddSet(s *Set, vals []SetElement) error { cc.mu.Lock() defer cc.mu.Unlock() // Based on nft implementation & linux source. // Link: https://github.com/torvalds/linux/blob/49a57857aeea06ca831043acbb0fa5e0f50602fd/net/netfilter/nf_tables_api.c#L3395 // Another reference: https://git.netfilter.org/nftables/tree/src if s.Anonymous && !s.Constant { return errors.New("anonymous structs must be constant") } if s.ID == 0 { allocSetID++ s.ID = allocSetID if s.Anonymous { s.Name = "__set%d" if s.IsMap { s.Name = "__map%d" } } } var flags uint32 if s.Anonymous { flags |= unix.NFT_SET_ANONYMOUS } if s.Constant { flags |= unix.NFT_SET_CONSTANT } if s.Interval { flags |= unix.NFT_SET_INTERVAL } if s.IsMap { flags |= unix.NFT_SET_MAP } if s.HasTimeout { flags |= unix.NFT_SET_TIMEOUT } if s.Dynamic { flags |= unix.NFT_SET_EVAL } if s.Concatenation { flags |= NFT_SET_CONCAT } tableInfo := []netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")}, {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")}, {Type: unix.NFTA_SET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}, {Type: unix.NFTA_SET_KEY_TYPE, Data: binaryutil.BigEndian.PutUint32(s.KeyType.nftMagic)}, {Type: unix.NFTA_SET_KEY_LEN, Data: binaryutil.BigEndian.PutUint32(s.KeyType.Bytes)}, {Type: unix.NFTA_SET_ID, Data: binaryutil.BigEndian.PutUint32(s.ID)}, } if s.IsMap { // Check if it is vmap case if s.DataType.nftMagic == 1 { // For Verdict data type, the expected magic is 0xfffff0 tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(unix.NFT_DATA_VERDICT))}, netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)}) } else { tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(s.DataType.nftMagic)}, netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)}) } } if s.HasTimeout && s.Timeout != 0 { // If Set's global timeout is specified, add it to set's attributes tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(s.Timeout.Milliseconds()))}) } if s.Constant { // nft cli tool adds the number of elements to set/map's descriptor // It make sense to do only if a set or map are constant, otherwise skip NFTA_SET_DESC attribute numberOfElements, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(len(vals)))}, }) if err != nil { return fmt.Errorf("fail to marshal number of elements %d: %v", len(vals), err) } tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: numberOfElements}) } if s.Concatenation { // Length of concatenated types is a must, otherwise segfaults when executing nft list ruleset var concatDefinition []byte elements := ConcatSetTypeElements(s.KeyType) for i, v := range elements { // Marshal base type size value valData, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(v.Bytes)}, }) if err != nil { return fmt.Errorf("fail to marshal element key size %d: %v", i, err) } // Marshal base type size description descSize, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_DESC_SIZE, Data: valData}, }) if err != nil { return fmt.Errorf("fail to marshal base type size description: %w", err) } concatDefinition = append(concatDefinition, descSize...) } // Marshal all base type descriptions into concatenation size description concatBytes, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NLA_F_NESTED | NFTA_SET_DESC_CONCAT, Data: concatDefinition}}) if err != nil { return fmt.Errorf("fail to marshal concat definition %v", err) } // Marshal concat size description as set description tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: concatBytes}) } if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian { tableInfo = append(tableInfo, // Semantically useless - kept for binary compatability with nft netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x02\x00\x00\x00")}) } else if s.KeyByteOrder == binaryutil.NativeEndian { // Per https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165 tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x01\x00\x00\x00")}) } if s.Counter { data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_LIST_ELEM, Data: []byte("counter\x00")}, {Type: unix.NFTA_SET_ELEM_PAD | unix.NFTA_SET_ELEM_DATA, Data: []byte{}}, }) if err != nil { return err } tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | NFTA_SET_ELEM_EXPRESSIONS, Data: data}) } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(tableInfo)...), }) // Set the values of the set if initial values were provided. if len(vals) > 0 { hdrType := unix.NFT_MSG_NEWSETELEM elements, err := s.makeElemList(vals, s.ID) if err != nil { return err } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...), }) } return nil } // DelSet deletes a specific set, along with all elements it contains. func (cc *Conn) DelSet(s *Set) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")}, {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")}, }) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(s.Table.Family), 0), data...), }) } // SetDeleteElements deletes data points from an nftables set. func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error { cc.mu.Lock() defer cc.mu.Unlock() if s.Anonymous { return errors.New("anonymous sets cannot be updated") } elements, err := s.makeElemList(vals, s.ID) if err != nil { return err } cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM), Flags: netlink.Request | netlink.Acknowledge | netlink.Create, }, Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...), }) return nil } // FlushSet deletes all data points from an nftables set. func (cc *Conn) FlushSet(s *Set) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")}, {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")}, }) cc.messages = append(cc.messages, netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(s.Table.Family), 0), data...), }) } var ( newSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET) delSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET) ) func setsFromMsg(msg netlink.Message) (*Set, error) { if got, want1, want2 := msg.Header.Type, newSetHeaderType, delSetHeaderType; got != want1 && got != want2 { return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2) } ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian var set Set for ad.Next() { switch ad.Type() { case unix.NFTA_SET_NAME: set.Name = ad.String() case unix.NFTA_SET_ID: set.ID = binary.BigEndian.Uint32(ad.Bytes()) case unix.NFTA_SET_TIMEOUT: set.Timeout = time.Duration(time.Millisecond * time.Duration(binary.BigEndian.Uint64(ad.Bytes()))) set.HasTimeout = true case unix.NFTA_SET_FLAGS: flags := ad.Uint32() set.Constant = (flags & unix.NFT_SET_CONSTANT) != 0 set.Anonymous = (flags & unix.NFT_SET_ANONYMOUS) != 0 set.Interval = (flags & unix.NFT_SET_INTERVAL) != 0 set.IsMap = (flags & unix.NFT_SET_MAP) != 0 set.HasTimeout = (flags & unix.NFT_SET_TIMEOUT) != 0 set.Concatenation = (flags & NFT_SET_CONCAT) != 0 case unix.NFTA_SET_KEY_TYPE: nftMagic := ad.Uint32() dt, err := parseSetDatatype(nftMagic) if err != nil { return nil, fmt.Errorf("could not determine data type: %w", err) } set.KeyType = dt case unix.NFTA_SET_KEY_LEN: set.KeyType.Bytes = binary.BigEndian.Uint32(ad.Bytes()) case unix.NFTA_SET_DATA_TYPE: nftMagic := ad.Uint32() // Special case for the data type verdict, in the message it is stored as 0xffffff00 but it is defined as 1 if nftMagic == 0xffffff00 { set.KeyType = TypeVerdict break } dt, err := parseSetDatatype(nftMagic) if err != nil { return nil, fmt.Errorf("could not determine data type: %w", err) } set.DataType = dt case unix.NFTA_SET_DATA_LEN: set.DataType.Bytes = binary.BigEndian.Uint32(ad.Bytes()) } } return &set, nil } func parseSetDatatype(magic uint32) (SetDatatype, error) { types := make([]SetDatatype, 0, 32/SetConcatTypeBits) for magic != 0 { t := magic & SetConcatTypeMask magic = magic >> SetConcatTypeBits dt, ok := nftDatatypesByMagic[t] if !ok { return TypeInvalid, fmt.Errorf("could not determine data type %+v", dt) } // Because we start with the last type, we insert the later types at the front. types = append([]SetDatatype{dt}, types...) } dt, err := ConcatSetType(types...) if err != nil { return TypeInvalid, fmt.Errorf("could not create data type: %w", err) } return dt, nil } var ( newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM) delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM) ) func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) { if got, want1, want2 := msg.Header.Type, newElemHeaderType, delElemHeaderType; got != want1 && got != want2 { return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2) } ad, err := netlink.NewAttributeDecoder(msg.Data[4:]) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian var elements []SetElement for ad.Next() { b := ad.Bytes() if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS { ad, err := netlink.NewAttributeDecoder(b) if err != nil { return nil, err } ad.ByteOrder = binary.BigEndian for ad.Next() { var elem SetElement switch ad.Type() { case unix.NFTA_LIST_ELEM: ad.Do(elem.decode(fam)) } elements = append(elements, elem) } } } return elements, nil } // GetSets returns the sets in the specified table. func (cc *Conn) GetSets(t *Table) ([]*Set, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")}, }) if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET), Flags: netlink.Request | netlink.Acknowledge | netlink.Dump, }, Data: append(extraHeader(uint8(t.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %v", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %v", err) } var sets []*Set for _, msg := range reply { s, err := setsFromMsg(msg) if err != nil { return nil, err } s.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family} sets = append(sets, s) } return sets, nil } // GetSetByName returns the set in the specified table if matching name is found. func (cc *Conn) GetSetByName(t *Table, name string) (*Set, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")}, {Type: unix.NFTA_SET_NAME, Data: []byte(name + "\x00")}, }) if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET), Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(t.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %w", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %w", err) } if len(reply) != 1 { return nil, fmt.Errorf("Receive: expected to receive 1 message but got %d", len(reply)) } rs, err := setsFromMsg(reply[0]) if err != nil { return nil, err } rs.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family} return rs, nil } // GetSetElements returns the elements in the specified set. func (cc *Conn) GetSetElements(s *Set) ([]SetElement, error) { conn, closer, err := cc.netlinkConn() if err != nil { return nil, err } defer func() { _ = closer() }() data, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")}, {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")}, }) if err != nil { return nil, err } message := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSETELEM), Flags: netlink.Request | netlink.Acknowledge | netlink.Dump, }, Data: append(extraHeader(uint8(s.Table.Family), 0), data...), } if _, err := conn.SendMessages([]netlink.Message{message}); err != nil { return nil, fmt.Errorf("SendMessages: %v", err) } reply, err := receiveAckAware(conn, message.Header.Flags) if err != nil { return nil, fmt.Errorf("Receive: %v", err) } var elems []SetElement for _, msg := range reply { s, err := elementsFromMsg(uint8(s.Table.Family), msg) if err != nil { return nil, err } elems = append(elems, s...) } return elems, nil } nftables-0.2.0/set_test.go000066400000000000000000000116731457332137300155100ustar00rootroot00000000000000package nftables import ( "testing" ) // unknownNFTMagic is an nftMagic value that's unhandled by this // library. We use two of them below. const unknownNFTMagic uint32 = 1< 0 { l-- if err := nats[l].unmarshalAB(fam, rev, &ab); err != nil { return err } } *x = nats return nil } func (x *NatIPv4Range) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { ab.PutUint(x.Flags) ab.PutBytesAligned32(x.MinIP.To4(), 4) ab.PutBytesAligned32(x.MaxIP.To4(), 4) ab.PutUint16BE(x.MinPort) ab.PutUint16BE(x.MaxPort) return nil } func (x *NatIPv4Range) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error { var err error if x.Flags, err = ab.Uint(); err != nil { return err } var ip []byte if ip, err = ab.BytesAligned32(4); err != nil { return err } x.MinIP = net.IP(ip) if ip, err = ab.BytesAligned32(4); err != nil { return err } x.MaxIP = net.IP(ip) if x.MinPort, err = ab.Uint16BE(); err != nil { return err } if x.MaxPort, err = ab.Uint16BE(); err != nil { return err } return nil } nftables-0.2.0/xt/target_masquerade_ip_test.go000066400000000000000000000021421457332137300215240ustar00rootroot00000000000000package xt import ( "net" "reflect" "testing" "golang.org/x/sys/unix" ) func TestTargetMasqueradeIP(t *testing.T) { t.Parallel() tests := []struct { name string fam byte rev uint32 info InfoAny empty InfoAny }{ { name: "un/marshal NatIPv4Range round-trip", fam: unix.NFPROTO_IPV4, rev: 0, info: &NatIPv4MultiRangeCompat{ NatIPv4Range{ Flags: 0x1234, MinIP: net.ParseIP("12.23.34.45").To4(), MaxIP: net.ParseIP("21.32.43.54").To4(), MinPort: 0x5678, MaxPort: 0xabcd, }, }, empty: new(NatIPv4MultiRangeCompat), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev) if err != nil { t.Fatalf("marshal error: %+v", err) } var recoveredInfo InfoAny = tt.empty err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data) if err != nil { t.Fatalf("unmarshal error: %+v", err) } if !reflect.DeepEqual(tt.info, recoveredInfo) { t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) } }) } } nftables-0.2.0/xt/unknown.go000066400000000000000000000010141457332137300157740ustar00rootroot00000000000000package xt // Unknown represents the bytes Info payload for unknown Info types where no // dedicated match/target info type has (yet) been defined. type Unknown []byte func (x *Unknown) marshal(fam TableFamily, rev uint32) ([]byte, error) { // In case of unknown payload we assume its creator knows what she/he does // and thus we don't do any alignment padding. Just take the payload "as // is". return *x, nil } func (x *Unknown) unmarshal(fam TableFamily, rev uint32, data []byte) error { *x = data return nil } nftables-0.2.0/xt/unknown_test.go000066400000000000000000000013601457332137300170370ustar00rootroot00000000000000package xt import ( "reflect" "testing" ) func TestUnknown(t *testing.T) { t.Parallel() payload := Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}) tests := []struct { name string info InfoAny }{ { name: "un/marshal Unknown round-trip", info: &payload, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { data, err := tt.info.marshal(0, 0) if err != nil { t.Fatalf("marshal error: %+v", err) } var recoveredInfo InfoAny = &Unknown{} err = recoveredInfo.unmarshal(0, 0, data) if err != nil { t.Fatalf("unmarshal error: %+v", err) } if !reflect.DeepEqual(tt.info, recoveredInfo) { t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) } }) } } nftables-0.2.0/xt/util.go000066400000000000000000000024671457332137300152670ustar00rootroot00000000000000package xt import ( "fmt" "net" "github.com/google/nftables/alignedbuff" "golang.org/x/sys/unix" ) func bool32(ab *alignedbuff.AlignedBuff) (bool, error) { v, err := ab.Uint32() if err != nil { return false, err } if v != 0 { return true, nil } return false, nil } func putBool32(ab *alignedbuff.AlignedBuff, b bool) { if b { ab.PutUint32(1) return } ab.PutUint32(0) } func iPv46(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IP, error) { ip, err := ab.BytesAligned32(16) if err != nil { return nil, err } switch fam { case unix.NFPROTO_IPV4: return net.IP(ip[:4]), nil case unix.NFPROTO_IPV6: return net.IP(ip), nil default: return nil, fmt.Errorf("unmarshal IP: unsupported table family %d", fam) } } func iPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IPMask, error) { v, err := iPv46(ab, fam) return net.IPMask(v), err } func putIPv46(ab *alignedbuff.AlignedBuff, fam TableFamily, ip net.IP) error { switch fam { case unix.NFPROTO_IPV4: ab.PutBytesAligned32(ip.To4(), 16) case unix.NFPROTO_IPV6: ab.PutBytesAligned32(ip.To16(), 16) default: return fmt.Errorf("marshal IP: unsupported table family %d", fam) } return nil } func putIPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily, mask net.IPMask) error { return putIPv46(ab, fam, net.IP(mask)) } nftables-0.2.0/xt/xt.go000066400000000000000000000055631457332137300147450ustar00rootroot00000000000000/* Package xt implements dedicated types for (some) of the "Info" payload in Match and Target expressions that bridge between the nftables and xtables worlds. Bridging between the more unified world of nftables and the slightly heterogenous world of xtables comes with some caveats. Unmarshalling the extension/translation information in Match and Target expressions requires information about the table family the information belongs to, as well as type and type revision information. In consequence, unmarshalling the Match and Target Info field payloads often (but not necessarily always) require the table family and revision information, so it gets passed to the type-specific unmarshallers. To complicate things more, even marshalling requires knowledge about the enclosing table family. The NatRange/NatRange2 types are an example, where it is necessary to differentiate between IPv4 and IPv6 address marshalling. Due to Go's net.IP habit to normally store IPv4 addresses as IPv4-compatible IPv6 addresses (see also RFC 4291, section 2.5.5.1) marshalling must be handled differently in the context of an IPv6 table compared to an IPv4 table. In an IPv4 table, an IPv4-compatible IPv6 address must be marshalled as a 32bit address, whereas in an IPv6 table the IPv4 address must be marshalled as an 128bit IPv4-compatible IPv6 address. Not relying on heuristics here we avoid behavior unexpected and most probably unknown to our API users. The net.IP habit of storing IPv4 addresses in two different storage formats is already a source for trouble, especially when comparing net.IPs from different Go module sources. We won't add to this confusion. (...or maybe we can, because of it?) An important property of all types of Info extension/translation payloads is that their marshalling and unmarshalling doesn't follow netlink's TLV (tag-length-value) architecture. Instead, Info payloads a basically plain binary blobs of their respective type-specific data structures, so host platform/architecture alignment and data type sizes apply. The alignedbuff package implements the different required data types alignments. Please note that Info payloads are always padded at their end to the next uint64 alignment. Kernel code is checking for the padded payload size and will reject payloads not correctly padded at their ends. Most of the time, we find explifcitly sized (unsigned integer) data types. However, there are notable exceptions where "unsigned int" is used: on 64bit platforms this mostly translates into 32bit(!). This differs from Go mapping uint to uint64 instead. This package currently clamps its mapping of C's "unsigned int" to Go's uint32 for marshalling and unmarshalling. If in the future 128bit platforms with a differently sized C unsigned int should come into production, then the alignedbuff package will need to be adapted accordingly, as it abstracts away this data type handling. */ package xt