pcap-cli/pkg/pcap/pcap.go (198 lines of code) (raw):

// Copyright 2024 Google LLC // // 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 pcap import ( "context" "fmt" "net" "regexp" "strings" "sync/atomic" "time" "github.com/GoogleCloudPlatform/pcap-sidecar/pcap-cli/internal/transformer" "github.com/google/gopacket" "github.com/google/gopacket/pcap" "github.com/wissance/stringFormatter" ) type ( TCPFlag = transformer.TCPFlag TCPFlags = transformer.TCPFlags L3Proto = transformer.L3Proto L4Proto = transformer.L4Proto PcapEphemeralPorts = transformer.PcapEphemeralPorts PcapFilterMode uint8 PcapVerbosity = transformer.PcapVerbosity PcapFilter struct { Raw *string } // PCAP owns the behavior that will be exposed to consumers PcapFilters interface { AddL3Proto(L3Proto) AddL3Protos(...L3Proto) AddIPv4(string) AddIPv4s(...string) AddIPv6(string) AddIPv6s(...string) AddIPv4Range(string) AddIPv4Ranges(...string) AddIPv6Range(string) AddIPv6Ranges(...string) AddL4Proto(L4Proto) AddL4Protos(...L4Proto) AllowSocket(string, string) bool DenySocket(string, string) bool AddPort(uint16) AddPorts(...uint16) DenyPort(uint16) DenyPorts(...uint16) AllowPort(uint16) AllowPorts(...uint16) AddTCPFlags(...TCPFlag) CombineAndAddTCPFlags(...TCPFlag) } PcapFilterProvider interface { fmt.Stringer Get(context.Context) (*string, bool) Apply(context.Context, *string, PcapFilterMode) *string } PcapConfig struct { Compat bool Debug bool Promisc bool Iface string Snaplen int TsType string Format string Filter string Output string Interval int Extension string Ordered bool ConnTrack bool Device *PcapDevice Filters []PcapFilterProvider CompatFilters PcapFilters Ephemerals *PcapEphemeralPorts Verbosity PcapVerbosity } PcapEngine interface { Start(context.Context, []PcapWriter, <-chan *time.Duration) error IsActive() bool } PcapDevice struct { NetInterface *net.Interface pcap.Interface } Pcap struct { config *PcapConfig isActive *atomic.Bool activeHandle gopacket.PacketDataSource inactiveHandle *pcap.InactiveHandle fn transformer.IPcapTransformer } Tcpdump struct { config *PcapConfig isActive *atomic.Bool tcpdump string } ) const ( PCAP_FILTER_MODE_AND PcapFilterMode = iota PCAP_FILTER_MODE_OR ) const ( PcapContextID = transformer.ContextID PcapContextLogName = transformer.ContextLogName PcapContextVerbosity = transformer.ContextVerbosity PcapContextDebug = transformer.ContextDebug ) const ( VERBOSITY_INFO = transformer.VERBOSITY_INFO VERBOSITY_DEBUG = transformer.VERBOSITY_DEBUG ) const ( PcapDefaultFilter = "(tcp or udp or icmp or icmp6) and (ip or ip6 or arp)" ) const ( pcap_min_ephemeral_port uint16 = 0x0400 // 1024 – start of registered ports per RFC 6056 PCAP_MIN_EPHEMERAL_PORT uint16 = 0x8000 // 32768 – preferred MIN ephemeral port ( not as high as 0x0C000 / 49152 ) PCAP_MAX_EPHEMERAL_PORT uint16 = 0xFFFF // 65535 ( Linux: 60999 / 0xEE47 ) ) const ( // see: https://github.com/google/gopacket/blob/master/pcap/pcap.go#L802-L808 anyDeviceName string = "any" anyDeviceIndex uint8 = 0 ) const ( TCP_FLAG_SYN = TCPFlag("SYN") TCP_FLAG_ACK = TCPFlag("ACK") TCP_FLAG_PSH = TCPFlag("PSH") TCP_FLAG_FIN = TCPFlag("FIN") TCP_FLAG_RST = TCPFlag("RST") TCP_FLAG_URG = TCPFlag("URG") TCP_FLAG_ECE = TCPFlag("ECE") TCP_FLAG_CWR = TCPFlag("CWR") L3_PROTO_IPv4 = L3Proto(0x04) L3_PROTO_IP4 = L3_PROTO_IPv4 L3_PROTO_IPv6 = L3Proto(0x29) L3_PROTO_IP6 = L3_PROTO_IPv6 L4_PROTO_TCP = L4Proto(0x06) L4_PROTO_UDP = L4Proto(0x11) L4_PROTO_ICMP = L4Proto(0x01) L4_PROTO_ICMP4 = L4_PROTO_ICMP L4_PROTO_ICMP6 = L4Proto(0x3A) ) func providePcapFilter( ctx context.Context, filter *string, providers []PcapFilterProvider, ) *string { select { case <-ctx.Done(): return filter default: } pcapFilter := "" // if `filter` is available, then providers are not used to build the BPF filter. if filter != nil && *filter != "" && !strings.EqualFold(*filter, "DISABLED") { // `filter` is extremely unsafe as it is a free form expression: // [ToDo] – validate `filter` to enforce correctness of expressions. pcapFilter = stringFormatter.Format("({0})", *filter) } else if len(providers) > 0 { for _, provider := range providers { if provider != nil { if f := provider.Apply(ctx, &pcapFilter, PCAP_FILTER_MODE_AND); f != nil { pcapFilter = *f } } } } else { pcapFilter = string(PcapDefaultFilter) } return &pcapFilter } func findAllDevs(compare func(*string) bool) ([]*PcapDevice, error) { devices, err := pcap.FindAllDevs() if err != nil { return nil, err } var devs []*PcapDevice for _, device := range devices { if compare(&device.Name) { if iface, err := net.InterfaceByName(device.Name); err == nil { devs = append(devs, &PcapDevice{iface, device}) } } } return devs, nil } func FindDevicesByRegex(exp *regexp.Regexp) ([]*PcapDevice, error) { compare := func(deviceName *string) bool { return exp.MatchString(*deviceName) } return findAllDevs(compare) } func FindDevicesByName(deviceName *string) ([]*PcapDevice, error) { name := *deviceName compare := func(deviceName *string) bool { return name == *deviceName } return findAllDevs(compare) } func NewPcapFilters() PcapFilters { return transformer.NewPcapFilters() }