pax_global_header00006660000000000000000000000064146176441350014525gustar00rootroot0000000000000052 comment=ce2c22706f9406c2af3c61906c5e624334c361df go-nfqueue-2.0.0/000077500000000000000000000000001461764413500135775ustar00rootroot00000000000000go-nfqueue-2.0.0/.github/000077500000000000000000000000001461764413500151375ustar00rootroot00000000000000go-nfqueue-2.0.0/.github/dependabot.yml000066400000000000000000000003421461764413500177660ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: gomod open-pull-requests-limit: 5 directory: / schedule: interval: weeklygo-nfqueue-2.0.0/.github/workflows/000077500000000000000000000000001461764413500171745ustar00rootroot00000000000000go-nfqueue-2.0.0/.github/workflows/go.yml000066400000000000000000000024071461764413500203270ustar00rootroot00000000000000on: push: branches: [ main ] pull_request: branches: [ '**' ] name: Go jobs: test: strategy: matrix: go-version: [1.18.x, 1.20.x, 1.21.x, 1.22.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v4 - name: Download Go dependencies env: GOPROXY: "https://proxy.golang.org" run: go mod download - name: Test with -race run: go test -race -count=1 ./... - name: Integration test if: matrix.platform == 'ubuntu-latest' && startsWith(matrix.go-version, '1.22') run: | sudo modprobe nfnetlink_queue sudo ip6tables -I OUTPUT -p ipv6-icmp -j NFQUEUE --queue-num 100 sudo iptables -I OUTPUT -p icmp -j NFQUEUE --queue-num 100 go test -v -tags integration -exec=sudo -count=1 ./... - name: staticcheck.io if: matrix.platform == 'ubuntu-latest' && startsWith(matrix.go-version, '1.22') uses: dominikh/staticcheck-action@v1.3.1 with: version: "2023.1.7" install-go: false cache-key: ${{ matrix.go-version }} go-nfqueue-2.0.0/LICENSE000066400000000000000000000021241461764413500146030ustar00rootroot00000000000000MIT License =========== Copyright (C) 2018-2020 Florian Lehner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-nfqueue-2.0.0/README.md000066400000000000000000000022151461764413500150560ustar00rootroot00000000000000go-nfqueue [![PkgGoDev](https://pkg.go.dev/badge/github.com/florianl/go-nfqueue)](https://pkg.go.dev/github.com/florianl/go-nfqueue) [![Build Status](https://travis-ci.org/florianl/go-nfqueue.svg?branch=master)](https://travis-ci.org/florianl/go-nfqueue) [![Go Report Card](https://goreportcard.com/badge/github.com/florianl/go-nfqueue)](https://goreportcard.com/report/github.com/florianl/go-nfqueue) ============ This is `go-nfqueue` and it is written in [golang](https://golang.org/). It provides a [C](https://en.wikipedia.org/wiki/C_(programming_language))-binding free API to the netfilter based queue subsystem of the [Linux kernel](https://www.kernel.org). ## Privileges This package processes information directly from the kernel and therefore it requires special privileges. You can provide this privileges by adjusting the `CAP_NET_ADMIN` capabilities. ``` setcap 'cap_net_admin=+ep' /your/executable ``` For documentation and more examples please take a look at [documentation](https://pkg.go.dev/github.com/florianl/go-nfqueue). ## Requirements * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) go-nfqueue-2.0.0/attribute.go000066400000000000000000000051571461764413500161410ustar00rootroot00000000000000package nfqueue import ( "bytes" "encoding/binary" "time" "github.com/florianl/go-nfqueue/v2/internal/unix" "github.com/mdlayher/netlink" ) func extractAttribute(log Logger, a *Attribute, data []byte) error { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return err } ad.ByteOrder = binary.BigEndian for ad.Next() { switch ad.Type() { case nfQaPacketHdr: packetID := binary.BigEndian.Uint32(ad.Bytes()[:4]) a.PacketID = &packetID hwProto := binary.BigEndian.Uint16(ad.Bytes()[4:6]) a.HwProtocol = &hwProto hook := uint8(ad.Bytes()[6]) a.Hook = &hook case nfQaMark: mark := ad.Uint32() a.Mark = &mark case nfQaTimestamp: var sec, usec int64 r := bytes.NewReader(ad.Bytes()[:8]) if err := binary.Read(r, binary.BigEndian, &sec); err != nil { return err } r = bytes.NewReader(ad.Bytes()[8:]) if err := binary.Read(r, binary.BigEndian, &usec); err != nil { return err } timestamp := time.Unix(sec, usec*1000) a.Timestamp = ×tamp case nfQaIfIndexInDev: inDev := ad.Uint32() a.InDev = &inDev case nfQaIfIndexOutDev: outDev := ad.Uint32() a.OutDev = &outDev case nfQaIfIndexPhysInDev: physInDev := ad.Uint32() a.PhysInDev = &physInDev case nfQaIfIndexPhysOutDev: physOutDev := ad.Uint32() a.PhysOutDev = &physOutDev case nfQaHwAddr: hwAddrLen := binary.BigEndian.Uint16(ad.Bytes()[:2]) hwAddr := (ad.Bytes())[4 : 4+hwAddrLen] a.HwAddr = &hwAddr case nfQaPayload: payload := ad.Bytes() a.Payload = &payload case nfQaCt: ct := ad.Bytes() a.Ct = &ct case nfQaCtInfo: ctInfo := ad.Uint32() a.CtInfo = &ctInfo case nfQaCapLen: capLen := ad.Uint32() a.CapLen = &capLen case nfQaSkbInfo: skbInfo := ad.Bytes() a.SkbInfo = &skbInfo case nfQaExp: exp := ad.Bytes() a.Exp = &exp case nfQaUID: uid := ad.Uint32() a.UID = &uid case nfQaGID: gid := ad.Uint32() a.GID = &gid case nfQaSecCtx: secCtx := ad.String() a.SecCtx = &secCtx case nfQaL2HDR: l2hdr := ad.Bytes() a.L2Hdr = &l2hdr case nfQaPriority: skbPrio := ad.Uint32() a.SkbPrio = &skbPrio default: log.Errorf("Unknown attribute Type: 0x%x\tData: %v", ad.Type(), ad.Bytes()) } } return ad.Err() } func checkHeader(data []byte) int { if (data[0] == unix.AF_INET || data[0] == unix.AF_INET6) && data[1] == unix.NFNETLINK_V0 { return 4 } return 0 } func extractAttributes(log Logger, msg []byte) (Attribute, error) { attrs := Attribute{} offset := checkHeader(msg[:2]) if err := extractAttribute(log, &attrs, msg[offset:]); err != nil { return attrs, err } return attrs, nil } go-nfqueue-2.0.0/doc.go000066400000000000000000000005621461764413500146760ustar00rootroot00000000000000/* Package nfqueue provides an API to interact with the nfqueue subsystem of the netfilter family from the linux kernel. This package processes information directly from the kernel and therefore it requires special privileges. You can provide this privileges by adjusting the CAP_NET_ADMIN capabilities. setcap 'cap_net_admin=+ep' /your/executable */ package nfqueue go-nfqueue-2.0.0/example_test.go000066400000000000000000000026361461764413500166270ustar00rootroot00000000000000//go:build linux // +build linux package nfqueue_test import ( "context" "fmt" "time" nfqueue "github.com/florianl/go-nfqueue/v2" "github.com/mdlayher/netlink" ) func ExampleNfqueue_RegisterWithErrorFunc() { // Send outgoing pings to nfqueue queue 100 // # sudo iptables -I OUTPUT -p icmp -j NFQUEUE --queue-num 100 // Set configuration options for nfqueue config := nfqueue.Config{ NfQueue: 100, MaxPacketLen: 0xFFFF, MaxQueueLen: 0xFF, Copymode: nfqueue.NfQnlCopyPacket, WriteTimeout: 15 * time.Millisecond, } nf, err := nfqueue.Open(&config) if err != nil { fmt.Println("could not open nfqueue socket:", err) return } defer nf.Close() // Avoid receiving ENOBUFS errors. if err := nf.SetOption(netlink.NoENOBUFS, true); err != nil { fmt.Printf("failed to set netlink option %v: %v\n", netlink.NoENOBUFS, err) return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() fn := func(a nfqueue.Attribute) int { id := *a.PacketID // Just print out the id and payload of the nfqueue packet fmt.Printf("[%d]\t%v\n", id, *a.Payload) nf.SetVerdict(id, nfqueue.NfAccept) return 0 } // Register your function to listen on nflqueue queue 100 err = nf.RegisterWithErrorFunc(ctx, fn, func(e error) int { fmt.Println(err) return -1 }) if err != nil { fmt.Println(err) return } // Block till the context expires <-ctx.Done() } go-nfqueue-2.0.0/go.mod000066400000000000000000000005421461764413500147060ustar00rootroot00000000000000module github.com/florianl/go-nfqueue/v2 require ( github.com/mdlayher/netlink v1.7.2 golang.org/x/sys v0.20.0 ) require ( github.com/google/go-cmp v0.6.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/mdlayher/socket v0.4.1 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect ) go 1.18 go-nfqueue-2.0.0/go.sum000066400000000000000000000021671461764413500147400ustar00rootroot00000000000000github.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.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= go-nfqueue-2.0.0/internal/000077500000000000000000000000001461764413500154135ustar00rootroot00000000000000go-nfqueue-2.0.0/internal/unix/000077500000000000000000000000001461764413500163765ustar00rootroot00000000000000go-nfqueue-2.0.0/internal/unix/doc.go000066400000000000000000000002221461764413500174660ustar00rootroot00000000000000// Package unix maps constants from golang.org/x/sys/unix to local constant // and makes them available for other platforms as well. package unix go-nfqueue-2.0.0/internal/unix/types_linux.go000066400000000000000000000004731461764413500213140ustar00rootroot00000000000000//go:build linux // +build linux package unix import ( linux "golang.org/x/sys/unix" ) // various constants const ( AF_INET = linux.AF_INET AF_INET6 = linux.AF_INET6 AF_UNSPEC = linux.AF_UNSPEC NFNETLINK_V0 = linux.NFNETLINK_V0 NETLINK_NETFILTER = linux.NETLINK_NETFILTER ) go-nfqueue-2.0.0/internal/unix/types_other.go000066400000000000000000000002711461764413500212720ustar00rootroot00000000000000//go:build !linux // +build !linux package unix const ( AF_INET = 0x2 AF_INET6 = 0xa AF_UNSPEC = 0x0 NFNETLINK_V0 = 0x0 NETLINK_NETFILTER = 0xc ) go-nfqueue-2.0.0/nfqueue.go000066400000000000000000000277041461764413500156100ustar00rootroot00000000000000package nfqueue import ( "context" "encoding/binary" "fmt" "sync" "time" "github.com/florianl/go-nfqueue/v2/internal/unix" "github.com/mdlayher/netlink" ) var _ Logger = (*devNull)(nil) // devNull satisfies the Logger interface. type devNull struct{} func (dn *devNull) Debugf(format string, args ...interface{}) {} func (dn *devNull) Errorf(format string, args ...interface{}) {} // Close the connection to the netfilter queue subsystem func (nfqueue *Nfqueue) Close() error { err := nfqueue.Con.Close() nfqueue.wg.Wait() return err } // SetVerdictWithMark signals the kernel the next action and the mark for a specified package id func (nfqueue *Nfqueue) SetVerdictWithMark(id uint32, verdict, mark int) error { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(mark)) attributes, err := netlink.MarshalAttributes([]netlink.Attribute{{ Type: nfQaMark, Data: buf, }}) if err != nil { return err } return nfqueue.setVerdict(id, verdict, false, attributes) } // SetVerdictWithConnMark signals the kernel the next action and the connmark for a specified package id func (nfqueue *Nfqueue) SetVerdictWithConnMark(id uint32, verdict, mark int) error { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(mark)) ctAttrs, err := netlink.MarshalAttributes([]netlink.Attribute{{ Type: ctaMark, Data: buf, }}) if err != nil { return err } attributes, err := netlink.MarshalAttributes([]netlink.Attribute{{ Type: netlink.Nested | nfQaCt, Data: ctAttrs, }}) if err != nil { return err } return nfqueue.setVerdict(id, verdict, false, attributes) } // SetVerdictModPacket signals the kernel the next action for an altered packet func (nfqueue *Nfqueue) SetVerdictModPacket(id uint32, verdict int, packet []byte) error { data, err := netlink.MarshalAttributes([]netlink.Attribute{{ Type: nfQaPayload, Data: packet, }}) if err != nil { return err } return nfqueue.setVerdict(id, verdict, false, data) } // SetVerdictModPacketWithMark signals the kernel the next action and mark for an altered packet func (nfqueue *Nfqueue) SetVerdictModPacketWithMark(id uint32, verdict, mark int, packet []byte) error { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(mark)) data, err := netlink.MarshalAttributes([]netlink.Attribute{ { Type: nfQaPayload, Data: packet, }, { Type: nfQaMark, Data: buf, }, }) if err != nil { return err } return nfqueue.setVerdict(id, verdict, false, data) } // SetVerdictModPacketWithConnMark signals the kernel the next action and connmark for an altered packet func (nfqueue *Nfqueue) SetVerdictModPacketWithConnMark(id uint32, verdict, mark int, packet []byte) error { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(mark)) ctAttrs, err := netlink.MarshalAttributes([]netlink.Attribute{{ Type: ctaMark, Data: buf, }}) if err != nil { return err } data, err := netlink.MarshalAttributes([]netlink.Attribute{ { Type: nfQaPayload, Data: packet, }, { Type: netlink.Nested | nfQaCt, Data: ctAttrs, }, }) if err != nil { return err } return nfqueue.setVerdict(id, verdict, false, data) } // SetVerdict signals the kernel the next action for a specified package id func (nfqueue *Nfqueue) SetVerdict(id uint32, verdict int) error { return nfqueue.setVerdict(id, verdict, false, []byte{}) } // SetVerdictBatch signals the kernel the next action for a batch of packages till id func (nfqueue *Nfqueue) SetVerdictBatch(id uint32, verdict int) error { return nfqueue.setVerdict(id, verdict, true, []byte{}) } // SetOption allows to enable or disable netlink socket options. func (nfqueue *Nfqueue) SetOption(o netlink.ConnOption, enable bool) error { return nfqueue.Con.SetOption(o, enable) } // Register your own function as callback for a netfilter queue. // // The registered callback will stop receiving data if an error // happened. To handle errors and continue receiving data with the // registered callback use RegisterWithErrorFunc() instead. // // Deprecated: Use RegisterWithErrorFunc() instead. func (nfqueue *Nfqueue) Register(ctx context.Context, fn HookFunc) error { return nfqueue.RegisterWithErrorFunc(ctx, fn, func(err error) int { if opError, ok := err.(*netlink.OpError); ok { if opError.Timeout() || opError.Temporary() { return 0 } } nfqueue.logger.Errorf("Could not receive message: %v", err) return 1 }) } // RegisterWithErrorFunc attaches a callback function to a netfilter queue and allows // custom error handling for errors encountered when reading from the underlying netlink socket. func (nfqueue *Nfqueue) RegisterWithErrorFunc(ctx context.Context, fn HookFunc, errfn ErrorFunc) error { // unbinding existing handler (if any) seq, err := nfqueue.setConfig(unix.AF_UNSPEC, 0, 0, []netlink.Attribute{ {Type: nfQaCfgCmd, Data: []byte{nfUlnlCfgCmdPfUnbind, 0x0, 0x0, byte(nfqueue.family)}}, }) if err != nil { return fmt.Errorf("could not unbind existing handlers (if any): %w", err) } // binding to family _, err = nfqueue.setConfig(unix.AF_UNSPEC, seq, 0, []netlink.Attribute{ {Type: nfQaCfgCmd, Data: []byte{nfUlnlCfgCmdPfBind, 0x0, 0x0, byte(nfqueue.family)}}, }) if err != nil { return fmt.Errorf("could not bind to family %d: %w", nfqueue.family, err) } // binding to the requested queue _, err = nfqueue.setConfig(uint8(unix.AF_UNSPEC), seq, nfqueue.queue, []netlink.Attribute{ {Type: nfQaCfgCmd, Data: []byte{nfUlnlCfgCmdBind, 0x0, 0x0, byte(nfqueue.family)}}, }) if err != nil { return fmt.Errorf("could not bind to requested queue %d: %w", nfqueue.queue, err) } // set copy mode and buffer size data := append(nfqueue.maxPacketLen, nfqueue.copymode) _, err = nfqueue.setConfig(uint8(unix.AF_UNSPEC), seq, nfqueue.queue, []netlink.Attribute{ {Type: nfQaCfgParams, Data: data}, }) if err != nil { return err } var attrs []netlink.Attribute if nfqueue.flags[0] != 0 || nfqueue.flags[1] != 0 || nfqueue.flags[2] != 0 || nfqueue.flags[3] != 0 { // set flags attrs = append(attrs, netlink.Attribute{Type: nfQaCfgFlags, Data: nfqueue.flags}) attrs = append(attrs, netlink.Attribute{Type: nfQaCfgMask, Data: nfqueue.flags}) } attrs = append(attrs, netlink.Attribute{Type: nfQaCfgQueueMaxLen, Data: nfqueue.maxQueueLen}) _, err = nfqueue.setConfig(uint8(unix.AF_UNSPEC), seq, nfqueue.queue, attrs) if err != nil { return err } nfqueue.wg.Add(1) go func() { defer nfqueue.wg.Done() nfqueue.socketCallback(ctx, fn, errfn, seq) }() return nil } // /include/uapi/linux/netfilter/nfnetlink.h:struct nfgenmsg{} res_id is Big Endian func putExtraHeader(familiy, version uint8, resid uint16) []byte { buf := make([]byte, 2) binary.BigEndian.PutUint16(buf, resid) return append([]byte{familiy, version}, buf...) } func (nfqueue *Nfqueue) setConfig(afFamily uint8, oseq uint32, resid uint16, attrs []netlink.Attribute) (uint32, error) { cmd, err := netlink.MarshalAttributes(attrs) if err != nil { return 0, err } data := putExtraHeader(afFamily, unix.NFNETLINK_V0, resid) data = append(data, cmd...) req := netlink.Message{ Header: netlink.Header{ Type: netlink.HeaderType((nfnlSubSysQueue << 8) | nfQnlMsgConfig), Flags: netlink.Request | netlink.Acknowledge, Sequence: oseq, }, Data: data, } return nfqueue.execute(req) } func (nfqueue *Nfqueue) execute(req netlink.Message) (uint32, error) { var seq uint32 reply, e := nfqueue.Con.Execute(req) if e != nil { return 0, e } if e := netlink.Validate(req, reply); e != nil { return 0, e } for _, msg := range reply { if seq != 0 { return 0, fmt.Errorf("number of received messages: %d: %w", len(reply), ErrUnexpMsg) } seq = msg.Header.Sequence } return seq, nil } func parseMsg(log Logger, msg netlink.Message) (Attribute, error) { a, err := extractAttributes(log, msg.Data) if err != nil { return a, err } return a, nil } // Nfqueue represents a netfilter queue handler type Nfqueue struct { // Con is the pure representation of a netlink socket Con *netlink.Conn logger Logger wg sync.WaitGroup flags []byte // uint32 maxPacketLen []byte // uint32 family uint8 queue uint16 maxQueueLen []byte // uint32 copymode uint8 setWriteTimeout func() error } // Logger provides logging functionality. type Logger interface { Debugf(format string, args ...interface{}) Errorf(format string, args ...interface{}) } // Open a connection to the netfilter queue subsystem func Open(config *Config) (*Nfqueue, error) { var nfqueue Nfqueue if config.Flags >= nfQaCfgFlagMax { return nil, ErrInvFlag } con, err := netlink.Dial(unix.NETLINK_NETFILTER, &netlink.Config{NetNS: config.NetNS}) if err != nil { return nil, err } nfqueue.Con = con // default size of copied packages to userspace nfqueue.maxPacketLen = []byte{0x00, 0x00, 0x00, 0x00} binary.BigEndian.PutUint32(nfqueue.maxPacketLen, config.MaxPacketLen) nfqueue.flags = []byte{0x00, 0x00, 0x00, 0x00} binary.BigEndian.PutUint32(nfqueue.flags, config.Flags) nfqueue.queue = config.NfQueue nfqueue.family = config.AfFamily nfqueue.maxQueueLen = []byte{0x00, 0x00, 0x00, 0x00} binary.BigEndian.PutUint32(nfqueue.maxQueueLen, config.MaxQueueLen) if config.Logger == nil { nfqueue.logger = new(devNull) } else { nfqueue.logger = config.Logger } nfqueue.copymode = config.Copymode if config.WriteTimeout > 0 { nfqueue.setWriteTimeout = func() error { deadline := time.Now().Add(config.WriteTimeout) return nfqueue.Con.SetWriteDeadline(deadline) } } else { nfqueue.setWriteTimeout = func() error { return nil } } return &nfqueue, nil } func (nfqueue *Nfqueue) setVerdict(id uint32, verdict int, batch bool, attributes []byte) error { /* struct nfqnl_msg_verdict_hdr { __be32 verdict; __be32 id; }; */ if verdict != NfDrop && verdict != NfAccept && verdict != NfStolen && verdict != NfQeueue && verdict != NfRepeat { return ErrInvalidVerdict } buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(id)) verdictData := append([]byte{0x0, 0x0, 0x0, byte(verdict)}, buf...) cmd, err := netlink.MarshalAttributes([]netlink.Attribute{ {Type: nfQaVerdictHdr, Data: verdictData}, }) if err != nil { return err } data := putExtraHeader(nfqueue.family, unix.NFNETLINK_V0, nfqueue.queue) data = append(data, cmd...) data = append(data, attributes...) req := netlink.Message{ Header: netlink.Header{ Flags: netlink.Request, Sequence: 0, }, Data: data, } if batch { req.Header.Type = netlink.HeaderType((nfnlSubSysQueue << 8) | nfQnlMsgVerdictBatch) } else { req.Header.Type = netlink.HeaderType((nfnlSubSysQueue << 8) | nfQnlMsgVerdict) } if err := nfqueue.setWriteTimeout(); err != nil { nfqueue.logger.Errorf("could not set write timeout: %v\n", err) } _, sErr := nfqueue.Con.Send(req) return sErr } func (nfqueue *Nfqueue) socketCallback(ctx context.Context, fn HookFunc, errfn ErrorFunc, seq uint32) { defer func() { // unbinding from queue _, err := nfqueue.setConfig(uint8(unix.AF_UNSPEC), seq, nfqueue.queue, []netlink.Attribute{ {Type: nfQaCfgCmd, Data: []byte{nfUlnlCfgCmdUnbind, 0x0, 0x0, byte(nfqueue.family)}}, }) if err != nil { nfqueue.logger.Errorf("Could not unbind from queue: %v", err) } }() nfqueue.wg.Add(1) go func() { defer nfqueue.wg.Done() // block until context is done <-ctx.Done() // Set the read deadline to a point in the past to interrupt // possible blocking Receive() calls. nfqueue.Con.SetReadDeadline(time.Now().Add(-1 * time.Second)) }() for { if err := ctx.Err(); err != nil { nfqueue.logger.Errorf("Stop receiving nfqueue messages: %v", err) return } replys, err := nfqueue.Con.Receive() if err != nil { if ret := errfn(err); ret != 0 { return } continue } for _, msg := range replys { if msg.Header.Type == netlink.Done { // this is the last message of a batch // continue to receive messages break } m, err := parseMsg(nfqueue.logger, msg) if err != nil { nfqueue.logger.Errorf("Could not parse message: %v", err) continue } if ret := fn(m); ret != 0 { return } } } } go-nfqueue-2.0.0/nfqueue_linux_integration_test.go000066400000000000000000000045541461764413500224670ustar00rootroot00000000000000//go:build integration && linux // +build integration,linux package nfqueue import ( "context" "os/exec" "testing" "time" ) func startDummyPingTraffic(t *testing.T, ctx context.Context) { t.Helper() if err := exec.CommandContext(ctx, "ping6", "2606:4700:4700::1111").Start(); err != nil { t.Fatalf("failed to start IPv6 ping: %v", err) } if err := exec.CommandContext(ctx, "ping", "1.1.1.1").Start(); err != nil { t.Fatalf("failed to start IPv4 ping: %v", err) } } func TestLinuxNfqueue(t *testing.T) { pingCtx, pingCancel := context.WithCancel(context.Background()) defer pingCancel() startDummyPingTraffic(t, pingCtx) // Set configuration options for nfqueue config := Config{ NfQueue: 100, MaxPacketLen: 0xFFFF, MaxQueueLen: 0xFF, Copymode: NfQnlCopyPacket, } // Open a socket to the netfilter log subsystem nfq, err := Open(&config) if err != nil { t.Fatalf("failed to open nfqueue socket: %v", err) } defer nfq.Close() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() fn := func(a Attribute) int { id := *a.PacketID // Just print out the id and payload of the nfqueue packet t.Logf("[%d]\t%v\n", id, *a.Payload) nfq.SetVerdict(id, NfAccept) return 0 } // Register your function to listen on nflog group 100 err = nfq.Register(ctx, fn) if err != nil { t.Fatalf("failed to register hook function: %v", err) } // Block till the context expires <-ctx.Done() } func TestTimeout(t *testing.T) { // Set configuration options for nfqueue config := Config{ NfQueue: 123, MaxPacketLen: 0xFFFF, MaxQueueLen: 0xFF, Copymode: NfQnlCopyPacket, } nfq, err := Open(&config) if err != nil { t.Fatalf("failed to open nfqueue socket: %v", err) } defer nfq.Close() ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) fn := func(a Attribute) int { id := *a.PacketID // Just print out the id and payload of the nfqueue packet t.Logf("[%d]\t%v\n", id, *a.Payload) nfq.SetVerdict(id, NfAccept) return 0 } // Register your function to listen on nflog group 123 // This also does a reading on the netlink socket err = nfq.Register(ctx, fn) if err != nil { t.Fatalf("failed to register hook function: %v", err) } // cancel the context to remove the registered hook from the nfqueue. cancel() // Block till the context expires <-ctx.Done() } go-nfqueue-2.0.0/types.go000066400000000000000000000106421461764413500152750ustar00rootroot00000000000000package nfqueue import ( "errors" "time" ) // Attribute contains various elements for nfqueue elements. // As not every value is contained in every nfqueue message, // the elements inside Attribute are pointers to these values // or nil, if not present. type Attribute struct { PacketID *uint32 Hook *uint8 Timestamp *time.Time Mark *uint32 InDev *uint32 PhysInDev *uint32 OutDev *uint32 PhysOutDev *uint32 Payload *[]byte CapLen *uint32 UID *uint32 GID *uint32 SecCtx *string L2Hdr *[]byte HwAddr *[]byte HwProtocol *uint16 Ct *[]byte CtInfo *uint32 SkbInfo *[]byte Exp *[]byte SkbPrio *uint32 } // HookFunc is a function, that receives events from a Netlinkgroup // To stop receiving messages on this HookFunc, return something different than 0. type HookFunc func(a Attribute) int // ErrorFunc is a function that receives all errors that happen while reading // from a Netlinkgroup. To stop receiving messages return something different than 0. type ErrorFunc func(e error) int // Config contains options for a Conn. type Config struct { // Network namespace the Nfqueue needs to operate in. If set to 0 (default), // no network namespace will be entered. NetNS int // Queue this Nfqueue socket will be assigned to NfQueue uint16 // Maximum number of packages within the Nfqueue. MaxQueueLen uint32 // Only used in combination with NfQnlCopyPacket. MaxPacketLen uint32 // Specifies how the kernel handles a packet in the nfqueue queue. Copymode uint8 // Optional flags for this Nfqueue socket. Flags uint32 // AfFamily for this Nfqueue socket. AfFamily uint8 // Deprecated: Cancel the context passed to RegisterWithErrorFunc() or Register() // to remove the hook from the nfqueue gracefully. ReadTimeout time.Duration // Time till a write action times out - only available for Go >= 1.12 WriteTimeout time.Duration // Interface to log internals. Logger Logger } // Various errors var ( ErrRecvMsg = errors.New("received error message") ErrUnexpMsg = errors.New("received unexpected message from kernel") ErrInvFlag = errors.New("invalid Flag") ErrNotLinux = errors.New("not implemented for OS other than linux") ErrInvalidVerdict = errors.New("invalid verdict") ) // nfLogSubSysQueue the netlink subsystem we will query const nfnlSubSysQueue = 0x03 const ( nfQaUnspec = iota nfQaPacketHdr nfQaVerdictHdr /* nfqnl_msg_verdict_hrd */ nfQaMark /* __u32 nfmark */ nfQaTimestamp /* nfqnl_msg_packet_timestamp */ nfQaIfIndexInDev /* __u32 ifindex */ nfQaIfIndexOutDev /* __u32 ifindex */ nfQaIfIndexPhysInDev /* __u32 ifindex */ nfQaIfIndexPhysOutDev /* __u32 ifindex */ nfQaHwAddr /* nfqnl_msg_packet_hw */ nfQaPayload /* opaque data payload */ nfQaCt /* nf_conntrack_netlink.h */ nfQaCtInfo /* enum ip_conntrack_info */ nfQaCapLen /* __u32 length of captured packet */ nfQaSkbInfo /* __u32 skb meta information */ nfQaExp /* nf_conntrack_netlink.h */ nfQaUID /* __u32 sk uid */ nfQaGID /* __u32 sk gid */ nfQaSecCtx /* security context string */ nfQaVLAN /* nested attribute: packet vlan info */ nfQaL2HDR /* full L2 header */ nfQaPriority /* skb->priority */ ) const ( _ = iota nfQaCfgCmd /* nfqnl_msg_config_cmd */ nfQaCfgParams /* nfqnl_msg_config_params */ nfQaCfgQueueMaxLen /* __u32 */ nfQaCfgMask /* identify which flags to change */ nfQaCfgFlags /* value of these flags (__u32) */ ) const ( _ = iota nfUlnlCfgCmdBind nfUlnlCfgCmdUnbind nfUlnlCfgCmdPfBind nfUlnlCfgCmdPfUnbind ) const ( nfQnlMsgPacket = iota nfQnlMsgVerdict /* verdict from userspace to kernel */ nfQnlMsgConfig /* connect to a particular queue */ nfQnlMsgVerdictBatch /* batch from userspace to kernel */ ) // Various configuration flags const ( NfQaCfgFlagFailOpen = (1 << iota) NfQaCfgFlagConntrack = (1 << iota) NfQaCfgFlagGSO = (1 << iota) NfQaCfgFlagUIDGid = (1 << iota) NfQaCfgFlagSecCx = (1 << iota) nfQaCfgFlagMax = (1 << iota) ) // copy modes const ( NfQnlCopyNone = iota NfQnlCopyMeta NfQnlCopyPacket ) // Verdicts const ( NfDrop = iota NfAccept NfStolen NfQeueue NfRepeat ) // conntrack attributes const ( ctaMark = 8 )