From 58ff8865df4cb26b80b643b3138d50cb68c8232b Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Thu, 10 Jun 2021 14:46:17 +0300 Subject: [PATCH] Fix windows packet capture (#943) Issues is that Go built-in net.Interfaces function in newer Windows versions return wrong interface names, which libpcap can't consume. Now we use pcap.FindDevices instead of net.Interfaces. See this Article for deep understanding of the issue https://haydz.github.io/2020/07/06/Go-Windows-NIC.html Additionally, found a bug causing big memory allocations, for large requests, when we perform check if messages finished or not. Because of this bug chunked body encoding check was not working properly. Was not caught in tests, because test was working on packet array level, and this issue happens when dealing with TCP message object. Additionally, added a small fix for windows Makefile task, it now generates proper file name. --- Makefile | 8 ++-- capture/capture.go | 95 ++++++++++++++++++++++-------------------- capture/sock_linux.go | 18 +++++++- capture/sock_others.go | 5 ++- proto/proto.go | 1 + tcp/tcp_message.go | 7 +--- 6 files changed, 77 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index a313231..55fb9c8 100644 --- a/Makefile +++ b/Makefile @@ -59,16 +59,16 @@ release-mac: rm -rf /tmp/gor-build release-windows: - echo $(pwd) docker run -it --rm \ -v `pwd`:/go/src/github.com/buger/goreplay \ -w /go/src/github.com/buger/goreplay \ -e CGO_ENABLED=1 \ docker.elastic.co/beats-dev/golang-crossbuild:1.16.4-main \ - --build-cmd "VERSION=make build" \ + --build-cmd "make VERSION=$(VERSION) build" \ -p "windows/amd64" - - mv ./gor ./gor-$(VERSION)$(PREFIX).exe + mv ./gor ./gor.exe + zip gor-$(VERSION)$(PREFIX)_windows.zip ./gor.exe + rm -rf ./gor.exe build: go build -o $(BIN_NAME) $(LDFLAGS) diff --git a/capture/capture.go b/capture/capture.go index dcb539b..3cc4810 100644 --- a/capture/capture.go +++ b/capture/capture.go @@ -43,7 +43,7 @@ type Listener struct { Transport string // transport layer default to tcp Activate func() error // function is used to activate the engine. it must be called before reading packets Handles map[string]gopacket.ZeroCopyPacketDataSource - Interfaces []net.Interface + Interfaces []pcap.Interface loopIndex int Reading chan bool // this channel is closed when the listener has started reading packets PcapOptions @@ -103,6 +103,10 @@ func NewListener(host string, port uint16, transport string, engine EngineType, l = &Listener{} l.host = host + if l.host == "localhost" { + l.host = "127.0.0.1" + } + l.port = port l.Transport = "tcp" if transport != "" { @@ -125,6 +129,7 @@ func NewListener(host string, port uint16, transport string, engine EngineType, l.Activate = l.activatePcapFile return } + err = l.setInterfaces() if err != nil { return nil, err @@ -168,7 +173,7 @@ func (l *Listener) ListenBackground(ctx context.Context, handler PacketHandler) // Filter returns automatic filter applied by goreplay // to a pcap handle of a specific interface -func (l *Listener) Filter(ifi net.Interface) (filter string) { +func (l *Listener) Filter(ifi pcap.Interface) (filter string) { // https://www.tcpdump.org/manpages/pcap-filter.7.html hosts := []string{l.host} @@ -218,7 +223,7 @@ func (l *Listener) Filter(ifi net.Interface) (filter string) { // PcapHandle returns new pcap Handle from dev on success. // this function should be called after setting all necessary options for this listener -func (l *Listener) PcapHandle(ifi net.Interface) (handle *pcap.Handle, err error) { +func (l *Listener) PcapHandle(ifi pcap.Interface) (handle *pcap.Handle, err error) { var inactive *pcap.InactiveHandle inactive, err = pcap.NewInactiveHandle(ifi.Name) if err != nil { @@ -243,12 +248,22 @@ func (l *Listener) PcapHandle(ifi net.Interface) (handle *pcap.Handle, err error return nil, fmt.Errorf("monitor mode error: %q, interface: %q", err, ifi.Name) } } + var snap int - if l.Snaplen { - snap = 64<<10 + 200 - } else if ifi.MTU > 0 { - snap = ifi.MTU + 200 + + if !l.Snaplen { + infs, _ := net.Interfaces() + for _, i := range infs { + if i.Name == ifi.Name { + snap = i.MTU + 200 + } + } } + + if snap == 0 { + snap = 64<<10 + 200 + } + err = inactive.SetSnapLen(snap) if err != nil { return nil, fmt.Errorf("snapshot length error: %q, interface: %q", err, ifi.Name) @@ -281,7 +296,7 @@ func (l *Listener) PcapHandle(ifi net.Interface) (handle *pcap.Handle, err error } // SocketHandle returns new unix ethernet handle associated with this listener settings -func (l *Listener) SocketHandle(ifi net.Interface) (handle Socket, err error) { +func (l *Listener) SocketHandle(ifi pcap.Interface) (handle Socket, err error) { handle, err = NewSocket(ifi) if err != nil { return nil, fmt.Errorf("sock raw error: %q, interface: %q", err, ifi.Name) @@ -418,7 +433,7 @@ func (l *Listener) activatePcapFile() (err error) { tmp := l.host l.host = "" - l.BPFFilter = l.Filter(net.Interface{}) + l.BPFFilter = l.Filter(pcap.Interface{}) l.host = tmp if e = handle.SetBPFFilter(l.BPFFilter); e != nil { @@ -430,60 +445,50 @@ func (l *Listener) activatePcapFile() (err error) { } func (l *Listener) setInterfaces() (err error) { - var ifis []net.Interface - ifis, err = net.Interfaces() + var pifis []pcap.Interface + pifis, err = pcap.FindAllDevs() + ifis, _ := net.Interfaces() if err != nil { return } - for i := range ifis { - if ifis[i].Flags&net.FlagLoopback != 0 { - l.loopIndex = ifis[i].Index + for _, pi := range pifis { + var ni net.Interface + for _, i := range ifis { + if i.Name == pi.Name { + ni = i + break + } } - if ifis[i].Flags&net.FlagUp == 0 { + + if net.Flags(pi.Flags)&net.FlagLoopback != 0 { + l.loopIndex = ni.Index + } + if net.Flags(pi.Flags)&net.FlagUp == 0 { continue } - if isDevice(l.host, ifis[i]) { - l.Interfaces = []net.Interface{ifis[i]} + if isDevice(l.host, pi) { + l.Interfaces = []pcap.Interface{pi} return } - addrs, e := ifis[i].Addrs() - if e != nil { - // don't give up on a failure from a single interface - continue - } - for _, addr := range addrs { - if cutMask(addr) == l.host { - l.Interfaces = []net.Interface{ifis[i]} + for _, addr := range pi.Addresses { + if addr.IP.String() == l.host { + l.Interfaces = []pcap.Interface{pi} return } } } - l.Interfaces = ifis + l.Interfaces = pifis return } -func cutMask(addr net.Addr) string { - mask := addr.String() - for i, v := range mask { - if v == '/' { - return mask[:i] - } - } - return mask +func isDevice(addr string, ifi pcap.Interface) bool { + return addr == ifi.Name } -func isDevice(addr string, ifi net.Interface) bool { - return addr == ifi.Name || addr == fmt.Sprintf("%d", ifi.Index) || (addr != "" && addr == ifi.HardwareAddr.String()) -} - -func interfaceAddresses(ifi net.Interface) []string { +func interfaceAddresses(ifi pcap.Interface) []string { var hosts []string - if addrs, err := ifi.Addrs(); err == nil { - for _, addr := range addrs { - if ip := addr.(*net.IPNet).IP.To16(); ip != nil { - hosts = append(hosts, ip.String()) - } - } + for _, addr := range ifi.Addresses { + hosts = append(hosts, addr.IP.String()) } return hosts } diff --git a/capture/sock_linux.go b/capture/sock_linux.go index 00c591e..a68c845 100644 --- a/capture/sock_linux.go +++ b/capture/sock_linux.go @@ -44,7 +44,23 @@ type SockRaw struct { } // NewSocket returns new M'maped sock_raw on packet version 2. -func NewSocket(ifi net.Interface) (*SockRaw, error) { +func NewSocket(pifi pcap.Interface) (*SockRaw, error) { + var ifi net.Interface + + infs, _ := net.Interfaces() + found := false + for _, i := range infs { + if i.Name == pifi.Name { + ifi = i + found = true + break + } + } + + if !found { + return nil, fmt.Errorf("Can't find matching interface") + } + // sock create fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(ETHALL)) if err != nil { diff --git a/capture/sock_others.go b/capture/sock_others.go index 42016b9..0d8559b 100644 --- a/capture/sock_others.go +++ b/capture/sock_others.go @@ -4,10 +4,11 @@ package capture import ( "errors" - "net" + + "github.com/google/gopacket/pcap" ) // NewSocket returns new M'maped sock_raw on packet version 2. -func NewSocket(_ net.Interface) (Socket, error) { +func NewSocket(_ pcap.Interface) (Socket, error) { return nil, errors.New("afpacket socket is only available on linux") } diff --git a/proto/proto.go b/proto/proto.go index bb5a776..5f51d1c 100644 --- a/proto/proto.go +++ b/proto/proto.go @@ -19,6 +19,7 @@ package proto import ( "bufio" "bytes" + _ "fmt" "net/http" "net/textproto" "strings" diff --git a/tcp/tcp_message.go b/tcp/tcp_message.go index 5bce426..b45aa05 100644 --- a/tcp/tcp_message.go +++ b/tcp/tcp_message.go @@ -3,6 +3,7 @@ package tcp import ( "encoding/binary" "encoding/hex" + _ "fmt" "sort" "time" @@ -112,11 +113,7 @@ func (m *Message) MissingChunk() bool { } func (m *Message) PacketData() [][]byte { - var totalLen int - for _, p := range m.packets { - totalLen += len(p.Payload) - } - tmp := make([][]byte, totalLen) + tmp := make([][]byte, len(m.packets)) for i, p := range m.packets { tmp[i] = p.Payload