diff --git a/ebpf/filter.go b/ebpf/filter.go new file mode 100644 index 000000000..fb778c7a2 --- /dev/null +++ b/ebpf/filter.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/cilium/ebpf" + "net" + + "syscall" +) + +// Filter represents a classic BPF filter program that can be applied to a socket +type Filter struct { + *ebpf.ProgramSpec +} + +// ApplyTo applies the current filter onto the provided UDPConn +func (filter Filter) ApplyTo(conn *net.UDPConn) error { + + file, err := conn.File() + if err != nil { + return err + } + + p, err := ebpf.NewProgramWithOptions(filter.ProgramSpec, ebpf.ProgramOptions{ + LogLevel: 6, + }) + + if err != nil { + return err + } + + if err := syscall.SetsockoptInt(int(file.Fd()), syscall.SOL_SOCKET, SO_ATTACH_BPF, p.FD()); err != nil { + return err + } + + return nil +} diff --git a/ebpf/main.go b/ebpf/main.go new file mode 100644 index 000000000..5cb0f92b7 --- /dev/null +++ b/ebpf/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/hex" + "fmt" + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "net" +) + +const ( + SO_ATTACH_BPF int = 50 + StunMagicCookie uint32 = 0x2112A442 +) + +func main() { + + // open a raw socket + /*fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_UDP) + if err != nil { + panic(err) + } + fmt.Print(fd)*/ + addr := net.UDPAddr{ + IP: net.IPv4zero, + Port: 12345, + } + conn, err := net.ListenUDP("udp4", &addr) + if err != nil { + return + } + + filter := &Filter{ + ProgramSpec: &ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + License: "GPL", + Instructions: asm.Instructions{ + asm.Mov.Reg(asm.R6, asm.R1), // LDABS requires ctx in R6 + asm.LoadAbs(-0x100000+22, asm.Half), + asm.JNE.Imm(asm.R0, int32(addr.Port), "skip"), + /* asm.LoadAbs(-0x100000+32, asm.Word), + asm.JNE.Imm(asm.R0, int32(StunMagicCookie), "skip"), + asm.Mov.Imm(asm.R0, -1).Sym("exit"),*/ + /*asm.Return(),*/ + asm.Mov.Imm(asm.R0, 0).Sym("skip"), + asm.Return(), + }, + }, + } + + err = filter.ApplyTo(conn) + if err != nil { + panic(err) + } + + fmt.Printf("start") + buf := make([]byte, 1024) + for { + n, ra, err := conn.ReadFrom(buf) + if err != nil { + panic(err) + } + + fmt.Printf("Bytes read: %d\n", n) + fmt.Printf("Remote Addr: %+v\n", ra) + fmt.Printf("Bytes HEX: %s\n", hex.EncodeToString(buf[:n])) + fmt.Printf("Bytes String: %s\n", string(buf[:n])) + fmt.Println() + } + +} diff --git a/ebpf/test/main.go b/ebpf/test/main.go new file mode 100644 index 000000000..92730954f --- /dev/null +++ b/ebpf/test/main.go @@ -0,0 +1,186 @@ +package main + +// This code is derived from https://github.com/cloudflare/cloudflare-blog/tree/master/2018-03-ebpf +// +// Copyright (c) 2015-2017 Cloudflare, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of the Cloudflare, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import ( + "fmt" + "net" + "syscall" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" +) + +// ExampleExtractDistance shows how to attach an eBPF socket filter to +// extract the network distance of an IP host. +func main() { + filter, TTLs, err := newDistanceFilter() + if err != nil { + panic(err) + } + defer filter.Close() + defer TTLs.Close() + + // Attach filter before the call to connect() + dialer := net.Dialer{ + Control: func(network, address string, c syscall.RawConn) (err error) { + const SO_ATTACH_BPF = 50 + + err = c.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_ATTACH_BPF, filter.FD()) + }) + return err + }, + } + + conn, err := dialer.Dial("tcp", "1.1.1.1:53") + if err != nil { + panic(err) + } + conn.Close() + + minDist, err := minDistance(TTLs) + if err != nil { + panic(err) + } + + fmt.Println("1.1.1.1:53 is", minDist, "hops away") +} + +func newDistanceFilter() (*ebpf.Program, *ebpf.Map, error) { + const ETH_P_IPV6 uint16 = 0x86DD + + ttls, err := ebpf.NewMap(&ebpf.MapSpec{ + Type: ebpf.Hash, + KeySize: 4, + ValueSize: 8, + MaxEntries: 4, + }) + if err != nil { + return nil, nil, err + } + + insns := asm.Instructions{ + // r1 has ctx + // r0 = ctx[16] (aka protocol) + asm.LoadMem(asm.R0, asm.R1, 16, asm.Word), + + // Perhaps ipv6 + asm.LoadImm(asm.R2, int64(ETH_P_IPV6), asm.DWord), + asm.HostTo(asm.BE, asm.R2, asm.Half), + asm.JEq.Reg(asm.R0, asm.R2, "ipv6"), + + // otherwise assume ipv4 + // 8th byte in IPv4 is TTL + // LDABS requires ctx in R6 + asm.Mov.Reg(asm.R6, asm.R1), + asm.LoadAbs(-0x100000+8, asm.Byte), + asm.Ja.Label("store-ttl"), + + // 7th byte in IPv6 is Hop count + // LDABS requires ctx in R6 + asm.Mov.Reg(asm.R6, asm.R1).Sym("ipv6"), + asm.LoadAbs(-0x100000+7, asm.Byte), + + // stash the load result into FP[-4] + asm.StoreMem(asm.RFP, -4, asm.R0, asm.Word).Sym("store-ttl"), + // stash the &FP[-4] into r2 + asm.Mov.Reg(asm.R2, asm.RFP), + asm.Add.Imm(asm.R2, -4), + + // r1 must point to map + asm.LoadMapPtr(asm.R1, ttls.FD()), + asm.FnMapLookupElem.Call(), + + // load ok? inc. Otherwise? jmp to mapupdate + asm.JEq.Imm(asm.R0, 0, "update-map"), + asm.Mov.Imm(asm.R1, 1), + asm.StoreXAdd(asm.R0, asm.R1, asm.DWord), + asm.Ja.Label("exit"), + + // MapUpdate + // r1 has map ptr + asm.LoadMapPtr(asm.R1, ttls.FD()).Sym("update-map"), + // r2 has key -> &FP[-4] + asm.Mov.Reg(asm.R2, asm.RFP), + asm.Add.Imm(asm.R2, -4), + // r3 has value -> &FP[-16] , aka 1 + asm.StoreImm(asm.RFP, -16, 1, asm.DWord), + asm.Mov.Reg(asm.R3, asm.RFP), + asm.Add.Imm(asm.R3, -16), + // r4 has flags, 0 + asm.Mov.Imm(asm.R4, 0), + asm.FnMapUpdateElem.Call(), + + // set exit code to -1, don't trunc packet + asm.Mov.Imm(asm.R0, -1).Sym("exit"), + asm.Return(), + } + + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "distance_filter", + Type: ebpf.SocketFilter, + License: "GPL", + Instructions: insns, + }) + if err != nil { + ttls.Close() + return nil, nil, err + } + + return prog, ttls, nil +} + +func minDistance(TTLs *ebpf.Map) (int, error) { + var ( + entries = TTLs.Iterate() + ttl uint32 + minDist uint32 = 255 + count uint64 + ) + for entries.Next(&ttl, &count) { + var dist uint32 + switch { + case ttl > 128: + dist = 255 - ttl + case ttl > 64: + dist = 128 - ttl + case ttl > 32: + dist = 64 - ttl + default: + dist = 32 - ttl + } + if minDist > dist { + minDist = dist + } + } + return int(minDist), entries.Err() +} diff --git a/go.mod b/go.mod index 80333d82d..a38f60153 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.16 require ( github.com/cenkalti/backoff/v4 v4.1.0 + github.com/cilium/ebpf v0.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 + github.com/google/gopacket v1.1.19 github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 @@ -18,7 +20,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/vishvananda/netlink v1.1.0 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c + golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 + golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c golang.zx2c4.com/wireguard/windows v0.4.5 diff --git a/go.sum b/go.sum index fbe723013..85e2ba976 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -45,6 +47,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -92,6 +95,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -154,6 +159,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -320,10 +326,12 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -412,6 +420,8 @@ golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -442,6 +452,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=