├── .github └── workflows │ ├── go.yml │ └── sourcegraph-lsif-indexing.yml ├── LICENSE ├── README.md ├── attribute.go ├── attributeExpect.go ├── attributeStats.go ├── bpf.go ├── bpf_test.go ├── conntrack.go ├── conntrack_linux_integration_test.go ├── conntrack_test.go ├── doc.go ├── example_DumpCPUStats_test.go ├── example_Dump_test.go ├── example_Register_test.go ├── export_test.go ├── filter.go ├── filter_test.go ├── go.mod ├── go.sum ├── internal └── unix │ ├── doc.go │ ├── types_linux.go │ └── types_other.go ├── nest.go ├── types.go └── types_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ main ] 4 | pull_request: 5 | branches: [ '**' ] 6 | 7 | name: Go 8 | jobs: 9 | 10 | test: 11 | strategy: 12 | matrix: 13 | go-version: [1.13.x, 1.23.x, 1.24.x] 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | exclude: 16 | # There is no arm64 version of Go for darwin. 17 | # This exclude can be removed if the minimal Go version 18 | # is > Go 1.16. 19 | - go-version: "1.13.x" 20 | platform: "macos-latest" 21 | runs-on: ${{ matrix.platform }} 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v3 25 | - name: Install Go 26 | uses: actions/setup-go@v4 27 | with: 28 | go-version: ${{ matrix.go-version }} 29 | - name: Download Go dependencies 30 | env: 31 | GOPROXY: "https://proxy.golang.org" 32 | run: go mod download 33 | - name: Test 34 | run: go test -count=1 ./... 35 | - name: Test with -race 36 | run: go test -race -count=1 ./... 37 | - name: Integration test 38 | if: matrix.platform == 'ubuntu-latest' 39 | run: go test -exec=sudo -tags integration ./... 40 | - name: gofmt check 41 | if: matrix.platform == 'ubuntu-latest' && startsWith(matrix.go-version, '1.24') 42 | run: diff <(echo -n) <(gofmt -d .) 43 | - name: staticcheck.io 44 | if: matrix.platform == 'ubuntu-latest' && startsWith(matrix.go-version, '1.24') 45 | uses: dominikh/staticcheck-action@v1.3.0 46 | with: 47 | version: "2025.1" 48 | install-go: false 49 | cache-key: ${{ matrix.go-version }} 50 | working-directory: . 51 | - name: staticcheck.io - internal 52 | if: matrix.platform == 'ubuntu-latest' && startsWith(matrix.go-version, '1.24') 53 | uses: dominikh/staticcheck-action@v1.3.1 54 | with: 55 | version: "2025.1" 56 | install-go: false 57 | cache-key: ${{ matrix.go-version }} 58 | working-directory: internal 59 | # ignore should not use ALL_CAPS in Go names in internal/ 60 | checks: all,-ST1003 61 | -------------------------------------------------------------------------------- /.github/workflows/sourcegraph-lsif-indexing.yml: -------------------------------------------------------------------------------- 1 | name: "sourcegraph LSIF indexing" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | # Set default permissions as read only. 8 | permissions: read-all 9 | 10 | jobs: 11 | lsif-go: 12 | runs-on: ubuntu-latest 13 | container: sourcegraph/lsif-go:latest 14 | steps: 15 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 16 | - name: Generate LSIF data 17 | run: lsif-go 18 | - name: Upload LSIF data 19 | # this will upload to Sourcegraph.com, you may need to substitute a different command. 20 | # by default, we ignore failures to avoid disrupting CI pipelines with non-critical errors. 21 | run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} -ignore-upload-failure 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (C) 2018-2020 Florian Lehner 5 | 6 | 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: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | 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. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-conntrack [![PkgGoDev](https://pkg.go.dev/badge/github.com/florianl/go-conntrack)](https://pkg.go.dev/github.com/florianl/go-conntrack) [![Go Report Card](https://goreportcard.com/badge/github.com/florianl/go-conntrack)](https://goreportcard.com/report/github.com/florianl/go-conntrack) [![Go](https://github.com/florianl/go-conntrack/actions/workflows/go.yml/badge.svg)](https://github.com/florianl/go-conntrack/actions/workflows/go.yml) 2 | ============ 3 | 4 | This is `go-conntrack` 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 conntrack subsystem of the [Linux kernel](https://www.kernel.org). 5 | 6 | ## Example 7 | 8 | ```golang 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/florianl/go-conntrack" 15 | ) 16 | 17 | func main() { 18 | nfct, err := conntrack.Open(&conntrack.Config{}) 19 | if err != nil { 20 | fmt.Println("could not create nfct:", err) 21 | return 22 | } 23 | defer nfct.Close() 24 | 25 | // Get all IPv4 entries of the expected table. 26 | sessions, err := nfct.Dump(conntrack.Expected, conntrack.IPv4) 27 | if err != nil { 28 | fmt.Println("could not dump sessions:", err) 29 | return 30 | } 31 | 32 | // Print out all expected sessions. 33 | for _, session := range sessions { 34 | fmt.Printf("%#v\n", session) 35 | } 36 | } 37 | ``` 38 | 39 | ## Requirements 40 | 41 | * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) 42 | -------------------------------------------------------------------------------- /attribute.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/florianl/go-conntrack/internal/unix" 10 | 11 | "github.com/mdlayher/netlink" 12 | ) 13 | 14 | const ( 15 | ctaUnspec = iota 16 | ctaTupleOrig 17 | ctaTupleReply 18 | ctaStatus 19 | ctaProtoinfo 20 | ctaHelp 21 | ctaNatSrc 22 | ctaTimeout 23 | ctaMark 24 | ctaCountersOrig 25 | ctaCountersReply 26 | ctaUse 27 | ctaID 28 | ctaNatDst 29 | ctaTupleMaster 30 | ctaSeqAdjOrig 31 | ctaSeqAdjRepl 32 | ctaSecmark 33 | ctaZone 34 | ctaSecCtx 35 | ctaTimestamp 36 | ctaMarkMask 37 | ctaLables 38 | ctaLablesMask 39 | ctaSynProxy 40 | ctaFilter 41 | ctaStatusMask 42 | ) 43 | 44 | const ( 45 | ctaTupleIP = 1 46 | ctaTupleProto = 2 47 | ctaTupleZone = 3 48 | ) 49 | 50 | const ( 51 | ctaIPv4Src = 1 52 | ctaIPv4Dst = 2 53 | ctaIPv6Src = 3 54 | ctaIPv6Dst = 4 55 | ) 56 | 57 | const ( 58 | ctaProtoNum = 1 59 | ctaProtoSrcPort = 2 60 | ctaProtoDstPort = 3 61 | ctaProtoIcmpID = 4 62 | ctaProtoIcmpType = 5 63 | ctaProtoIcmpCode = 6 64 | ctaProtoIcmpv6ID = 7 65 | ctaProtoIcmpv6Type = 8 66 | ctaProtoIcmpv6Code = 9 67 | ) 68 | 69 | const ( 70 | ctaProtoinfoTCP = 1 71 | ctaProtoinfoDCCP = 2 72 | ctaProtoinfoSCTP = 3 73 | ) 74 | 75 | const ( 76 | ctaProtoinfoTCPState = 1 77 | ctaProtoinfoTCPWScaleOrig = 2 78 | ctaProtoinfoTCPWScaleRepl = 3 79 | ctaProtoinfoTCPFlagsOrig = 4 80 | ctaProtoinfoTCPFlagsRepl = 5 81 | ) 82 | 83 | const ( 84 | ctaProtoinfoDCCPState = 1 85 | ctaProtoinfoDCCPRole = 2 86 | ctaProtoinfoDCCPHandshakeSeq = 3 87 | ) 88 | 89 | const ( 90 | ctaProtoinfoSCTPState = 1 91 | ctaProtoinfoSCTPVTagOriginal = 2 92 | ctaProtoinfoSCTPVTagReply = 3 93 | ) 94 | 95 | const ( 96 | ctaCounterPackets = 1 97 | ctaCounterBytes = 2 98 | ctaCounter32Packets = 3 99 | ctaCounter32Bytes = 4 100 | ) 101 | 102 | const ( 103 | ctaTimestampStart = 1 104 | ctaTimestampStop = 2 105 | ) 106 | 107 | const ( 108 | ctaSecCtxName = 1 109 | ) 110 | 111 | const ( 112 | ctaHelpName = 1 113 | ctaHelpInfo = 2 114 | ) 115 | 116 | const ( 117 | ctaSeqAdjCorrPos = 1 118 | ctaSeqAdjOffsetBefore = 2 119 | ctaSeqAdjOffsetAfter = 3 120 | ) 121 | 122 | const ( 123 | ctaNatV4MinIP = 1 124 | ctaNatV4MaxIP = 2 125 | ctaNatProto = 3 126 | ctaNatV6MinIP = 4 127 | ctaNatV6MaxIP = 5 128 | ) 129 | 130 | const nlafNested = (1 << 15) 131 | 132 | func extractSecCtx(v *SecCtx, logger *log.Logger, data []byte) error { 133 | ad, err := netlink.NewAttributeDecoder(data) 134 | if err != nil { 135 | return err 136 | } 137 | ad.ByteOrder = binary.BigEndian 138 | for ad.Next() { 139 | switch ad.Type() { 140 | case ctaSecCtxName: 141 | tmp := ad.String() 142 | v.Name = &tmp 143 | default: 144 | logger.Printf("extractSecCtx(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 145 | } 146 | } 147 | return ad.Err() 148 | } 149 | 150 | func extractTimestamp(v *Timestamp, logger *log.Logger, data []byte) error { 151 | ad, err := netlink.NewAttributeDecoder(data) 152 | if err != nil { 153 | return err 154 | } 155 | ad.ByteOrder = binary.BigEndian 156 | for ad.Next() { 157 | switch ad.Type() { 158 | case ctaTimestampStart: 159 | tmp := ad.Uint64() 160 | ts := time.Unix(0, int64(tmp)) 161 | v.Start = &ts 162 | case ctaTimestampStop: 163 | tmp := ad.Uint64() 164 | ts := time.Unix(0, int64(tmp)) 165 | v.Stop = &ts 166 | default: 167 | logger.Printf("extractTimestamp(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 168 | } 169 | } 170 | return ad.Err() 171 | } 172 | 173 | func extractCounter(v *Counter, logger *log.Logger, data []byte) error { 174 | ad, err := netlink.NewAttributeDecoder(data) 175 | if err != nil { 176 | return err 177 | } 178 | ad.ByteOrder = binary.BigEndian 179 | for ad.Next() { 180 | switch ad.Type() { 181 | case ctaCounterPackets: 182 | tmp := ad.Uint64() 183 | v.Packets = &tmp 184 | case ctaCounterBytes: 185 | tmp := ad.Uint64() 186 | v.Bytes = &tmp 187 | case ctaCounter32Packets: 188 | tmp := ad.Uint32() 189 | v.Packets32 = &tmp 190 | case ctaCounter32Bytes: 191 | tmp := ad.Uint32() 192 | v.Bytes32 = &tmp 193 | default: 194 | logger.Printf("extractCounter(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 195 | } 196 | } 197 | return ad.Err() 198 | } 199 | 200 | func extractDCCPInfo(v *DCCPInfo, logger *log.Logger, data []byte) error { 201 | ad, err := netlink.NewAttributeDecoder(data) 202 | if err != nil { 203 | return err 204 | } 205 | ad.ByteOrder = binary.BigEndian 206 | for ad.Next() { 207 | switch ad.Type() { 208 | case ctaProtoinfoDCCPState: 209 | tmp := ad.Uint8() 210 | v.State = &tmp 211 | case ctaProtoinfoDCCPRole: 212 | tmp := ad.Uint8() 213 | v.Role = &tmp 214 | case ctaProtoinfoDCCPHandshakeSeq: 215 | tmp := ad.Uint64() 216 | v.HandshakeSeq = &tmp 217 | default: 218 | logger.Printf("extractDCCPInfo(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 219 | } 220 | } 221 | return ad.Err() 222 | } 223 | 224 | func extractSCTPInfo(v *SCTPInfo, logger *log.Logger, data []byte) error { 225 | ad, err := netlink.NewAttributeDecoder(data) 226 | if err != nil { 227 | return err 228 | } 229 | ad.ByteOrder = binary.BigEndian 230 | for ad.Next() { 231 | switch ad.Type() { 232 | case ctaProtoinfoSCTPState: 233 | tmp := ad.Uint8() 234 | v.State = &tmp 235 | case ctaProtoinfoSCTPVTagOriginal: 236 | tmp := ad.Uint32() 237 | v.VTagOriginal = &tmp 238 | case ctaProtoinfoSCTPVTagReply: 239 | tmp := ad.Uint32() 240 | v.VTagReply = &tmp 241 | default: 242 | logger.Printf("extractSCTPInfo(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 243 | } 244 | } 245 | return ad.Err() 246 | } 247 | 248 | func extractSeqAdj(v *SeqAdj, logger *log.Logger, data []byte) error { 249 | ad, err := netlink.NewAttributeDecoder(data) 250 | if err != nil { 251 | return err 252 | } 253 | ad.ByteOrder = binary.BigEndian 254 | for ad.Next() { 255 | switch ad.Type() { 256 | case ctaSeqAdjCorrPos: 257 | tmp := ad.Uint32() 258 | v.CorrectionPos = &tmp 259 | case ctaSeqAdjOffsetBefore: 260 | tmp := ad.Uint32() 261 | v.OffsetBefore = &tmp 262 | case ctaSeqAdjOffsetAfter: 263 | tmp := ad.Uint32() 264 | v.OffsetAfter = &tmp 265 | default: 266 | logger.Printf("extractSeqAdj(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 267 | } 268 | } 269 | return ad.Err() 270 | } 271 | 272 | func extractNat(v *Nat, logger *log.Logger, data []byte) error { 273 | ad, err := netlink.NewAttributeDecoder(data) 274 | if err != nil { 275 | return err 276 | } 277 | ad.ByteOrder = binary.BigEndian 278 | for ad.Next() { 279 | switch ad.Type() { 280 | case ctaNatV4MinIP: 281 | tmp := net.IP(ad.Bytes()) 282 | v.IPMin = &tmp 283 | case ctaNatV4MaxIP: 284 | tmp := net.IP(ad.Bytes()) 285 | v.IPMax = &tmp 286 | case ctaNatV6MinIP: 287 | tmp := net.IP(ad.Bytes()) 288 | v.IPMin = &tmp 289 | case ctaNatV6MaxIP: 290 | tmp := net.IP(ad.Bytes()) 291 | v.IPMax = &tmp 292 | default: 293 | logger.Printf("extractNat(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 294 | } 295 | } 296 | return ad.Err() 297 | } 298 | 299 | func marshalNat(logger *log.Logger, v *Nat) ([]byte, error) { 300 | ae := netlink.NewAttributeEncoder() 301 | 302 | if v.IPMin != nil { 303 | if v.IPMin.To4() == nil && v.IPMin.To16() != nil { 304 | ae.Bytes(ctaNatV6MinIP, *v.IPMin) 305 | } else { 306 | tmp := (*v.IPMin).To4() 307 | ae.Bytes(ctaNatV4MinIP, tmp) 308 | } 309 | } 310 | if v.IPMax != nil { 311 | if v.IPMax.To4() == nil && v.IPMax.To16() != nil { 312 | ae.Bytes(ctaNatV6MaxIP, *v.IPMax) 313 | } else { 314 | tmp := (*v.IPMax).To4() 315 | ae.Bytes(ctaNatV4MaxIP, tmp) 316 | } 317 | } 318 | return ae.Encode() 319 | } 320 | 321 | func extractTCPInfo(v *TCPInfo, logger *log.Logger, data []byte) error { 322 | ad, err := netlink.NewAttributeDecoder(data) 323 | if err != nil { 324 | return err 325 | } 326 | ad.ByteOrder = binary.BigEndian 327 | for ad.Next() { 328 | switch ad.Type() { 329 | case ctaProtoinfoTCPState: 330 | tmp := ad.Uint8() 331 | v.State = &tmp 332 | case ctaProtoinfoTCPWScaleOrig: 333 | tmp := ad.Uint8() 334 | v.WScaleOrig = &tmp 335 | case ctaProtoinfoTCPWScaleRepl: 336 | tmp := ad.Uint8() 337 | v.WScaleRepl = &tmp 338 | case ctaProtoinfoTCPFlagsOrig: 339 | flags := &TCPFlags{} 340 | tmp := ad.Bytes() 341 | if len(tmp) > 0 { 342 | flags.Flags = &tmp[0] 343 | } 344 | if len(tmp) > 1 { 345 | flags.Mask = &tmp[1] 346 | } 347 | v.FlagsOrig = flags 348 | case ctaProtoinfoTCPFlagsRepl: 349 | flags := &TCPFlags{} 350 | tmp := ad.Bytes() 351 | if len(tmp) > 0 { 352 | flags.Flags = &tmp[0] 353 | } 354 | if len(tmp) > 1 { 355 | flags.Mask = &tmp[1] 356 | } 357 | v.FlagsReply = flags 358 | default: 359 | logger.Printf("extractTCPInfo(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 360 | } 361 | } 362 | return ad.Err() 363 | } 364 | 365 | func marshalTCPInfo(logger *log.Logger, v *TCPInfo) ([]byte, error) { 366 | ae := netlink.NewAttributeEncoder() 367 | 368 | if v.State != nil { 369 | ae.ByteOrder = binary.BigEndian 370 | ae.Uint8(ctaProtoinfoTCPState, *v.State) 371 | ae.ByteOrder = nativeEndian 372 | } 373 | if v.WScaleOrig != nil { 374 | ae.ByteOrder = binary.BigEndian 375 | ae.Uint8(ctaProtoinfoTCPWScaleOrig, *v.WScaleOrig) 376 | ae.ByteOrder = nativeEndian 377 | } 378 | if v.WScaleRepl != nil { 379 | ae.ByteOrder = binary.BigEndian 380 | ae.Uint8(ctaProtoinfoTCPWScaleRepl, *v.WScaleRepl) 381 | ae.ByteOrder = nativeEndian 382 | } 383 | if v.FlagsOrig != nil { 384 | tmp := []byte{0x00, 0xff} 385 | if v.FlagsOrig.Flags != nil { 386 | tmp[0] = *v.FlagsOrig.Flags 387 | } 388 | if v.FlagsOrig.Mask != nil { 389 | tmp[1] = *v.FlagsOrig.Mask 390 | } 391 | ae.Bytes(ctaProtoinfoTCPFlagsOrig, tmp) 392 | } 393 | if v.FlagsReply != nil { 394 | tmp := []byte{0x00, 0xff} 395 | if v.FlagsReply.Flags != nil { 396 | tmp[0] = *v.FlagsReply.Flags 397 | } 398 | if v.FlagsReply.Mask != nil { 399 | tmp[1] = *v.FlagsReply.Mask 400 | } 401 | ae.Bytes(ctaProtoinfoTCPFlagsRepl, tmp) 402 | } 403 | 404 | return ae.Encode() 405 | } 406 | 407 | func extractProtoInfo(v *ProtoInfo, logger *log.Logger, data []byte) error { 408 | ad, err := netlink.NewAttributeDecoder(data) 409 | if err != nil { 410 | return err 411 | } 412 | ad.ByteOrder = nativeEndian 413 | for ad.Next() { 414 | switch ad.Type() { 415 | case ctaProtoinfoTCP: 416 | tcp := &TCPInfo{} 417 | if err := extractTCPInfo(tcp, logger, ad.Bytes()); err != nil { 418 | return err 419 | } 420 | v.TCP = tcp 421 | case ctaProtoinfoDCCP: 422 | dccp := &DCCPInfo{} 423 | if err := extractDCCPInfo(dccp, logger, ad.Bytes()); err != nil { 424 | return err 425 | } 426 | v.DCCP = dccp 427 | case ctaProtoinfoSCTP: 428 | sctp := &SCTPInfo{} 429 | if err := extractSCTPInfo(sctp, logger, ad.Bytes()); err != nil { 430 | return err 431 | } 432 | v.SCTP = sctp 433 | default: 434 | logger.Printf("extractProtoInfo(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 435 | } 436 | } 437 | return ad.Err() 438 | } 439 | 440 | func marshalProtoInfo(logger *log.Logger, v *ProtoInfo) ([]byte, error) { 441 | ae := netlink.NewAttributeEncoder() 442 | 443 | if v.TCP != nil { 444 | data, err := marshalTCPInfo(logger, v.TCP) 445 | if err != nil { 446 | return []byte{}, err 447 | } 448 | ae.Bytes(ctaProtoinfoTCP|nlafNested, data) 449 | } 450 | 451 | return ae.Encode() 452 | } 453 | 454 | func extractHelper(v *Helper, logger *log.Logger, data []byte) error { 455 | ad, err := netlink.NewAttributeDecoder(data) 456 | if err != nil { 457 | return err 458 | } 459 | ad.ByteOrder = binary.BigEndian 460 | for ad.Next() { 461 | switch ad.Type() { 462 | case ctaHelpName: 463 | tmp := ad.String() 464 | v.Name = &tmp 465 | case ctaHelpInfo: 466 | tmp := ad.String() 467 | v.Info = &tmp 468 | default: 469 | logger.Printf("extractHelper(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 470 | } 471 | } 472 | return ad.Err() 473 | } 474 | 475 | func marshalHelper(logger *log.Logger, v *Helper) ([]byte, error) { 476 | ae := netlink.NewAttributeEncoder() 477 | 478 | if v.Name != nil { 479 | ae.String(ctaHelpName, *v.Name) 480 | } 481 | if v.Info != nil { 482 | ae.String(ctaHelpInfo, *v.Info) 483 | } 484 | 485 | return ae.Encode() 486 | } 487 | 488 | func extractProtoTuple(logger *log.Logger, data []byte) (ProtoTuple, error) { 489 | var proto ProtoTuple 490 | ad, err := netlink.NewAttributeDecoder(data) 491 | if err != nil { 492 | return proto, err 493 | } 494 | ad.ByteOrder = binary.BigEndian 495 | for ad.Next() { 496 | switch ad.Type() { 497 | case ctaProtoNum: 498 | tmp := ad.Uint8() 499 | proto.Number = &tmp 500 | case ctaProtoSrcPort: 501 | tmp := ad.Uint16() 502 | proto.SrcPort = &tmp 503 | case ctaProtoDstPort: 504 | tmp := ad.Uint16() 505 | proto.DstPort = &tmp 506 | case ctaProtoIcmpID: 507 | tmp := ad.Uint16() 508 | proto.IcmpID = &tmp 509 | case ctaProtoIcmpType: 510 | tmp := ad.Uint8() 511 | proto.IcmpType = &tmp 512 | case ctaProtoIcmpCode: 513 | tmp := ad.Uint8() 514 | proto.IcmpCode = &tmp 515 | case ctaProtoIcmpv6ID: 516 | tmp := ad.Uint16() 517 | proto.Icmpv6ID = &tmp 518 | case ctaProtoIcmpv6Type: 519 | tmp := ad.Uint8() 520 | proto.Icmpv6Type = &tmp 521 | case ctaProtoIcmpv6Code: 522 | tmp := ad.Uint8() 523 | proto.Icmpv6Code = &tmp 524 | default: 525 | logger.Printf("extractProtoTuple(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 526 | } 527 | } 528 | return proto, ad.Err() 529 | } 530 | 531 | func marshalProtoTuple(logger *log.Logger, v *ProtoTuple) ([]byte, error) { 532 | ae := netlink.NewAttributeEncoder() 533 | ae.ByteOrder = binary.BigEndian 534 | if v.Number != nil { 535 | ae.Uint8(ctaProtoNum, *v.Number) 536 | } 537 | if v.SrcPort != nil { 538 | ae.Uint16(ctaProtoSrcPort, *v.SrcPort) 539 | } 540 | if v.DstPort != nil { 541 | ae.Uint16(ctaProtoDstPort, *v.DstPort) 542 | } 543 | if v.IcmpID != nil { 544 | ae.Uint16(ctaProtoIcmpID, *v.IcmpID) 545 | } 546 | if v.IcmpType != nil { 547 | ae.Uint8(ctaProtoIcmpType, *v.IcmpType) 548 | } 549 | if v.IcmpCode != nil { 550 | ae.Uint8(ctaProtoIcmpCode, *v.IcmpCode) 551 | } 552 | if v.Icmpv6ID != nil { 553 | ae.Uint16(ctaProtoIcmpv6ID, *v.Icmpv6ID) 554 | } 555 | if v.Icmpv6Type != nil { 556 | ae.Uint8(ctaProtoIcmpv6Type, *v.Icmpv6Type) 557 | } 558 | if v.Icmpv6Code != nil { 559 | ae.Uint8(ctaProtoIcmpv6Code, *v.Icmpv6Code) 560 | } 561 | 562 | return ae.Encode() 563 | } 564 | 565 | func extractIP(logger *log.Logger, data []byte) (net.IP, net.IP, error) { 566 | var src, dst net.IP 567 | ad, err := netlink.NewAttributeDecoder(data) 568 | if err != nil { 569 | return src, dst, err 570 | } 571 | ad.ByteOrder = nativeEndian 572 | for ad.Next() { 573 | switch ad.Type() { 574 | case ctaIPv4Src: 575 | src = net.IP(ad.Bytes()) 576 | case ctaIPv4Dst: 577 | dst = net.IP(ad.Bytes()) 578 | case ctaIPv6Src: 579 | src = net.IP(ad.Bytes()) 580 | case ctaIPv6Dst: 581 | dst = net.IP(ad.Bytes()) 582 | default: 583 | logger.Printf("extractIP(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 584 | } 585 | } 586 | return src, dst, ad.Err() 587 | } 588 | 589 | func marshalIP(logger *log.Logger, v *IPTuple) ([]byte, error) { 590 | ae := netlink.NewAttributeEncoder() 591 | 592 | if v.Src != nil { 593 | if v.Src.To4() == nil && v.Src.To16() != nil { 594 | ae.Bytes(ctaIPv6Src, *v.Src) 595 | } else { 596 | tmp := (*v.Src).To4() 597 | ae.Bytes(ctaIPv4Src, tmp) 598 | } 599 | } 600 | 601 | if v.Dst != nil { 602 | if v.Dst.To4() == nil && v.Dst.To16() != nil { 603 | ae.Bytes(ctaIPv6Dst, *v.Dst) 604 | } else { 605 | tmp := (*v.Dst).To4() 606 | ae.Bytes(ctaIPv4Dst, tmp) 607 | } 608 | } 609 | 610 | return ae.Encode() 611 | } 612 | 613 | func extractIPTuple(v *IPTuple, logger *log.Logger, data []byte) error { 614 | ad, err := netlink.NewAttributeDecoder(data) 615 | if err != nil { 616 | return err 617 | } 618 | ad.ByteOrder = nativeEndian 619 | for ad.Next() { 620 | switch ad.Type() { 621 | case ctaTupleIP: 622 | src, dst, err := extractIP(logger, ad.Bytes()) 623 | if err != nil { 624 | return err 625 | } 626 | v.Src = &src 627 | v.Dst = &dst 628 | case ctaTupleProto: 629 | proto, err := extractProtoTuple(logger, ad.Bytes()) 630 | if err != nil { 631 | return err 632 | } 633 | v.Proto = &proto 634 | case ctaTupleZone: 635 | ad.ByteOrder = binary.BigEndian 636 | zone := ad.Uint16() 637 | v.Zone = &zone 638 | ad.ByteOrder = nativeEndian 639 | default: 640 | logger.Printf("extractIPTuple(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 641 | } 642 | } 643 | return ad.Err() 644 | } 645 | 646 | func marshalIPTuple(logger *log.Logger, v *IPTuple) ([]byte, error) { 647 | ae := netlink.NewAttributeEncoder() 648 | 649 | if v.Src != nil || v.Dst != nil { 650 | data, err := marshalIP(logger, v) 651 | if err != nil { 652 | return []byte{}, err 653 | } 654 | ae.Bytes(ctaTupleIP|nlafNested, data) 655 | } 656 | 657 | if v.Proto != nil { 658 | data, err := marshalProtoTuple(logger, v.Proto) 659 | if err != nil { 660 | return []byte{}, err 661 | } 662 | ae.Bytes(ctaTupleProto|nlafNested, data) 663 | } 664 | 665 | return ae.Encode() 666 | } 667 | 668 | func extractAttribute(c *Con, logger *log.Logger, data []byte) error { 669 | ad, err := netlink.NewAttributeDecoder(data) 670 | if err != nil { 671 | return err 672 | } 673 | for ad.Next() { 674 | switch ad.Type() { 675 | case ctaTupleOrig: 676 | tuple := &IPTuple{} 677 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 678 | return err 679 | } 680 | c.Origin = tuple 681 | case ctaTupleReply: 682 | tuple := &IPTuple{} 683 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 684 | return err 685 | } 686 | c.Reply = tuple 687 | case ctaProtoinfo: 688 | protoInfo := &ProtoInfo{} 689 | if err := extractProtoInfo(protoInfo, logger, ad.Bytes()); err != nil { 690 | return err 691 | } 692 | c.ProtoInfo = protoInfo 693 | case ctaHelp: 694 | help := &Helper{} 695 | if err := extractHelper(help, logger, ad.Bytes()); err != nil { 696 | return err 697 | } 698 | c.Helper = help 699 | case ctaID: 700 | ad.ByteOrder = binary.BigEndian 701 | tmp := ad.Uint32() 702 | c.ID = &tmp 703 | ad.ByteOrder = nativeEndian 704 | case ctaStatus: 705 | ad.ByteOrder = binary.BigEndian 706 | tmp := ad.Uint32() 707 | c.Status = &tmp 708 | ad.ByteOrder = nativeEndian 709 | case ctaUse: 710 | ad.ByteOrder = binary.BigEndian 711 | tmp := ad.Uint32() 712 | c.Use = &tmp 713 | ad.ByteOrder = nativeEndian 714 | case ctaMark: 715 | ad.ByteOrder = binary.BigEndian 716 | tmp := ad.Uint32() 717 | c.Mark = &tmp 718 | ad.ByteOrder = nativeEndian 719 | case ctaMarkMask: 720 | ad.ByteOrder = binary.BigEndian 721 | tmp := ad.Uint32() 722 | c.MarkMask = &tmp 723 | ad.ByteOrder = nativeEndian 724 | case ctaTimeout: 725 | ad.ByteOrder = binary.BigEndian 726 | tmp := ad.Uint32() 727 | c.Timeout = &tmp 728 | ad.ByteOrder = nativeEndian 729 | case ctaCountersOrig: 730 | orig := &Counter{} 731 | if err := extractCounter(orig, logger, ad.Bytes()); err != nil { 732 | return err 733 | } 734 | c.CounterOrigin = orig 735 | case ctaCountersReply: 736 | reply := &Counter{} 737 | if err := extractCounter(reply, logger, ad.Bytes()); err != nil { 738 | return err 739 | } 740 | c.CounterReply = reply 741 | case ctaSeqAdjOrig: 742 | orig := &SeqAdj{} 743 | if err := extractSeqAdj(orig, logger, ad.Bytes()); err != nil { 744 | return err 745 | } 746 | c.SeqAdjOrig = orig 747 | case ctaSeqAdjRepl: 748 | reply := &SeqAdj{} 749 | if err := extractSeqAdj(reply, logger, ad.Bytes()); err != nil { 750 | return err 751 | } 752 | c.SeqAdjRepl = reply 753 | case ctaZone: 754 | ad.ByteOrder = binary.BigEndian 755 | zone := ad.Uint16() 756 | c.Zone = &zone 757 | ad.ByteOrder = nativeEndian 758 | case ctaSecCtx: 759 | secCtx := &SecCtx{} 760 | if err := extractSecCtx(secCtx, logger, ad.Bytes()); err != nil { 761 | return err 762 | } 763 | c.SecCtx = secCtx 764 | case ctaTimestamp: 765 | ts := &Timestamp{} 766 | if err := extractTimestamp(ts, logger, ad.Bytes()); err != nil { 767 | return err 768 | } 769 | c.Timestamp = ts 770 | case ctaNatSrc: 771 | nat := &Nat{} 772 | if err := extractNat(nat, logger, ad.Bytes()); err != nil { 773 | return err 774 | } 775 | c.NatSrc = nat 776 | case ctaStatusMask: 777 | ad.ByteOrder = binary.BigEndian 778 | tmp := ad.Uint32() 779 | c.StatusMask = &tmp 780 | ad.ByteOrder = nativeEndian 781 | default: 782 | logger.Printf("extractAttribute() - Unknown attribute: %d %d %v\n", ad.Type()&0xFF, ad.Type(), ad.Bytes()) 783 | } 784 | } 785 | return ad.Err() 786 | } 787 | 788 | func checkHeader(data []byte) int { 789 | if (data[0] == unix.AF_INET || data[0] == unix.AF_INET6) && data[1] == unix.NFNETLINK_V0 { 790 | return 4 791 | } 792 | return 0 793 | } 794 | 795 | func extractAttributes(logger *log.Logger, c *Con, msg []byte) error { 796 | offset := checkHeader(msg[:2]) 797 | if err := extractAttribute(c, logger, msg[offset:]); err != nil { 798 | return err 799 | } 800 | return nil 801 | } 802 | -------------------------------------------------------------------------------- /attributeExpect.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | 7 | "github.com/mdlayher/netlink" 8 | ) 9 | 10 | const ( 11 | ctaExpUnspec = iota 12 | ctaExpMaster 13 | ctaExpTuple 14 | ctaExpMask 15 | ctaExpTimeout 16 | ctaExpID 17 | ctaExpHelpName 18 | ctaExpZone 19 | ctaExpFlags 20 | ctaExpClass 21 | ctaExpNat 22 | ctaExpFn 23 | ) 24 | 25 | const ( 26 | ctaExpNatUnspec = iota 27 | ctaExpNatDir 28 | ctaExpNatTuple 29 | ) 30 | 31 | func extractNatInfo(v *NatInfo, logger *log.Logger, data []byte) error { 32 | ad, err := netlink.NewAttributeDecoder(data) 33 | if err != nil { 34 | return err 35 | } 36 | ad.ByteOrder = nativeEndian 37 | for ad.Next() { 38 | switch ad.Type() { 39 | case ctaExpNatDir: 40 | ad.ByteOrder = binary.BigEndian 41 | tmp := ad.Uint32() 42 | v.Dir = &tmp 43 | ad.ByteOrder = nativeEndian 44 | case ctaExpNatTuple: 45 | tuple := &IPTuple{} 46 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 47 | return err 48 | } 49 | v.Tuple = tuple 50 | default: 51 | logger.Printf("extractNatInfo(): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 52 | } 53 | } 54 | return ad.Err() 55 | } 56 | 57 | func marshalNatInfo(logger *log.Logger, v *NatInfo) ([]byte, error) { 58 | ae := netlink.NewAttributeEncoder() 59 | 60 | if v.Dir != nil { 61 | ae.ByteOrder = binary.BigEndian 62 | ae.Uint32(ctaExpNatDir, *v.Dir) 63 | ae.ByteOrder = nativeEndian 64 | } 65 | if v.Tuple != nil { 66 | data, err := marshalIPTuple(logger, v.Tuple) 67 | if err != nil { 68 | return []byte{}, err 69 | } 70 | ae.Bytes(ctaExpNatTuple|nlafNested, data) 71 | } 72 | 73 | return ae.Encode() 74 | } 75 | 76 | func extractAttributeExpect(c *Con, logger *log.Logger, data []byte) error { 77 | ad, err := netlink.NewAttributeDecoder(data) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | c.Exp = &Exp{} 83 | 84 | for ad.Next() { 85 | switch ad.Type() { 86 | case ctaExpMaster: 87 | tuple := &IPTuple{} 88 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 89 | return err 90 | } 91 | c.Origin = tuple 92 | case ctaExpTuple: 93 | tuple := &IPTuple{} 94 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 95 | return err 96 | } 97 | c.Exp.Tuple = tuple 98 | case ctaExpMask: 99 | tuple := &IPTuple{} 100 | if err := extractIPTuple(tuple, logger, ad.Bytes()); err != nil { 101 | return err 102 | } 103 | c.Exp.Mask = tuple 104 | case ctaExpTimeout: 105 | ad.ByteOrder = binary.BigEndian 106 | tmp := ad.Uint32() 107 | c.Exp.Timeout = &tmp 108 | ad.ByteOrder = nativeEndian 109 | case ctaExpID: 110 | ad.ByteOrder = binary.BigEndian 111 | tmp := ad.Uint32() 112 | c.Exp.ID = &tmp 113 | ad.ByteOrder = nativeEndian 114 | case ctaExpHelpName: 115 | tmp := ad.String() 116 | c.Exp.HelperName = &tmp 117 | case ctaExpZone: 118 | ad.ByteOrder = binary.BigEndian 119 | zone := ad.Uint16() 120 | c.Exp.Zone = &zone 121 | ad.ByteOrder = nativeEndian 122 | case ctaExpFlags: 123 | ad.ByteOrder = binary.BigEndian 124 | tmp := ad.Uint32() 125 | c.Exp.Flags = &tmp 126 | ad.ByteOrder = nativeEndian 127 | case ctaExpClass: 128 | ad.ByteOrder = binary.BigEndian 129 | tmp := ad.Uint32() 130 | c.Exp.Class = &tmp 131 | ad.ByteOrder = nativeEndian 132 | case ctaExpNat: 133 | tmp := &NatInfo{} 134 | if err := extractNatInfo(tmp, logger, ad.Bytes()); err != nil { 135 | return err 136 | } 137 | c.Exp.Nat = tmp 138 | case ctaExpFn: 139 | tmp := ad.String() 140 | c.Exp.Fn = &tmp 141 | default: 142 | logger.Printf("extractAttributeExpect() - Unknown attribute: %d %d %v\n", ad.Type()&0xFF, ad.Type(), ad.Bytes()) 143 | } 144 | } 145 | return ad.Err() 146 | } 147 | 148 | func extractExpectAttributes(logger *log.Logger, c *Con, msg []byte) error { 149 | offset := checkHeader(msg[:2]) 150 | if err := extractAttributeExpect(c, logger, msg[offset:]); err != nil { 151 | return err 152 | } 153 | return nil 154 | } 155 | -------------------------------------------------------------------------------- /attributeStats.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | 7 | "github.com/mdlayher/netlink" 8 | ) 9 | 10 | const ( 11 | ctaStatsUnspec = iota 12 | ctaStatsSearched /* no longer used */ 13 | ctaStatsFound 14 | ctaStatsNew /* no longer used */ 15 | ctaStatsInvalid 16 | ctaStatsIgnore 17 | ctaStatsDelete /* no longer used */ 18 | ctaStatsDeleteList /* no longer used */ 19 | ctaStatsInsert 20 | ctaStatsInsertFailed 21 | ctaStatsDrop 22 | ctaStatsEarlyDrop 23 | ctaStatsError 24 | ctaStatsSearchRestart 25 | ) 26 | 27 | const ( 28 | ctaStatsExpUnspec = iota 29 | ctaStatsExpNew 30 | ctaStatsExpCreate 31 | ctaStatsExpDelete 32 | ) 33 | 34 | func extractCPUStats(s *CPUStat, logger *log.Logger, data []byte) error { 35 | ad, err := netlink.NewAttributeDecoder(data) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | // the CPU ID does not have its own attribute 41 | s.ID = binary.BigEndian.Uint32(data[0:4]) 42 | 43 | ad.ByteOrder = binary.BigEndian 44 | for ad.Next() { 45 | switch ad.Type() { 46 | case ctaStatsFound: 47 | tmp := ad.Uint32() 48 | s.Found = &tmp 49 | case ctaStatsInvalid: 50 | tmp := ad.Uint32() 51 | s.Invalid = &tmp 52 | case ctaStatsIgnore: 53 | tmp := ad.Uint32() 54 | s.Ignore = &tmp 55 | case ctaStatsInsert: 56 | tmp := ad.Uint32() 57 | s.Insert = &tmp 58 | case ctaStatsInsertFailed: 59 | tmp := ad.Uint32() 60 | s.InsertFailed = &tmp 61 | case ctaStatsDrop: 62 | tmp := ad.Uint32() 63 | s.Drop = &tmp 64 | case ctaStatsEarlyDrop: 65 | tmp := ad.Uint32() 66 | s.EarlyDrop = &tmp 67 | case ctaStatsError: 68 | tmp := ad.Uint32() 69 | s.Error = &tmp 70 | case ctaStatsSearchRestart: 71 | tmp := ad.Uint32() 72 | s.SearchRestart = &tmp 73 | default: 74 | logger.Printf("extractCPUStats()): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 75 | } 76 | } 77 | return ad.Err() 78 | } 79 | 80 | func extractExpCPUStats(s *CPUStat, logger *log.Logger, data []byte) error { 81 | ad, err := netlink.NewAttributeDecoder(data) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | // the CPU ID does not have its own attribute 87 | s.ID = binary.BigEndian.Uint32(data[0:4]) 88 | 89 | ad.ByteOrder = binary.BigEndian 90 | for ad.Next() { 91 | switch ad.Type() { 92 | case ctaStatsExpNew: 93 | tmp := ad.Uint32() 94 | s.ExpNew = &tmp 95 | case ctaStatsExpCreate: 96 | tmp := ad.Uint32() 97 | s.ExpCreate = &tmp 98 | case ctaStatsExpDelete: 99 | tmp := ad.Uint32() 100 | s.ExpDelete = &tmp 101 | default: 102 | logger.Printf("extractExpCPUStats()): %d | %d\t %v", ad.Type(), ad.Type()&0xFF, ad.Bytes()) 103 | } 104 | } 105 | return ad.Err() 106 | } 107 | -------------------------------------------------------------------------------- /bpf.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "sort" 8 | 9 | "github.com/florianl/go-conntrack/internal/unix" 10 | "golang.org/x/net/bpf" 11 | ) 12 | 13 | // Various errors which may occur when processing filters 14 | var ( 15 | ErrFilterLength = errors.New("number of filtering instructions are too high") 16 | ErrFilterAttributeLength = errors.New("incorrect length of filter attribute") 17 | ErrFilterAttributeMaskLength = errors.New("incorrect length of filter mask") 18 | ErrFilterAttributeNotImplemented = errors.New("filter attribute not implemented") 19 | ErrFilterAttributeNegateMix = errors.New("can not mix negation for attribute of the same type") 20 | ) 21 | 22 | // various consts from include/uapi/linux/bpf_common.h 23 | const ( 24 | bpfMAXINSTR = 4096 25 | 26 | failedJump = uint8(255) 27 | 28 | bpfVerdictAccept = 0xffffffff 29 | bpfVerdictReject = 0x00000000 30 | ) 31 | 32 | type filterCheckStruct struct { 33 | ct, len int 34 | mask bool 35 | nest []uint32 36 | } 37 | 38 | var filterCheck = map[ConnAttrType]filterCheckStruct{ 39 | AttrOrigIPv4Src: {ct: ctaIPv4Src, len: 4, mask: true, nest: []uint32{ctaTupleOrig, ctaTupleIP}}, 40 | AttrOrigIPv4Dst: {ct: ctaIPv4Dst, len: 4, mask: true, nest: []uint32{ctaTupleOrig, ctaTupleIP}}, 41 | AttrOrigIPv6Src: {ct: ctaIPv6Src, len: 16, mask: true, nest: []uint32{ctaTupleOrig, ctaTupleIP}}, 42 | AttrOrigIPv6Dst: {ct: ctaIPv6Dst, len: 16, mask: true, nest: []uint32{ctaTupleOrig, ctaTupleIP}}, 43 | AttrOrigPortSrc: {ct: ctaProtoSrcPort, len: 2, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 44 | AttrOrigPortDst: {ct: ctaProtoDstPort, len: 2, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 45 | AttrReplIPv4Src: {ct: ctaIPv4Src, len: 4, mask: true, nest: []uint32{ctaTupleReply, ctaTupleIP}}, 46 | AttrReplIPv4Dst: {ct: ctaIPv4Dst, len: 4, mask: true, nest: []uint32{ctaTupleReply, ctaTupleIP}}, 47 | AttrReplIPv6Src: {ct: ctaIPv6Src, len: 16, mask: true, nest: []uint32{ctaTupleReply, ctaTupleIP}}, 48 | AttrReplIPv6Dst: {ct: ctaIPv6Dst, len: 16, mask: true, nest: []uint32{ctaTupleReply, ctaTupleIP}}, 49 | AttrReplPortSrc: {ct: ctaProtoSrcPort, len: 2, nest: []uint32{ctaTupleReply, ctaTupleProto}}, 50 | AttrReplPortDst: {ct: ctaProtoDstPort, len: 2, nest: []uint32{ctaTupleReply, ctaTupleProto}}, 51 | AttrIcmpType: {ct: ctaProtoIcmpType, len: 1, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 52 | AttrIcmpCode: {ct: ctaProtoIcmpCode, len: 1, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 53 | AttrIcmpID: {ct: ctaProtoIcmpID, len: 2, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 54 | AttrIcmpv6Type: {ct: ctaProtoIcmpv6Type, len: 1, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 55 | AttrIcmpv6Code: {ct: ctaProtoIcmpv6Code, len: 1, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 56 | AttrIcmpv6ID: {ct: ctaProtoIcmpv6ID, len: 2, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 57 | AttrOrigL3Proto: {ct: ctaUnspec}, 58 | AttrReplL3Proto: {ct: ctaUnspec}, 59 | AttrOrigL4Proto: {ct: ctaProtoNum, len: 1, nest: []uint32{ctaTupleOrig, ctaTupleProto}}, 60 | AttrReplL4Proto: {ct: ctaProtoNum, len: 1, nest: []uint32{ctaTupleReply, ctaTupleProto}}, 61 | AttrTCPState: {ct: ctaProtoinfoTCPState, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoTCP}}, 62 | AttrSNatIPv4: {ct: ctaUnspec}, 63 | AttrDNatIPv4: {ct: ctaUnspec}, 64 | AttrSNatPort: {ct: ctaUnspec}, 65 | AttrDNatPort: {ct: ctaUnspec}, 66 | AttrTimeout: {ct: ctaTimeout, len: 4}, 67 | AttrMark: {ct: ctaMark, len: 4, mask: true}, 68 | AttrMarkMask: {ct: ctaMarkMask, len: 4}, 69 | AttrOrigCounterPackets: {ct: ctaCounterPackets, len: 8, nest: []uint32{ctaCountersOrig}}, 70 | AttrReplCounterPackets: {ct: ctaCounterPackets, len: 8, nest: []uint32{ctaCountersReply}}, 71 | AttrOrigCounterBytes: {ct: ctaCounterBytes, len: 8, nest: []uint32{ctaCountersOrig}}, 72 | AttrReplCounterBytes: {ct: ctaCounterBytes, len: 8, nest: []uint32{ctaCountersReply}}, 73 | AttrUse: {ct: ctaUse, len: 4}, 74 | AttrID: {ct: ctaID, len: 4}, 75 | AttrStatus: {ct: ctaStatus, len: 4}, 76 | AttrTCPFlagsOrig: {ct: ctaProtoinfoTCPFlagsOrig, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoTCP}}, 77 | AttrTCPFlagsRepl: {ct: ctaProtoinfoTCPFlagsRepl, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoTCP}}, 78 | AttrTCPMaskOrig: {ct: ctaUnspec}, 79 | AttrTCPMaskRepl: {ct: ctaUnspec}, 80 | AttrMasterIPv4Src: {ct: ctaUnspec}, 81 | AttrMasterIPv4Dst: {ct: ctaUnspec}, 82 | AttrMasterIPv6Src: {ct: ctaUnspec}, 83 | AttrMasterIPv6Dst: {ct: ctaUnspec}, 84 | AttrMasterPortSrc: {ct: ctaUnspec}, 85 | AttrMasterPortDst: {ct: ctaUnspec}, 86 | AttrMasterL3Proto: {ct: ctaUnspec}, 87 | AttrMasterL4Proto: {ct: ctaUnspec}, 88 | AttrSecmark: {ct: ctaSecmark, len: 4}, 89 | AttrOrigNatSeqCorrectionPos: {ct: ctaUnspec}, 90 | AttrOrigNatSeqOffsetBefore: {ct: ctaUnspec}, 91 | AttrOrigNatSeqOffsetAfter: {ct: ctaUnspec}, 92 | AttrReplNatSeqCorrectionPos: {ct: ctaUnspec}, 93 | AttrReplNatSeqOffsetBefore: {ct: ctaUnspec}, 94 | AttrReplNatSeqOffsetAfter: {ct: ctaUnspec}, 95 | AttrSctpState: {ct: ctaProtoinfoSCTPState, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoSCTP}}, 96 | AttrSctpVtagOrig: {ct: ctaProtoinfoSCTPVTagOriginal, len: 4, nest: []uint32{ctaProtoinfo, ctaProtoinfoSCTP}}, 97 | AttrSctpVtagRepl: {ct: ctaProtoinfoSCTPVTagReply, len: 4, nest: []uint32{ctaProtoinfo, ctaProtoinfoSCTP}}, 98 | AttrDccpState: {ct: ctaProtoinfoDCCPState, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoDCCP}}, 99 | AttrDccpRole: {ct: ctaProtoinfoDCCPRole, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoDCCP}}, 100 | AttrTCPWScaleOrig: {ct: ctaProtoinfoTCPWScaleOrig, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoTCP}}, 101 | AttrTCPWScaleRepl: {ct: ctaProtoinfoTCPWScaleRepl, len: 1, nest: []uint32{ctaProtoinfo, ctaProtoinfoTCP}}, 102 | AttrZone: {ct: ctaZone, len: 2}, 103 | AttrSecCtx: {ct: ctaUnspec}, 104 | AttrTimestampStart: {ct: ctaTimestampStart, len: 8, nest: []uint32{ctaTimestamp}}, 105 | AttrTimestampStop: {ct: ctaTimestampStop, len: 8, nest: []uint32{ctaTimestamp}}, 106 | AttrHelperInfo: {ct: ctaUnspec}, 107 | AttrConnlabels: {ct: ctaUnspec}, 108 | AttrConnlabelsMask: {ct: ctaUnspec}, 109 | AttrOrigzone: {ct: ctaUnspec}, 110 | AttrReplzone: {ct: ctaUnspec}, 111 | AttrSNatIPv6: {ct: ctaUnspec}, 112 | AttrDNatIPv6: {ct: ctaUnspec}, 113 | } 114 | 115 | func encodeValue(data []byte) (val uint32) { 116 | switch len(data) { 117 | case 1: 118 | val = uint32(data[0]) 119 | case 2: 120 | val = uint32(binary.BigEndian.Uint16(data)) 121 | case 4: 122 | val = binary.BigEndian.Uint32(data) 123 | } 124 | return 125 | } 126 | 127 | func compareValue(masking bool, sameAttrType bool, filterLen, dataLen, i uint32, bpfOp uint16, filter ConnAttr) []bpf.RawInstruction { 128 | var raw []bpf.RawInstruction 129 | 130 | if masking { 131 | for i := 0; i < (int(dataLen) / 4); i++ { 132 | tmp := bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_IND | bpfOp, K: uint32(4 * (i + 1))} 133 | raw = append(raw, tmp) 134 | mask := encodeValue(filter.Mask[i*4 : (i+1)*4]) 135 | tmp = bpf.RawInstruction{Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, K: mask} 136 | raw = append(raw, tmp) 137 | val := encodeValue(filter.Data[i*4 : (i+1)*4]) 138 | val &= mask 139 | if i == (int(dataLen)/4 - 1) { 140 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: val, Jt: 255} 141 | } else { 142 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: val, Jf: 255} 143 | } 144 | raw = append(raw, tmp) 145 | } 146 | } else { 147 | if !sameAttrType { 148 | tmp := bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_IND | bpfOp, K: uint32(4)} 149 | raw = append(raw, tmp) 150 | } 151 | jumps := (filterLen - i) 152 | val := encodeValue(filter.Data) 153 | tmp := bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: val, Jt: uint8(jumps)} 154 | raw = append(raw, tmp) 155 | 156 | } 157 | 158 | return raw 159 | } 160 | 161 | func compareValues(filters []ConnAttr) []bpf.RawInstruction { 162 | var raw []bpf.RawInstruction 163 | var bpfOp uint16 164 | masking := filterCheck[filters[0].Type].mask 165 | var dataLen = len(filters[0].Data) 166 | 167 | switch dataLen { 168 | case 1: 169 | bpfOp = unix.BPF_B 170 | case 2: 171 | bpfOp = unix.BPF_H 172 | case 4: 173 | bpfOp = unix.BPF_W 174 | case 16: 175 | bpfOp = unix.BPF_W 176 | } 177 | 178 | sort.Slice(filters, func(i, j int) bool { 179 | return filters[i].Type > filters[j].Type 180 | }) 181 | 182 | lastFilterType := attrUnspec 183 | for i, filter := range filters { 184 | tmp := compareValue(masking, lastFilterType == filter.Type, uint32(len(filters)), uint32(dataLen), uint32(i), bpfOp, filter) 185 | raw = append(raw, tmp...) 186 | lastFilterType = filter.Type 187 | } 188 | 189 | return raw 190 | } 191 | 192 | func filterAttribute(filters []ConnAttr) []bpf.RawInstruction { 193 | var raw []bpf.RawInstruction 194 | nested := len(filterCheck[filters[0].Type].nest) 195 | 196 | // sizeof(nlmsghdr) + sizeof(nfgenmsg) = 20 197 | tmp := bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_IMM, K: 0x14} 198 | raw = append(raw, tmp) 199 | 200 | if nested != 0 { 201 | for _, nest := range filterCheck[filters[0].Type].nest { 202 | // find nest attribute 203 | tmp = bpf.RawInstruction{Op: unix.BPF_LDX | unix.BPF_IMM, K: nest} 204 | raw = append(raw, tmp) 205 | tmp = bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, K: 0xfffff00c} 206 | raw = append(raw, tmp) 207 | 208 | // jump, if nest not found 209 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: 0, Jt: failedJump} 210 | raw = append(raw, tmp) 211 | 212 | tmp = bpf.RawInstruction{Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, K: 4} 213 | raw = append(raw, tmp) 214 | } 215 | } 216 | 217 | // find final attribute 218 | tmp = bpf.RawInstruction{Op: unix.BPF_LDX | unix.BPF_IMM, K: uint32(filterCheck[filters[0].Type].ct)} 219 | raw = append(raw, tmp) 220 | tmp = bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, K: 0xfffff00c} 221 | raw = append(raw, tmp) 222 | 223 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: 0, Jt: failedJump} 224 | raw = append(raw, tmp) 225 | 226 | tmp = bpf.RawInstruction{Op: unix.BPF_MISC | unix.BPF_TAX} 227 | raw = append(raw, tmp) 228 | 229 | // compare expected and actual value 230 | tmps := compareValues(filters) 231 | raw = append(raw, tmps...) 232 | 233 | // negate filter 234 | jump := uint8(0) 235 | if filters[0].Negate { 236 | raw = append(raw, bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JA, K: 1}) 237 | jump = 1 238 | } 239 | 240 | // Failed jumps are set to 255. Now we correct them to the actual failed jump instruction 241 | j := uint8(1) 242 | for i := len(raw) - 1; i > 0; i-- { 243 | if (raw[i].Jt == failedJump) && (raw[i].Op == unix.BPF_JMP|unix.BPF_JEQ|unix.BPF_K) { 244 | raw[i].Jt = j - jump 245 | } else if (raw[i].Jf == failedJump) && (raw[i].Op == unix.BPF_JMP|unix.BPF_JEQ|unix.BPF_K) { 246 | raw[i].Jf = j - 1 247 | } 248 | j++ 249 | } 250 | 251 | // reject 252 | raw = append(raw, bpf.RawInstruction{Op: unix.BPF_RET | unix.BPF_K, K: bpfVerdictReject}) 253 | 254 | return raw 255 | } 256 | 257 | // create filter instructions, to check for the subsystem 258 | func filterSubsys(subsys uint32) []bpf.RawInstruction { 259 | var raw []bpf.RawInstruction 260 | 261 | // Offset between start nlmshdr to nlmsg_type in byte 262 | tmp := bpf.RawInstruction{Op: unix.BPF_LDX | unix.BPF_IMM, K: 4} 263 | raw = append(raw, tmp) 264 | 265 | // Size of the subsytem id in byte 266 | tmp = bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, K: 1} 267 | raw = append(raw, tmp) 268 | 269 | // A == subsys ? jump + 1 : accept 270 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 1, K: subsys} 271 | raw = append(raw, tmp) 272 | 273 | // verdict -> accept 274 | tmp = bpf.RawInstruction{Op: unix.BPF_RET | unix.BPF_K, K: bpfVerdictAccept} 275 | raw = append(raw, tmp) 276 | 277 | return raw 278 | } 279 | 280 | func constructFilter(subsys Table, filters []ConnAttr) ([]bpf.RawInstruction, error) { 281 | var raw []bpf.RawInstruction 282 | filterMap := make(map[ConnAttrType][]ConnAttr) 283 | 284 | tmp := filterSubsys(uint32(subsys)) 285 | raw = append(raw, tmp...) 286 | 287 | for _, filter := range filters { 288 | if _, ok := filterCheck[filter.Type]; !ok { 289 | return nil, ErrFilterAttributeNotImplemented 290 | } 291 | if len(filter.Data) != filterCheck[filter.Type].len { 292 | return nil, ErrFilterAttributeLength 293 | } 294 | if filterCheck[filter.Type].mask && len(filter.Mask) != filterCheck[filter.Type].len { 295 | return nil, ErrFilterAttributeMaskLength 296 | } 297 | filterMap[filter.Type] = append(filterMap[filter.Type], filter) 298 | 299 | if len(filterMap[filter.Type]) == 1 { 300 | filterMap[filter.Type][0].Negate = filter.Negate 301 | } else { 302 | if filter.Negate != filterMap[filter.Type][0].Negate { 303 | return nil, ErrFilterAttributeNegateMix 304 | } 305 | } 306 | } 307 | 308 | // We can not simple range over the map, because the order of selected items can vary 309 | for key := 0; key <= int(attrMax); key++ { 310 | if x, ok := filterMap[ConnAttrType(key)]; ok { 311 | switch key { 312 | case int(AttrMark): 313 | tmp = filterMarkAttribute(x) 314 | default: 315 | tmp = filterAttribute(x) 316 | } 317 | raw = append(raw, tmp...) 318 | } 319 | } 320 | 321 | // final verdict -> Accept 322 | finalVerdict := bpf.RawInstruction{Op: unix.BPF_RET | unix.BPF_K, K: bpfVerdictAccept} 323 | raw = append(raw, finalVerdict) 324 | 325 | if len(raw) >= bpfMAXINSTR { 326 | return nil, ErrFilterLength 327 | } 328 | return raw, nil 329 | } 330 | 331 | func filterMarkAttribute(filters []ConnAttr) []bpf.RawInstruction { 332 | var raw []bpf.RawInstruction 333 | 334 | // sizeof(nlmsghdr) + sizeof(nfgenmsg) = 20 335 | tmp := bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_IMM, K: 0x14} 336 | raw = append(raw, tmp) 337 | 338 | // find final attribute 339 | tmp = bpf.RawInstruction{Op: unix.BPF_LDX | unix.BPF_IMM, K: uint32(filterCheck[filters[0].Type].ct)} 340 | raw = append(raw, tmp) 341 | tmp = bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, K: 0xfffff00c} 342 | raw = append(raw, tmp) 343 | 344 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: 0, Jt: 2} 345 | raw = append(raw, tmp) 346 | 347 | tmp = bpf.RawInstruction{Op: unix.BPF_MISC | unix.BPF_TAX} 348 | raw = append(raw, tmp) 349 | 350 | tmp = bpf.RawInstruction{Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, K: uint32(len(filters[0].Data))} 351 | raw = append(raw, tmp) 352 | 353 | tmp = bpf.RawInstruction{Op: unix.BPF_MISC | unix.BPF_TAX} 354 | raw = append(raw, tmp) 355 | 356 | for _, filter := range filters { 357 | var dataLen = len(filter.Data) 358 | for i := 0; i < (int(dataLen) / 4); i++ { 359 | mask := encodeValue(filter.Mask[i*4 : (i+1)*4]) 360 | tmp = bpf.RawInstruction{Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, K: mask} 361 | raw = append(raw, tmp) 362 | 363 | val := encodeValue(filter.Data[i*4 : (i+1)*4]) 364 | val &= mask 365 | tmp = bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: val, Jt: failedJump} 366 | raw = append(raw, tmp) 367 | 368 | tmp = bpf.RawInstruction{Op: unix.BPF_MISC | unix.BPF_TXA} 369 | raw = append(raw, tmp) 370 | } 371 | } 372 | 373 | var j uint8 = 1 374 | 375 | // Failed jumps are set to 255. Now we correct them to the actual failed jump instruction 376 | for i := len(raw) - 1; i > 0; i-- { 377 | if (raw[i].Jt == failedJump) && (raw[i].Op == unix.BPF_JMP|unix.BPF_JEQ|unix.BPF_K) { 378 | raw[i].Jt = j 379 | } 380 | j++ 381 | } 382 | 383 | // negate filter 384 | if filters[0].Negate { 385 | raw = append(raw, bpf.RawInstruction{Op: unix.BPF_JMP | unix.BPF_JA, K: 1}) 386 | } 387 | 388 | // reject 389 | raw = append(raw, bpf.RawInstruction{Op: unix.BPF_RET | unix.BPF_K, K: bpfVerdictReject}) 390 | 391 | return raw 392 | } 393 | 394 | func (nfct *Nfct) attachFilter(subsys Table, filters []ConnAttr) error { 395 | bpfFilters, err := constructFilter(subsys, filters) 396 | if err != nil { 397 | return err 398 | } 399 | if nfct.debug { 400 | fmtInstructions := fmtRawInstructions(bpfFilters) 401 | nfct.logger.Println("---BPF filter start---") 402 | nfct.logger.Print(fmtInstructions) 403 | nfct.logger.Println("---BPF filter end---") 404 | } 405 | 406 | return nfct.Con.SetBPF(bpfFilters) 407 | } 408 | 409 | func (nfct *Nfct) removeFilter() error { 410 | return nfct.Con.RemoveBPF() 411 | } 412 | 413 | func fmtRawInstruction(raw bpf.RawInstruction) string { 414 | return fmt.Sprintf("code=%30s\tjt=%.2x jf=%.2x k=%.8x", 415 | code2str(raw.Op&0xFFFF), 416 | raw.Jt&0xFF, 417 | raw.Jf&0xFF, 418 | raw.K&0xFFFFFFFF) 419 | } 420 | 421 | func fmtRawInstructions(raw []bpf.RawInstruction) string { 422 | var output string 423 | 424 | for i, instr := range raw { 425 | output += fmt.Sprintf("(%.4x) %s\n", i, fmtRawInstruction(instr)) 426 | } 427 | 428 | return output 429 | } 430 | 431 | // From libnetfilter_conntrack:src/conntrack/bsf.c 432 | func code2str(op uint16) string { 433 | switch op { 434 | case unix.BPF_LD | unix.BPF_IMM: 435 | return "BPF_LD|BPF_IMM" 436 | case unix.BPF_LDX | unix.BPF_IMM: 437 | return "BPF_LDX|BPF_IMM" 438 | case unix.BPF_LD | unix.BPF_B | unix.BPF_ABS: 439 | return "BPF_LD|BPF_B|BPF_ABS" 440 | case unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K: 441 | return "BPF_JMP|BPF_JEQ|BPF_K" 442 | case unix.BPF_ALU | unix.BPF_AND | unix.BPF_K: 443 | return "BPF_ALU|BPF_AND|BPF_K" 444 | case unix.BPF_JMP | unix.BPF_JA: 445 | return "BPF_JMP|BPF_JA" 446 | case unix.BPF_RET | unix.BPF_K: 447 | return "BPF_RET|BPF_K" 448 | case unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K: 449 | return "BPF_ALU|BPF_ADD|BPF_K" 450 | case unix.BPF_MISC | unix.BPF_TAX: 451 | return "BPF_MISC|BPF_TAX" 452 | case unix.BPF_MISC | unix.BPF_TXA: 453 | return "BPF_MISC|BPF_TXA" 454 | case unix.BPF_LD | unix.BPF_B | unix.BPF_IND: 455 | return "BPF_LD|BPF_B|BPF_IND" 456 | case unix.BPF_LD | unix.BPF_H | unix.BPF_IND: 457 | return "BPF_LD|BPF_H|BPF_IND" 458 | case unix.BPF_LD | unix.BPF_W | unix.BPF_IND: 459 | return "BPF_LD|BPF_W|BPF_IND" 460 | } 461 | return "UNKNOWN_INSTRUCTION" 462 | } 463 | -------------------------------------------------------------------------------- /bpf_test.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/florianl/go-conntrack/internal/unix" 9 | 10 | "golang.org/x/net/bpf" 11 | ) 12 | 13 | func TestOldConstructFilter(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | table Table 17 | filters []ConnAttr 18 | rawInstr []bpf.RawInstruction 19 | err error 20 | }{ 21 | // Example from libnetfilter_conntrack/utils/conntrack_filter.c 22 | {name: "conntrack_filter.c", table: Conntrack, filters: []ConnAttr{ 23 | {Type: AttrOrigL4Proto, Data: []byte{0x11}}, // TCP 24 | {Type: AttrOrigL4Proto, Data: []byte{0x06}}, // UDP 25 | {Type: AttrTCPState, Data: []byte{0x3}}, // TCP_CONNTRACK_ESTABLISHED 26 | {Type: AttrOrigIPv4Src, Data: []byte{0x7F, 0x0, 0x0, 0x1}, Mask: []byte{0xff, 0xff, 0xff, 0xff}, Negate: true}, // SrcIP != 127.0.0.1 27 | {Type: AttrOrigIPv6Src, Data: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, // SrcIP != ::1 28 | Mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Negate: true}, 29 | }, 30 | rawInstr: []bpf.RawInstruction{ 31 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 32 | {Op: 0x0050, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 33 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 34 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 35 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 36 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 37 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 38 | {Op: 0x0015, Jt: 0x0d, Jf: 0x00, K: 0x00000000}, 39 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 40 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 41 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 42 | {Op: 0x0015, Jt: 0x09, Jf: 0x00, K: 0x00000000}, 43 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 44 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 45 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 46 | {Op: 0x0015, Jt: 0x05, Jf: 0x00, K: 0x00000000}, 47 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 48 | {Op: 0x0040, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 49 | {Op: 0x0054, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 50 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x7f000001}, 51 | {Op: 0x0005, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 52 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 53 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 54 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 55 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 56 | {Op: 0x0015, Jt: 0x16, Jf: 0x00, K: 0x00000000}, 57 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 58 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 59 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 60 | {Op: 0x0015, Jt: 0x12, Jf: 0x00, K: 0x00000000}, 61 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 62 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000003}, 63 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 64 | {Op: 0x0015, Jt: 0x0e, Jf: 0x00, K: 0x00000000}, 65 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 66 | {Op: 0x0040, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 67 | {Op: 0x0054, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 68 | {Op: 0x0015, Jt: 0x00, Jf: 0x0a, K: 0x00000000}, 69 | {Op: 0x0040, Jt: 0x00, Jf: 0x00, K: 0x00000008}, 70 | {Op: 0x0054, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 71 | {Op: 0x0015, Jt: 0x00, Jf: 0x07, K: 0x00000000}, 72 | {Op: 0x0040, Jt: 0x00, Jf: 0x00, K: 0x0000000c}, 73 | {Op: 0x0054, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 74 | {Op: 0x0015, Jt: 0x00, Jf: 0x04, K: 0x00000000}, 75 | {Op: 0x0040, Jt: 0x00, Jf: 0x00, K: 0x00000010}, 76 | {Op: 0x0054, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 77 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 78 | {Op: 0x0005, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 79 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 80 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 81 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 82 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 83 | {Op: 0x0015, Jt: 0x0d, Jf: 0x00, K: 0x00000000}, 84 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 85 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000002}, 86 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 87 | {Op: 0x0015, Jt: 0x09, Jf: 0x00, K: 0x00000000}, 88 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 89 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 90 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 91 | {Op: 0x0015, Jt: 0x05, Jf: 0x00, K: 0x00000000}, 92 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 93 | {Op: 0x0050, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 94 | {Op: 0x0015, Jt: 0x02, Jf: 0x00, K: 0x00000011}, 95 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000006}, 96 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 97 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 98 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 99 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 100 | {Op: 0x0015, Jt: 0x0c, Jf: 0x00, K: 0x00000000}, 101 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 102 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 103 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 104 | {Op: 0x0015, Jt: 0x08, Jf: 0x00, K: 0x00000000}, 105 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 106 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 107 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 108 | {Op: 0x0015, Jt: 0x04, Jf: 0x00, K: 0x00000000}, 109 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 110 | {Op: 0x0050, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 111 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000003}, 112 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 113 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 114 | }, 115 | }, 116 | {name: "tcp and port 22", table: Conntrack, filters: []ConnAttr{ 117 | {Type: AttrOrigL4Proto, Data: []byte{0x11}}, // TCP 118 | {Type: AttrOrigPortDst, Data: []byte{0x00, 0x16}}, // 22 119 | }, rawInstr: []bpf.RawInstruction{ 120 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 121 | {Op: 0x0050, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 122 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 123 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 124 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 125 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 126 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 127 | {Op: 0x0015, Jt: 0x0c, Jf: 0x00, K: 0x00000000}, 128 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 129 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000002}, 130 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 131 | {Op: 0x0015, Jt: 0x08, Jf: 0x00, K: 0x00000000}, 132 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 133 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000003}, 134 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 135 | {Op: 0x0015, Jt: 0x04, Jf: 0x00, K: 0x00000000}, 136 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 137 | {Op: 0x0048, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 138 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000016}, 139 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 140 | {Op: 0x0000, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 141 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 142 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 143 | {Op: 0x0015, Jt: 0x0c, Jf: 0x00, K: 0x00000000}, 144 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 145 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x000000002}, 146 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 147 | {Op: 0x0015, Jt: 0x08, Jf: 0x00, K: 0x00000000}, 148 | {Op: 0x0004, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 149 | {Op: 0x0001, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 150 | {Op: 0x0030, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 151 | {Op: 0x0015, Jt: 0x04, Jf: 0x00, K: 0x00000000}, 152 | {Op: 0x0007, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 153 | {Op: 0x0050, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 154 | {Op: 0x0015, Jt: 0x01, Jf: 0x00, K: 0x00000011}, 155 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 156 | {Op: 0x0006, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 157 | }}, 158 | } 159 | 160 | for _, tc := range tests { 161 | t.Run(tc.name, func(t *testing.T) { 162 | rawInstr, err := constructFilter(tc.table, tc.filters) 163 | if err != tc.err { 164 | t.Fatal(err) 165 | } 166 | if len(rawInstr) != len(tc.rawInstr) { 167 | t.Fatalf("different length:\n- want: %#v\n- got: %#v", tc.rawInstr, rawInstr) 168 | } 169 | for i, v := range rawInstr { 170 | if v != tc.rawInstr[i] { 171 | t.Fatalf("unexpected reply:\n- want: %#v\n- got: %#v", tc.rawInstr, rawInstr) 172 | } 173 | } 174 | 175 | }) 176 | } 177 | } 178 | 179 | func TestConstructFilter(t *testing.T) { 180 | mark1ByteValue := make([]byte, 4) 181 | binary.BigEndian.PutUint32(mark1ByteValue, 1) 182 | mark10ByteValue := make([]byte, 4) 183 | binary.BigEndian.PutUint32(mark10ByteValue, 10) 184 | mark11ByteValue := make([]byte, 4) 185 | binary.BigEndian.PutUint32(mark11ByteValue, 11) 186 | mark50ByteValue := make([]byte, 4) 187 | binary.BigEndian.PutUint32(mark50ByteValue, 50) 188 | mark1000ByteValue := make([]byte, 4) 189 | binary.BigEndian.PutUint32(mark1000ByteValue, 1000) 190 | 191 | tests := map[string]struct { 192 | table Table 193 | filters []ConnAttr 194 | rawInstr []bpf.RawInstruction 195 | err error 196 | }{ 197 | "mark positive filter: [1]": { 198 | table: Conntrack, 199 | filters: []ConnAttr{ 200 | {Type: AttrMark, Data: mark1ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: false}, 201 | }, 202 | rawInstr: []bpf.RawInstruction{ 203 | //--- check subsys --- 204 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 205 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 206 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 207 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 208 | //--- check mark --- 209 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 210 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000008}, 211 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 212 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x00000000}, 213 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 214 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 215 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 216 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 217 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x00000001}, 218 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 219 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 220 | //---- final verdict ---- 221 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 222 | }}, 223 | "mark positive filter: [10,50,1000]": { 224 | table: Conntrack, 225 | filters: []ConnAttr{ 226 | {Type: AttrMark, Data: mark10ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: false}, 227 | {Type: AttrMark, Data: mark50ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: false}, 228 | {Type: AttrMark, Data: mark1000ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: false}, 229 | }, 230 | rawInstr: []bpf.RawInstruction{ 231 | //--- check subsys --- 232 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 233 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 234 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 235 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 236 | //--- check mark --- 237 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 238 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000008}, 239 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 240 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x00000000}, 241 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 242 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 243 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 244 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 245 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x08, Jf: 0x00, K: 0x0000000a}, 246 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 247 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 248 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x05, Jf: 0x00, K: 0x00000032}, 249 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 250 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 251 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x000003e8}, 252 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 253 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 254 | //---- final verdict ---- 255 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 256 | }}, 257 | "mark negative filter: [10,11]": { 258 | table: Conntrack, filters: []ConnAttr{ 259 | {Type: AttrMark, Data: mark10ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: true}, 260 | {Type: AttrMark, Data: mark11ByteValue, Mask: []byte{255, 255, 255, 255}, Negate: true}, 261 | }, 262 | rawInstr: []bpf.RawInstruction{ 263 | //--- check subsys --- 264 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 265 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 266 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 267 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 268 | //--- check mark --- 269 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 270 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000008}, 271 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 272 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x00000000}, 273 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 274 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 275 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 276 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 277 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x05, Jf: 0x00, K: 0x0000000a}, 278 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 279 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 280 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x0000000b}, 281 | {Op: unix.BPF_MISC | unix.BPF_TXA, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 282 | {Op: unix.BPF_JMP | unix.BPF_JA, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 283 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 284 | //---- final verdict ---- 285 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 286 | }}, 287 | "tcp": { 288 | table: Conntrack, 289 | filters: []ConnAttr{ 290 | {Type: AttrOrigL4Proto, Data: []byte{0x06}}, // TCP 291 | }, 292 | rawInstr: []bpf.RawInstruction{ 293 | // ---- check subsys --- 294 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 295 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 296 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 297 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 298 | // ---- check proto ---- 299 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 300 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 301 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 302 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x0c, Jf: 0x00, K: 0x00000000}, 303 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 304 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000002}, 305 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 306 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x08, Jf: 0x00, K: 0x00000000}, 307 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 308 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 309 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 310 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x04, Jf: 0x00, K: 0x00000000}, 311 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 312 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 313 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000006}, 314 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 315 | // ---- final verdict ---- 316 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 317 | }}, 318 | "tcp or udp": { 319 | table: Conntrack, 320 | filters: []ConnAttr{ 321 | {Type: AttrOrigL4Proto, Data: []byte{0x06}}, // TCP 322 | {Type: AttrOrigL4Proto, Data: []byte{0x11}}, // UDP 323 | }, 324 | rawInstr: []bpf.RawInstruction{ 325 | // ---- check subsys --- 326 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 327 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 328 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 329 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 330 | // ---- check proto ---- 331 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 332 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 333 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 334 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x0d, Jf: 0x00, K: 0x00000000}, 335 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 336 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000002}, 337 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 338 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x09, Jf: 0x00, K: 0x00000000}, 339 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 340 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 341 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 342 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x05, Jf: 0x00, K: 0x00000000}, 343 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 344 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 345 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x02, Jf: 0x00, K: 0x00000006}, 346 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000011}, 347 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 348 | // ---- final verdict ---- 349 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 350 | }, 351 | }, 352 | "src 127.0.0.1 or src 127.0.0.2 or src 127.0.0.3": { 353 | table: Conntrack, 354 | filters: []ConnAttr{ 355 | {Type: AttrOrigIPv4Src, Data: []byte{0x7F, 0x0, 0x0, 0x1}, Mask: []byte{0xff, 0xff, 0xff, 0xff}}, 356 | {Type: AttrOrigIPv4Src, Data: []byte{0x7F, 0x0, 0x0, 0x2}, Mask: []byte{0xff, 0xff, 0xff, 0xff}}, 357 | {Type: AttrOrigIPv4Src, Data: []byte{0x7F, 0x0, 0x0, 0x3}, Mask: []byte{0xff, 0xff, 0xff, 0xff}}, 358 | }, 359 | rawInstr: []bpf.RawInstruction{ 360 | // ---- check subsys --- 361 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 362 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 363 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x00000001}, 364 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 365 | // ---- check src IPv4 ---- 366 | {Op: unix.BPF_LD | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000014}, 367 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 368 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 369 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x13, Jf: 0x00, K: 0x00000000}, 370 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 371 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 372 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 373 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x0f, Jf: 0x00, K: 0x00000000}, 374 | {Op: unix.BPF_ALU | unix.BPF_ADD | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 375 | {Op: unix.BPF_LDX | unix.BPF_IMM, Jt: 0x00, Jf: 0x00, K: 0x00000001}, 376 | {Op: unix.BPF_LD | unix.BPF_B | unix.BPF_ABS, Jt: 0x00, Jf: 0x00, K: 0xfffff00c}, 377 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x0b, Jf: 0x00, K: 0x00000000}, 378 | {Op: unix.BPF_MISC | unix.BPF_TAX, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 379 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 380 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 381 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x07, Jf: 0x00, K: 0x7f000001}, 382 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 383 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 384 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x04, Jf: 0x00, K: 0x7f000002}, 385 | {Op: unix.BPF_LD | unix.BPF_W | unix.BPF_IND, Jt: 0x00, Jf: 0x00, K: 0x00000004}, 386 | {Op: unix.BPF_ALU | unix.BPF_AND | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 387 | {Op: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 0x01, Jf: 0x00, K: 0x7f000003}, 388 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0x00000000}, 389 | // ---- final verdict ---- 390 | {Op: unix.BPF_RET | unix.BPF_K, Jt: 0x00, Jf: 0x00, K: 0xffffffff}, 391 | }, 392 | }, 393 | } 394 | 395 | for name, tc := range tests { 396 | t.Run(name, func(t *testing.T) { 397 | rawInstr, err := constructFilter(tc.table, tc.filters) 398 | if !errors.Is(err, tc.err) { 399 | t.Fatal(err) 400 | } 401 | if len(rawInstr) != len(tc.rawInstr) { 402 | t.Fatalf("different length:\n- want:\n%s\n- got:\n%s", 403 | fmtRawInstructions(tc.rawInstr), fmtRawInstructions(rawInstr)) 404 | } 405 | var isErr bool 406 | for i, v := range rawInstr { 407 | if v != tc.rawInstr[i] { 408 | t.Errorf("unexpected %d. instruction:\n- want:\n%s\n- got:\n%s", 409 | i, fmtRawInstruction(tc.rawInstr[i]), fmtRawInstruction(rawInstr[i])) 410 | isErr = true 411 | } 412 | } 413 | 414 | if isErr { 415 | t.Fatalf("unexpected reply:\n- want:\n%s\n- got:\n%s", 416 | fmtRawInstructions(tc.rawInstr), fmtRawInstructions(rawInstr)) 417 | } 418 | }) 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /conntrack.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "fmt" 7 | "log" 8 | "time" 9 | "unsafe" 10 | 11 | "github.com/florianl/go-conntrack/internal/unix" 12 | 13 | "github.com/mdlayher/netlink" 14 | "github.com/mdlayher/netlink/nlenc" 15 | ) 16 | 17 | // Supported conntrack subsystems 18 | const ( 19 | // Conntrack is the default table containing a list of all tracked connections 20 | Conntrack Table = unix.NFNL_SUBSYS_CTNETLINK 21 | 22 | // Expected is a table containing information about related connections to existing ones 23 | Expected Table = unix.NFNL_SUBSYS_CTNETLINK_EXP 24 | 25 | // Timeout is a table containing timeout information of connection flows. 26 | Timeout Table = unix.NFNL_SUBSYS_CTNETLINK_TIMEOUT 27 | ) 28 | 29 | const ( 30 | ipctnlMsgCtNew = iota 31 | ipctnlMsgCtGet 32 | ipctnlMsgCtDelete 33 | ipctnlMsgCtGetCtrZero 34 | ipctnlMsgCtGetStatsCPU 35 | ipctnlMsgCtGetStats 36 | ipctnlMsgCtGetDying 37 | ipctnlMsgCtGetUnconfirmed 38 | ) 39 | 40 | const ( 41 | ipctnlMsgExpNew = iota 42 | ipctnlMsgExpGet 43 | ipctnlMsgExpDelete 44 | ipctnlMsgExpGetStatsCPU 45 | ) 46 | 47 | // for detailes see https://github.com/tensorflow/tensorflow/blob/master/tensorflow/go/tensor.go#L488-L505 48 | var nativeEndian binary.ByteOrder 49 | 50 | func init() { 51 | buf := [2]byte{} 52 | *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) 53 | 54 | switch buf { 55 | case [2]byte{0xCD, 0xAB}: 56 | nativeEndian = binary.LittleEndian 57 | case [2]byte{0xAB, 0xCD}: 58 | nativeEndian = binary.BigEndian 59 | default: 60 | panic("Could not determine native endianness.") 61 | } 62 | } 63 | 64 | // devNull satisfies io.Writer, in case *log.Logger is not provided 65 | type devNull struct{} 66 | 67 | func (devNull) Write(p []byte) (int, error) { 68 | return 0, nil 69 | } 70 | 71 | // Open a connection to the conntrack subsystem 72 | func Open(config *Config) (*Nfct, error) { 73 | var nfct Nfct 74 | 75 | con, err := netlink.Dial(unix.NETLINK_NETFILTER, &netlink.Config{NetNS: config.NetNS, DisableNSLockThread: config.DisableNSLockThread}) 76 | if err != nil { 77 | return nil, err 78 | } 79 | nfct.Con = con 80 | 81 | if config.Logger == nil { 82 | nfct.logger = log.New(new(devNull), "", 0) 83 | } else { 84 | nfct.logger = config.Logger 85 | } 86 | 87 | if config.WriteTimeout > 0 { 88 | nfct.setWriteTimeout = func() error { 89 | deadline := time.Now().Add(config.WriteTimeout) 90 | return nfct.Con.SetWriteDeadline(deadline) 91 | } 92 | } else { 93 | nfct.setWriteTimeout = func() error { return nil } 94 | } 95 | 96 | nfct.addConntrackInformation = config.AddConntrackInformation 97 | 98 | return &nfct, nil 99 | } 100 | 101 | // Close the connection to the conntrack subsystem. 102 | func (nfct *Nfct) Close() error { 103 | if nfct.ctxCancel != nil { 104 | nfct.ctxCancel() 105 | 106 | // Block until filters are removed and socket unsubscribed from groups 107 | <-nfct.shutdown 108 | } 109 | 110 | if nfct.errChan != nil { 111 | close(nfct.errChan) 112 | } 113 | return nfct.Con.Close() 114 | } 115 | 116 | // SetOption allows to enable or disable netlink socket options. 117 | func (nfct *Nfct) SetOption(o netlink.ConnOption, enable bool) error { 118 | return nfct.Con.SetOption(o, enable) 119 | } 120 | 121 | // Flush a conntrack subsystem 122 | func (nfct *Nfct) Flush(t Table, f Family) error { 123 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, 0) 124 | req := netlink.Message{ 125 | Header: netlink.Header{ 126 | Type: netlink.HeaderType(t << 8), 127 | Flags: netlink.Request | netlink.Acknowledge, 128 | }, 129 | Data: data, 130 | } 131 | 132 | if t == Conntrack { 133 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtDelete) 134 | } else if t == Expected { 135 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpDelete) 136 | } else { 137 | return ErrUnknownCtTable 138 | } 139 | 140 | return nfct.execute(req) 141 | } 142 | 143 | // Dump a conntrack subsystem 144 | func (nfct *Nfct) Dump(t Table, f Family) ([]Con, error) { 145 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, 0) 146 | req := netlink.Message{ 147 | Header: netlink.Header{ 148 | Type: netlink.HeaderType(t << 8), 149 | Flags: netlink.Request | netlink.Dump, 150 | }, 151 | Data: data, 152 | } 153 | 154 | if t == Conntrack { 155 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtGet) 156 | } else if t == Expected { 157 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpGet) 158 | } else { 159 | return nil, ErrUnknownCtTable 160 | } 161 | 162 | return nfct.query(req) 163 | } 164 | 165 | // Create a new entry in the conntrack subsystem with certain attributes 166 | func (nfct *Nfct) Create(t Table, f Family, attributes Con) error { 167 | query, err := nestAttributes(nfct.logger, &attributes) 168 | if err != nil { 169 | return err 170 | } 171 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, unix.NFNL_SUBSYS_CTNETLINK) 172 | data = append(data, query...) 173 | 174 | req := netlink.Message{ 175 | Header: netlink.Header{ 176 | Type: netlink.HeaderType(t << 8), 177 | Flags: netlink.Request | netlink.Acknowledge | netlink.Create | netlink.Excl, 178 | }, 179 | Data: data, 180 | } 181 | 182 | if t == Conntrack { 183 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtNew) 184 | } else if t == Expected { 185 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpNew) 186 | } else { 187 | return ErrUnknownCtTable 188 | } 189 | 190 | return nfct.execute(req) 191 | } 192 | 193 | // Query conntrack subsystem with certain attributes 194 | func (nfct *Nfct) Query(t Table, f Family, filter FilterAttr) ([]Con, error) { 195 | query, err := nestFilter(filter) 196 | if err != nil { 197 | return nil, err 198 | } 199 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, unix.NFNL_SUBSYS_CTNETLINK) 200 | data = append(data, query...) 201 | 202 | req := netlink.Message{ 203 | Header: netlink.Header{ 204 | Type: netlink.HeaderType(t << 8), 205 | Flags: netlink.Request | netlink.Dump, 206 | }, 207 | Data: data, 208 | } 209 | 210 | if t == Conntrack { 211 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtGet) 212 | } else if t == Expected { 213 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpGet) 214 | } else { 215 | return nil, ErrUnknownCtTable 216 | } 217 | return nfct.query(req) 218 | } 219 | 220 | // Get returns matching conntrack entries with certain attributes 221 | func (nfct *Nfct) Get(t Table, f Family, match Con) ([]Con, error) { 222 | if t != Conntrack { 223 | return nil, ErrUnknownCtTable 224 | } 225 | query, err := nestAttributes(nfct.logger, &match) 226 | if err != nil { 227 | return []Con{}, err 228 | } 229 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, unix.NFNL_SUBSYS_CTNETLINK) 230 | data = append(data, query...) 231 | 232 | req := netlink.Message{ 233 | Header: netlink.Header{ 234 | Type: netlink.HeaderType(t << 8), 235 | Flags: netlink.Request | netlink.Acknowledge, 236 | }, 237 | Data: data, 238 | } 239 | 240 | if t == Conntrack { 241 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtGet) 242 | } else if t == Expected { 243 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpGet) 244 | } else { 245 | return []Con{}, ErrUnknownCtTable 246 | } 247 | 248 | return nfct.query(req) 249 | } 250 | 251 | // Update an existing conntrack entry 252 | func (nfct *Nfct) Update(t Table, f Family, attributes Con) error { 253 | if t != Conntrack { 254 | return ErrUnknownCtTable 255 | } 256 | 257 | query, err := nestAttributes(nfct.logger, &attributes) 258 | if err != nil { 259 | return err 260 | } 261 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, unix.NFNL_SUBSYS_CTNETLINK) 262 | data = append(data, query...) 263 | 264 | req := netlink.Message{ 265 | Header: netlink.Header{ 266 | Type: netlink.HeaderType(t << 8), 267 | Flags: netlink.Request | netlink.Acknowledge, 268 | }, 269 | Data: data, 270 | } 271 | 272 | if t == Conntrack { 273 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtNew) 274 | } else if t == Expected { 275 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpNew) 276 | } else { 277 | return ErrUnknownCtTable 278 | } 279 | 280 | return nfct.execute(req) 281 | } 282 | 283 | // Delete elements from the conntrack subsystem with certain attributes 284 | func (nfct *Nfct) Delete(t Table, f Family, filters Con) error { 285 | query, err := nestAttributes(nfct.logger, &filters) 286 | if err != nil { 287 | return err 288 | } 289 | data := putExtraHeader(uint8(f), unix.NFNETLINK_V0, unix.NFNL_SUBSYS_CTNETLINK) 290 | data = append(data, query...) 291 | 292 | req := netlink.Message{ 293 | Header: netlink.Header{ 294 | Type: netlink.HeaderType(t << 8), 295 | Flags: netlink.Request | netlink.Acknowledge, 296 | }, 297 | Data: data, 298 | } 299 | 300 | if t == Conntrack { 301 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtDelete) 302 | } else if t == Expected { 303 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpDelete) 304 | } else { 305 | return ErrUnknownCtTable 306 | } 307 | 308 | return nfct.execute(req) 309 | } 310 | 311 | // DumpCPUStats dumps per CPU statistics 312 | func (nfct *Nfct) DumpCPUStats(t Table) ([]CPUStat, error) { 313 | data := putExtraHeader(unix.AF_UNSPEC, unix.NFNETLINK_V0, 0) 314 | req := netlink.Message{ 315 | Header: netlink.Header{ 316 | Type: netlink.HeaderType(t << 8), 317 | Flags: netlink.Request | netlink.Dump, 318 | }, 319 | Data: data, 320 | } 321 | if t == Conntrack { 322 | req.Header.Type |= netlink.HeaderType(ipctnlMsgCtGetStatsCPU) 323 | } else if t == Expected { 324 | req.Header.Type |= netlink.HeaderType(ipctnlMsgExpGetStatsCPU) 325 | } else { 326 | return nil, ErrUnknownCtTable 327 | } 328 | return nfct.getCPUStats(req) 329 | } 330 | 331 | // ParseAttributes extracts all the attributes from the given data 332 | func ParseAttributes(logger *log.Logger, data []byte) (Con, error) { 333 | // At least 2 bytes are needed for the header check 334 | if len(data) < 2 { 335 | return Con{}, ErrDataLength 336 | } 337 | c := Con{} 338 | err := extractAttributes(logger, &c, data) 339 | return c, err 340 | } 341 | 342 | // HookFunc is a function, that receives events from a Netlinkgroup. 343 | // Return something different than 0, to stop receiving messages. 344 | type HookFunc func(c Con) int 345 | 346 | // AttachErrChan creates and attaches an error channel to the Nfct object. 347 | // If an unexpected error is received this error will be reported via this 348 | // channel. 349 | // A call of (*Nfct).Close() will also close this channel. 350 | func (nfct *Nfct) AttachErrChan() <-chan error { 351 | if nfct.errChan != nil { 352 | return nfct.errChan 353 | } 354 | errChan := make(chan error) 355 | nfct.errChan = errChan 356 | return errChan 357 | } 358 | 359 | // Register your function to receive events from a Netlinkgroup. If an unexpected error 360 | // is received it will stop from processing further events. 361 | // If your function returns something different than 0, it will stop. 362 | func (nfct *Nfct) Register(ctx context.Context, t Table, group NetlinkGroup, fn HookFunc) error { 363 | return nfct.register(ctx, t, group, []ConnAttr{}, fn) 364 | } 365 | 366 | // RegisterFiltered registers your function to receive events from a Netlinkgroup and applies a filter. 367 | // If an unexpected error is received it will stop from processing further events. 368 | // If your function returns something different than 0, it will stop. 369 | // ConnAttr of the same ConnAttrType will be linked by an OR operation. 370 | // Otherwise, ConnAttr of different ConnAttrType will be connected by an AND operation for the filter. 371 | // Note: When you add filters for IPv4 specific fields, it will automatically filter for IPv4-only events. 372 | // The same rule applies for IPv6. However, if you apply a filter for both IPv4- and IPv6-specific fields, 373 | // it will result in filtering out all events, meaning no event will match. 374 | func (nfct *Nfct) RegisterFiltered(ctx context.Context, t Table, group NetlinkGroup, filter []ConnAttr, fn HookFunc) error { 375 | return nfct.register(ctx, t, group, filter, fn) 376 | } 377 | 378 | // EnableDebug print bpf filter for RegisterFiltered function 379 | func (nfct *Nfct) EnableDebug() { 380 | nfct.debug = true 381 | } 382 | 383 | func (nfct *Nfct) register(ctx context.Context, t Table, groups NetlinkGroup, filter []ConnAttr, fn func(c Con) int) error { 384 | nfct.ctx, nfct.ctxCancel = context.WithCancel(ctx) 385 | nfct.shutdown = make(chan struct{}) 386 | 387 | if err := nfct.manageGroups(t, uint32(groups), true); err != nil { 388 | return err 389 | } 390 | if err := nfct.attachFilter(t, filter); err != nil { 391 | return err 392 | } 393 | 394 | enricher := func(*Con, netlink.Header) {} 395 | if nfct.addConntrackInformation { 396 | enricher = func(c *Con, h netlink.Header) { 397 | var group NetlinkGroup 398 | 399 | if h.Type&0xFF == ipctnlMsgCtNew { 400 | if h.Flags&(netlink.Create|netlink.Excl) != 0 { 401 | group = NetlinkCtNew 402 | } else { 403 | group = NetlinkCtUpdate 404 | } 405 | } else { 406 | group = NetlinkCtDestroy 407 | } 408 | 409 | info := InfoSource{ 410 | Table: Table((h.Type & 0x300) >> 8), 411 | NetlinkGroup: group, 412 | } 413 | c.Info = &info 414 | } 415 | } 416 | 417 | go func() { 418 | go func() { 419 | // block until context is done 420 | <-nfct.ctx.Done() 421 | // Set the read deadline to a point in the past to interrupt 422 | // possible blocking Receive() calls. 423 | nfct.Con.SetReadDeadline(time.Now().Add(-1 * time.Second)) 424 | 425 | if err := nfct.removeFilter(); err != nil { 426 | nfct.logger.Printf("could not remove filter: %v", err) 427 | } 428 | if err := nfct.manageGroups(t, uint32(groups), false); err != nil { 429 | nfct.logger.Printf("could not unsubscribe from group: %v", err) 430 | } 431 | close(nfct.shutdown) 432 | }() 433 | 434 | for { 435 | reply, err := nfct.Con.Receive() 436 | if err != nil { 437 | if nfct.ctx.Err() != nil { 438 | // TODO: Here we ignore internal/poll.ErrFileClosing which is expected after 439 | // nfct.ctx is done. Maybe improve graceful handling. 440 | return 441 | } 442 | if opError, ok := err.(*netlink.OpError); ok { 443 | if opError.Timeout() || opError.Temporary() { 444 | continue 445 | } 446 | } 447 | if nfct.errChan != nil { 448 | nfct.errChan <- err 449 | } else { 450 | nfct.logger.Printf("receiving error: %v", err) 451 | } 452 | return 453 | } 454 | 455 | c := Con{} 456 | for _, msg := range reply { 457 | if err := parseConnectionMsg(nfct.logger, &c, msg, (int(msg.Header.Type)&0x300)>>8, int(msg.Header.Type)&0xF); err != nil { 458 | nfct.logger.Printf("could not parse received message: %v", err) 459 | continue 460 | } 461 | enricher(&c, msg.Header) 462 | if ret := fn(c); ret != 0 { 463 | return 464 | } 465 | } 466 | 467 | } 468 | }() 469 | return nil 470 | } 471 | 472 | func (nfct *Nfct) manageGroups(t Table, groups uint32, join bool) error { 473 | var manage func(group uint32) error 474 | 475 | if groups == 0 { 476 | nfct.logger.Println("will not join group 0") 477 | return nil 478 | } 479 | 480 | manage = nfct.Con.LeaveGroup 481 | if join { 482 | manage = nfct.Con.JoinGroup 483 | } 484 | 485 | var mapping map[uint32]uint32 486 | var nlGroups []NetlinkGroup 487 | switch t { 488 | case Conntrack: 489 | mapping = map[uint32]uint32{ 490 | uint32(NetlinkCtNew): 1, // NFNLGRP_CONNTRACK_NEW 491 | uint32(NetlinkCtUpdate): 2, // NFNLGRP_CONNTRACK_UPDATE 492 | uint32(NetlinkCtDestroy): 3, // NFNLGRP_CONNTRACK_DESTROY 493 | } 494 | nlGroups = append(nlGroups, []NetlinkGroup{NetlinkCtNew, NetlinkCtUpdate, NetlinkCtDestroy}...) 495 | case Expected: 496 | mapping = map[uint32]uint32{ 497 | uint32(NetlinkCtExpectedNew): 4, // NFNLGRP_CONNTRACK_EXP_NEW 498 | uint32(NetlinkCtExpectedUpdate): 5, // NFNLGRP_CONNTRACK_EXP_UPDATE 499 | uint32(NetlinkCtExpectedDestroy): 6, // NFNLGRP_CONNTRACK_EXP_DESTROY 500 | } 501 | nlGroups = append(nlGroups, []NetlinkGroup{NetlinkCtExpectedNew, NetlinkCtExpectedUpdate, NetlinkCtExpectedDestroy}...) 502 | default: 503 | return ErrUnknownCtTable 504 | } 505 | 506 | for _, v := range nlGroups { 507 | if groups&uint32(v) == uint32(v) { 508 | if err := manage(mapping[groups&uint32(v)]); err != nil { 509 | return err 510 | } 511 | } 512 | } 513 | return nil 514 | } 515 | 516 | // ErrMsg as defined in nlmsgerr 517 | type ErrMsg struct { 518 | Code int 519 | Len uint32 520 | Type uint16 521 | Flags uint16 522 | Seq uint32 523 | Pid uint32 524 | } 525 | 526 | func unmarschalErrMsg(b []byte) (ErrMsg, error) { 527 | var msg ErrMsg 528 | 529 | msg.Code = int(nlenc.Uint32(b[0:4])) 530 | msg.Len = nlenc.Uint32(b[4:8]) 531 | msg.Type = nlenc.Uint16(b[8:10]) 532 | msg.Flags = nlenc.Uint16(b[10:12]) 533 | msg.Seq = nlenc.Uint32(b[12:16]) 534 | msg.Pid = nlenc.Uint32(b[16:20]) 535 | 536 | return msg, nil 537 | } 538 | 539 | func (nfct *Nfct) execute(req netlink.Message) error { 540 | if err := nfct.setWriteTimeout(); err != nil { 541 | nfct.logger.Printf("could not set write timeout: %v", err) 542 | } 543 | reply, e := nfct.Con.Execute(req) 544 | if e != nil { 545 | return e 546 | } 547 | if e := netlink.Validate(req, reply); e != nil { 548 | return e 549 | } 550 | for _, msg := range reply { 551 | errMsg, err := unmarschalErrMsg(msg.Data) 552 | if err != nil { 553 | return err 554 | } 555 | if errMsg.Code != 0 { 556 | return fmt.Errorf("%#v", errMsg) 557 | } 558 | } 559 | return nil 560 | } 561 | 562 | func (nfct *Nfct) send(req netlink.Message) error { 563 | if err := nfct.setWriteTimeout(); err != nil { 564 | nfct.logger.Printf("could not set write timeout: %v", err) 565 | } 566 | verify, err := nfct.Con.Send(req) 567 | if err != nil { 568 | return err 569 | } 570 | 571 | if err := netlink.Validate(req, []netlink.Message{verify}); err != nil { 572 | return err 573 | } 574 | 575 | return nil 576 | } 577 | 578 | func (nfct *Nfct) query(req netlink.Message) ([]Con, error) { 579 | 580 | if err := nfct.send(req); err != nil { 581 | return nil, err 582 | } 583 | 584 | reply, err := nfct.Con.Receive() 585 | if err != nil { 586 | return nil, err 587 | } 588 | 589 | var conn []Con 590 | for _, msg := range reply { 591 | c := Con{} 592 | if err := parseConnectionMsg(nfct.logger, &c, msg, (int(req.Header.Type)&0x300)>>8, int(req.Header.Type)&0xF); err != nil { 593 | return nil, err 594 | } 595 | // check if c is an empty struct 596 | if (Con{}) == c { 597 | continue 598 | } 599 | conn = append(conn, c) 600 | } 601 | return conn, nil 602 | } 603 | 604 | func (nfct *Nfct) getCPUStats(req netlink.Message) ([]CPUStat, error) { 605 | var stats []CPUStat 606 | if err := nfct.send(req); err != nil { 607 | return nil, err 608 | } 609 | reply, err := nfct.Con.Receive() 610 | if err != nil { 611 | return nil, err 612 | } 613 | 614 | for _, msg := range reply { 615 | if msg.Header.Type == netlink.Error { 616 | errMsg, err := unmarschalErrMsg(msg.Data) 617 | if err != nil { 618 | nfct.logger.Printf("could not unmarshal ErrMsg: %v", err) 619 | continue 620 | } 621 | if errMsg.Code == 0 { 622 | continue 623 | } 624 | nfct.logger.Printf("unknown error: %v", errMsg) 625 | continue 626 | } 627 | 628 | var stat CPUStat 629 | offset := checkHeader(msg.Data[:2]) 630 | switch Table((int(req.Header.Type) & 0x300) >> 8) { 631 | case Conntrack: 632 | if err := extractCPUStats(&stat, nfct.logger, msg.Data[offset:]); err != nil { 633 | nfct.logger.Printf("could not extract CPU stats: %v", err) 634 | continue 635 | } 636 | case Expected: 637 | if err := extractExpCPUStats(&stat, nfct.logger, msg.Data[offset:]); err != nil { 638 | nfct.logger.Printf("could not extract CPU stats: %v", err) 639 | continue 640 | } 641 | default: 642 | return nil, fmt.Errorf("unknown table") 643 | } 644 | 645 | stats = append(stats, stat) 646 | } 647 | return stats, nil 648 | } 649 | 650 | // /include/uapi/linux/netfilter/nfnetlink.h:struct nfgenmsg{} res_id is Big Endian 651 | func putExtraHeader(familiy, version uint8, resid uint16) []byte { 652 | buf := make([]byte, 2) 653 | binary.BigEndian.PutUint16(buf, resid) 654 | return append([]byte{familiy, version}, buf...) 655 | } 656 | 657 | type extractFunc func(*log.Logger, *Con, []byte) error 658 | 659 | func parseConnectionMsg(logger *log.Logger, c *Con, msg netlink.Message, reqTable, reqType int) error { 660 | 661 | if msg.Header.Type == netlink.Error { 662 | errMsg, err := unmarschalErrMsg(msg.Data) 663 | if err != nil { 664 | return err 665 | } 666 | if errMsg.Code == 0 { 667 | return nil 668 | } 669 | return fmt.Errorf("%#v", errMsg) 670 | } 671 | 672 | var fnMap map[int]extractFunc 673 | 674 | switch reqTable { 675 | case unix.NFNL_SUBSYS_CTNETLINK: 676 | fnMap = map[int]extractFunc{ 677 | ipctnlMsgCtNew: extractAttributes, 678 | ipctnlMsgCtGet: extractAttributes, 679 | ipctnlMsgCtDelete: extractAttributes, 680 | } 681 | case unix.NFNL_SUBSYS_CTNETLINK_EXP: 682 | fnMap = map[int]extractFunc{ 683 | ipctnlMsgExpNew: extractExpectAttributes, 684 | ipctnlMsgExpGet: extractExpectAttributes, 685 | ipctnlMsgExpDelete: extractExpectAttributes, 686 | } 687 | default: 688 | return fmt.Errorf("unknown conntrack table") 689 | } 690 | 691 | if fn, ok := fnMap[reqType]; ok { 692 | return fn(logger, c, msg.Data) 693 | } 694 | 695 | return fmt.Errorf("unknown message type: 0x%02x", reqType) 696 | } 697 | -------------------------------------------------------------------------------- /conntrack_linux_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && linux 2 | // +build integration,linux 3 | 4 | package conntrack 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "os/exec" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestLinuxConntrackUpdatePing(t *testing.T) { 15 | // ping is needed to create a session, we can work with 16 | _, err := exec.LookPath("ping") 17 | if err != nil { 18 | t.Fatalf("could not find ping binary") 19 | } 20 | 21 | ctx, cancel := context.WithCancel(context.Background()) 22 | // Create a session 23 | cmd := exec.CommandContext(ctx, "ping", "127.0.0.2") 24 | if err := cmd.Start(); err != nil { 25 | t.Fatalf("could not start ping to 127.0.0.2: %v", err) 26 | } 27 | defer cancel() 28 | 29 | // Give the kernel some time, to track the session 30 | time.Sleep(2 * time.Second) 31 | 32 | nfct, err := Open(&Config{}) 33 | if err != nil { 34 | t.Fatalf("could not open socket: %v", err) 35 | } 36 | defer nfct.Close() 37 | 38 | cons, err := nfct.Dump(Conntrack, IPv4) 39 | if err != nil { 40 | t.Fatalf("could not dump sessions: %v", err) 41 | } 42 | 43 | var pingSession Con 44 | for _, c := range cons { 45 | if c.Origin == nil || c.Origin.Proto == nil || c.Origin.Proto.Number == nil || *c.Origin.Proto.Number != 1 { 46 | continue 47 | } 48 | if (*c.Origin.Src).Equal(net.ParseIP("127.0.0.1")) && (*c.Origin.Dst).Equal(net.ParseIP("127.0.0.2")) { 49 | pingSession = c 50 | break 51 | } 52 | } 53 | 54 | if pingSession.Mark == nil { 55 | t.Logf("could not identify ping session") 56 | return 57 | } 58 | origMark := *pingSession.Mark 59 | 60 | *pingSession.Mark = 0xFF00AA11 61 | 62 | // Update the conntrack entry 63 | if err := nfct.Update(Conntrack, IPv4, pingSession); err != nil { 64 | t.Fatalf("could not update conntrack entry: %v", err) 65 | } 66 | 67 | c, err := nfct.Get(Conntrack, IPv4, pingSession) 68 | if err != nil { 69 | t.Fatalf("could not get session: %v", err) 70 | } 71 | 72 | if len(c) != 1 { 73 | t.Fatalf("could not get updated session") 74 | } 75 | 76 | if origMark == *c[0].Mark { 77 | t.Fatalf("could not update mark of the session") 78 | } 79 | t.Logf("original mark 0x%x vs modified mark 0x%x\n", origMark, *c[0].Mark) 80 | } 81 | 82 | func TestLinuxConntrackDeleteEntry(t *testing.T) { 83 | // ping is needed to create a session, we can work with 84 | _, err := exec.LookPath("ping") 85 | if err != nil { 86 | t.Fatalf("Could not find ping binary") 87 | } 88 | 89 | ctx, cancel := context.WithCancel(context.Background()) 90 | // Create a session 91 | cmd := exec.CommandContext(ctx, "ping", "-i 2", "127.0.0.4") 92 | if err := cmd.Start(); err != nil { 93 | t.Fatalf("Could not start ping to 127.0.0.4: %v", err) 94 | } 95 | defer cancel() 96 | 97 | // Give the kernel some time, to track the session 98 | time.Sleep(3 * time.Second) 99 | 100 | nfct, err := Open(&Config{}) 101 | if err != nil { 102 | t.Fatalf("Could not open socket: %v", err) 103 | } 104 | defer nfct.Close() 105 | 106 | conns, err := nfct.Dump(Conntrack, IPv4) 107 | if err != nil { 108 | t.Fatalf("Could not dump sessions: %v", err) 109 | } 110 | 111 | var origConntrackID uint32 112 | 113 | for _, c := range conns { 114 | if c.Origin == nil || c.Origin.Src == nil || c.Origin.Dst == nil { 115 | continue 116 | } 117 | if (*c.Origin.Src).Equal(net.ParseIP("127.0.0.1")) && (*c.Origin.Dst).Equal(net.ParseIP("127.0.0.4")) { 118 | origConntrackID = *c.ID 119 | if err := nfct.Delete(Conntrack, IPv4, c); err != nil { 120 | t.Fatalf("could not delete session: %v", err) 121 | } 122 | break 123 | } 124 | } 125 | 126 | // there will be a session for the ping, as it is still running. 127 | // But as we deleted the original session, there has to be a new AttrID 128 | conns2, err2 := nfct.Dump(Conntrack, IPv4) 129 | if err2 != nil { 130 | t.Fatalf("could not dump sessions: %v", err) 131 | } 132 | 133 | for _, c := range conns2 { 134 | if c.Origin == nil || c.Origin.Src == nil || c.Origin.Dst == nil { 135 | continue 136 | } 137 | if (*c.Origin.Src).Equal(net.ParseIP("127.0.0.1")) && (*c.Origin.Dst).Equal(net.ParseIP("127.0.0.4")) { 138 | if *c.ID == origConntrackID { 139 | t.Fatalf("original ping session was not deleted") 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /conntrack_test.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/mdlayher/netlink" 8 | "github.com/mdlayher/netlink/nltest" 9 | ) 10 | 11 | func TestFlush(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | family Family 15 | want []netlink.Message 16 | }{ 17 | {name: "Flush IPv4", family: IPv4, want: []netlink.Message{ 18 | { 19 | Header: netlink.Header{ 20 | Length: 20, 21 | // NFNL_SUBSYS_CTNETLINK<<8|IPCTNL_MSG_CT_DELETE 22 | Type: netlink.HeaderType(1<<8 | 2), 23 | // NLM_F_REQUEST|NLM_F_ACK 24 | Flags: netlink.Request | netlink.Acknowledge, 25 | // Can and will be ignored 26 | Sequence: 0, 27 | // Can and will be ignored 28 | PID: nltest.PID, 29 | }, 30 | // nfgen_family=AF_INET, version=NFNETLINK_V0, res_id=htons(0) 31 | Data: []byte{0x2, 0x0, 0x0, 0x0}, 32 | }, 33 | }, 34 | }, 35 | {name: "Flush IPv6", family: IPv6, want: []netlink.Message{ 36 | { 37 | Header: netlink.Header{ 38 | Length: 20, 39 | // NFNL_SUBSYS_CTNETLINK<<8|IPCTNL_MSG_CT_DELETE 40 | Type: netlink.HeaderType(1<<8 | 2), 41 | // NLM_F_REQUEST|NLM_F_ACK 42 | Flags: netlink.Request | netlink.Acknowledge, 43 | // Can and will be ignored 44 | Sequence: 0, 45 | // Can and will be ignored 46 | PID: nltest.PID, 47 | }, 48 | // nfgen_family=AF_INET6, version=NFNETLINK_V0, res_id=htons(0) 49 | Data: []byte{0xA, 0x0, 0x0, 0x0}, 50 | }, 51 | }, 52 | }, 53 | } 54 | 55 | for _, tc := range tests { 56 | t.Run(tc.name, func(t *testing.T) { 57 | // Fake a netfilter conntrack connection 58 | nfct := &Nfct{} 59 | AdjustWriteTimeout(nfct, func() error { return nil }) 60 | nfct.Con = nltest.Dial(func(reqs []netlink.Message) ([]netlink.Message, error) { 61 | if len(reqs) == 0 { 62 | return nil, nil 63 | } 64 | if len(reqs) != 1 { 65 | t.Fatalf("Expected only one request, got %d", len(reqs)) 66 | } 67 | 68 | // To ignore the Sequence number, we set it to the same value 69 | tc.want[0].Header.Sequence = reqs[0].Header.Sequence 70 | 71 | if len(reqs) != len(tc.want) { 72 | t.Fatalf("differen length:\n- want: %#v\n- got: %#v\n", tc.want, reqs) 73 | } 74 | 75 | for i := 0; i < len(reqs); i++ { 76 | if len(reqs[i].Data) != len(tc.want[i].Data) { 77 | t.Fatalf("differen length:\n- want: %#v\n- got: %#v\n", tc.want[i], reqs[i]) 78 | } 79 | if reqs[i].Header.Type != tc.want[i].Header.Type { 80 | t.Fatalf("differen header types:\n- want: %#v\n- got: %#v\n", tc.want[i].Header.Type, reqs[i].Header.Type) 81 | } 82 | if reqs[i].Header.Flags != tc.want[i].Header.Flags { 83 | t.Fatalf("differen header flags:\n- want: %#v\n- got: %#v\n", tc.want[i].Header.Flags, reqs[i].Header.Flags) 84 | } 85 | for j, v := range reqs[i].Data { 86 | if v != tc.want[i].Data[j] { 87 | t.Fatalf("unexpected reply:\n- want: %#v\n- got: %#v", tc.want[i].Data, reqs[i].Data) 88 | } 89 | } 90 | } 91 | return nil, nil 92 | }) 93 | defer nfct.Con.Close() 94 | 95 | if err := nfct.Flush(Conntrack, tc.family); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | }) 100 | } 101 | } 102 | 103 | func TestCreate(t *testing.T) { 104 | 105 | srcIP := net.ParseIP("1.1.1.1") 106 | dstIP := net.ParseIP("2.2.2.2") 107 | var tcp uint8 = 17 108 | var srcPort uint16 = 22 109 | var dstPort uint16 = 10 110 | var timeout uint32 = 100 111 | var tcpState uint8 = 8 112 | 113 | tests := []struct { 114 | name string 115 | attributes Con 116 | want []netlink.Message 117 | }{ 118 | {name: "noAttributes", want: []netlink.Message{ 119 | { 120 | Header: netlink.Header{ 121 | Length: 20, 122 | // NFNL_SUBSYS_CTNETLINK<<8|IPCTNL_MSG_CT_NEW 123 | Type: netlink.HeaderType(1 << 8), 124 | // NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK|NLM_F_EXCL 125 | Flags: netlink.Request | netlink.Create | netlink.Acknowledge | netlink.Excl, 126 | // Can and will be ignored 127 | Sequence: 0, 128 | // Can and will be ignored 129 | PID: nltest.PID, 130 | }, 131 | // nfgen_family=AF_INET, version=NFNETLINK_V0, NFNL_SUBSYS_CTNETLINK 132 | Data: []byte{0x2, 0x0, 0x0, 0x1}, 133 | }, 134 | }}, 135 | // Example from libnetfilter_conntrack/utils/conntrack_create.c 136 | {name: "conntrack_create.c", attributes: Con{ 137 | Origin: &IPTuple{Src: &srcIP, Dst: &dstIP, Proto: &ProtoTuple{Number: &tcp, SrcPort: &srcPort, DstPort: &dstPort}}, 138 | Reply: &IPTuple{Src: &dstIP, Dst: &srcIP, Proto: &ProtoTuple{Number: &tcp, SrcPort: &dstPort, DstPort: &srcPort}}, 139 | Timeout: &timeout, 140 | ProtoInfo: &ProtoInfo{TCP: &TCPInfo{State: &tcpState}}}, 141 | want: []netlink.Message{ 142 | { 143 | Header: netlink.Header{ 144 | Length: 80, 145 | // NFNL_SUBSYS_CTNETLINK<<8|IPCTNL_MSG_CT_NEW 146 | Type: netlink.HeaderType(1 << 8), 147 | // NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK|NLM_F_EXCL 148 | Flags: netlink.Request | netlink.Create | netlink.Acknowledge | netlink.Excl, 149 | // Can and will be ignored 150 | Sequence: 0, 151 | // Can and will be ignored 152 | PID: nltest.PID, 153 | }, 154 | // nfgen_family=AF_INET, version=NFNETLINK_V0, NFNL_SUBSYS_CTNETLINK + netlinkes Attributes 155 | Data: []byte{0x2, 0x0, 0x0, 0x1, 0x34, 0x0, 0x1, 0x80, 0x14, 0x0, 0x1, 0x80, 0x8, 0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x1, 0x8, 0x0, 0x2, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1c, 0x0, 0x2, 0x80, 0x5, 0x0, 0x1, 0x0, 0x11, 0x0, 0x0, 0x0, 0x6, 0x0, 0x2, 0x0, 0x0, 0x16, 0x0, 0x0, 0x6, 0x0, 0x3, 0x0, 0x0, 0xa, 0x0, 0x0, 0x34, 0x0, 0x2, 0x80, 0x14, 0x0, 0x1, 0x80, 0x8, 0x0, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x8, 0x0, 0x2, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1c, 0x0, 0x2, 0x80, 0x5, 0x0, 0x1, 0x0, 0x11, 0x0, 0x0, 0x0, 0x6, 0x0, 0x2, 0x0, 0x0, 0xa, 0x0, 0x0, 0x6, 0x0, 0x3, 0x0, 0x0, 0x16, 0x0, 0x0, 0x8, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x64, 0x10, 0x0, 0x4, 0x80, 0xc, 0x0, 0x1, 0x80, 0x5, 0x0, 0x1, 0x0, 0x8, 0x0, 0x0, 0x0}, 156 | }, 157 | }}, 158 | } 159 | 160 | for _, tc := range tests { 161 | t.Run(tc.name, func(t *testing.T) { 162 | nfct := &Nfct{} 163 | AdjustWriteTimeout(nfct, func() error { return nil }) 164 | nfct.Con = nltest.Dial(func(reqs []netlink.Message) ([]netlink.Message, error) { 165 | if len(reqs) == 0 { 166 | return nil, nil 167 | } 168 | if len(reqs) != 1 { 169 | t.Fatalf("Expected only one request, got %d", len(reqs)) 170 | } 171 | // To ignore the Sequence number, we set it to the same value 172 | tc.want[0].Header.Sequence = reqs[0].Header.Sequence 173 | 174 | if len(reqs) != len(tc.want) { 175 | t.Fatalf("differen length:\n- want: %#v\n- got: %#v\n", tc.want, reqs) 176 | } 177 | 178 | for i := 0; i < len(reqs); i++ { 179 | if len(reqs[i].Data) != len(tc.want[i].Data) { 180 | t.Fatalf("differen length:\n- want: %#v\n- got: %#v\n", tc.want[i], reqs[i]) 181 | } 182 | if reqs[i].Header.Type != tc.want[i].Header.Type { 183 | t.Fatalf("differen header types:\n- want: %#v\n- got: %#v\n", tc.want[i].Header.Type, reqs[i].Header.Type) 184 | } 185 | if reqs[i].Header.Flags != tc.want[i].Header.Flags { 186 | t.Fatalf("differen header flags:\n- want: %#v\n- got: %#v\n", tc.want[i].Header.Flags, reqs[i].Header.Flags) 187 | } 188 | for j, v := range reqs[i].Data { 189 | if v != tc.want[i].Data[j] { 190 | t.Fatalf("unexpected reply:\n- want: %#v\n- got: %#v", tc.want[i].Data, reqs[i].Data) 191 | } 192 | } 193 | } 194 | return nil, nil 195 | }) 196 | defer nfct.Con.Close() 197 | 198 | if err := nfct.Create(Conntrack, IPv4, tc.attributes); err != nil { 199 | t.Fatal(err) 200 | } 201 | }) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package conntrack provides an API to interact with the conntrack subsystem of the netfilter family from the linux kernel. 3 | 4 | Example: 5 | 6 | package main 7 | import ( 8 | "fmt" 9 | ct "github.com/florianl/go-conntrack" 10 | ) 11 | func main(){ 12 | nfct, err := ct.Open(&ct.Config{}) 13 | if err != nil { 14 | fmt.Println("Could not create nfct:", err) 15 | return 16 | } 17 | defer nfct.Close() 18 | sessions, err := nfct.Dump(ct.Conntrack, ct.IPv4) 19 | if err != nil { 20 | fmt.Println("Could not dump sessions:", err) 21 | return 22 | } 23 | for _, session := range sessions { 24 | fmt.Printf("[%2d] %s - %s\n", session.Origin.Proto.Number, session.Origin.Src, session.Origin.Dst) 25 | } 26 | } 27 | 28 | This package processes information directly from the kernel and therefore it requires special privileges. You 29 | can provide this privileges by adjusting the CAP_NET_ADMIN capabilities. 30 | 31 | setcap 'cap_net_admin=+ep' /your/executable 32 | */ 33 | package conntrack 34 | -------------------------------------------------------------------------------- /example_DumpCPUStats_test.go: -------------------------------------------------------------------------------- 1 | package conntrack_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | ct "github.com/florianl/go-conntrack" 7 | ) 8 | 9 | func ExampleNfct_DumpCPUStats() { 10 | nfct, err := ct.Open(&ct.Config{}) 11 | if err != nil { 12 | fmt.Println("could not create nfct:", err) 13 | return 14 | } 15 | defer nfct.Close() 16 | stats, err := nfct.DumpCPUStats(ct.Conntrack) 17 | if err != nil { 18 | fmt.Println("could not dump CPU stats:", err) 19 | return 20 | } 21 | 22 | fmt.Printf("ID\tIgnore\tInvalid\tError\n") 23 | for _, stat := range stats { 24 | fmt.Printf("%2d\t%4d\t%4d\t%4d\n", stat.ID, *stat.Ignore, *stat.Invalid, *stat.Error) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example_Dump_test.go: -------------------------------------------------------------------------------- 1 | package conntrack_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | ct "github.com/florianl/go-conntrack" 7 | ) 8 | 9 | func ExampleNfct_Dump() { 10 | nfct, err := ct.Open(&ct.Config{}) 11 | if err != nil { 12 | fmt.Println("could not create nfct:", err) 13 | return 14 | } 15 | defer nfct.Close() 16 | sessions, err := nfct.Dump(ct.Expected, ct.IPv4) 17 | if err != nil { 18 | fmt.Println("could not dump sessions:", err) 19 | return 20 | } 21 | 22 | for _, session := range sessions { 23 | fmt.Printf("%#v\n", session) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example_Register_test.go: -------------------------------------------------------------------------------- 1 | package conntrack_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | ct "github.com/florianl/go-conntrack" 9 | ) 10 | 11 | func ExampleNfct_Register() { 12 | nfct, err := ct.Open(&ct.Config{}) 13 | if err != nil { 14 | fmt.Println("could not create nfct:", err) 15 | return 16 | } 17 | defer nfct.Close() 18 | 19 | monitor := func(c ct.Con) int { 20 | fmt.Printf("%#v\n", c) 21 | return 0 22 | } 23 | 24 | if err := nfct.Register(context.Background(), ct.Expected, ct.NetlinkCtExpectedNew|ct.NetlinkCtExpectedUpdate|ct.NetlinkCtExpectedDestroy, monitor); err != nil { 25 | fmt.Println("could not register callback:", err) 26 | return 27 | } 28 | 29 | time.Sleep(10 * time.Second) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | // Export for testing 4 | var AdjustWriteTimeout = adjustWriteTimeout 5 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/mdlayher/netlink" 7 | ) 8 | 9 | // Error which may occur when processing the filter attribute 10 | var ( 11 | ErrFilterAttrLength = errors.New("incorrect length of filter attribute") 12 | ) 13 | 14 | func nestFilter(filter FilterAttr) ([]byte, error) { 15 | var attrs []netlink.Attribute 16 | 17 | if len(filter.Mark) != 4 { 18 | return nil, ErrFilterAttrLength 19 | } 20 | if len(filter.MarkMask) != 4 { 21 | return nil, ErrFilterAttrLength 22 | } 23 | attrs = append(attrs, netlink.Attribute{Type: ctaMark, Data: filter.Mark}, netlink.Attribute{Type: ctaMarkMask, Data: filter.MarkMask}) 24 | 25 | return netlink.MarshalAttributes(attrs) 26 | } 27 | -------------------------------------------------------------------------------- /filter_test.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNestFilter(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | filter FilterAttr 12 | data []byte 13 | err error 14 | }{ 15 | {name: "empty filter", filter: FilterAttr{}, err: ErrFilterAttrLength}, 16 | {name: "simple filter", filter: FilterAttr{Mark: []byte{0x11, 0x11, 0x11, 0x11}, MarkMask: []byte{0xFF, 0xFF, 0xFF, 0xFF}}, 17 | data: []byte{0x8, 0x0, 0x8, 0x0, 0x11, 0x11, 0x11, 0x11, 0x8, 0x0, 0x15, 0x0, 0xff, 0xff, 0xff, 0xff}}, 18 | } 19 | 20 | for _, tc := range tests { 21 | t.Run(tc.name, func(t *testing.T) { 22 | 23 | data, err := nestFilter(tc.filter) 24 | if err != tc.err { 25 | t.Fatal(err) 26 | } 27 | if !reflect.DeepEqual(data, tc.data) { 28 | t.Fatalf("unexpected replies:\n- want: %#v\n- got: %#v", tc.data, data) 29 | } 30 | 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/florianl/go-conntrack 2 | 3 | require ( 4 | github.com/mdlayher/netlink v1.5.0 5 | golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d 6 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e 7 | ) 8 | 9 | go 1.13 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 3 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= 5 | github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= 6 | github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= 9 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 10 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 11 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 12 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 13 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 15 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 16 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 17 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 18 | github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= 19 | github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 20 | github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= 21 | github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= 22 | github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= 23 | github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw= 24 | github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs= 25 | github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA= 26 | github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= 27 | github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo= 28 | github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ= 29 | github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs= 30 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 31 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 32 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 33 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 34 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 35 | github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= 36 | github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8= 37 | github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE= 38 | github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= 39 | github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= 40 | github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= 41 | github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= 42 | github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= 43 | github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= 44 | github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8= 45 | github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= 46 | github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= 47 | github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= 48 | github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= 49 | github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q= 50 | github.com/mdlayher/netlink v1.5.0 h1:r4fa439+SsMarM0rMONU3iSshSV3ArVqJl6H/zjrhh4= 51 | github.com/mdlayher/netlink v1.5.0/go.mod h1:1Kr8BBFxGyUyNmztC9WLOayqYVAd2wsgOZm18nqGuzQ= 52 | github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= 53 | github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g= 54 | github.com/mdlayher/socket v0.1.0 h1:PBV/PxzCp56zuT74AY8vz2dtifidvlI2p65OTqIsqhM= 55 | github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= 56 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 57 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 58 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 59 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 60 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 61 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 62 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 63 | golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= 64 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 65 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 66 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 67 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 68 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 69 | golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 70 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 71 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 72 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 73 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 74 | golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 75 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 76 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 77 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 78 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 79 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 80 | golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 81 | golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 82 | golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 83 | golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= 84 | golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 86 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 87 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 88 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 90 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 93 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 105 | golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 107 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 108 | golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 114 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 115 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 117 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 118 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 119 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 120 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 121 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 122 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 123 | golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= 124 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 125 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 126 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 127 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 128 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 129 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 130 | honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= 131 | honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= 132 | honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= 133 | -------------------------------------------------------------------------------- /internal/unix/doc.go: -------------------------------------------------------------------------------- 1 | // Package unix maps constants from golang.org/x/sys/unix to local constants and makes 2 | // them available for other platforms as well. 3 | package unix 4 | -------------------------------------------------------------------------------- /internal/unix/types_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package unix 5 | 6 | import ( 7 | linux "golang.org/x/sys/unix" 8 | ) 9 | 10 | // comment to make linter happy 11 | const ( 12 | AF_UNSPEC = linux.AF_UNSPEC 13 | AF_INET = linux.AF_INET 14 | AF_INET6 = linux.AF_INET6 15 | NFNETLINK_V0 = linux.NFNETLINK_V0 16 | NFNL_SUBSYS_CTNETLINK = linux.NFNL_SUBSYS_CTNETLINK 17 | NFNL_SUBSYS_CTNETLINK_EXP = linux.NFNL_SUBSYS_CTNETLINK_EXP 18 | NFNL_SUBSYS_CTNETLINK_TIMEOUT = linux.NFNL_SUBSYS_CTNETLINK_TIMEOUT 19 | NETLINK_NETFILTER = linux.NETLINK_NETFILTER 20 | 21 | // Instruction classes 22 | BPF_LD = linux.BPF_LD 23 | BPF_LDX = linux.BPF_LDX 24 | BPF_ALU = linux.BPF_ALU 25 | BPF_JMP = linux.BPF_JMP 26 | BPF_RET = linux.BPF_RET 27 | BPF_MISC = linux.BPF_MISC 28 | 29 | // ld/ldx fields 30 | BPF_W = linux.BPF_W 31 | BPF_H = linux.BPF_H 32 | BPF_B = linux.BPF_B 33 | BPF_IMM = linux.BPF_IMM 34 | BPF_ABS = linux.BPF_ABS 35 | BPF_IND = linux.BPF_IND 36 | 37 | // alu/jmp fields 38 | BPF_ADD = linux.BPF_ADD 39 | BPF_AND = linux.BPF_AND 40 | BPF_JA = linux.BPF_JA 41 | BPF_JEQ = linux.BPF_JEQ 42 | BPF_K = linux.BPF_K 43 | 44 | // include/uapi/linux/filter.h 45 | BPF_TAX = linux.BPF_TAX 46 | BPF_TXA = linux.BPF_TXA 47 | ) 48 | -------------------------------------------------------------------------------- /internal/unix/types_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package unix 5 | 6 | const ( 7 | AF_UNSPEC = 0x0 8 | AF_INET = 0x2 9 | AF_INET6 = 0xa 10 | NFNETLINK_V0 = 0x0 11 | NFNL_SUBSYS_CTNETLINK = 0x1 12 | NFNL_SUBSYS_CTNETLINK_EXP = 0x2 13 | NFNL_SUBSYS_CTNETLINK_TIMEOUT = 0x8 14 | NETLINK_NETFILTER = 0xc 15 | 16 | // Instruction classes 17 | BPF_LD = 0x00 18 | BPF_LDX = 0x01 19 | BPF_ALU = 0x04 20 | BPF_JMP = 0x05 21 | BPF_RET = 0x06 22 | BPF_MISC = 0x07 23 | 24 | // ld/ldx fields 25 | BPF_W = 0x00 26 | BPF_H = 0x08 27 | BPF_B = 0x10 28 | BPF_IMM = 0x00 29 | BPF_ABS = 0x20 30 | BPF_IND = 0x40 31 | 32 | // alu/jmp fields 33 | BPF_ADD = 0x00 34 | BPF_AND = 0x50 35 | BPF_JA = 0x00 36 | BPF_JEQ = 0x10 37 | BPF_K = 0x00 38 | 39 | // include/uapi/linux/filter.h 40 | BPF_TAX = 0x00 41 | BPF_TXA = 0x80 42 | ) 43 | -------------------------------------------------------------------------------- /nest.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | 7 | "github.com/mdlayher/netlink" 8 | ) 9 | 10 | func nestAttributes(logger *log.Logger, filters *Con) ([]byte, error) { 11 | ae := netlink.NewAttributeEncoder() 12 | 13 | if filters.Origin != nil { 14 | data, err := marshalIPTuple(logger, filters.Origin) 15 | if err != nil { 16 | return []byte{}, err 17 | } 18 | ae.Bytes(ctaTupleOrig|nlafNested, data) 19 | } 20 | if filters.Reply != nil { 21 | data, err := marshalIPTuple(logger, filters.Reply) 22 | if err != nil { 23 | return []byte{}, err 24 | } 25 | ae.Bytes(ctaTupleReply|nlafNested, data) 26 | } 27 | 28 | if filters.ID != nil { 29 | ae.ByteOrder = binary.BigEndian 30 | ae.Uint32(ctaID, *filters.ID) 31 | ae.ByteOrder = nativeEndian 32 | } 33 | if filters.Mark != nil { 34 | ae.ByteOrder = binary.BigEndian 35 | ae.Uint32(ctaMark, *filters.Mark) 36 | ae.ByteOrder = nativeEndian 37 | } 38 | 39 | if filters.MarkMask != nil { 40 | ae.ByteOrder = binary.BigEndian 41 | ae.Uint32(ctaMarkMask, *filters.MarkMask) 42 | ae.ByteOrder = nativeEndian 43 | } 44 | 45 | if filters.Timeout != nil { 46 | ae.ByteOrder = binary.BigEndian 47 | ae.Uint32(ctaTimeout, *filters.Timeout) 48 | ae.ByteOrder = nativeEndian 49 | } 50 | if filters.Status != nil { 51 | ae.ByteOrder = binary.BigEndian 52 | ae.Uint32(ctaStatus, *filters.Status) 53 | ae.ByteOrder = nativeEndian 54 | } 55 | if filters.ProtoInfo != nil { 56 | data, err := marshalProtoInfo(logger, filters.ProtoInfo) 57 | if err != nil { 58 | return []byte{}, err 59 | } 60 | ae.Bytes(ctaProtoinfo|nlafNested, data) 61 | } 62 | if filters.Helper != nil { 63 | data, err := marshalHelper(logger, filters.Helper) 64 | if err != nil { 65 | return []byte{}, err 66 | } 67 | ae.Bytes(ctaHelp|nlafNested, data) 68 | } 69 | 70 | if filters.NatSrc != nil { 71 | data, err := marshalNat(logger, filters.NatSrc) 72 | if err != nil { 73 | return []byte{}, err 74 | } 75 | ae.Bytes(ctaNatSrc|nlafNested, data) 76 | } 77 | 78 | if filters.Exp != nil { 79 | if err := nestExpectedAttributes(logger, ae, filters.Exp); err != nil { 80 | return []byte{}, err 81 | } 82 | } 83 | 84 | return ae.Encode() 85 | } 86 | 87 | func nestExpectedAttributes(logger *log.Logger, ae *netlink.AttributeEncoder, filters *Exp) error { 88 | 89 | if filters.Master != nil { 90 | data, err := marshalIPTuple(logger, filters.Master) 91 | if err != nil { 92 | return err 93 | } 94 | ae.Bytes(ctaExpMaster|nlafNested, data) 95 | } 96 | if filters.Mask != nil { 97 | data, err := marshalIPTuple(logger, filters.Mask) 98 | if err != nil { 99 | return err 100 | } 101 | ae.Bytes(ctaExpMask|nlafNested, data) 102 | } 103 | if filters.Tuple != nil { 104 | data, err := marshalIPTuple(logger, filters.Tuple) 105 | if err != nil { 106 | return err 107 | } 108 | ae.Bytes(ctaExpTuple|nlafNested, data) 109 | } 110 | if filters.Flags != nil { 111 | ae.ByteOrder = binary.BigEndian 112 | ae.Uint32(ctaExpFlags, *filters.Flags) 113 | ae.ByteOrder = nativeEndian 114 | } 115 | if filters.Class != nil { 116 | ae.ByteOrder = binary.BigEndian 117 | ae.Uint32(ctaExpClass, *filters.Class) 118 | ae.ByteOrder = nativeEndian 119 | } 120 | if filters.ID != nil { 121 | ae.ByteOrder = binary.BigEndian 122 | ae.Uint32(ctaExpID, *filters.ID) 123 | ae.ByteOrder = nativeEndian 124 | } 125 | if filters.Timeout != nil { 126 | ae.ByteOrder = binary.BigEndian 127 | ae.Uint32(ctaExpTimeout, *filters.Timeout) 128 | ae.ByteOrder = nativeEndian 129 | } 130 | if filters.Zone != nil { 131 | ae.ByteOrder = binary.BigEndian 132 | ae.Uint16(ctaExpZone, *filters.Zone) 133 | ae.ByteOrder = nativeEndian 134 | } 135 | if filters.HelperName != nil { 136 | ae.ByteOrder = binary.BigEndian 137 | ae.String(ctaExpHelpName, *filters.HelperName) 138 | ae.ByteOrder = nativeEndian 139 | } 140 | if filters.Fn != nil { 141 | ae.ByteOrder = binary.BigEndian 142 | ae.String(ctaExpFn, *filters.Fn) 143 | ae.ByteOrder = nativeEndian 144 | } 145 | if filters.Nat != nil { 146 | data, err := marshalNatInfo(logger, filters.Nat) 147 | if err != nil { 148 | return err 149 | } 150 | ae.Bytes(ctaExpNat|nlafNested, data) 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "net" 9 | "time" 10 | 11 | "github.com/florianl/go-conntrack/internal/unix" 12 | 13 | "github.com/mdlayher/netlink" 14 | ) 15 | 16 | // Config contains options for a Conn. 17 | type Config struct { 18 | // NetNS specifies the network namespace the Conn will operate in. 19 | // 20 | // If set (non-zero), Conn will enter the specified network namespace and 21 | // an error will occur in Dial if the operation fails. 22 | // 23 | // If not set (zero), a best-effort attempt will be made to enter the 24 | // network namespace of the calling thread: this means that any changes made 25 | // to the calling thread's network namespace will also be reflected in Conn. 26 | // If this operation fails (due to lack of permissions or because network 27 | // namespaces are disabled by kernel configuration), Dial will not return 28 | // an error, and the Conn will operate in the default network namespace of 29 | // the process. This enables non-privileged use of Conn in applications 30 | // which do not require elevated privileges. 31 | NetNS int 32 | 33 | // Time till a read action times out - only available for Go >= 1.12 34 | // 35 | // Deprecated: Cancel the context passed to RegisterFiltered() or Register() 36 | // to remove the conntrack gracefully. Setting this value does no longer 37 | // have an effect on conntrack. 38 | ReadTimeout time.Duration 39 | 40 | // Time till a write action times out - only available for Go >= 1.12 41 | WriteTimeout time.Duration 42 | 43 | // Interface to log internals. 44 | Logger *log.Logger 45 | 46 | // DisableNSLockThread disables package netlink's default goroutine thread 47 | // locking behavior. 48 | // 49 | // By default, the library will lock the processing goroutine to its 50 | // corresponding OS thread in order to enable communication over netlink to 51 | // a different network namespace. 52 | // 53 | // If the caller already knows that the netlink socket is in the same 54 | // namespace as the calling thread, this can introduce a performance 55 | // impact. This option disables the OS thread locking behavior if 56 | // performance considerations are of interest. 57 | // 58 | // If disabled, it is the responsibility of the caller to make sure that all 59 | // threads are running in the correct namespace. 60 | // 61 | // When DisableNSLockThread is set, the caller cannot set the NetNS value. 62 | DisableNSLockThread bool 63 | 64 | // AddConntrackInformation enriches Con and provides additional information of 65 | // the Netlink/Conntrack origin. 66 | AddConntrackInformation bool 67 | } 68 | 69 | // Nfct represents a conntrack handler 70 | type Nfct struct { 71 | // Con is the pure representation of a netlink socket 72 | Con *netlink.Conn 73 | 74 | logger *log.Logger 75 | 76 | errChan chan error 77 | 78 | debug bool 79 | 80 | setWriteTimeout func() error 81 | 82 | ctx context.Context 83 | ctxCancel context.CancelFunc 84 | shutdown chan struct{} 85 | 86 | addConntrackInformation bool 87 | } 88 | 89 | // adjust the WriteTimeout (mostly for testing) 90 | func adjustWriteTimeout(nfct *Nfct, fn func() error) { 91 | nfct.setWriteTimeout = fn 92 | } 93 | 94 | // SecCtx contains additional information about the security context 95 | type SecCtx struct { 96 | Name *string 97 | } 98 | 99 | // Timestamp contains start and/or stop times 100 | type Timestamp struct { 101 | Start *time.Time 102 | Stop *time.Time 103 | } 104 | 105 | // TCPFlags contains additional information for TCP flags 106 | type TCPFlags struct { 107 | Flags *uint8 108 | Mask *uint8 109 | } 110 | 111 | // TCPInfo contains additional information for TCP sessions 112 | type TCPInfo struct { 113 | State *uint8 114 | WScaleOrig *uint8 115 | WScaleRepl *uint8 116 | FlagsOrig *TCPFlags 117 | FlagsReply *TCPFlags 118 | } 119 | 120 | // DCCPInfo contains additional information for DCCP sessions 121 | type DCCPInfo struct { 122 | State *uint8 123 | Role *uint8 124 | HandshakeSeq *uint64 125 | } 126 | 127 | // SCTPInfo contains additional information for SCTP sessions 128 | type SCTPInfo struct { 129 | State *uint8 130 | VTagOriginal *uint32 131 | VTagReply *uint32 132 | } 133 | 134 | // Helper contains additional information 135 | type Helper struct { 136 | Name *string 137 | Info *string 138 | } 139 | 140 | // SeqAdj contains additional information about corrections 141 | type SeqAdj struct { 142 | CorrectionPos *uint32 143 | OffsetBefore *uint32 144 | OffsetAfter *uint32 145 | } 146 | 147 | // Counter contains additional information about the traffic 148 | type Counter struct { 149 | Packets *uint64 150 | Bytes *uint64 151 | Packets32 *uint32 152 | Bytes32 *uint32 153 | } 154 | 155 | // ProtoInfo contains additional information to certain protocols 156 | type ProtoInfo struct { 157 | TCP *TCPInfo 158 | DCCP *DCCPInfo 159 | SCTP *SCTPInfo 160 | } 161 | 162 | // ProtoTuple contains information about the used protocol 163 | type ProtoTuple struct { 164 | Number *uint8 165 | SrcPort *uint16 166 | DstPort *uint16 167 | IcmpID *uint16 168 | IcmpType *uint8 169 | IcmpCode *uint8 170 | Icmpv6ID *uint16 171 | Icmpv6Type *uint8 172 | Icmpv6Code *uint8 173 | } 174 | 175 | // IPTuple contains the source and destination IP 176 | type IPTuple struct { 177 | Src *net.IP 178 | Dst *net.IP 179 | Proto *ProtoTuple 180 | Zone *uint16 181 | } 182 | 183 | // NatInfo contains addition NAT information of a connection 184 | type NatInfo struct { 185 | Dir *uint32 186 | Tuple *IPTuple 187 | } 188 | 189 | // Exp extends the information of a connection by information from the expected table 190 | type Exp struct { 191 | Master *IPTuple 192 | Tuple *IPTuple 193 | Mask *IPTuple 194 | Flags *uint32 195 | Class *uint32 196 | ID *uint32 197 | Timeout *uint32 198 | Zone *uint16 199 | HelperName *string 200 | Fn *string 201 | Nat *NatInfo 202 | } 203 | 204 | // Nat contains information for source/destination NAT 205 | type Nat struct { 206 | IPMin *net.IP 207 | IPMax *net.IP 208 | Proto *ProtoTuple 209 | } 210 | 211 | // Con contains all the information of a connection 212 | type Con struct { 213 | Info *InfoSource 214 | 215 | Origin *IPTuple 216 | Reply *IPTuple 217 | ProtoInfo *ProtoInfo 218 | CounterOrigin *Counter 219 | CounterReply *Counter 220 | Helper *Helper 221 | NatSrc *Nat 222 | SeqAdjOrig *SeqAdj 223 | SeqAdjRepl *SeqAdj 224 | ID *uint32 225 | Status *uint32 226 | StatusMask *uint32 227 | Use *uint32 228 | Mark *uint32 229 | MarkMask *uint32 230 | Timeout *uint32 231 | Zone *uint16 232 | Timestamp *Timestamp 233 | SecCtx *SecCtx 234 | Exp *Exp 235 | } 236 | 237 | // InfoSource provides further information from Netlink about a connection. 238 | type InfoSource struct { 239 | // Conntrack table this information originates from. 240 | Table Table 241 | 242 | // NetlinkGroup this information originates from. 243 | NetlinkGroup NetlinkGroup 244 | } 245 | 246 | // CPUStat contains various conntrack related per CPU statistics 247 | type CPUStat struct { 248 | // ID of the CPU 249 | ID uint32 250 | 251 | // Values from the conntrack table 252 | Found *uint32 253 | Invalid *uint32 254 | Ignore *uint32 255 | Insert *uint32 256 | InsertFailed *uint32 257 | Drop *uint32 258 | EarlyDrop *uint32 259 | Error *uint32 260 | SearchRestart *uint32 261 | 262 | // Values from the expect table 263 | ExpNew *uint32 264 | ExpCreate *uint32 265 | ExpDelete *uint32 266 | } 267 | 268 | // Table specifies the subsystem of conntrack 269 | type Table int 270 | 271 | // NetlinkGroup represents a Netlink multicast group 272 | type NetlinkGroup uint32 273 | 274 | // Supported Netlink groups 275 | const ( 276 | NetlinkCtNew NetlinkGroup = 1 << iota 277 | NetlinkCtUpdate NetlinkGroup = 1 << iota 278 | NetlinkCtDestroy NetlinkGroup = 1 << iota 279 | NetlinkCtExpectedNew NetlinkGroup = 1 << iota 280 | NetlinkCtExpectedUpdate NetlinkGroup = 1 << iota 281 | NetlinkCtExpectedDestroy NetlinkGroup = 1 << iota 282 | ) 283 | 284 | // Family specifies the network family 285 | type Family uint8 286 | 287 | // Supported family types 288 | const ( 289 | IPv6 Family = unix.AF_INET6 290 | IPv4 Family = unix.AF_INET 291 | ) 292 | 293 | // FilterAttr represents a very basic filter 294 | type FilterAttr struct { 295 | Mark, MarkMask []byte 296 | } 297 | 298 | // ConnAttr represents the type and value of a attribute of a connection 299 | type ConnAttr struct { 300 | Type ConnAttrType 301 | Data []byte 302 | // Mask is required for specific attributes, if you want to filter for them 303 | Mask []byte 304 | // Negates this attribute for filtering 305 | Negate bool 306 | } 307 | 308 | func (ca ConnAttr) String() string { 309 | return fmt.Sprintf("Type: %2d - Data: [%v] - Mask: [%v] - Negate: %t\n", ca.Type, ca.Data, ca.Mask, ca.Negate) 310 | } 311 | 312 | // ConnAttrType specifies the attribute of a connection 313 | type ConnAttrType uint16 314 | 315 | // Attributes of a connection 316 | // based on libnetfilter_conntrack.h 317 | const ( 318 | AttrOrigIPv4Src ConnAttrType = iota /* u32 bits, requires a mask if applied as filter */ 319 | AttrOrigIPv4Dst ConnAttrType = iota /* u32 bits, requires a mask if applied as filter */ 320 | AttrReplIPv4Src ConnAttrType = iota /* u32 bits, requires a mask if applied as filter */ 321 | AttrReplIPv4Dst ConnAttrType = iota /* u32 bits, requires a mask if applied as filter */ 322 | AttrOrigIPv6Src ConnAttrType = iota /* u128 bits */ 323 | AttrOrigIPv6Dst ConnAttrType = iota /* u128 bits */ 324 | AttrReplIPv6Src ConnAttrType = iota /* u128 bits */ 325 | AttrReplIPv6Dst ConnAttrType = iota /* u128 bits */ 326 | AttrOrigPortSrc ConnAttrType = iota /* u16 bits */ 327 | AttrOrigPortDst ConnAttrType = iota /* u16 bits */ 328 | AttrReplPortSrc ConnAttrType = iota /* u16 bits */ 329 | AttrReplPortDst ConnAttrType = iota /* u16 bits */ 330 | AttrIcmpType ConnAttrType = iota /* u8 bits */ 331 | AttrIcmpCode ConnAttrType = iota /* u8 bits */ 332 | AttrIcmpID ConnAttrType = iota /* u16 bits */ 333 | AttrOrigL3Proto ConnAttrType = iota /* u8 bits */ 334 | AttrReplL3Proto ConnAttrType = iota /* u8 bits */ 335 | AttrOrigL4Proto ConnAttrType = iota /* u8 bits */ 336 | AttrReplL4Proto ConnAttrType = iota /* u8 bits */ 337 | AttrTCPState ConnAttrType = iota /* u8 bits */ 338 | AttrSNatIPv4 ConnAttrType = iota /* u32 bits */ 339 | AttrDNatIPv4 ConnAttrType = iota /* u32 bits */ 340 | AttrSNatPort ConnAttrType = iota /* u16 bits */ 341 | AttrDNatPort ConnAttrType = iota /* u16 bits */ 342 | AttrTimeout ConnAttrType = iota /* u32 bits */ 343 | AttrMark ConnAttrType = iota /* u32 bits, requires a mask if applied as filter */ 344 | AttrMarkMask ConnAttrType = iota /* u32 bits */ 345 | AttrOrigCounterPackets ConnAttrType = iota /* u64 bits */ 346 | AttrReplCounterPackets ConnAttrType = iota /* u64 bits */ 347 | AttrOrigCounterBytes ConnAttrType = iota /* u64 bits */ 348 | AttrReplCounterBytes ConnAttrType = iota /* u64 bits */ 349 | AttrUse ConnAttrType = iota /* u32 bits */ 350 | AttrID ConnAttrType = iota /* u32 bits */ 351 | AttrStatus ConnAttrType = iota /* u32 bits */ 352 | AttrTCPFlagsOrig ConnAttrType = iota /* u8 bits */ 353 | AttrTCPFlagsRepl ConnAttrType = iota /* u8 bits */ 354 | AttrTCPMaskOrig ConnAttrType = iota /* u8 bits */ 355 | AttrTCPMaskRepl ConnAttrType = iota /* u8 bits */ 356 | AttrMasterIPv4Src ConnAttrType = iota /* u32 bits */ 357 | AttrMasterIPv4Dst ConnAttrType = iota /* u32 bits */ 358 | AttrMasterIPv6Src ConnAttrType = iota /* u128 bits */ 359 | AttrMasterIPv6Dst ConnAttrType = iota /* u128 bits */ 360 | AttrMasterPortSrc ConnAttrType = iota /* u16 bits */ 361 | AttrMasterPortDst ConnAttrType = iota /* u16 bits */ 362 | AttrMasterL3Proto ConnAttrType = iota /* u8 bits */ 363 | AttrMasterL4Proto ConnAttrType = iota /* u8 bits */ 364 | AttrSecmark ConnAttrType = iota /* u32 bits */ 365 | AttrOrigNatSeqCorrectionPos ConnAttrType = iota /* u32 bits */ 366 | AttrOrigNatSeqOffsetBefore ConnAttrType = iota /* u32 bits */ 367 | AttrOrigNatSeqOffsetAfter ConnAttrType = iota /* u32 bits */ 368 | AttrReplNatSeqCorrectionPos ConnAttrType = iota /* u32 bits */ 369 | AttrReplNatSeqOffsetBefore ConnAttrType = iota /* u32 bits */ 370 | AttrReplNatSeqOffsetAfter ConnAttrType = iota /* u32 bits */ 371 | AttrSctpState ConnAttrType = iota /* u8 bits */ 372 | AttrSctpVtagOrig ConnAttrType = iota /* u32 bits */ 373 | AttrSctpVtagRepl ConnAttrType = iota /* u32 bits */ 374 | AttrHelperName ConnAttrType = iota /* string (30 bytes max) */ 375 | AttrDccpState ConnAttrType = iota /* u8 bits */ 376 | AttrDccpRole ConnAttrType = iota /* u8 bits */ 377 | AttrDccpHandshakeSeq ConnAttrType = iota /* u64 bits */ 378 | AttrTCPWScaleOrig ConnAttrType = iota /* u8 bits */ 379 | AttrTCPWScaleRepl ConnAttrType = iota /* u8 bits */ 380 | AttrZone ConnAttrType = iota /* u16 bits */ 381 | AttrSecCtx ConnAttrType = iota /* string */ 382 | AttrTimestampStart ConnAttrType = iota /* u64 bits linux >= 2.6.38 */ 383 | AttrTimestampStop ConnAttrType = iota /* u64 bits linux >= 2.6.38 */ 384 | AttrHelperInfo ConnAttrType = iota /* variable length */ 385 | AttrConnlabels ConnAttrType = iota /* variable length */ 386 | AttrConnlabelsMask ConnAttrType = iota /* variable length */ 387 | AttrOrigzone ConnAttrType = iota /* u16 bits */ 388 | AttrReplzone ConnAttrType = iota /* u16 bits */ 389 | AttrSNatIPv6 ConnAttrType = iota /* u128 bits */ 390 | AttrDNatIPv6 ConnAttrType = iota /* u128 bits */ 391 | 392 | AttrIcmpv6Type ConnAttrType = iota /* u8 bits */ 393 | AttrIcmpv6Code ConnAttrType = iota /* u8 bits */ 394 | AttrIcmpv6ID ConnAttrType = iota /* u16 bits */ 395 | 396 | attrMax ConnAttrType = iota /* This is for internal use only */ 397 | 398 | AttrExpID ConnAttrType = iota /* u32 bits */ 399 | AttrExpFlags ConnAttrType = iota /* u32 bits */ 400 | AttrExpClass ConnAttrType = iota /* u32 bits */ 401 | AttrExpNATDir ConnAttrType = iota /* u32 bits */ 402 | 403 | // for internal use only 404 | attrUnspec ConnAttrType = iota 405 | ) 406 | 407 | // Various errors which may occur when processing attributes 408 | var ( 409 | ErrAttrLength = errors.New("incorrect length of attribute") 410 | ErrAttrNotImplemented = errors.New("attribute not implemented") 411 | ErrAttrNotExist = errors.New("type of attribute does not exist") 412 | ErrDataLength = errors.New("incorrect length of provided data") 413 | ) 414 | 415 | // ErrUnknownCtTable will be return, if the function can not be performed on this subsystem 416 | var ErrUnknownCtTable = errors.New("not supported for this subsystem") 417 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestConnAttr_String(t *testing.T) { 9 | ca := ConnAttr{ 10 | Type: AttrOrigIPv4Src, 11 | Data: []byte{0x7F, 0x00, 0x00, 0x01}, 12 | Mask: []byte{0xFF, 0xFF, 0xFF, 0xFF}, 13 | } 14 | fmt.Printf("%v\n", ca) 15 | // Output: Type: 0 - Data: [[127 0 0 1]] - Mask: [[255 255 255 255]] - Negate: false 16 | } 17 | --------------------------------------------------------------------------------