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