github.com/yaling888/clash@v1.53.0/component/ebpf/redir/auto_redirect.go (about)

     1  //go:build linux
     2  
     3  package redir
     4  
     5  import (
     6  	"encoding/binary"
     7  	"fmt"
     8  	"io"
     9  	"math/bits"
    10  	"net/netip"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/cilium/ebpf"
    15  	"github.com/cilium/ebpf/rlimit"
    16  	"github.com/vishvananda/netlink"
    17  	"golang.org/x/sys/unix"
    18  
    19  	"github.com/yaling888/clash/common/pool"
    20  	C "github.com/yaling888/clash/constant"
    21  	"github.com/yaling888/clash/transport/socks5"
    22  )
    23  
    24  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/redir.c
    25  
    26  const (
    27  	mapKey1 uint32 = 0
    28  	mapKey2 uint32 = 1
    29  	mapKey3 uint32 = 2
    30  )
    31  
    32  var isLittleEndian = binary.NativeEndian.Uint16([]byte{0x12, 0x34}) == binary.LittleEndian.Uint16([]byte{0x12, 0x34})
    33  
    34  type EBpfRedirect struct {
    35  	objs         io.Closer
    36  	originMap    *ebpf.Map
    37  	qdisc        netlink.Qdisc
    38  	filter       netlink.Filter
    39  	filterEgress netlink.Filter
    40  
    41  	ifName    string
    42  	ifIndex   int
    43  	ifMark    uint32
    44  	rtIndex   uint32
    45  	redirIp   uint32
    46  	redirPort uint16
    47  
    48  	bpfPath string
    49  }
    50  
    51  func NewEBpfRedirect(ifName string, ifIndex int, ifMark uint32, routeIndex uint32, redirAddrPort netip.AddrPort) *EBpfRedirect {
    52  	return &EBpfRedirect{
    53  		ifName:    ifName,
    54  		ifIndex:   ifIndex,
    55  		ifMark:    ifMark,
    56  		rtIndex:   routeIndex,
    57  		redirIp:   binary.NativeEndian.Uint32(redirAddrPort.Addr().AsSlice()),
    58  		redirPort: hostToNetwork16(redirAddrPort.Port()),
    59  	}
    60  }
    61  
    62  func (e *EBpfRedirect) Start() error {
    63  	if err := rlimit.RemoveMemlock(); err != nil {
    64  		return fmt.Errorf("remove memory lock: %w", err)
    65  	}
    66  
    67  	e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
    68  	if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
    69  		return fmt.Errorf("failed to create bpf fs subpath: %w", err)
    70  	}
    71  
    72  	var objs bpfObjects
    73  	if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
    74  		Maps: ebpf.MapOptions{
    75  			PinPath: e.bpfPath,
    76  		},
    77  	}); err != nil {
    78  		e.Close()
    79  		return fmt.Errorf("loading objects: %w", err)
    80  	}
    81  
    82  	e.objs = &objs
    83  	e.originMap = objs.bpfMaps.PairOriginalDstMap
    84  
    85  	if err := objs.bpfMaps.RedirParamsMap.Update(mapKey1, e.rtIndex, ebpf.UpdateAny); err != nil {
    86  		e.Close()
    87  		return fmt.Errorf("storing objects: %w", err)
    88  	}
    89  
    90  	redirIp := e.redirIp
    91  	redirPort := e.redirPort
    92  	if isLittleEndian {
    93  		redirIp = bits.ReverseBytes32(redirIp)
    94  		redirPort = bits.ReverseBytes16(redirPort)
    95  	}
    96  	if err := objs.bpfMaps.RedirParamsMap.Update(mapKey2, redirIp, ebpf.UpdateAny); err != nil {
    97  		e.Close()
    98  		return fmt.Errorf("storing objects: %w", err)
    99  	}
   100  
   101  	if err := objs.bpfMaps.RedirParamsMap.Update(mapKey3, uint32(redirPort), ebpf.UpdateAny); err != nil {
   102  		e.Close()
   103  		return fmt.Errorf("storing objects: %w", err)
   104  	}
   105  
   106  	attrs := netlink.QdiscAttrs{
   107  		LinkIndex: e.ifIndex,
   108  		Handle:    netlink.MakeHandle(0xffff, 0),
   109  		Parent:    netlink.HANDLE_CLSACT,
   110  	}
   111  
   112  	qdisc := &netlink.GenericQdisc{
   113  		QdiscAttrs: attrs,
   114  		QdiscType:  "clsact",
   115  	}
   116  
   117  	e.qdisc = qdisc
   118  
   119  	if err := netlink.QdiscAdd(qdisc); err != nil {
   120  		if os.IsExist(err) {
   121  			_ = netlink.QdiscDel(qdisc)
   122  			err = netlink.QdiscAdd(qdisc)
   123  		}
   124  
   125  		if err != nil {
   126  			e.Close()
   127  			return fmt.Errorf("cannot add clsact qdisc: %w", err)
   128  		}
   129  	}
   130  
   131  	filterAttrs := netlink.FilterAttrs{
   132  		LinkIndex: e.ifIndex,
   133  		Parent:    netlink.HANDLE_MIN_INGRESS,
   134  		Handle:    netlink.MakeHandle(0, 1),
   135  		Protocol:  unix.ETH_P_IP,
   136  		Priority:  0,
   137  	}
   138  
   139  	filter := &netlink.BpfFilter{
   140  		FilterAttrs:  filterAttrs,
   141  		Fd:           objs.bpfPrograms.TcRedirIngressFunc.FD(),
   142  		Name:         "clash-redir-ingress-" + e.ifName,
   143  		DirectAction: true,
   144  	}
   145  
   146  	if err := netlink.FilterAdd(filter); err != nil {
   147  		e.Close()
   148  		return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
   149  	}
   150  
   151  	e.filter = filter
   152  
   153  	filterAttrsEgress := netlink.FilterAttrs{
   154  		LinkIndex: e.ifIndex,
   155  		Parent:    netlink.HANDLE_MIN_EGRESS,
   156  		Handle:    netlink.MakeHandle(0, 1),
   157  		Protocol:  unix.ETH_P_IP,
   158  		Priority:  0,
   159  	}
   160  
   161  	filterEgress := &netlink.BpfFilter{
   162  		FilterAttrs:  filterAttrsEgress,
   163  		Fd:           objs.bpfPrograms.TcRedirEgressFunc.FD(),
   164  		Name:         "clash-redir-egress-" + e.ifName,
   165  		DirectAction: true,
   166  	}
   167  
   168  	if err := netlink.FilterAdd(filterEgress); err != nil {
   169  		e.Close()
   170  		return fmt.Errorf("cannot attach ebpf object to filter egress: %w", err)
   171  	}
   172  
   173  	e.filterEgress = filterEgress
   174  
   175  	return nil
   176  }
   177  
   178  func (e *EBpfRedirect) Close() {
   179  	if e.filter != nil {
   180  		_ = netlink.FilterDel(e.filter)
   181  	}
   182  	if e.filterEgress != nil {
   183  		_ = netlink.FilterDel(e.filterEgress)
   184  	}
   185  	if e.qdisc != nil {
   186  		_ = netlink.QdiscDel(e.qdisc)
   187  	}
   188  	if e.objs != nil {
   189  		_ = e.objs.Close()
   190  	}
   191  	_ = os.Remove(filepath.Join(e.bpfPath, "redir_params_map"))
   192  	_ = os.Remove(filepath.Join(e.bpfPath, "pair_original_dst_map"))
   193  }
   194  
   195  func (e *EBpfRedirect) Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error) {
   196  	rAddr := srcAddrPort.Addr().Unmap()
   197  	if rAddr.Is6() {
   198  		return nil, fmt.Errorf("remote address is ipv6")
   199  	}
   200  
   201  	srcIp := binary.NativeEndian.Uint32(rAddr.AsSlice())
   202  	scrPort := hostToNetwork16(srcAddrPort.Port())
   203  
   204  	key := bpfRedirInfo{
   205  		Sip:   srcIp,
   206  		Sport: scrPort,
   207  		Dip:   e.redirIp,
   208  		Dport: e.redirPort,
   209  	}
   210  
   211  	origin := bpfOriginInfo{}
   212  
   213  	err := e.originMap.Lookup(key, &origin)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	addr := pool.BufferWriter{}
   219  	addr.PutUint8(socks5.AtypIPv4)
   220  	addr.PutUint32(origin.Ip)
   221  	addr.PutUint16(origin.Port)
   222  	return addr.Bytes(), nil
   223  }
   224  
   225  func hostToNetwork16(v uint16) uint16 {
   226  	if isLittleEndian {
   227  		return bits.ReverseBytes16(v)
   228  	}
   229  	return v
   230  }