├── .github └── workflows │ ├── linux-test.yml │ ├── static-analysis.yml │ ├── test.yml │ └── unsupported.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── doc.go ├── go.mod ├── go.sum ├── packet.go ├── packet_internal_test.go ├── packet_linux.go ├── packet_linux_test.go └── packet_others.go /.github/workflows/linux-test.yml: -------------------------------------------------------------------------------- 1 | name: Linux Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go-version: ["1.18", "1.19", "1.20"] 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Set up Go 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v3 28 | 29 | - name: Build test binary 30 | run: go test -c -race 31 | 32 | # These tests run on Linux only. Apply capabilities and verify we can 33 | # actually use real AF_PACKET sockets. 34 | - name: Apply CAP_NET_RAW 35 | run: sudo setcap cap_net_raw+ep ./packet.test 36 | 37 | - name: Run tests with capabilities 38 | run: ./packet.test -test.v 39 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | go-version: ["1.20"] 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | id: go 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@v3 27 | 28 | - name: Install staticcheck 29 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 30 | 31 | - name: Print staticcheck version 32 | run: staticcheck -version 33 | 34 | - name: Run staticcheck 35 | run: staticcheck ./... 36 | 37 | - name: Install enumcheck 38 | run: go install loov.dev/enumcheck@latest 39 | 40 | - name: Run enumcheck 41 | run: enumcheck ./... 42 | 43 | - name: Run go vet 44 | run: go vet ./... 45 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go-version: ["1.20"] 17 | os: [ubuntu-latest, macos-latest] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - name: Set up Go 22 | uses: actions/setup-go@v3 23 | with: 24 | go-version: ${{ matrix.go-version }} 25 | id: go 26 | 27 | - name: Check out code into the Go module directory 28 | uses: actions/checkout@v3 29 | 30 | # Run basic tests, we just want to make sure there is parity on Linux and 31 | # macOS, and back to the oldest version of Go this library supports. 32 | - name: Run tests 33 | run: go test ./... 34 | -------------------------------------------------------------------------------- /.github/workflows/unsupported.yml: -------------------------------------------------------------------------------- 1 | name: Unsupported 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go-version: ["1.20"] 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Set up Go 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v3 28 | 29 | # Although this package doesn't support Windows, we want to verify that 30 | # everything builds properly. 31 | - name: Verify build for non-UNIX platforms 32 | run: go build 33 | env: 34 | GOOS: windows 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/packet 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | # v1.1.2 4 | 5 | - [Improvement]: updated dependencies, test with Go 1.20. 6 | 7 | # v1.1.1 8 | 9 | - [Bug Fix]: fix test compilation on big endian machines. 10 | 11 | # v1.1.0 12 | 13 | **This is the first release of package packet that only supports Go 1.18+. Users 14 | on older versions of Go must use v1.0.0.** 15 | 16 | - [Improvement]: drop support for older versions of Go so we can begin using 17 | modern versions of `x/sys` and other dependencies. 18 | 19 | ## v1.0.0 20 | 21 | **This is the last release of package vsock that supports Go 1.17 and below.** 22 | 23 | - Initial stable commit! The API is mostly a direct translation of the previous 24 | `github.com/mdlayher/raw` package APIs, with some updates to make everything 25 | focused explicitly on Linux and `AF_PACKET` sockets. Functionally, the two 26 | packages are equivalent, and `*raw.Conn` is now backed by `*packet.Conn` in 27 | the latest version of the `raw` package. 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (C) 2022 Matt Layher 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # packet [![Test Status](https://github.com/mdlayher/packet/workflows/Test/badge.svg)](https://github.com/mdlayher/packet/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/packet.svg)](https://pkg.go.dev/github.com/mdlayher/packet) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/packet)](https://goreportcard.com/report/github.com/mdlayher/packet) 2 | 3 | Package `packet` provides access to Linux packet sockets (`AF_PACKET`). MIT 4 | Licensed. 5 | 6 | ## Stability 7 | 8 | See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between 9 | releases. 10 | 11 | This package has a stable v1 API and any future breaking changes will prompt 12 | the release of a new major version. Features and bug fixes will continue to 13 | occur in the v1.x.x series. 14 | 15 | This package only supports the two most recent major versions of Go, mirroring 16 | Go's own release policy. Older versions of Go may lack critical features and bug 17 | fixes which are necessary for this package to function correctly. 18 | 19 | ## History 20 | 21 | One of my first major Go networking projects was 22 | [`github.com/mdlayher/raw`](https://github.com/mdlayher/raw), which provided 23 | access to Linux `AF_PACKET` sockets and *BSD equivalent mechanisms for sending 24 | and receiving Ethernet frames. However, the *BSD support languished and I lack 25 | the expertise and time to properly maintain code for operating systems I do not 26 | use on a daily basis. 27 | 28 | Package `packet` is a successor to package `raw`, but exclusively focused on 29 | Linux and `AF_PACKET` sockets. The APIs are nearly identical, but with a few 30 | changes which take into account some of the lessons learned while working on 31 | `raw`. 32 | 33 | Users are highly encouraged to migrate any existing Linux uses of `raw` to 34 | package `packet` instead. This package will be supported for the foreseeable 35 | future and will receive continued updates as necessary. 36 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package packet provides access to Linux packet sockets (AF_PACKET). 2 | package packet 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mdlayher/packet 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.9 7 | github.com/josharian/native v1.1.0 8 | github.com/mdlayher/socket v0.4.1 9 | golang.org/x/net v0.9.0 10 | golang.org/x/sys v0.7.0 11 | ) 12 | 13 | require golang.org/x/sync v0.1.0 // indirect 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 2 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= 4 | github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 5 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 6 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 7 | golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= 8 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 9 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 10 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 12 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | "time" 7 | 8 | "golang.org/x/net/bpf" 9 | ) 10 | 11 | const ( 12 | // network is the network reported in net.OpError. 13 | network = "packet" 14 | 15 | // Operation names which may be returned in net.OpError. 16 | opClose = "close" 17 | opGetsockopt = "getsockopt" 18 | opListen = "listen" 19 | opRawControl = "raw-control" 20 | opRawRead = "raw-read" 21 | opRawWrite = "raw-write" 22 | opRead = "read" 23 | opSet = "set" 24 | opSetsockopt = "setsockopt" 25 | opSyscallConn = "syscall-conn" 26 | opWrite = "write" 27 | ) 28 | 29 | // Config contains options for a Conn. 30 | type Config struct { 31 | // Filter is an optional assembled BPF filter which can be applied to the 32 | // Conn before bind(2) is called. 33 | // 34 | // The Conn.SetBPF method serves the same purpose once a Conn has already 35 | // been opened, but setting Filter applies the BPF filter before the Conn is 36 | // bound. This ensures that unexpected packets will not be captured before 37 | // the Conn is opened. 38 | Filter []bpf.RawInstruction 39 | } 40 | 41 | // Type is a socket type used when creating a Conn with Listen. 42 | //enumcheck:exhaustive 43 | type Type int 44 | 45 | // Possible Type values. Note that the zero value is not valid: callers must 46 | // always specify one of Raw or Datagram when calling Listen. 47 | const ( 48 | _ Type = iota 49 | Raw 50 | Datagram 51 | ) 52 | 53 | // Listen opens a packet sockets connection on the specified interface, using 54 | // the given socket type and protocol values. 55 | // 56 | // The socket type must be one of the Type constants: Raw or Datagram. 57 | // 58 | // The Config specifies optional configuration for the Conn. A nil *Config 59 | // applies the default configuration. 60 | func Listen(ifi *net.Interface, socketType Type, protocol int, cfg *Config) (*Conn, error) { 61 | l, err := listen(ifi, socketType, protocol, cfg) 62 | if err != nil { 63 | return nil, opError(opListen, err, &Addr{HardwareAddr: ifi.HardwareAddr}) 64 | } 65 | 66 | return l, nil 67 | } 68 | 69 | // TODO(mdlayher): we want to support FileConn for advanced use cases, but this 70 | // library would also need a big endian protocol value and an interface index. 71 | // For now we won't bother, but reconsider in the future. 72 | 73 | var ( 74 | _ net.PacketConn = &Conn{} 75 | _ syscall.Conn = &Conn{} 76 | _ bpf.Setter = &Conn{} 77 | ) 78 | 79 | // A Conn is an Linux packet sockets (AF_PACKET) implementation of a 80 | // net.PacketConn. 81 | type Conn struct { 82 | c *conn 83 | 84 | // Metadata about the local connection. 85 | addr *Addr 86 | ifIndex int 87 | protocol uint16 88 | } 89 | 90 | // Close closes the connection. 91 | func (c *Conn) Close() error { 92 | return c.opError(opClose, c.c.Close()) 93 | } 94 | 95 | // LocalAddr returns the local network address. The Addr returned is shared by 96 | // all invocations of LocalAddr, so do not modify it. 97 | func (c *Conn) LocalAddr() net.Addr { return c.addr } 98 | 99 | // ReadFrom implements the net.PacketConn ReadFrom method. 100 | func (c *Conn) ReadFrom(b []byte) (int, net.Addr, error) { 101 | return c.readFrom(b) 102 | } 103 | 104 | // WriteTo implements the net.PacketConn WriteTo method. 105 | func (c *Conn) WriteTo(b []byte, addr net.Addr) (int, error) { 106 | return c.writeTo(b, addr) 107 | } 108 | 109 | // SetDeadline implements the net.PacketConn SetDeadline method. 110 | func (c *Conn) SetDeadline(t time.Time) error { 111 | return c.opError(opSet, c.c.SetDeadline(t)) 112 | } 113 | 114 | // SetReadDeadline implements the net.PacketConn SetReadDeadline method. 115 | func (c *Conn) SetReadDeadline(t time.Time) error { 116 | return c.opError(opSet, c.c.SetReadDeadline(t)) 117 | } 118 | 119 | // SetWriteDeadline implements the net.PacketConn SetWriteDeadline method. 120 | func (c *Conn) SetWriteDeadline(t time.Time) error { 121 | return c.opError(opSet, c.c.SetWriteDeadline(t)) 122 | } 123 | 124 | // SetBPF attaches an assembled BPF program to the Conn. 125 | func (c *Conn) SetBPF(filter []bpf.RawInstruction) error { 126 | return c.opError(opSetsockopt, c.c.SetBPF(filter)) 127 | } 128 | 129 | // SetPromiscuous enables or disables promiscuous mode on the Conn, allowing it 130 | // to receive traffic that is not addressed to the Conn's network interface. 131 | func (c *Conn) SetPromiscuous(enable bool) error { 132 | return c.setPromiscuous(enable) 133 | } 134 | 135 | // Stats contains statistics about a Conn reported by the Linux kernel. 136 | type Stats struct { 137 | // The total number of packets received. 138 | Packets uint32 139 | 140 | // The number of packets dropped. 141 | Drops uint32 142 | 143 | // The total number of times that a receive queue is frozen. May be zero if 144 | // the Linux kernel is not new enough to support TPACKET_V3 statistics. 145 | FreezeQueueCount uint32 146 | } 147 | 148 | // Stats retrieves statistics about the Conn from the Linux kernel. 149 | // 150 | // Note that calling Stats will reset the kernel's internal counters for this 151 | // Conn. If you want to maintain cumulative statistics by polling Stats over 152 | // time, you must do so in your calling code. 153 | func (c *Conn) Stats() (*Stats, error) { return c.stats() } 154 | 155 | // SyscallConn returns a raw network connection. This implements the 156 | // syscall.Conn interface. 157 | func (c *Conn) SyscallConn() (syscall.RawConn, error) { 158 | rc, err := c.c.SyscallConn() 159 | if err != nil { 160 | return nil, c.opError(opSyscallConn, err) 161 | } 162 | 163 | return &rawConn{ 164 | rc: rc, 165 | addr: c.addr, 166 | }, nil 167 | } 168 | 169 | // opError is a convenience for the function opError that also passes the local 170 | // and remote addresses of the Conn. 171 | func (c *Conn) opError(op string, err error) error { 172 | return opError(op, err, c.addr) 173 | } 174 | 175 | // TODO(mdlayher): see if we can port smarter net.OpError logic into 176 | // socket.Conn's SyscallConn type to avoid the need for this wrapper. 177 | 178 | var _ syscall.RawConn = &rawConn{} 179 | 180 | // A rawConn is a syscall.RawConn that wraps an internal syscall.RawConn in order 181 | // to produce net.OpError error values. 182 | type rawConn struct { 183 | rc syscall.RawConn 184 | addr *Addr 185 | } 186 | 187 | // Control implements the syscall.RawConn Control method. 188 | func (rc *rawConn) Control(fn func(fd uintptr)) error { 189 | return rc.opError(opRawControl, rc.rc.Control(fn)) 190 | } 191 | 192 | // Control implements the syscall.RawConn Read method. 193 | func (rc *rawConn) Read(fn func(fd uintptr) (done bool)) error { 194 | return rc.opError(opRawRead, rc.rc.Read(fn)) 195 | } 196 | 197 | // Control implements the syscall.RawConn Write method. 198 | func (rc *rawConn) Write(fn func(fd uintptr) (done bool)) error { 199 | return rc.opError(opRawWrite, rc.rc.Write(fn)) 200 | } 201 | 202 | // opError is a convenience for the function opError that also passes the 203 | // address of the rawConn. 204 | func (rc *rawConn) opError(op string, err error) error { 205 | return opError(op, err, rc.addr) 206 | } 207 | 208 | var _ net.Addr = &Addr{} 209 | 210 | // TODO(mdlayher): expose sll_hatype and sll_pkttype on receive Addr only. 211 | 212 | // An Addr is a physical-layer address. 213 | type Addr struct { 214 | HardwareAddr net.HardwareAddr 215 | } 216 | 217 | // Network returns the address's network name, "packet". 218 | func (a *Addr) Network() string { return network } 219 | 220 | // String returns the string representation of an Addr. 221 | func (a *Addr) String() string { 222 | return a.HardwareAddr.String() 223 | } 224 | 225 | // opError unpacks err if possible, producing a net.OpError with the input 226 | // parameters in order to implement net.PacketConn. As a convenience, opError 227 | // returns nil if the input error is nil. 228 | func opError(op string, err error, local net.Addr) error { 229 | if err == nil { 230 | return nil 231 | } 232 | 233 | // TODO(mdlayher): try to comply with net.PacketConn as best as we can; land 234 | // a nettest.TestPacketConn API upstream. 235 | return &net.OpError{ 236 | Op: op, 237 | Net: network, 238 | Addr: local, 239 | Err: err, 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /packet_internal_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package packet 5 | 6 | import ( 7 | "encoding/binary" 8 | "fmt" 9 | "math" 10 | "testing" 11 | 12 | "github.com/google/go-cmp/cmp" 13 | "github.com/josharian/native" 14 | ) 15 | 16 | func Test_htons(t *testing.T) { 17 | tests := []struct { 18 | name string 19 | i int 20 | vLE, vBE uint16 21 | ok bool 22 | }{ 23 | { 24 | name: "negative", 25 | i: -1, 26 | }, 27 | { 28 | name: "too large", 29 | i: math.MaxUint16 + 1, 30 | }, 31 | { 32 | name: "IPv4", 33 | i: 0x0800, 34 | vLE: 0x0008, 35 | vBE: 0x0800, 36 | ok: true, 37 | }, 38 | { 39 | name: "IPv6", 40 | i: 0x86dd, 41 | vLE: 0xdd86, 42 | vBE: 0x86dd, 43 | ok: true, 44 | }, 45 | } 46 | 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | v, err := htons(tt.i) 50 | if tt.ok && err != nil { 51 | t.Fatalf("failed to perform htons: %v", err) 52 | } 53 | if !tt.ok && err == nil { 54 | t.Fatal("expected an error, but none occurred") 55 | } 56 | if err != nil { 57 | t.Logf("err: %v", err) 58 | return 59 | } 60 | 61 | // Depending on our GOARCH, the result may be big or little endian. 62 | var want uint16 63 | if native.Endian == binary.ByteOrder(binary.LittleEndian) { 64 | want = tt.vLE 65 | } else { 66 | want = tt.vBE 67 | } 68 | 69 | if diff := cmp.Diff(hex(want), hex(v)); diff != "" { 70 | t.Fatalf("unexpected output for %s GOARCH (-want +got):\n%s", native.Endian.String(), diff) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func hex(v uint16) string { 77 | return fmt.Sprintf("%#04x", v) 78 | } 79 | -------------------------------------------------------------------------------- /packet_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package packet 5 | 6 | import ( 7 | "context" 8 | "encoding/binary" 9 | "errors" 10 | "math" 11 | "net" 12 | "os" 13 | 14 | "github.com/josharian/native" 15 | "github.com/mdlayher/socket" 16 | "golang.org/x/sys/unix" 17 | ) 18 | 19 | // A conn is the net.PacketConn implementation for packet sockets. We can use 20 | // socket.Conn directly on Linux to implement most of the necessary methods. 21 | type conn = socket.Conn 22 | 23 | // readFrom implements the net.PacketConn ReadFrom method using recvfrom(2). 24 | func (c *Conn) readFrom(b []byte) (int, net.Addr, error) { 25 | // From net.PacketConn documentation: 26 | // 27 | // "[ReadFrom] returns the number of bytes read (0 <= n <= len(p)) and any 28 | // error encountered. Callers should always process the n > 0 bytes returned 29 | // before considering the error err." 30 | // 31 | // c.opError will return nil if no error, but either way we return all the 32 | // information that we have. 33 | n, sa, err := c.c.Recvfrom(context.Background(), b, 0) 34 | return n, fromSockaddr(sa), c.opError(opRead, err) 35 | } 36 | 37 | // writeTo implements the net.PacketConn WriteTo method. 38 | func (c *Conn) writeTo(b []byte, addr net.Addr) (int, error) { 39 | sa, err := c.toSockaddr("sendto", addr) 40 | if err != nil { 41 | return 0, c.opError(opWrite, err) 42 | } 43 | 44 | // TODO(mdlayher): it's curious that unix.Sendto does not return the number 45 | // of bytes actually sent. Fake it for now, but investigate upstream. 46 | if err := c.c.Sendto(context.Background(), b, 0, sa); err != nil { 47 | return 0, c.opError(opWrite, err) 48 | } 49 | 50 | return len(b), nil 51 | } 52 | 53 | // setPromiscuous wraps setsockopt(2) for the unix.PACKET_MR_PROMISC option. 54 | func (c *Conn) setPromiscuous(enable bool) error { 55 | mreq := unix.PacketMreq{ 56 | Ifindex: int32(c.ifIndex), 57 | Type: unix.PACKET_MR_PROMISC, 58 | } 59 | 60 | membership := unix.PACKET_DROP_MEMBERSHIP 61 | if enable { 62 | membership = unix.PACKET_ADD_MEMBERSHIP 63 | } 64 | 65 | return c.opError( 66 | opSetsockopt, 67 | c.c.SetsockoptPacketMreq(unix.SOL_PACKET, membership, &mreq), 68 | ) 69 | } 70 | 71 | // stats wraps getsockopt(2) for tpacket_stats* types. 72 | func (c *Conn) stats() (*Stats, error) { 73 | const ( 74 | level = unix.SOL_PACKET 75 | name = unix.PACKET_STATISTICS 76 | ) 77 | 78 | // Try to fetch V3 statistics first, they contain more detailed information. 79 | if stats, err := c.c.GetsockoptTpacketStatsV3(level, name); err == nil { 80 | return &Stats{ 81 | Packets: stats.Packets, 82 | Drops: stats.Drops, 83 | FreezeQueueCount: stats.Freeze_q_cnt, 84 | }, nil 85 | } 86 | 87 | // There was an error fetching v3 stats, try to fall back. 88 | stats, err := c.c.GetsockoptTpacketStats(level, name) 89 | if err != nil { 90 | return nil, c.opError(opGetsockopt, err) 91 | } 92 | 93 | return &Stats{ 94 | Packets: stats.Packets, 95 | Drops: stats.Drops, 96 | // FreezeQueueCount is not present. 97 | }, nil 98 | } 99 | 100 | // listen is the entry point for Listen on Linux. 101 | func listen(ifi *net.Interface, socketType Type, protocol int, cfg *Config) (*Conn, error) { 102 | if cfg == nil { 103 | // Default configuration. 104 | cfg = &Config{} 105 | } 106 | 107 | // Convert Type to the matching SOCK_* constant. 108 | var typ int 109 | switch socketType { 110 | case Raw: 111 | typ = unix.SOCK_RAW 112 | case Datagram: 113 | typ = unix.SOCK_DGRAM 114 | default: 115 | return nil, errors.New("packet: invalid Type value") 116 | } 117 | 118 | // Protocol is intentionally zero in call to socket(2); we can set it on 119 | // bind(2) instead. Package raw notes: "Do not specify a protocol to avoid 120 | // capturing packets which to not match cfg.Filter." 121 | c, err := socket.Socket(unix.AF_PACKET, typ, 0, network, nil) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | conn, err := bind(c, ifi.Index, protocol, cfg) 127 | if err != nil { 128 | _ = c.Close() 129 | return nil, err 130 | } 131 | 132 | return conn, nil 133 | } 134 | 135 | // bind binds the *socket.Conn to finalize *Conn setup. 136 | func bind(c *socket.Conn, ifIndex, protocol int, cfg *Config) (*Conn, error) { 137 | if len(cfg.Filter) > 0 { 138 | // The caller wants to apply a BPF filter before bind(2). 139 | if err := c.SetBPF(cfg.Filter); err != nil { 140 | return nil, err 141 | } 142 | } 143 | 144 | // packet(7) says we sll_protocol must be in network byte order. 145 | pnet, err := htons(protocol) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | // TODO(mdlayher): investigate the possibility of sll_ifindex = 0 because we 151 | // could bind to any interface. 152 | err = c.Bind(&unix.SockaddrLinklayer{ 153 | Protocol: pnet, 154 | Ifindex: ifIndex, 155 | }) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | lsa, err := c.Getsockname() 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | // Parse the physical layer address; sll_halen tells us how many bytes of 166 | // sll_addr we should treat as valid. 167 | lsall := lsa.(*unix.SockaddrLinklayer) 168 | addr := make(net.HardwareAddr, lsall.Halen) 169 | copy(addr, lsall.Addr[:]) 170 | 171 | return &Conn{ 172 | c: c, 173 | 174 | addr: &Addr{HardwareAddr: addr}, 175 | ifIndex: ifIndex, 176 | protocol: pnet, 177 | }, nil 178 | } 179 | 180 | // fromSockaddr converts an opaque unix.Sockaddr to *Addr. If sa is nil, it 181 | // returns nil. It panics if sa is not of type *unix.SockaddrLinklayer. 182 | func fromSockaddr(sa unix.Sockaddr) *Addr { 183 | if sa == nil { 184 | return nil 185 | } 186 | 187 | sall := sa.(*unix.SockaddrLinklayer) 188 | 189 | return &Addr{ 190 | // The syscall already allocated sa; just slice into it with the 191 | // appropriate length and type conversion rather than making a copy. 192 | HardwareAddr: net.HardwareAddr(sall.Addr[:sall.Halen]), 193 | } 194 | } 195 | 196 | // toSockaddr converts a net.Addr to an opaque unix.Sockaddr. It returns an 197 | // error if the fields cannot be packed into a *unix.SockaddrLinklayer. 198 | func (c *Conn) toSockaddr( 199 | op string, 200 | addr net.Addr, 201 | ) (unix.Sockaddr, error) { 202 | // The typical error convention for net.Conn types is 203 | // net.OpError(os.SyscallError(syscall.Errno)), so all calls here should 204 | // return os.SyscallError(syscall.Errno) so the caller can apply the final 205 | // net.OpError wrapper. 206 | 207 | // Ensure the correct Addr type. 208 | a, ok := addr.(*Addr) 209 | if !ok || a.HardwareAddr == nil { 210 | return nil, os.NewSyscallError(op, unix.EINVAL) 211 | } 212 | 213 | // Pack Addr and Conn metadata into the appropriate sockaddr fields. From 214 | // packet(7): 215 | // 216 | // "When you send packets it is enough to specify sll_family, sll_addr, 217 | // sll_halen, sll_ifindex, and sll_protocol. The other fields should be 0." 218 | // 219 | // sll_family is set on the conversion to unix.RawSockaddrLinklayer. 220 | sa := unix.SockaddrLinklayer{ 221 | Ifindex: c.ifIndex, 222 | Protocol: c.protocol, 223 | } 224 | 225 | // Ensure the input address does not exceed the amount of space available; 226 | // for example an IPoIB address is 20 bytes. 227 | if len(a.HardwareAddr) > len(sa.Addr) { 228 | return nil, os.NewSyscallError(op, unix.EINVAL) 229 | } 230 | 231 | sa.Halen = uint8(len(a.HardwareAddr)) 232 | copy(sa.Addr[:], a.HardwareAddr) 233 | 234 | return &sa, nil 235 | } 236 | 237 | // htons converts a short (uint16) from host-to-network byte order. 238 | func htons(i int) (uint16, error) { 239 | if i < 0 || i > math.MaxUint16 { 240 | return 0, errors.New("packet: protocol value out of range") 241 | } 242 | 243 | // Store as big endian, retrieve as native endian. 244 | var b [2]byte 245 | binary.BigEndian.PutUint16(b[:], uint16(i)) 246 | 247 | return native.Endian.Uint16(b[:]), nil 248 | } 249 | -------------------------------------------------------------------------------- /packet_linux_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.16 2 | // +build go1.16 3 | 4 | // Just because the library builds and runs on older versions of Go doesn't mean 5 | // we have to apply the same restrictions for tests. Go 1.16 is the oldest 6 | // upstream supported version of Go as of February 2022. 7 | 8 | package packet_test 9 | 10 | import ( 11 | "encoding/binary" 12 | "errors" 13 | "net" 14 | "os" 15 | "testing" 16 | "time" 17 | 18 | "github.com/mdlayher/packet" 19 | "golang.org/x/sys/unix" 20 | ) 21 | 22 | func TestConnListen(t *testing.T) { 23 | // Open a connection on an Ethernet interface and begin listening for 24 | // incoming Ethernet frames. We assume that this interface will receive some 25 | // sort of traffic in the next 30 seconds and we will capture that traffic 26 | // by looking for any EtherType value (ETH_P_ALL). 27 | c, ifi := testConn(t) 28 | t.Logf("interface: %q, MTU: %d", ifi.Name, ifi.MTU) 29 | 30 | if err := c.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil { 31 | t.Fatalf("failed to set read deadline: %v", err) 32 | } 33 | 34 | b := make([]byte, ifi.MTU) 35 | n, addr, err := c.ReadFrom(b) 36 | if err != nil { 37 | t.Fatalf("failed to read Ethernet frame: %v", err) 38 | } 39 | 40 | // Received some data, assume some Stats were populated. 41 | stats, err := c.Stats() 42 | if err != nil { 43 | t.Fatalf("failed to fetch stats: %v", err) 44 | } 45 | if stats.Packets == 0 { 46 | t.Fatal("stats indicated 0 received packets") 47 | } 48 | 49 | t.Logf(" - packets: %d, drops: %d, freeze queue count: %d", 50 | stats.Packets, stats.Drops, stats.FreezeQueueCount) 51 | 52 | // TODO(mdlayher): we could import github.com/mdlayher/ethernet, but parsing 53 | // an Ethernet frame header is fairly easy and this keeps the go.mod tidy. 54 | 55 | // Need at least destination MAC, source MAC, and EtherType. 56 | const header = 6 + 6 + 2 57 | if n < header { 58 | t.Fatalf("did not read a complete Ethernet frame from %v, only %d bytes read", 59 | addr, n) 60 | } 61 | 62 | // Parse the header to provide tidy log output. 63 | var ( 64 | dst = net.HardwareAddr(b[0:6]) 65 | src = net.HardwareAddr(b[6:12]) 66 | et = binary.BigEndian.Uint16(b[12:14]) 67 | ) 68 | 69 | // Check for the most likely EtherType values. 70 | var ets string 71 | switch et { 72 | case 0x0800: 73 | ets = "IPv4" 74 | case 0x0806: 75 | ets = "ARP" 76 | case 0x86dd: 77 | ets = "IPv6" 78 | default: 79 | ets = "unknown" 80 | } 81 | 82 | // And finally print what we found for the user. 83 | t.Log("Ethernet frame:") 84 | t.Logf(" - destination: %s", dst) 85 | t.Logf(" - source: %s", src) 86 | t.Logf(" - ethertype: %#04x (%s)", et, ets) 87 | t.Logf(" - payload: %d bytes", n-header) 88 | } 89 | 90 | // testConn produces a *packet.Conn bound to the returned *net.Interface. The 91 | // caller does not need to call Close on the *packet.Conn. 92 | func testConn(t *testing.T) (*packet.Conn, *net.Interface) { 93 | t.Helper() 94 | 95 | // TODO(mdlayher): probably parameterize the EtherType. 96 | ifi := testInterface(t) 97 | c, err := packet.Listen(ifi, packet.Raw, unix.ETH_P_ALL, nil) 98 | if err != nil { 99 | if errors.Is(err, os.ErrPermission) { 100 | t.Skipf("skipping, permission denied (try setting CAP_NET_RAW capability): %v", err) 101 | } 102 | 103 | t.Fatalf("failed to listen: %v", err) 104 | } 105 | 106 | t.Cleanup(func() { c.Close() }) 107 | return c, ifi 108 | } 109 | 110 | // testInterface looks for a suitable Ethernet interface to bind a *packet.Conn. 111 | func testInterface(t *testing.T) *net.Interface { 112 | ifis, err := net.Interfaces() 113 | if err != nil { 114 | t.Fatalf("failed to get network interfaces: %v", err) 115 | } 116 | 117 | if len(ifis) == 0 { 118 | t.Skip("skipping, no network interfaces found") 119 | } 120 | 121 | // Try to find a suitable network interface for tests. 122 | var tried []string 123 | for _, ifi := range ifis { 124 | tried = append(tried, ifi.Name) 125 | 126 | // true is used to line up other checks. 127 | ok := true && 128 | // Look for an Ethernet interface. 129 | len(ifi.HardwareAddr) == 6 && 130 | // Look for up, multicast, broadcast. 131 | ifi.Flags&(net.FlagUp|net.FlagMulticast|net.FlagBroadcast) != 0 132 | 133 | if ok { 134 | return &ifi 135 | } 136 | } 137 | 138 | t.Skipf("skipping, could not find a usable network interface, tried: %s", tried) 139 | panic("unreachable") 140 | } 141 | -------------------------------------------------------------------------------- /packet_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package packet 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | "runtime" 10 | "syscall" 11 | "time" 12 | 13 | "golang.org/x/net/bpf" 14 | ) 15 | 16 | // errUnimplemented is returned by all functions on non-Linux platforms. 17 | var errUnimplemented = fmt.Errorf("packet: not implemented on %s", runtime.GOOS) 18 | 19 | func listen(_ *net.Interface, _ Type, _ int, _ *Config) (*Conn, error) { return nil, errUnimplemented } 20 | 21 | func (*Conn) readFrom(_ []byte) (int, net.Addr, error) { return 0, nil, errUnimplemented } 22 | func (*Conn) writeTo(_ []byte, _ net.Addr) (int, error) { return 0, errUnimplemented } 23 | func (*Conn) setPromiscuous(_ bool) error { return errUnimplemented } 24 | func (*Conn) stats() (*Stats, error) { return nil, errUnimplemented } 25 | 26 | type conn struct{} 27 | 28 | func (*conn) Close() error { return errUnimplemented } 29 | func (*conn) SetDeadline(_ time.Time) error { return errUnimplemented } 30 | func (*conn) SetReadDeadline(_ time.Time) error { return errUnimplemented } 31 | func (*conn) SetWriteDeadline(_ time.Time) error { return errUnimplemented } 32 | func (*conn) SetBPF(_ []bpf.RawInstruction) error { return errUnimplemented } 33 | func (*conn) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented } 34 | --------------------------------------------------------------------------------