From 0bbc1d923d7c3fb681c7aaaa14831ba3c126647b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 22 Oct 2018 19:30:06 +0200 Subject: [PATCH] refactor replayer code into pcapreplayer package --- internal/dhcp4/dhcp4_test.go | 50 +--- internal/dhcp6/dhcp6_test.go | 58 +---- internal/testing/pcapreplayer/pcapreplayer.go | 216 ++++++++++++++++++ 3 files changed, 234 insertions(+), 90 deletions(-) create mode 100644 internal/testing/pcapreplayer/pcapreplayer.go diff --git a/internal/dhcp4/dhcp4_test.go b/internal/dhcp4/dhcp4_test.go index 6836370..0a6d1ee 100644 --- a/internal/dhcp4/dhcp4_test.go +++ b/internal/dhcp4/dhcp4_test.go @@ -15,58 +15,26 @@ package dhcp4 import ( - "fmt" "net" "os" + "path/filepath" "testing" "time" "github.com/google/go-cmp/cmp" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcapgo" + "github.com/rtr7/router7/internal/testing/pcapreplayer" ) -type packet struct { - data []byte - ip net.IP - err error -} - -type replayer struct { - pcapr *pcapgo.Reader -} - -func (r *replayer) Close() error { return nil } -func (r *replayer) Write(b []byte) error { /*log.Printf("-> %v", b); */ return nil } -func (r *replayer) SetReadTimeout(t time.Duration) error { return nil } - -func (r *replayer) ReadFrom() ([]byte, net.IP, error) { - data, _, err := r.pcapr.ReadPacketData() - if err != nil { - return nil, nil, err - } - pkt := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.DecodeOptions{}) - // TODO: get source IP - udp := pkt.Layer(layers.LayerTypeUDP) - if udp == nil { - return nil, nil, fmt.Errorf("pcap contained unexpected non-UDP packet") - } - - //log.Printf("ReadFrom(): %v, %v, pkt = %+v", udp.LayerPayload(), err, pkt) - return udp.LayerPayload(), net.ParseIP("192.168.23.1"), err -} - func TestDHCP4(t *testing.T) { - f, err := os.Open("testdata/fiber7.pcap") - if err != nil { - t.Fatal(err) - } - defer f.Close() - pcapr, err := pcapgo.NewReader(f) + pcappath := os.Getenv("ROUTER7_PCAP_DIR") + if pcappath != "" { + pcappath = filepath.Join(pcappath, "dhcp4.pcap") + } + conn, err := pcapreplayer.NewDHCP4Conn("testdata/fiber7.pcap", pcappath) if err != nil { t.Fatal(err) } + defer conn.Close() mac, err := net.ParseMAC("d8:58:d7:00:4e:df") if err != nil { @@ -77,7 +45,7 @@ func TestDHCP4(t *testing.T) { c := Client{ hardwareAddr: mac, timeNow: func() time.Time { return now }, - connection: &replayer{pcapr: pcapr}, + connection: conn, generateXID: func(b []byte) { if got, want := len(b), 4; got != want { t.Fatalf("github.com/d2g/dhcp4client request unexpected amount of bytes: got %d, want %d", got, want) diff --git a/internal/dhcp6/dhcp6_test.go b/internal/dhcp6/dhcp6_test.go index fe8a55d..a0e0cbe 100644 --- a/internal/dhcp6/dhcp6_test.go +++ b/internal/dhcp6/dhcp6_test.go @@ -15,66 +15,26 @@ package dhcp6 import ( - "fmt" "net" "os" + "path/filepath" "testing" "time" "github.com/google/go-cmp/cmp" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcapgo" + "github.com/rtr7/router7/internal/testing/pcapreplayer" ) -type packet struct { - data []byte - ip net.IP - err error -} - -type replayer struct { - pcapr *pcapgo.Reader -} - -func (r *replayer) LocalAddr() net.Addr { return nil } -func (r *replayer) Close() error { return nil } -func (r *replayer) WriteTo(b []byte, addr net.Addr) (n int, err error) { - //log.Printf("-> %v", b) - return len(b), nil -} -func (r *replayer) SetDeadline(t time.Time) error { return nil } -func (r *replayer) SetReadDeadline(t time.Time) error { return nil } -func (r *replayer) SetWriteDeadline(t time.Time) error { return nil } - -func (r *replayer) ReadFrom(buf []byte) (int, net.Addr, error) { - data, _, err := r.pcapr.ReadPacketData() - if err != nil { - return 0, nil, err - } - pkt := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.DecodeOptions{}) - // TODO: get source IP - udp := pkt.Layer(layers.LayerTypeUDP) - if udp == nil { - return 0, nil, fmt.Errorf("pcap contained unexpected non-UDP packet") - } - - //log.Printf("ReadFrom(): %x, %v, pkt = %+v", udp.LayerPayload(), err, pkt) - copy(buf, udp.LayerPayload()) - return len(udp.LayerPayload()), &net.IPAddr{IP: net.ParseIP("192.168.23.1")}, err -} - func TestDHCP6(t *testing.T) { - f, err := os.Open("testdata/fiber7.pcap") + pcappath := os.Getenv("ROUTER7_PCAP_DIR") + if pcappath != "" { + pcappath = filepath.Join(pcappath, "dhcp6.pcap") + } + conn, err := pcapreplayer.NewPacketConn("testdata/fiber7.pcap", pcappath) if err != nil { t.Fatal(err) } - defer f.Close() - pcapr, err := pcapgo.NewReader(f) - if err != nil { - t.Fatal(err) - } - + defer conn.Close() laddr, err := net.ResolveUDPAddr("udp6", "[fe80::42:aff:fea5:966e]:546") if err != nil { t.Fatal(err) @@ -85,7 +45,7 @@ func TestDHCP6(t *testing.T) { // name to get the MAC address. InterfaceName: "lo", LocalAddr: laddr, - Conn: &replayer{pcapr: pcapr}, + Conn: conn, TransactionIDs: []uint32{ 0x48e59e, // SOLICIT 0x738c3b, // REQUEST diff --git a/internal/testing/pcapreplayer/pcapreplayer.go b/internal/testing/pcapreplayer/pcapreplayer.go new file mode 100644 index 0000000..b2e862f --- /dev/null +++ b/internal/testing/pcapreplayer/pcapreplayer.go @@ -0,0 +1,216 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pcapreplayer provides a net.PacketConn which replays a pcap file, and +// optionally records a new golden file. +package pcapreplayer + +import ( + "fmt" + "net" + "os" + "path/filepath" + "time" + + "github.com/d2g/dhcp4client" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcapgo" +) + +func pcapopen(input, output string) (*pcapgo.Reader, *pcapgo.Writer, error) { + f, err := os.Open(input) + if err != nil { + return nil, nil, err + } + pcapr, err := pcapgo.NewReader(f) + if err != nil { + return nil, nil, err + } + + var pcapw *pcapgo.Writer + if output != "" { + if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil { + return nil, nil, err + } + of, err := os.Create(output) + if err != nil { + return nil, nil, err + } + pcapw = pcapgo.NewWriter(of) + if err := pcapw.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil { + return nil, nil, err + } + } + return pcapr, pcapw, nil +} + +func readFrom(r *pcapgo.Reader, buf []byte) (int, net.IP, error) { + data, _, err := r.ReadPacketData() + if err != nil { + return 0, nil, err + } + pkt := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.DecodeOptions{}) + // TODO: get source IP + udp := pkt.Layer(layers.LayerTypeUDP) + if udp == nil { + return 0, nil, fmt.Errorf("pcap contained unexpected non-UDP packet") + } + + //log.Printf("ReadFrom(): %x, %v, pkt = %+v", udp.LayerPayload(), err, pkt) + copy(buf, udp.LayerPayload()) + return len(udp.LayerPayload()), net.ParseIP("192.168.23.1"), err +} + +func mustParseMAC(s string) net.HardwareAddr { + hw, err := net.ParseMAC(s) + if err != nil { + panic(err) + } + return hw +} + +type layer interface { + gopacket.NetworkLayer + + // Cannot embed gopacket.SerializableLayer because it includes a conflicting + // definition of LayerType(): + SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error +} + +func pcapwrite(w *pcapgo.Writer, ip layer, udp *layers.UDP, b []byte) error { + buf := gopacket.NewSerializeBuffer() + udp.SetNetworkLayerForChecksum(ip) + var ethernetType layers.EthernetType + switch ip.LayerType() { + case layers.LayerTypeIPv4: + ethernetType = layers.EthernetTypeIPv4 + case layers.LayerTypeIPv6: + ethernetType = layers.EthernetTypeIPv6 + } + gopacket.SerializeLayers(buf, + gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + }, + &layers.Ethernet{ + SrcMAC: mustParseMAC("00:00:5E:00:53:00"), // RFC7042 + DstMAC: mustParseMAC("33:33:00:01:00:02"), // IPv6mcast_01:00:02 + EthernetType: ethernetType, + }, + ip, + udp, + gopacket.Payload(b), + ) + got := buf.Bytes() + + ci := gopacket.CaptureInfo{ + CaptureLength: len(got), + Length: len(got), + } + if err := w.WritePacket(ci, got); err != nil { + return fmt.Errorf("pcap.WritePacket(): %v", err) + } + return nil +} + +// packetConn is a net.PacketConn which replays a pcap file. +type packetConn struct { + pcapr *pcapgo.Reader + pcapw *pcapgo.Writer +} + +// NewPCAP returns a net.PacketConn which replays packets from pcap file input, +// writing packets to pcap file output (if non-empty). +// +// See https://en.wikipedia.org/wiki/Pcap for details on pcap. +func NewPacketConn(input, output string) (net.PacketConn, error) { + pcapr, pcapw, err := pcapopen(input, output) + return &packetConn{pcapr, pcapw}, err +} + +func (r *packetConn) LocalAddr() net.Addr { return nil } +func (r *packetConn) Close() error { return nil } +func (r *packetConn) SetDeadline(t time.Time) error { return nil } +func (r *packetConn) SetReadDeadline(t time.Time) error { return nil } +func (r *packetConn) SetWriteDeadline(t time.Time) error { return nil } + +func (r *packetConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + if r.pcapw == nil { + return len(b), nil + } + + return len(b), pcapwrite(r.pcapw, + &layers.IPv6{ + Version: 6, + TrafficClass: 0, + NextHeader: layers.IPProtocolUDP, + HopLimit: 1, + SrcIP: net.ParseIP("fe80::200:5eff:fe00:5300"), + DstIP: net.ParseIP("ff02::1:2"), + }, + &layers.UDP{ + SrcPort: 546, + DstPort: 547, + }, + b) +} + +func (r *packetConn) ReadFrom(buf []byte) (int, net.Addr, error) { + l, ip, err := readFrom(r.pcapr, buf) + return l, &net.IPAddr{IP: ip}, err +} + +// dhcp4conn is a dhcp4client.ConnectionInt which replays a pcap file. +type dhcp4conn struct { + pcapr *pcapgo.Reader + pcapw *pcapgo.Writer +} + +// NewDHCP4Conn returns a dhcp4client.ConnectionInt which replays packets from +// pcap file input, writing packets to pcap file output (if non-empty). +// +// See https://en.wikipedia.org/wiki/Pcap for details on pcap. +func NewDHCP4Conn(input, output string) (dhcp4client.ConnectionInt, error) { + pcapr, pcapw, err := pcapopen(input, output) + return &dhcp4conn{pcapr, pcapw}, err +} + +func (r *dhcp4conn) Close() error { return nil } +func (r *dhcp4conn) SetReadTimeout(t time.Duration) error { return nil } + +func (r *dhcp4conn) Write(b []byte) error { + if r.pcapw == nil { + return nil + } + return pcapwrite(r.pcapw, + &layers.IPv4{ + Version: 4, + TTL: 255, + Protocol: layers.IPProtocolUDP, + SrcIP: net.ParseIP("0.0.0.0"), + DstIP: net.ParseIP("255.255.255.255"), + }, + &layers.UDP{ + SrcPort: 68, + DstPort: 67, + }, + b) +} + +func (r *dhcp4conn) ReadFrom() ([]byte, net.IP, error) { + buf := make([]byte, 9000) + _, ip, err := readFrom(r.pcapr, buf) + return buf, ip, err +}