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 }