github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/example_sock_extract_dist_test.go (about) 1 package ebpf_test 2 3 // This code is derived from https://github.com/cloudflare/cloudflare-blog/tree/master/2018-03-ebpf 4 // 5 // Copyright (c) 2015-2017 Cloudflare, Inc. All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions are 9 // met: 10 // 11 // * Redistributions of source code must retain the above copyright 12 // notice, this list of conditions and the following disclaimer. 13 // * Redistributions in binary form must reproduce the above 14 // copyright notice, this list of conditions and the following disclaimer 15 // in the documentation and/or other materials provided with the 16 // distribution. 17 // * Neither the name of the Cloudflare, Inc. nor the names of its 18 // contributors may be used to endorse or promote products derived from 19 // this software without specific prior written permission. 20 // 21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33 import ( 34 "fmt" 35 "net" 36 "syscall" 37 38 "github.com/cilium/ebpf" 39 "github.com/cilium/ebpf/asm" 40 ) 41 42 // ExampleExtractDistance shows how to attach an eBPF socket filter to 43 // extract the network distance of an IP host. 44 func Example_extractDistance() { 45 filter, TTLs, err := newDistanceFilter() 46 if err != nil { 47 panic(err) 48 } 49 defer filter.Close() 50 defer TTLs.Close() 51 52 // Attach filter before the call to connect() 53 dialer := net.Dialer{ 54 Control: func(network, address string, c syscall.RawConn) (err error) { 55 const SO_ATTACH_BPF = 50 56 57 err = c.Control(func(fd uintptr) { 58 err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_ATTACH_BPF, filter.FD()) 59 }) 60 return err 61 }, 62 } 63 64 conn, err := dialer.Dial("tcp", "1.1.1.1:53") 65 if err != nil { 66 panic(err) 67 } 68 conn.Close() 69 70 minDist, err := minDistance(TTLs) 71 if err != nil { 72 panic(err) 73 } 74 75 fmt.Println("1.1.1.1:53 is", minDist, "hops away") 76 } 77 78 func newDistanceFilter() (*ebpf.Program, *ebpf.Map, error) { 79 const ETH_P_IPV6 uint16 = 0x86DD 80 81 ttls, err := ebpf.NewMap(&ebpf.MapSpec{ 82 Type: ebpf.Hash, 83 KeySize: 4, 84 ValueSize: 8, 85 MaxEntries: 4, 86 }) 87 if err != nil { 88 return nil, nil, err 89 } 90 91 insns := asm.Instructions{ 92 // r1 has ctx 93 // r0 = ctx[16] (aka protocol) 94 asm.LoadMem(asm.R0, asm.R1, 16, asm.Word), 95 96 // Perhaps ipv6 97 asm.LoadImm(asm.R2, int64(ETH_P_IPV6), asm.DWord), 98 asm.HostTo(asm.BE, asm.R2, asm.Half), 99 asm.JEq.Reg(asm.R0, asm.R2, "ipv6"), 100 101 // otherwise assume ipv4 102 // 8th byte in IPv4 is TTL 103 // LDABS requires ctx in R6 104 asm.Mov.Reg(asm.R6, asm.R1), 105 asm.LoadAbs(-0x100000+8, asm.Byte), 106 asm.Ja.Label("store-ttl"), 107 108 // 7th byte in IPv6 is Hop count 109 // LDABS requires ctx in R6 110 asm.Mov.Reg(asm.R6, asm.R1).WithSymbol("ipv6"), 111 asm.LoadAbs(-0x100000+7, asm.Byte), 112 113 // stash the load result into FP[-4] 114 asm.StoreMem(asm.RFP, -4, asm.R0, asm.Word).WithSymbol("store-ttl"), 115 // stash the &FP[-4] into r2 116 asm.Mov.Reg(asm.R2, asm.RFP), 117 asm.Add.Imm(asm.R2, -4), 118 119 // r1 must point to map 120 asm.LoadMapPtr(asm.R1, ttls.FD()), 121 asm.FnMapLookupElem.Call(), 122 123 // load ok? inc. Otherwise? jmp to mapupdate 124 asm.JEq.Imm(asm.R0, 0, "update-map"), 125 asm.Mov.Imm(asm.R1, 1), 126 asm.StoreXAdd(asm.R0, asm.R1, asm.DWord), 127 asm.Ja.Label("exit"), 128 129 // MapUpdate 130 // r1 has map ptr 131 asm.LoadMapPtr(asm.R1, ttls.FD()).WithSymbol("update-map"), 132 // r2 has key -> &FP[-4] 133 asm.Mov.Reg(asm.R2, asm.RFP), 134 asm.Add.Imm(asm.R2, -4), 135 // r3 has value -> &FP[-16] , aka 1 136 asm.StoreImm(asm.RFP, -16, 1, asm.DWord), 137 asm.Mov.Reg(asm.R3, asm.RFP), 138 asm.Add.Imm(asm.R3, -16), 139 // r4 has flags, 0 140 asm.Mov.Imm(asm.R4, 0), 141 asm.FnMapUpdateElem.Call(), 142 143 // set exit code to -1, don't trunc packet 144 asm.Mov.Imm(asm.R0, -1).WithSymbol("exit"), 145 asm.Return(), 146 } 147 148 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 149 Name: "distance_filter", 150 Type: ebpf.SocketFilter, 151 License: "GPL", 152 Instructions: insns, 153 }) 154 if err != nil { 155 ttls.Close() 156 return nil, nil, err 157 } 158 159 return prog, ttls, nil 160 } 161 162 func minDistance(TTLs *ebpf.Map) (int, error) { 163 var ( 164 entries = TTLs.Iterate() 165 ttl uint32 166 minDist uint32 = 255 167 count uint64 168 ) 169 for entries.Next(&ttl, &count) { 170 var dist uint32 171 switch { 172 case ttl > 128: 173 dist = 255 - ttl 174 case ttl > 64: 175 dist = 128 - ttl 176 case ttl > 32: 177 dist = 64 - ttl 178 default: 179 dist = 32 - ttl 180 } 181 if minDist > dist { 182 minDist = dist 183 } 184 } 185 return int(minDist), entries.Err() 186 }