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 }