From c1032086f7934ae143ff74978bce05c4cbb80347 Mon Sep 17 00:00:00 2001 From: Sergey Galkin Date: Sat, 4 Feb 2023 22:39:08 -0700 Subject: [PATCH] OS X (Darwin) support --- command/root.go | 2 +- go.mod | 6 +- go.sum | 10 +-- pkg/ip/ip_darwin.go | 62 ++++++++++++++++ pkg/ip/ip_other.go | 6 +- pkg/packet/afpacket/readwriter_darwin.go | 92 ++++++++++++++++++++++++ pkg/packet/afpacket/readwriter_other.go | 4 +- 7 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 pkg/ip/ip_darwin.go create mode 100644 pkg/packet/afpacket/readwriter_darwin.go diff --git a/command/root.go b/command/root.go index 7d63b2b..c5e9c7d 100644 --- a/command/root.go +++ b/command/root.go @@ -165,7 +165,7 @@ func startPacketScanEngine(ctx context.Context, conf *packetScanConfig) error { r := &conf.scanRange // setup network interface to read/write packets - ps, err := afpacket.NewPacketSource(r.Interface.Name, conf.vpnMode) + ps, err := afpacket.NewPacketSource(r.Interface.Name) if err != nil { return err } diff --git a/go.mod b/go.mod index 6b8a28e..9511d6c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( go.uber.org/ratelimit v0.2.0 go.uber.org/zap v1.23.0 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d ) require ( @@ -27,22 +28,21 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.4.2 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect google.golang.org/grpc v1.42.0 // indirect diff --git a/go.sum b/go.sum index 1efd86f..c42ce90 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gopacket v1.1.20-0.20210304165259-20562ffb40f8 h1:FU2/d0krhJFVXjbGP3S9dJJFLOfSG0drhIZuTdyvzqE= github.com/google/gopacket v1.1.20-0.20210304165259-20562ffb40f8/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -83,7 +84,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -106,8 +106,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= @@ -183,9 +183,9 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/ip/ip_darwin.go b/pkg/ip/ip_darwin.go new file mode 100644 index 0000000..e1e99ca --- /dev/null +++ b/pkg/ip/ip_darwin.go @@ -0,0 +1,62 @@ +//go:build darwin +// +build darwin + +package ip + +import ( + "fmt" + "net" + "os/exec" + "regexp" +) + +func GetDefaultInterface() (iface *net.Interface, ifaceIP net.IP, err error) { + return discoverGWDefaults() +} + +func GetDefaultGatewayIP(iface *net.Interface) (gatewayIP net.IP, err error) { + iface, ip, err := discoverGWDefaults() + return ip, err +} + +func discoverGWDefaults() (*net.Interface, net.IP, error) { + routeCmd := exec.Command("/sbin/route", "-n", "get", "0.0.0.0") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, nil, err + } + + // Darwin default format: + // route to: default + //destination: default + // mask: default + // gateway: 10.0.0.1 + // interface: en0 + // flags: + // recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire + // 0 0 0 0 0 0 1500 0 + + ifname := parseValue("interface", string(output)) + if len(ifname) <= 0 { + return nil, nil, fmt.Errorf("no interface found") + } + + gw := parseValue("gateway", string(output)) + if len(gw) <= 0 { + return nil, nil, fmt.Errorf("no gateway found") + } + + ip := net.ParseIP(gw) + ifi, err := net.InterfaceByName(ifname) + + return ifi, ip, err +} + +func parseValue(key, data string) string { + r := regexp.MustCompile(fmt.Sprintf(".*%s:\\s*([^\\s\\n]*){0,1}\\s*\\n.*", key)) + match := r.FindStringSubmatch(data) + if match != nil && len(match) == 2 { + return match[1] + } + return "" +} diff --git a/pkg/ip/ip_other.go b/pkg/ip/ip_other.go index 12351b5..40e5abc 100644 --- a/pkg/ip/ip_other.go +++ b/pkg/ip/ip_other.go @@ -1,15 +1,19 @@ -// +build !linux +//go:build !linux && !darwin +// +build !linux,!darwin package ip import ( "errors" + //xnet "golang.org/x/net" "net" ) var errOS = errors.New("OS platform is not supported") func GetDefaultInterface() (iface *net.Interface, ifaceIP net.IP, err error) { + + //xnet.FetchRIB() err = errOS return } diff --git a/pkg/packet/afpacket/readwriter_darwin.go b/pkg/packet/afpacket/readwriter_darwin.go new file mode 100644 index 0000000..deaeb0a --- /dev/null +++ b/pkg/packet/afpacket/readwriter_darwin.go @@ -0,0 +1,92 @@ +//go:build darwin || dragonfly || freebsd || netbsd || openbsd +// +build darwin dragonfly freebsd netbsd openbsd + +package afpacket + +import ( + "github.com/google/gopacket" + "github.com/google/gopacket/bsdbpf" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "golang.org/x/sys/unix" + "reflect" + "syscall" + "unsafe" +) + +type Source struct { + handle *bsdbpf.BPFSniffer + handleOptions *bsdbpf.Options + handleFd int + linkType layers.LinkType +} + +func NewPacketSource(iface string) (*Source, error) { + options := bsdbpf.Options{ + Promisc: true, + Immediate: true, + PreserveLinkAddr: true, + } + + handle, err := bsdbpf.NewBPFSniffer(iface, &options) + if err != nil { + return nil, err + } + + /* + * BPFSniffer does not provide API to set BPF Filter, moreover it does not allow to access BPF device fd. + * Therefore, in order to do not duplicate the code, it requires a bit of dark magic + */ + + rs := reflect.ValueOf(handle).Elem() + rf := rs.Field(2) // BPFSniffer.fd is the 3rd field + rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() + f := rf.Interface() + val := reflect.ValueOf(f) + fd := int(val.Int()) + + // Locally generated packets on the interface should not be returned by BPF. + err = unix.IoctlSetPointerInt(fd, syscall.BIOCSSEESENT, 0) + if err != nil { + goto errlbl + } + + return &Source{handle, &options, fd, layers.LinkTypeEthernet}, nil +errlbl: + handle.Close() + return nil, err +} + +func (s *Source) SetBPFFilter(bpfFilter string, maxPacketLength int) error { + pcapBPF, err := pcap.CompileBPFFilter(s.linkType, maxPacketLength, bpfFilter) + if err != nil { + return err + } + + bpfIns := make([]syscall.BpfInsn, 0, len(pcapBPF)) + for _, ins := range pcapBPF { + sysIns := syscall.BpfInsn{ + Code: ins.Code, + Jt: ins.Jt, + Jf: ins.Jf, + K: ins.K, + } + bpfIns = append(bpfIns, sysIns) + } + + return syscall.SetBpf(s.handleFd, bpfIns) +} + +func (s *Source) Close() { + s.handle.Close() +} + +func (s *Source) ReadPacketData() ([]byte, *gopacket.CaptureInfo, error) { + data, ci, err := s.handle.ReadPacketData() + return data, &ci, err +} + +func (s *Source) WritePacketData(pkt []byte) error { + _, err := unix.Write(s.handleFd, pkt) + return err +} diff --git a/pkg/packet/afpacket/readwriter_other.go b/pkg/packet/afpacket/readwriter_other.go index 301c4a9..4af81d8 100644 --- a/pkg/packet/afpacket/readwriter_other.go +++ b/pkg/packet/afpacket/readwriter_other.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd +// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd package afpacket