From e1802217b4861931028299b4066e1670dbc1d04c Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:00:28 -0400 Subject: [PATCH 1/8] Add ExcludeConfig.DstIPs option --- netlink/archival-record.go | 7 +++ netlink/archival-record_test.go | 81 +++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 netlink/archival-record_test.go diff --git a/netlink/archival-record.go b/netlink/archival-record.go index 482424d..f79585a 100644 --- a/netlink/archival-record.go +++ b/netlink/archival-record.go @@ -53,6 +53,7 @@ type ExcludeConfig struct { Local bool // SrcPorts excludes connections from specific source ports. SrcPorts map[uint16]bool + DstIPs map[[16]byte]bool } // ParseRouteAttr parses a byte array into slice of NetlinkRouteAttr struct. @@ -94,6 +95,12 @@ func MakeArchivalRecord(msg *NetlinkMessage, exclude *ExcludeConfig) (*ArchivalR if exclude.Local && (isLocal(idm.ID.SrcIP()) || isLocal(idm.ID.DstIP())) { return nil, nil } + if exclude.DstIPs != nil && exclude.DstIPs[idm.ID.IDiagDst] { + // Note: byte-key lookup is preferable for performance than + // net.IP-to-String formatting. And, a byte array can be a map key, + // while a net.IP byte slice cannot. + return nil, nil + } } record := ArchivalRecord{RawIDM: raw} diff --git a/netlink/archival-record_test.go b/netlink/archival-record_test.go new file mode 100644 index 0000000..83ac412 --- /dev/null +++ b/netlink/archival-record_test.go @@ -0,0 +1,81 @@ +// Package netlink contains the bare minimum needed to partially parse netlink messages. +package netlink + +import ( + "testing" + "unsafe" + + //"github.com/m-lab/go/pretty" + + "github.com/m-lab/tcp-info/inetdiag" +) + +func inet2bytes(inet *inetdiag.InetDiagMsg) []byte { + const sz = int(unsafe.Sizeof(inetdiag.InetDiagMsg{})) + return (*[sz]byte)(unsafe.Pointer(inet))[:] +} + +func TestMakeArchivalRecord(t *testing.T) { + //fmt.Println(s) + // ptr := unsafe.Pointer(&inet) + // (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] + // return (*InetDiagMsg)(unsafe.Pointer(&raw[0])), nil + id := inetdiag.LinuxSockID{ + IDiagSPort: [2]byte{0, 77}, // src port + IDiagSrc: [16]byte{127, 0, 0, 1}, // localhost + IDiagDst: [16]byte{172, 25, 0, 1}, // dst ip + } + tests := []struct { + name string + msg *NetlinkMessage + exclude *ExcludeConfig + want *ArchivalRecord + wantErr bool + }{ + { + name: "exclude-local", + msg: &NetlinkMessage{ + Header: NlMsghdr{Type: 20}, + Data: inet2bytes(&inetdiag.InetDiagMsg{ID: id}), + }, + exclude: &ExcludeConfig{ + Local: true, + }, + }, + { + name: "exclude-srcport", + msg: &NetlinkMessage{ + Header: NlMsghdr{Type: 20}, + Data: inet2bytes(&inetdiag.InetDiagMsg{ID: id}), + }, + exclude: &ExcludeConfig{ + SrcPorts: map[uint16]bool{77: true}, + }, + }, + { + name: "exclude-dstip", + msg: &NetlinkMessage{ + Header: NlMsghdr{Type: 20}, + Data: inet2bytes(&inetdiag.InetDiagMsg{ID: id}), + }, + exclude: &ExcludeConfig{ + DstIPs: map[[16]byte]bool{[16]byte{172, 25, 0, 1}: true}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // All cases should return nil. + got, err := MakeArchivalRecord(tt.msg, tt.exclude) + if err != nil { + t.Errorf("MakeArchivalRecord() error = %v, wantErr nil", err) + return + } + if got != nil { + t.Errorf("MakeArchivalRecord() = %v, want nil", got) + } + }) + } + /* + */ +} From 95f939d249910f49cb645f8a7a519b0b0890930d Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:01:06 -0400 Subject: [PATCH 2/8] Add support for excluding static DstIPs --- main.go | 25 +++++++++++++++++++++++++ main_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/main.go b/main.go index e5a5b18..af8afe5 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "context" "flag" "log" + "net" "os" "runtime" "runtime/trace" @@ -57,6 +58,7 @@ var ( enableTrace bool outputDir string excludeSrcPorts = flagx.StringArray{} + excludeDstIPs = flagx.StringArray{} ) func init() { @@ -67,6 +69,7 @@ func init() { flag.BoolVar(&enableTrace, "trace", false, "Enable trace") flag.StringVar(&outputDir, "output", "", "Directory in which to put the resulting tree of data. Default is the current directory.") flag.Var(&excludeSrcPorts, "exclude-srcport", "Exclude snapshots with these local ports from saved archives.") + flag.Var(&excludeDstIPs, "exclude-dstip", "Exclude snapshots with these remote IPs from saved archives.") } // NOTES: @@ -115,6 +118,28 @@ func main() { Local: true, } + if len(excludeDstIPs) != 0 { + dstIPs := map[[16]byte]bool{} + for _, ipstr := range excludeDstIPs { + ip := net.ParseIP(ipstr) + if ip == nil { + log.Printf("skipping; cannot convert %q to ip", ipstr) + continue + } + ipbuf := [16]byte{} + if ip.To4() != nil { + // NOTE: The Linux-native byte position for IPv4 addresses is the first four bytes. + // The net.IP package format uses the last four bytes. Copy the net.IP bytes to a + // new array to generate a key for dstIPs. + copy(ipbuf[:], ip[12:]) + } else { + copy(ipbuf[:], ip[:]) + } + dstIPs[ipbuf] = true + } + ex.DstIPs = dstIPs + } + if len(excludeSrcPorts) != 0 { srcPorts := map[uint16]bool{} for _, port := range excludeSrcPorts { diff --git a/main_test.go b/main_test.go index d780053..0e45b87 100644 --- a/main_test.go +++ b/main_test.go @@ -32,3 +32,55 @@ func TestMain(t *testing.T) { // REPS=1 should cause main to run once and then exit. main() } + +func TestMainWithExcludeOptions(t *testing.T) { + // Write files to a temp directory. + dir, err := ioutil.TempDir("", "TestMain") + rtx.Must(err, "Could not create tempdir") + defer os.RemoveAll(dir) + + // Make sure that starting up main() does not cause any panics. There's not + // a lot else we can test, but we can at least make sure that it doesn't + // immediately crash. + for _, v := range []struct{ name, val string }{ + {"REPS", "1"}, + {"TRACE", "true"}, + {"OUTPUT", dir}, + {"TCPINFO_EVENTSOCKET", dir + "/eventsock.sock"}, + {"PROMETHEUSX_LISTEN_ADDRESS", ":0"}, + {"EXCLUDE_SRCPORT", "443"}, + {"EXCLUDE_DSTIP", "172.25.0.1"}, + } { + cleanup := osx.MustSetenv(v.name, v.val) + defer cleanup() + } + + // REPS=1 should cause main to run once and then exit. + main() +} + +func TestMainWithBadExcludeOptions(t *testing.T) { + // Write files to a temp directory. + dir, err := ioutil.TempDir("", "TestMain") + rtx.Must(err, "Could not create tempdir") + defer os.RemoveAll(dir) + + // Make sure that starting up main() does not cause any panics. There's not + // a lot else we can test, but we can at least make sure that it doesn't + // immediately crash. + for _, v := range []struct{ name, val string }{ + {"REPS", "1"}, + {"TRACE", "true"}, + {"OUTPUT", dir}, + {"TCPINFO_EVENTSOCKET", dir + "/eventsock.sock"}, + {"PROMETHEUSX_LISTEN_ADDRESS", ":0"}, + {"EXCLUDE_SRCPORT", "NOT_AN_INT"}, + {"EXCLUDE_DSTIP", ";not-an-ip;"}, + } { + cleanup := osx.MustSetenv(v.name, v.val) + defer cleanup() + } + + // REPS=1 should cause main to run once and then exit. + main() +} From b49583c87800dcff1f6443ab6d2aa40071c945d7 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:36:45 -0400 Subject: [PATCH 3/8] Add ExcludeConfig helper functions and tests --- netlink/archival-record.go | 35 +++++++++++++++ netlink/archival-record_test.go | 80 ++++++++++++++++++++++++++++++--- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/netlink/archival-record.go b/netlink/archival-record.go index f79585a..df1fc42 100644 --- a/netlink/archival-record.go +++ b/netlink/archival-record.go @@ -6,10 +6,12 @@ import ( "bytes" "encoding/binary" "encoding/json" + "errors" "flag" "io" "log" "net" + "strconv" "time" "unsafe" @@ -56,6 +58,39 @@ type ExcludeConfig struct { DstIPs map[[16]byte]bool } +func (ex *ExcludeConfig) AddSrcPort(port string) error { + i, err := strconv.ParseInt(port, 10, 16) + if err != nil { + return err + } + if ex.SrcPorts == nil { + ex.SrcPorts = map[uint16]bool{} + } + ex.SrcPorts[uint16(i)] = true + return nil +} + +func (ex *ExcludeConfig) AddDstIP(dst string) error { + ip := net.ParseIP(dst) + if ip == nil { + return errors.New("invalid ip: " + dst) + } + if ex.DstIPs == nil { + ex.DstIPs = map[[16]byte]bool{} + } + buf := [16]byte{} + if ip.To4() != nil { + // NOTE: The Linux-native byte position for IPv4 addresses is the first four bytes. + // The net.IP package format uses the last four bytes. Copy the net.IP bytes to a + // new array to generate a key for dstIPs. + copy(buf[:], ip[12:]) + } else { + copy(buf[:], ip[:]) + } + ex.DstIPs[buf] = true + return nil +} + // ParseRouteAttr parses a byte array into slice of NetlinkRouteAttr struct. // Derived from "github.com/vishvananda/netlink/nl/nl_linux.go" func ParseRouteAttr(b []byte) ([]NetlinkRouteAttr, error) { diff --git a/netlink/archival-record_test.go b/netlink/archival-record_test.go index 83ac412..a60848e 100644 --- a/netlink/archival-record_test.go +++ b/netlink/archival-record_test.go @@ -2,11 +2,10 @@ package netlink import ( + "reflect" "testing" "unsafe" - //"github.com/m-lab/go/pretty" - "github.com/m-lab/tcp-info/inetdiag" ) @@ -16,10 +15,6 @@ func inet2bytes(inet *inetdiag.InetDiagMsg) []byte { } func TestMakeArchivalRecord(t *testing.T) { - //fmt.Println(s) - // ptr := unsafe.Pointer(&inet) - // (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] - // return (*InetDiagMsg)(unsafe.Pointer(&raw[0])), nil id := inetdiag.LinuxSockID{ IDiagSPort: [2]byte{0, 77}, // src port IDiagSrc: [16]byte{127, 0, 0, 1}, // localhost @@ -79,3 +74,76 @@ func TestMakeArchivalRecord(t *testing.T) { /* */ } + +func TestExcludeConfig_AddSrcPort(t *testing.T) { + tests := []struct { + name string + port string + wantPorts map[uint16]bool + wantErr bool + }{ + { + name: "success", + port: "9999", // "not-a-port"}, + wantPorts: map[uint16]bool{ + 9999: true, + }, + }, + { + name: "error", + port: "not-a-port", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ex := &ExcludeConfig{} + if err := ex.AddSrcPort(tt.port); (err != nil) != tt.wantErr { + t.Errorf("ExcludeConfig.AddSrcPort() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(ex.SrcPorts, tt.wantPorts) { + t.Errorf("ExcludeConfig.SrcPorts = %#v, want %#v", ex.SrcPorts, tt.wantPorts) + } + }) + } +} + +func TestExcludeConfig_AddDstIP(t *testing.T) { + tests := []struct { + name string + dst string + wantIPs map[[16]byte]bool + wantErr bool + }{ + { + name: "success-ipv4", + dst: "172.25.0.1", + wantIPs: map[[16]byte]bool{ + [16]byte{172, 25, 0, 1}: true, + }, + }, + { + name: "success-ipv6", + dst: "fd0a:008d:ba3f:a834::", + wantIPs: map[[16]byte]bool{ + [16]byte{0xfd, 0x0a, 0x00, 0x8d, 0xba, 0x3f, 0xa8, 0x34}: true, + }, + }, + { + name: "error", + dst: ";not-an-ip;", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ex := &ExcludeConfig{} + if err := ex.AddDstIP(tt.dst); (err != nil) != tt.wantErr { + t.Errorf("ExcludeConfig.AddDstIP() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(ex.DstIPs, tt.wantIPs) { + t.Errorf("ExcludeConfig.DstIPs = %#v, want %#v", ex.DstIPs, tt.wantIPs) + } + }) + } +} From da59492a1b702b8674c591c56ebdef329d07f775 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:37:00 -0400 Subject: [PATCH 4/8] Simplify with ExcludeConfig helper functions --- main.go | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/main.go b/main.go index af8afe5..275e21d 100644 --- a/main.go +++ b/main.go @@ -7,11 +7,9 @@ import ( "context" "flag" "log" - "net" "os" "runtime" "runtime/trace" - "strconv" "github.com/m-lab/tcp-info/eventsocket" @@ -119,38 +117,22 @@ func main() { } if len(excludeDstIPs) != 0 { - dstIPs := map[[16]byte]bool{} - for _, ipstr := range excludeDstIPs { - ip := net.ParseIP(ipstr) - if ip == nil { - log.Printf("skipping; cannot convert %q to ip", ipstr) + for _, dip := range excludeDstIPs { + err := ex.AddDstIP(dip) + if err != nil { + log.Printf("skipping; cannot convert ip %q; %v", dip, err) continue } - ipbuf := [16]byte{} - if ip.To4() != nil { - // NOTE: The Linux-native byte position for IPv4 addresses is the first four bytes. - // The net.IP package format uses the last four bytes. Copy the net.IP bytes to a - // new array to generate a key for dstIPs. - copy(ipbuf[:], ip[12:]) - } else { - copy(ipbuf[:], ip[:]) - } - dstIPs[ipbuf] = true } - ex.DstIPs = dstIPs } - if len(excludeSrcPorts) != 0 { - srcPorts := map[uint16]bool{} for _, port := range excludeSrcPorts { - i, err := strconv.ParseInt(port, 10, 16) + err := ex.AddSrcPort(port) if err != nil { - log.Printf("skipping; cannot convert %q to integer", port) + log.Printf("skipping; cannot convert port %q; %v", port, err) continue } - srcPorts[uint16(i)] = true } - ex.SrcPorts = srcPorts } // Make the saver and construct the message channel, buffering up to 2 batches From 84ab82eb94528d8af546b3c1f1a22fc7c1e8f705 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:49:42 -0400 Subject: [PATCH 5/8] Remove comments --- netlink/archival-record_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/netlink/archival-record_test.go b/netlink/archival-record_test.go index a60848e..ff69522 100644 --- a/netlink/archival-record_test.go +++ b/netlink/archival-record_test.go @@ -71,8 +71,6 @@ func TestMakeArchivalRecord(t *testing.T) { } }) } - /* - */ } func TestExcludeConfig_AddSrcPort(t *testing.T) { From 776afd5370787e3f48910e187af541865f9b2383 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:52:12 -0400 Subject: [PATCH 6/8] Add docstrings --- netlink/archival-record.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netlink/archival-record.go b/netlink/archival-record.go index df1fc42..1c07ab5 100644 --- a/netlink/archival-record.go +++ b/netlink/archival-record.go @@ -58,6 +58,7 @@ type ExcludeConfig struct { DstIPs map[[16]byte]bool } +// AddSrcPort adds the given port to the set of source ports to exclude. func (ex *ExcludeConfig) AddSrcPort(port string) error { i, err := strconv.ParseInt(port, 10, 16) if err != nil { @@ -70,6 +71,7 @@ func (ex *ExcludeConfig) AddSrcPort(port string) error { return nil } +// AddDstIP adds the given dst IP address to the set of destination IPs to exclude. func (ex *ExcludeConfig) AddDstIP(dst string) error { ip := net.ParseIP(dst) if ip == nil { From 43b583a85763dab52260e9401306fa06b34a7d46 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 5 Jul 2023 19:53:25 -0400 Subject: [PATCH 7/8] Remove old comment --- netlink/archival-record_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlink/archival-record_test.go b/netlink/archival-record_test.go index ff69522..67e7a93 100644 --- a/netlink/archival-record_test.go +++ b/netlink/archival-record_test.go @@ -82,7 +82,7 @@ func TestExcludeConfig_AddSrcPort(t *testing.T) { }{ { name: "success", - port: "9999", // "not-a-port"}, + port: "9999", wantPorts: map[uint16]bool{ 9999: true, }, From b46879b67c0633d5debe6ba7c4f04480c82065e1 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Thu, 6 Jul 2023 08:01:03 -0400 Subject: [PATCH 8/8] Rename key variable --- netlink/archival-record.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netlink/archival-record.go b/netlink/archival-record.go index 1c07ab5..07664ea 100644 --- a/netlink/archival-record.go +++ b/netlink/archival-record.go @@ -80,16 +80,16 @@ func (ex *ExcludeConfig) AddDstIP(dst string) error { if ex.DstIPs == nil { ex.DstIPs = map[[16]byte]bool{} } - buf := [16]byte{} + key := [16]byte{} if ip.To4() != nil { // NOTE: The Linux-native byte position for IPv4 addresses is the first four bytes. // The net.IP package format uses the last four bytes. Copy the net.IP bytes to a // new array to generate a key for dstIPs. - copy(buf[:], ip[12:]) + copy(key[:], ip[12:]) } else { - copy(buf[:], ip[:]) + copy(key[:], ip[:]) } - ex.DstIPs[buf] = true + ex.DstIPs[key] = true return nil }