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  }