cmd/pshark/main.go (163 lines of code) (raw):

/* Copyright (c) Facebook, Inc. and its affiliates. 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 main import ( "flag" "fmt" "net" "os" "strconv" "strings" log "github.com/sirupsen/logrus" "github.com/davecgh/go-spew/spew" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcapgo" ptp "github.com/facebook/time/ptp/protocol" ) // for flags // MultiMessageType is a wrapper around []string to parse from flags type MultiMessageType []ptp.MessageType // Set adds message type to the filter func (m *MultiMessageType) Set(messageType string) error { for v, s := range ptp.MessageTypeToString { if s == strings.ToUpper(messageType) { *m = append([]ptp.MessageType(*m), v) return nil } } return fmt.Errorf("unsupported msg type %q", messageType) } // String returns joined list of message types func (m *MultiMessageType) String() string { s := []string{} for _, v := range []ptp.MessageType(*m) { s = append(s, v.String()) } return strings.Join(s, ",") } // GetDefaults returns default message type filter func (m *MultiMessageType) GetDefaults() []ptp.MessageType { res := []ptp.MessageType{} for v := range ptp.MessageTypeToString { res = append(res, v) } return res } // SetDefault sets default message type filter func (m *MultiMessageType) SetDefault() { if len([]ptp.MessageType(*m)) != 0 { return } for _, v := range m.GetDefaults() { *m = append([]ptp.MessageType(*m), v) } } // Tiny wrapper code to support gopacket integration // LayerPTP wraps around ptp packet type LayerPTP struct { layers.BaseLayer Packet ptp.Packet } // LayerTypePTP is registered as a layer with gopacket var LayerTypePTP = gopacket.RegisterLayerType( 1588, gopacket.LayerTypeMetadata{ Name: "PTPv2", Decoder: gopacket.DecodeFunc(decodePTP), }, ) // LayerType returns type this layer implements func (l *LayerPTP) LayerType() gopacket.LayerType { return LayerTypePTP } // Payload is empty as it's the final layer func (l *LayerPTP) Payload() []byte { return nil } // decodePTP actually does the decoding func decodePTP(data []byte, p gopacket.PacketBuilder) error { d := &LayerPTP{} ptpPacket, err := ptp.DecodePacket(data) if err != nil { return fmt.Errorf("decoding PTPv2 packet: %w", err) } d.BaseLayer = layers.BaseLayer{Contents: data[:]} d.Packet = ptpPacket p.AddLayer(d) p.SetApplicationLayer(d) return nil } // packetHandle abstracts packet handles provided by pcapgo.Reader and pcapgo.NGReader type packetHandle interface { gopacket.PacketDataSource LinkType() layers.LinkType } func run(input string, filter []ptp.MessageType) error { // register mapping betwenn ports and our custom PTP layer layers.RegisterUDPPortLayerType(layers.UDPPort(ptp.PortEvent), LayerTypePTP) layers.RegisterUDPPortLayerType(layers.UDPPort(ptp.PortGeneral), LayerTypePTP) filterMap := map[ptp.MessageType]bool{} for _, v := range filter { filterMap[v] = true } var handle packetHandle var err error // open the input file f, err := os.Open(input) if err != nil { return err } defer f.Close() // try NGReader, if it fails - fall back to Reader handle, err = pcapgo.NewNgReader(f, pcapgo.DefaultNgReaderOptions) if err != nil { if _, ierr := f.Seek(0, 0); ierr != nil { return fmt.Errorf("seeking in %s: %w", input, ierr) } handle, err = pcapgo.NewReader(f) if err != nil { return fmt.Errorf("decoding %s: %w", input, err) } } // Loop through packets in file packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // thanks to the mapping we can easily pick PTP packets ptpLayer := packet.Layer(LayerTypePTP) if ptpLayer != nil { ptpContent, _ := ptpLayer.(*LayerPTP) if !filterMap[ptpContent.Packet.MessageType()] { continue } // decode src and dst adddress and port var srcIP, dstIP net.IP var srcPort, dstPort layers.UDPPort ip6Layer := packet.Layer(layers.LayerTypeIPv6) if ip6Layer != nil { ip, _ := ip6Layer.(*layers.IPv6) srcIP = ip.SrcIP dstIP = ip.DstIP } else { ip4Layer := packet.Layer(layers.LayerTypeIPv4) ip, _ := ip4Layer.(*layers.IPv4) srcIP = ip.SrcIP dstIP = ip.DstIP } udpLayer := packet.Layer(layers.LayerTypeUDP) if udpLayer != nil { udp, _ := udpLayer.(*layers.UDP) srcPort = udp.SrcPort dstPort = udp.DstPort } // dump ip:port info on stdout spew.Printf("%s -> %s\n", net.JoinHostPort(srcIP.String(), strconv.Itoa(int(srcPort))), net.JoinHostPort(dstIP.String(), strconv.Itoa(int(dstPort))), ) // dump the packet itself spew.Dump(ptpContent.Packet) spew.Println() } if err := packet.ErrorLayer(); err != nil { return fmt.Errorf("failed to decode: %w", err.Error()) } } return nil } func main() { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "pshark: PTP-specific poor man's tshark. Dumps PTPv2 packets parsed from capture file to stdout.\nUsage:\n") fmt.Fprintf(flag.CommandLine.Output(), "%s [file]\n", os.Args[0]) fmt.Fprint(flag.CommandLine.Output(), "where [file] is any .pcap or .pcapng packet capture\n") flag.PrintDefaults() } var msgTypes MultiMessageType flag.Var(&msgTypes, "msgtype", fmt.Sprintf("Only print certain PTP message types. Choose from: %v. Repeat for multiple", msgTypes.GetDefaults())) flag.Parse() if len(flag.Args()) != 1 { flag.Usage() os.Exit(1) } msgTypes.SetDefault() if err := run(flag.Arg(0), msgTypes); err != nil { log.Fatal(err) } }