support ebpf
This commit is contained in:
parent
93dbc0cc7f
commit
2c10af7b1d
30 changed files with 6270 additions and 8 deletions
8
Makefile
8
Makefile
|
@ -147,3 +147,11 @@ lint:
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(BINDIR)/*
|
rm $(BINDIR)/*
|
||||||
|
|
||||||
|
CLANG ?= clang-14
|
||||||
|
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
|
||||||
|
|
||||||
|
ebpf: export BPF_CLANG := $(CLANG)
|
||||||
|
ebpf: export BPF_CFLAGS := $(CFLAGS)
|
||||||
|
ebpf:
|
||||||
|
cd component/ebpf/ && go generate ./...
|
99
component/ebpf/bpf/bpf_endian.h
Normal file
99
component/ebpf/bpf/bpf_endian.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||||
|
#ifndef __BPF_ENDIAN__
|
||||||
|
#define __BPF_ENDIAN__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Isolate byte #n and put it into byte #m, for __u##b type.
|
||||||
|
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
||||||
|
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
||||||
|
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
||||||
|
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
||||||
|
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
||||||
|
*/
|
||||||
|
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
||||||
|
|
||||||
|
#define ___bpf_swab16(x) ((__u16)( \
|
||||||
|
___bpf_mvb(x, 16, 0, 1) | \
|
||||||
|
___bpf_mvb(x, 16, 1, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab32(x) ((__u32)( \
|
||||||
|
___bpf_mvb(x, 32, 0, 3) | \
|
||||||
|
___bpf_mvb(x, 32, 1, 2) | \
|
||||||
|
___bpf_mvb(x, 32, 2, 1) | \
|
||||||
|
___bpf_mvb(x, 32, 3, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab64(x) ((__u64)( \
|
||||||
|
___bpf_mvb(x, 64, 0, 7) | \
|
||||||
|
___bpf_mvb(x, 64, 1, 6) | \
|
||||||
|
___bpf_mvb(x, 64, 2, 5) | \
|
||||||
|
___bpf_mvb(x, 64, 3, 4) | \
|
||||||
|
___bpf_mvb(x, 64, 4, 3) | \
|
||||||
|
___bpf_mvb(x, 64, 5, 2) | \
|
||||||
|
___bpf_mvb(x, 64, 6, 1) | \
|
||||||
|
___bpf_mvb(x, 64, 7, 0)))
|
||||||
|
|
||||||
|
/* LLVM's BPF target selects the endianness of the CPU
|
||||||
|
* it compiles on, or the user specifies (bpfel/bpfeb),
|
||||||
|
* respectively. The used __BYTE_ORDER__ is defined by
|
||||||
|
* the compiler, we cannot rely on __BYTE_ORDER from
|
||||||
|
* libc headers, since it doesn't reflect the actual
|
||||||
|
* requested byte order.
|
||||||
|
*
|
||||||
|
* Note, LLVM's BPF target has different __builtin_bswapX()
|
||||||
|
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
||||||
|
* in bpfel and bpfeb case, which means below, that we map
|
||||||
|
* to cpu_to_be16(). We could use it unconditionally in BPF
|
||||||
|
* case, but better not rely on it, so that this header here
|
||||||
|
* can be used from application and BPF program side, which
|
||||||
|
* use different targets.
|
||||||
|
*/
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_htons(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_htonl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
||||||
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) (x)
|
||||||
|
# define __bpf_htons(x) (x)
|
||||||
|
# define __bpf_constant_ntohs(x) (x)
|
||||||
|
# define __bpf_constant_htons(x) (x)
|
||||||
|
# define __bpf_ntohl(x) (x)
|
||||||
|
# define __bpf_htonl(x) (x)
|
||||||
|
# define __bpf_constant_ntohl(x) (x)
|
||||||
|
# define __bpf_constant_htonl(x) (x)
|
||||||
|
# define __bpf_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_cpu_to_be64(x) (x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) (x)
|
||||||
|
#else
|
||||||
|
# error "Fix your compiler's __BYTE_ORDER__?!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define bpf_htons(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htons(x) : __bpf_htons(x))
|
||||||
|
#define bpf_ntohs(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
||||||
|
#define bpf_htonl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
||||||
|
#define bpf_ntohl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
||||||
|
#define bpf_cpu_to_be64(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
||||||
|
#define bpf_be64_to_cpu(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
||||||
|
|
||||||
|
#endif /* __BPF_ENDIAN__ */
|
4139
component/ebpf/bpf/bpf_helper_defs.h
Normal file
4139
component/ebpf/bpf/bpf_helper_defs.h
Normal file
File diff suppressed because it is too large
Load diff
262
component/ebpf/bpf/bpf_helpers.h
Normal file
262
component/ebpf/bpf/bpf_helpers.h
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||||
|
#ifndef __BPF_HELPERS__
|
||||||
|
#define __BPF_HELPERS__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that bpf programs need to include either
|
||||||
|
* vmlinux.h (auto-generated from BTF) or linux/types.h
|
||||||
|
* in advance since bpf_helper_defs.h uses such types
|
||||||
|
* as __u64.
|
||||||
|
*/
|
||||||
|
#include "bpf_helper_defs.h"
|
||||||
|
|
||||||
|
#define __uint(name, val) int (*name)[val]
|
||||||
|
#define __type(name, val) typeof(val) *name
|
||||||
|
#define __array(name, val) typeof(val) *name[]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper macro to place programs, maps, license in
|
||||||
|
* different sections in elf_bpf file. Section names
|
||||||
|
* are interpreted by libbpf depending on the context (BPF programs, BPF maps,
|
||||||
|
* extern variables, etc).
|
||||||
|
* To allow use of SEC() with externs (e.g., for extern .maps declarations),
|
||||||
|
* make sure __attribute__((unused)) doesn't trigger compilation warning.
|
||||||
|
*/
|
||||||
|
#define SEC(name) \
|
||||||
|
_Pragma("GCC diagnostic push") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \
|
||||||
|
__attribute__((section(name), used)) \
|
||||||
|
_Pragma("GCC diagnostic pop") \
|
||||||
|
|
||||||
|
/* Avoid 'linux/stddef.h' definition of '__always_inline'. */
|
||||||
|
#undef __always_inline
|
||||||
|
#define __always_inline inline __attribute__((always_inline))
|
||||||
|
|
||||||
|
#ifndef __noinline
|
||||||
|
#define __noinline __attribute__((noinline))
|
||||||
|
#endif
|
||||||
|
#ifndef __weak
|
||||||
|
#define __weak __attribute__((weak))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use __hidden attribute to mark a non-static BPF subprogram effectively
|
||||||
|
* static for BPF verifier's verification algorithm purposes, allowing more
|
||||||
|
* extensive and permissive BPF verification process, taking into account
|
||||||
|
* subprogram's caller context.
|
||||||
|
*/
|
||||||
|
#define __hidden __attribute__((visibility("hidden")))
|
||||||
|
|
||||||
|
/* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
|
||||||
|
* any system-level headers (such as stddef.h, linux/version.h, etc), and
|
||||||
|
* commonly-used macros like NULL and KERNEL_VERSION aren't available through
|
||||||
|
* vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
|
||||||
|
* them on their own. So as a convenience, provide such definitions here.
|
||||||
|
*/
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL ((void *)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef KERNEL_VERSION
|
||||||
|
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper macros to manipulate data structures
|
||||||
|
*/
|
||||||
|
#ifndef offsetof
|
||||||
|
#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
|
||||||
|
#endif
|
||||||
|
#ifndef container_of
|
||||||
|
#define container_of(ptr, type, member) \
|
||||||
|
({ \
|
||||||
|
void *__mptr = (void *)(ptr); \
|
||||||
|
((type *)(__mptr - offsetof(type, member))); \
|
||||||
|
})
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper macro to throw a compilation error if __bpf_unreachable() gets
|
||||||
|
* built into the resulting code. This works given BPF back end does not
|
||||||
|
* implement __builtin_trap(). This is useful to assert that certain paths
|
||||||
|
* of the program code are never used and hence eliminated by the compiler.
|
||||||
|
*
|
||||||
|
* For example, consider a switch statement that covers known cases used by
|
||||||
|
* the program. __bpf_unreachable() can then reside in the default case. If
|
||||||
|
* the program gets extended such that a case is not covered in the switch
|
||||||
|
* statement, then it will throw a build error due to the default case not
|
||||||
|
* being compiled out.
|
||||||
|
*/
|
||||||
|
#ifndef __bpf_unreachable
|
||||||
|
# define __bpf_unreachable() __builtin_trap()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to perform a tail call with a constant/immediate map slot.
|
||||||
|
*/
|
||||||
|
#if __clang_major__ >= 8 && defined(__bpf__)
|
||||||
|
static __always_inline void
|
||||||
|
bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
|
||||||
|
{
|
||||||
|
if (!__builtin_constant_p(slot))
|
||||||
|
__bpf_unreachable();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provide a hard guarantee that LLVM won't optimize setting r2 (map
|
||||||
|
* pointer) and r3 (constant map index) from _different paths_ ending
|
||||||
|
* up at the _same_ call insn as otherwise we won't be able to use the
|
||||||
|
* jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
|
||||||
|
* given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
|
||||||
|
* tracking for prog array pokes") for details on verifier tracking.
|
||||||
|
*
|
||||||
|
* Note on clobber list: we need to stay in-line with BPF calling
|
||||||
|
* convention, so even if we don't end up using r0, r4, r5, we need
|
||||||
|
* to mark them as clobber so that LLVM doesn't end up using them
|
||||||
|
* before / after the call.
|
||||||
|
*/
|
||||||
|
asm volatile("r1 = %[ctx]\n\t"
|
||||||
|
"r2 = %[map]\n\t"
|
||||||
|
"r3 = %[slot]\n\t"
|
||||||
|
"call 12"
|
||||||
|
:: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot)
|
||||||
|
: "r0", "r1", "r2", "r3", "r4", "r5");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper structure used by eBPF C program
|
||||||
|
* to describe BPF map attributes to libbpf loader
|
||||||
|
*/
|
||||||
|
struct bpf_map_def {
|
||||||
|
unsigned int type;
|
||||||
|
unsigned int key_size;
|
||||||
|
unsigned int value_size;
|
||||||
|
unsigned int max_entries;
|
||||||
|
unsigned int map_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum libbpf_pin_type {
|
||||||
|
LIBBPF_PIN_NONE,
|
||||||
|
/* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
|
||||||
|
LIBBPF_PIN_BY_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum libbpf_tristate {
|
||||||
|
TRI_NO = 0,
|
||||||
|
TRI_YES = 1,
|
||||||
|
TRI_MODULE = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define __kconfig __attribute__((section(".kconfig")))
|
||||||
|
#define __ksym __attribute__((section(".ksyms")))
|
||||||
|
|
||||||
|
#ifndef ___bpf_concat
|
||||||
|
#define ___bpf_concat(a, b) a ## b
|
||||||
|
#endif
|
||||||
|
#ifndef ___bpf_apply
|
||||||
|
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
|
||||||
|
#endif
|
||||||
|
#ifndef ___bpf_nth
|
||||||
|
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
|
||||||
|
#endif
|
||||||
|
#ifndef ___bpf_narg
|
||||||
|
#define ___bpf_narg(...) \
|
||||||
|
___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ___bpf_fill0(arr, p, x) do {} while (0)
|
||||||
|
#define ___bpf_fill1(arr, p, x) arr[p] = x
|
||||||
|
#define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args)
|
||||||
|
#define ___bpf_fill(arr, args...) \
|
||||||
|
___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
|
||||||
|
* in a structure.
|
||||||
|
*/
|
||||||
|
#define BPF_SEQ_PRINTF(seq, fmt, args...) \
|
||||||
|
({ \
|
||||||
|
static const char ___fmt[] = fmt; \
|
||||||
|
unsigned long long ___param[___bpf_narg(args)]; \
|
||||||
|
\
|
||||||
|
_Pragma("GCC diagnostic push") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||||
|
___bpf_fill(___param, args); \
|
||||||
|
_Pragma("GCC diagnostic pop") \
|
||||||
|
\
|
||||||
|
bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \
|
||||||
|
___param, sizeof(___param)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
|
||||||
|
* an array of u64.
|
||||||
|
*/
|
||||||
|
#define BPF_SNPRINTF(out, out_size, fmt, args...) \
|
||||||
|
({ \
|
||||||
|
static const char ___fmt[] = fmt; \
|
||||||
|
unsigned long long ___param[___bpf_narg(args)]; \
|
||||||
|
\
|
||||||
|
_Pragma("GCC diagnostic push") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||||
|
___bpf_fill(___param, args); \
|
||||||
|
_Pragma("GCC diagnostic pop") \
|
||||||
|
\
|
||||||
|
bpf_snprintf(out, out_size, ___fmt, \
|
||||||
|
___param, sizeof(___param)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#ifdef BPF_NO_GLOBAL_DATA
|
||||||
|
#define BPF_PRINTK_FMT_MOD
|
||||||
|
#else
|
||||||
|
#define BPF_PRINTK_FMT_MOD static const
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __bpf_printk(fmt, ...) \
|
||||||
|
({ \
|
||||||
|
BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \
|
||||||
|
bpf_trace_printk(____fmt, sizeof(____fmt), \
|
||||||
|
##__VA_ARGS__); \
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
|
||||||
|
* instead of an array of u64.
|
||||||
|
*/
|
||||||
|
#define __bpf_vprintk(fmt, args...) \
|
||||||
|
({ \
|
||||||
|
static const char ___fmt[] = fmt; \
|
||||||
|
unsigned long long ___param[___bpf_narg(args)]; \
|
||||||
|
\
|
||||||
|
_Pragma("GCC diagnostic push") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||||
|
___bpf_fill(___param, args); \
|
||||||
|
_Pragma("GCC diagnostic pop") \
|
||||||
|
\
|
||||||
|
bpf_trace_vprintk(___fmt, sizeof(___fmt), \
|
||||||
|
___param, sizeof(___param)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
|
||||||
|
* Otherwise use __bpf_vprintk
|
||||||
|
*/
|
||||||
|
#define ___bpf_pick_printk(...) \
|
||||||
|
___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
|
||||||
|
__bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
|
||||||
|
__bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
|
||||||
|
__bpf_printk /*1*/, __bpf_printk /*0*/)
|
||||||
|
|
||||||
|
/* Helper macro to print out debug messages */
|
||||||
|
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
|
||||||
|
|
||||||
|
#endif
|
342
component/ebpf/bpf/redir.c
Normal file
342
component/ebpf/bpf/redir.c
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
//#include <linux/types.h>
|
||||||
|
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
//#include <linux/if_packet.h>
|
||||||
|
//#include <linux/if_vlan.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
//#include <linux/udp.h>
|
||||||
|
|
||||||
|
#include <linux/pkt_cls.h>
|
||||||
|
|
||||||
|
#include "bpf_endian.h"
|
||||||
|
#include "bpf_helpers.h"
|
||||||
|
|
||||||
|
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
|
||||||
|
#define IP_DST_OFF (ETH_HLEN + offsetof(struct iphdr, daddr))
|
||||||
|
#define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
|
||||||
|
#define IP_PROTO_OFF (ETH_HLEN + offsetof(struct iphdr, protocol))
|
||||||
|
#define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check))
|
||||||
|
#define TCP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, source))
|
||||||
|
#define TCP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, dest))
|
||||||
|
//#define UDP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, check))
|
||||||
|
//#define UDP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, source))
|
||||||
|
//#define UDP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, dest))
|
||||||
|
#define IS_PSEUDO 0x10
|
||||||
|
|
||||||
|
struct origin_info {
|
||||||
|
__be32 ip;
|
||||||
|
__be16 port;
|
||||||
|
__u16 pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct origin_info *origin_info_unused __attribute__((unused));
|
||||||
|
|
||||||
|
struct redir_info {
|
||||||
|
__be32 sip;
|
||||||
|
__be32 dip;
|
||||||
|
__be16 sport;
|
||||||
|
__be16 dport;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct redir_info *redir_info_unused __attribute__((unused));
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_LRU_HASH);
|
||||||
|
__type(key, struct redir_info);
|
||||||
|
__type(value, struct origin_info);
|
||||||
|
__uint(max_entries, 65535);
|
||||||
|
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||||
|
} pair_original_dst_map SEC(".maps");
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u32);
|
||||||
|
__uint(max_entries, 3);
|
||||||
|
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||||
|
} redir_params_map SEC(".maps");
|
||||||
|
|
||||||
|
static __always_inline int rewrite_ip(struct __sk_buff *skb, __be32 new_ip, bool is_dest) {
|
||||||
|
int ret, off = 0, flags = IS_PSEUDO;
|
||||||
|
__be32 old_ip;
|
||||||
|
|
||||||
|
if (is_dest)
|
||||||
|
ret = bpf_skb_load_bytes(skb, IP_DST_OFF, &old_ip, 4);
|
||||||
|
else
|
||||||
|
ret = bpf_skb_load_bytes(skb, IP_SRC_OFF, &old_ip, 4);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
off = TCP_CSUM_OFF;
|
||||||
|
// __u8 proto;
|
||||||
|
//
|
||||||
|
// ret = bpf_skb_load_bytes(skb, IP_PROTO_OFF, &proto, 1);
|
||||||
|
// if (ret < 0) {
|
||||||
|
// return BPF_DROP;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// switch (proto) {
|
||||||
|
// case IPPROTO_TCP:
|
||||||
|
// off = TCP_CSUM_OFF;
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case IPPROTO_UDP:
|
||||||
|
// off = UDP_CSUM_OFF;
|
||||||
|
// flags |= BPF_F_MARK_MANGLED_0;
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case IPPROTO_ICMPV6:
|
||||||
|
// off = offsetof(struct icmp6hdr, icmp6_cksum);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (off) {
|
||||||
|
ret = bpf_l4_csum_replace(skb, off, old_ip, new_ip, flags | sizeof(new_ip));
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
ret = bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dest)
|
||||||
|
ret = bpf_skb_store_bytes(skb, IP_DST_OFF, &new_ip, sizeof(new_ip), 0);
|
||||||
|
else
|
||||||
|
ret = bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline int rewrite_port(struct __sk_buff *skb, __be16 new_port, bool is_dest) {
|
||||||
|
int ret, off = 0;
|
||||||
|
__be16 old_port;
|
||||||
|
|
||||||
|
if (is_dest)
|
||||||
|
ret = bpf_skb_load_bytes(skb, TCP_DST_OFF, &old_port, 2);
|
||||||
|
else
|
||||||
|
ret = bpf_skb_load_bytes(skb, TCP_SRC_OFF, &old_port, 2);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
off = TCP_CSUM_OFF;
|
||||||
|
|
||||||
|
ret = bpf_l4_csum_replace(skb, off, old_port, new_port, sizeof(new_port));
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dest)
|
||||||
|
ret = bpf_skb_store_bytes(skb, TCP_DST_OFF, &new_port, sizeof(new_port), 0);
|
||||||
|
else
|
||||||
|
ret = bpf_skb_store_bytes(skb, TCP_SRC_OFF, &new_port, sizeof(new_port), 0);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline bool is_lan_ip(__be32 addr) {
|
||||||
|
if (addr == 0xffffffff)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
__u8 fist = (__u8)(addr & 0xff);
|
||||||
|
|
||||||
|
if (fist == 127 || fist == 10)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
__u8 second = (__u8)((addr >> 8) & 0xff);
|
||||||
|
|
||||||
|
if (fist == 172 && second >= 16 && second <= 31)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (fist == 192 && second == 168)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("tc_clash_auto_redir_ingress")
|
||||||
|
int tc_redir_ingress_func(struct __sk_buff *skb) {
|
||||||
|
void *data = (void *)(long)skb->data;
|
||||||
|
void *data_end = (void *)(long)skb->data_end;
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if ((void *)(eth + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (eth->h_proto != bpf_htons(ETH_P_IP))
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||||
|
if ((void *)(iph + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__u32 key = 0, *route_index, *redir_ip, *redir_port;
|
||||||
|
|
||||||
|
route_index = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
if (!route_index)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (iph->protocol == IPPROTO_ICMP && *route_index != 0)
|
||||||
|
return bpf_redirect(*route_index, 0);
|
||||||
|
|
||||||
|
if (iph->protocol != IPPROTO_TCP)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||||
|
if ((void *)(tcph + 1) > data_end)
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
|
||||||
|
key = 1;
|
||||||
|
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
if (!redir_ip)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
key = 2;
|
||||||
|
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
if (!redir_port)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__be32 new_ip = bpf_htonl(*redir_ip);
|
||||||
|
__be16 new_port = bpf_htonl(*redir_port) >> 16;
|
||||||
|
__be32 old_ip = iph->daddr;
|
||||||
|
__be16 old_port = tcph->dest;
|
||||||
|
|
||||||
|
if (old_ip == new_ip || is_lan_ip(old_ip) || bpf_ntohs(old_port) == 53) {
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct redir_info p_key = {
|
||||||
|
.sip = iph->saddr,
|
||||||
|
.sport = tcph->source,
|
||||||
|
.dip = new_ip,
|
||||||
|
.dport = new_port,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tcph->syn && !tcph->ack) {
|
||||||
|
struct origin_info origin = {
|
||||||
|
.ip = old_ip,
|
||||||
|
.port = old_port,
|
||||||
|
};
|
||||||
|
|
||||||
|
bpf_map_update_elem(&pair_original_dst_map, &p_key, &origin, BPF_NOEXIST);
|
||||||
|
|
||||||
|
if (rewrite_ip(skb, new_ip, true) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrite_port(skb, new_port, true) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
|
||||||
|
if (!origin) {
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrite_ip(skb, new_ip, true) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrite_port(skb, new_port, true) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("tc_clash_auto_redir_egress")
|
||||||
|
int tc_redir_egress_func(struct __sk_buff *skb) {
|
||||||
|
void *data = (void *)(long)skb->data;
|
||||||
|
void *data_end = (void *)(long)skb->data_end;
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if ((void *)(eth + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (eth->h_proto != bpf_htons(ETH_P_IP))
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__u32 key = 0, *redir_ip, *redir_port; // *clash_mark
|
||||||
|
|
||||||
|
// clash_mark = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
// if (clash_mark && *clash_mark != 0 && *clash_mark == skb->mark)
|
||||||
|
// return TC_ACT_OK;
|
||||||
|
|
||||||
|
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||||
|
if ((void *)(iph + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (iph->protocol != IPPROTO_TCP)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||||
|
if ((void *)(tcph + 1) > data_end)
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
|
||||||
|
key = 1;
|
||||||
|
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
if (!redir_ip)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
key = 2;
|
||||||
|
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||||
|
if (!redir_port)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__be32 new_ip = bpf_htonl(*redir_ip);
|
||||||
|
__be16 new_port = bpf_htonl(*redir_port) >> 16;
|
||||||
|
__be32 old_ip = iph->saddr;
|
||||||
|
__be16 old_port = tcph->source;
|
||||||
|
|
||||||
|
if (old_ip != new_ip || old_port != new_port) {
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct redir_info p_key = {
|
||||||
|
.sip = iph->daddr,
|
||||||
|
.sport = tcph->dest,
|
||||||
|
.dip = iph->saddr,
|
||||||
|
.dport = tcph->source,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
|
||||||
|
if (!origin) {
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcph->fin && tcph->ack) {
|
||||||
|
bpf_map_delete_elem(&pair_original_dst_map, &p_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrite_ip(skb, origin->ip, false) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrite_port(skb, origin->port, false) < 0) {
|
||||||
|
return TC_ACT_SHOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
103
component/ebpf/bpf/tc.c
Normal file
103
component/ebpf/bpf/tc.c
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
//#include <linux/tcp.h>
|
||||||
|
//#include <linux/udp.h>
|
||||||
|
#include <linux/pkt_cls.h>
|
||||||
|
|
||||||
|
#include "bpf_endian.h"
|
||||||
|
#include "bpf_helpers.h"
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u32);
|
||||||
|
__uint(max_entries, 2);
|
||||||
|
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||||
|
} tc_params_map SEC(".maps");
|
||||||
|
|
||||||
|
static __always_inline bool is_lan_ip(__be32 addr) {
|
||||||
|
if (addr == 0xffffffff)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
__u8 fist = (__u8)(addr & 0xff);
|
||||||
|
|
||||||
|
if (fist == 127 || fist == 10)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
__u8 second = (__u8)((addr >> 8) & 0xff);
|
||||||
|
|
||||||
|
if (fist == 172 && second >= 16 && second <= 31)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (fist == 192 && second == 168)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("tc_clash_redirect_to_tun")
|
||||||
|
int tc_tun_func(struct __sk_buff *skb) {
|
||||||
|
void *data = (void *)(long)skb->data;
|
||||||
|
void *data_end = (void *)(long)skb->data_end;
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if ((void *)(eth + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (eth->h_proto == bpf_htons(ETH_P_ARP))
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__u32 key = 0, *clash_mark, *tun_ifindex;
|
||||||
|
|
||||||
|
clash_mark = bpf_map_lookup_elem(&tc_params_map, &key);
|
||||||
|
if (!clash_mark)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (skb->mark == *clash_mark)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
|
||||||
|
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||||
|
if ((void *)(iph + 1) > data_end)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
if (iph->protocol == IPPROTO_ICMP)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
__be32 daddr = iph->daddr;
|
||||||
|
|
||||||
|
if (is_lan_ip(daddr))
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
// if (iph->protocol == IPPROTO_TCP) {
|
||||||
|
// struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||||
|
// if ((void *)(tcph + 1) > data_end)
|
||||||
|
// return TC_ACT_OK;
|
||||||
|
//
|
||||||
|
// __u16 source = bpf_ntohs(tcph->source);
|
||||||
|
// if (source == 22 || source == 80 || source == 443 || source == 8080 || source == 8443 || source == 9090 || (source >= 7890 && source <= 7895))
|
||||||
|
// return TC_ACT_OK;
|
||||||
|
// } else if (iph->protocol == IPPROTO_UDP) {
|
||||||
|
// struct udphdr *udph = (struct udphdr *)(iph + 1);
|
||||||
|
// if ((void *)(udph + 1) > data_end)
|
||||||
|
// return TC_ACT_OK;
|
||||||
|
//
|
||||||
|
// __u16 source = bpf_ntohs(udph->source);
|
||||||
|
// if (source == 53 || (source >= 135 && source <= 139))
|
||||||
|
// return TC_ACT_OK;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
key = 1;
|
||||||
|
tun_ifindex = bpf_map_lookup_elem(&tc_params_map, &key);
|
||||||
|
if (!tun_ifindex)
|
||||||
|
return TC_ACT_OK;
|
||||||
|
|
||||||
|
//return bpf_redirect(*tun_ifindex, BPF_F_INGRESS); // __bpf_rx_skb
|
||||||
|
return bpf_redirect(*tun_ifindex, 0); // __bpf_tx_skb / __dev_xmit_skb
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
13
component/ebpf/byteorder/byteorder.go
Normal file
13
component/ebpf/byteorder/byteorder.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package byteorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetIPv4ToHost32 converts an net.IP to a uint32 in host byte order. ip
|
||||||
|
// must be a IPv4 address, otherwise the function will panic.
|
||||||
|
func NetIPv4ToHost32(ip net.IP) uint32 {
|
||||||
|
ipv4 := ip.To4()
|
||||||
|
_ = ipv4[3] // Assert length of ipv4.
|
||||||
|
return Native.Uint32(ipv4)
|
||||||
|
}
|
12
component/ebpf/byteorder/byteorder_bigendian.go
Normal file
12
component/ebpf/byteorder/byteorder_bigendian.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||||
|
|
||||||
|
package byteorder
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
var Native binary.ByteOrder = binary.BigEndian
|
||||||
|
|
||||||
|
func HostToNetwork16(u uint16) uint16 { return u }
|
||||||
|
func HostToNetwork32(u uint32) uint32 { return u }
|
||||||
|
func NetworkToHost16(u uint16) uint16 { return u }
|
||||||
|
func NetworkToHost32(u uint32) uint32 { return u }
|
15
component/ebpf/byteorder/byteorder_littleendian.go
Normal file
15
component/ebpf/byteorder/byteorder_littleendian.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
|
||||||
|
|
||||||
|
package byteorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Native binary.ByteOrder = binary.LittleEndian
|
||||||
|
|
||||||
|
func HostToNetwork16(u uint16) uint16 { return bits.ReverseBytes16(u) }
|
||||||
|
func HostToNetwork32(u uint32) uint32 { return bits.ReverseBytes32(u) }
|
||||||
|
func NetworkToHost16(u uint16) uint16 { return bits.ReverseBytes16(u) }
|
||||||
|
func NetworkToHost32(u uint32) uint32 { return bits.ReverseBytes32(u) }
|
33
component/ebpf/ebpf.go
Normal file
33
component/ebpf/ebpf.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TcEBpfProgram struct {
|
||||||
|
pros []C.EBpf
|
||||||
|
rawNICs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TcEBpfProgram) RawNICs() []string {
|
||||||
|
return t.rawNICs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TcEBpfProgram) Close() {
|
||||||
|
for _, p := range t.pros {
|
||||||
|
p.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TcEBpfProgram) Lookup(srcAddrPort netip.AddrPort) (addr socks5.Addr, err error) {
|
||||||
|
for _, p := range t.pros {
|
||||||
|
addr, err = p.Lookup(srcAddrPort)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
112
component/ebpf/ebpf_linux.go
Normal file
112
component/ebpf/ebpf_linux.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/ebpf/redir"
|
||||||
|
"github.com/Dreamacro/clash/component/ebpf/tc"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTcEBpfProgram new redirect to tun ebpf program
|
||||||
|
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
|
||||||
|
tunIface, err := netlink.LinkByName(tunName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lookup network iface %q: %w", tunName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tunIndex := uint32(tunIface.Attrs().Index)
|
||||||
|
|
||||||
|
dialer.DefaultRoutingMark.Store(C.ClashTrafficMark)
|
||||||
|
|
||||||
|
ifMark := uint32(dialer.DefaultRoutingMark.Load())
|
||||||
|
|
||||||
|
var pros []C.EBpf
|
||||||
|
for _, ifaceName := range ifaceNames {
|
||||||
|
iface, err := netlink.LinkByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
if iface.Attrs().OperState != netlink.OperUp {
|
||||||
|
return nil, fmt.Errorf("network iface %q is down", ifaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := iface.Attrs()
|
||||||
|
index := attrs.Index
|
||||||
|
|
||||||
|
tcPro := tc.NewEBpfTc(ifaceName, index, ifMark, tunIndex)
|
||||||
|
if err = tcPro.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pros = append(pros, tcPro)
|
||||||
|
}
|
||||||
|
|
||||||
|
systemSetting(ifaceNames...)
|
||||||
|
|
||||||
|
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedirEBpfProgram new auto redirect ebpf program
|
||||||
|
func NewRedirEBpfProgram(ifaceNames []string, redirPort uint16, defaultRouteInterfaceName string) (*TcEBpfProgram, error) {
|
||||||
|
defaultRouteInterface, err := netlink.LinkByName(defaultRouteInterfaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lookup network iface %q: %w", defaultRouteInterfaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRouteIndex := uint32(defaultRouteInterface.Attrs().Index)
|
||||||
|
|
||||||
|
var pros []C.EBpf
|
||||||
|
for _, ifaceName := range ifaceNames {
|
||||||
|
iface, err := netlink.LinkByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := iface.Attrs()
|
||||||
|
index := attrs.Index
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(iface, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lookup network iface %q address: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("network iface %q does not contain any ipv4 addresses", ifaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
address, _ := netip.AddrFromSlice(addrs[0].IP)
|
||||||
|
redirAddrPort := netip.AddrPortFrom(address, redirPort)
|
||||||
|
|
||||||
|
redirPro := redir.NewEBpfRedirect(ifaceName, index, 0, defaultRouteIndex, redirAddrPort)
|
||||||
|
if err = redirPro.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pros = append(pros, redirPro)
|
||||||
|
}
|
||||||
|
|
||||||
|
systemSetting(ifaceNames...)
|
||||||
|
|
||||||
|
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func systemSetting(ifaceNames ...string) {
|
||||||
|
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
|
||||||
|
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding=1")
|
||||||
|
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_local=1")
|
||||||
|
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_redirects=1")
|
||||||
|
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.rp_filter=0")
|
||||||
|
|
||||||
|
for _, ifaceName := range ifaceNames {
|
||||||
|
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.forwarding=1", ifaceName))
|
||||||
|
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_local=1", ifaceName))
|
||||||
|
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_redirects=1", ifaceName))
|
||||||
|
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.rp_filter=0", ifaceName))
|
||||||
|
}
|
||||||
|
}
|
17
component/ebpf/ebpf_others.go
Normal file
17
component/ebpf/ebpf_others.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTcEBpfProgram new ebpf tc program
|
||||||
|
func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) {
|
||||||
|
return nil, fmt.Errorf("system not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedirEBpfProgram new ebpf redirect program
|
||||||
|
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
|
||||||
|
return nil, fmt.Errorf("system not supported")
|
||||||
|
}
|
216
component/ebpf/redir/auto_redirect.go
Normal file
216
component/ebpf/redir/auto_redirect.go
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/ebpf/byteorder"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/redir.c
|
||||||
|
|
||||||
|
const (
|
||||||
|
mapKey1 uint32 = 0
|
||||||
|
mapKey2 uint32 = 1
|
||||||
|
mapKey3 uint32 = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type EBpfRedirect struct {
|
||||||
|
objs io.Closer
|
||||||
|
originMap *ebpf.Map
|
||||||
|
qdisc netlink.Qdisc
|
||||||
|
filter netlink.Filter
|
||||||
|
filterEgress netlink.Filter
|
||||||
|
|
||||||
|
ifName string
|
||||||
|
ifIndex int
|
||||||
|
ifMark uint32
|
||||||
|
rtIndex uint32
|
||||||
|
redirIp uint32
|
||||||
|
redirPort uint16
|
||||||
|
|
||||||
|
bpfPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEBpfRedirect(ifName string, ifIndex int, ifMark uint32, routeIndex uint32, redirAddrPort netip.AddrPort) *EBpfRedirect {
|
||||||
|
return &EBpfRedirect{
|
||||||
|
ifName: ifName,
|
||||||
|
ifIndex: ifIndex,
|
||||||
|
ifMark: ifMark,
|
||||||
|
rtIndex: routeIndex,
|
||||||
|
redirIp: binary.BigEndian.Uint32(redirAddrPort.Addr().AsSlice()),
|
||||||
|
redirPort: redirAddrPort.Port(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfRedirect) Start() error {
|
||||||
|
if err := rlimit.RemoveMemlock(); err != nil {
|
||||||
|
return fmt.Errorf("remove memory lock: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
|
||||||
|
if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("failed to create bpf fs subpath: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var objs bpfObjects
|
||||||
|
if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
|
||||||
|
Maps: ebpf.MapOptions{
|
||||||
|
PinPath: e.bpfPath,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("loading objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.objs = &objs
|
||||||
|
e.originMap = objs.bpfMaps.PairOriginalDstMap
|
||||||
|
|
||||||
|
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey1, e.rtIndex, ebpf.UpdateAny); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("storing objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey2, e.redirIp, ebpf.UpdateAny); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("storing objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey3, uint32(e.redirPort), ebpf.UpdateAny); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("storing objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := netlink.QdiscAttrs{
|
||||||
|
LinkIndex: e.ifIndex,
|
||||||
|
Handle: netlink.MakeHandle(0xffff, 0),
|
||||||
|
Parent: netlink.HANDLE_CLSACT,
|
||||||
|
}
|
||||||
|
|
||||||
|
qdisc := &netlink.GenericQdisc{
|
||||||
|
QdiscAttrs: attrs,
|
||||||
|
QdiscType: "clsact",
|
||||||
|
}
|
||||||
|
|
||||||
|
e.qdisc = qdisc
|
||||||
|
|
||||||
|
if err := netlink.QdiscAdd(qdisc); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
_ = netlink.QdiscDel(qdisc)
|
||||||
|
err = netlink.QdiscAdd(qdisc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterAttrs := netlink.FilterAttrs{
|
||||||
|
LinkIndex: e.ifIndex,
|
||||||
|
Parent: netlink.HANDLE_MIN_INGRESS,
|
||||||
|
Handle: netlink.MakeHandle(0, 1),
|
||||||
|
Protocol: unix.ETH_P_IP,
|
||||||
|
Priority: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &netlink.BpfFilter{
|
||||||
|
FilterAttrs: filterAttrs,
|
||||||
|
Fd: objs.bpfPrograms.TcRedirIngressFunc.FD(),
|
||||||
|
Name: "clash-redir-ingress-" + e.ifName,
|
||||||
|
DirectAction: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.FilterAdd(filter); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.filter = filter
|
||||||
|
|
||||||
|
filterAttrsEgress := netlink.FilterAttrs{
|
||||||
|
LinkIndex: e.ifIndex,
|
||||||
|
Parent: netlink.HANDLE_MIN_EGRESS,
|
||||||
|
Handle: netlink.MakeHandle(0, 1),
|
||||||
|
Protocol: unix.ETH_P_IP,
|
||||||
|
Priority: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEgress := &netlink.BpfFilter{
|
||||||
|
FilterAttrs: filterAttrsEgress,
|
||||||
|
Fd: objs.bpfPrograms.TcRedirEgressFunc.FD(),
|
||||||
|
Name: "clash-redir-egress-" + e.ifName,
|
||||||
|
DirectAction: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.FilterAdd(filterEgress); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("cannot attach ebpf object to filter egress: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.filterEgress = filterEgress
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfRedirect) Close() {
|
||||||
|
if e.filter != nil {
|
||||||
|
_ = netlink.FilterDel(e.filter)
|
||||||
|
}
|
||||||
|
if e.filterEgress != nil {
|
||||||
|
_ = netlink.FilterDel(e.filterEgress)
|
||||||
|
}
|
||||||
|
if e.qdisc != nil {
|
||||||
|
_ = netlink.QdiscDel(e.qdisc)
|
||||||
|
}
|
||||||
|
if e.objs != nil {
|
||||||
|
_ = e.objs.Close()
|
||||||
|
}
|
||||||
|
_ = os.Remove(filepath.Join(e.bpfPath, "redir_params_map"))
|
||||||
|
_ = os.Remove(filepath.Join(e.bpfPath, "pair_original_dst_map"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfRedirect) Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error) {
|
||||||
|
rAddr := srcAddrPort.Addr().Unmap()
|
||||||
|
if rAddr.Is6() {
|
||||||
|
return nil, fmt.Errorf("remote address is ipv6")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcIp := binary.BigEndian.Uint32(rAddr.AsSlice())
|
||||||
|
scrPort := srcAddrPort.Port()
|
||||||
|
|
||||||
|
key := bpfRedirInfo{
|
||||||
|
Sip: byteorder.HostToNetwork32(srcIp),
|
||||||
|
Sport: byteorder.HostToNetwork16(scrPort),
|
||||||
|
Dip: byteorder.HostToNetwork32(e.redirIp),
|
||||||
|
Dport: byteorder.HostToNetwork16(e.redirPort),
|
||||||
|
}
|
||||||
|
|
||||||
|
origin := bpfOriginInfo{}
|
||||||
|
|
||||||
|
err := e.originMap.Lookup(key, &origin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := make([]byte, net.IPv4len+3)
|
||||||
|
addr[0] = socks5.AtypIPv4
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(addr[1:1+net.IPv4len], byteorder.NetworkToHost32(origin.Ip)) // big end
|
||||||
|
binary.BigEndian.PutUint16(addr[1+net.IPv4len:3+net.IPv4len], byteorder.NetworkToHost16(origin.Port)) // big end
|
||||||
|
return addr, nil
|
||||||
|
}
|
138
component/ebpf/redir/bpf_bpfeb.go
Normal file
138
component/ebpf/redir/bpf_bpfeb.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||||
|
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bpfOriginInfo struct {
|
||||||
|
Ip uint32
|
||||||
|
Port uint16
|
||||||
|
Pad uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type bpfRedirInfo struct {
|
||||||
|
Sip uint32
|
||||||
|
Dip uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
|
||||||
|
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
|
||||||
|
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
|
||||||
|
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.PairOriginalDstMap,
|
||||||
|
m.RedirParamsMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
|
||||||
|
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.TcRedirEgressFunc,
|
||||||
|
p.TcRedirIngressFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//go:embed bpf_bpfeb.o
|
||||||
|
var _BpfBytes []byte
|
BIN
component/ebpf/redir/bpf_bpfeb.o
Normal file
BIN
component/ebpf/redir/bpf_bpfeb.o
Normal file
Binary file not shown.
138
component/ebpf/redir/bpf_bpfel.go
Normal file
138
component/ebpf/redir/bpf_bpfel.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
|
||||||
|
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bpfOriginInfo struct {
|
||||||
|
Ip uint32
|
||||||
|
Port uint16
|
||||||
|
Pad uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type bpfRedirInfo struct {
|
||||||
|
Sip uint32
|
||||||
|
Dip uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
|
||||||
|
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
|
||||||
|
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
|
||||||
|
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.PairOriginalDstMap,
|
||||||
|
m.RedirParamsMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
|
||||||
|
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.TcRedirEgressFunc,
|
||||||
|
p.TcRedirIngressFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//go:embed bpf_bpfel.o
|
||||||
|
var _BpfBytes []byte
|
BIN
component/ebpf/redir/bpf_bpfel.o
Normal file
BIN
component/ebpf/redir/bpf_bpfel.o
Normal file
Binary file not shown.
119
component/ebpf/tc/bpf_bpfeb.go
Normal file
119
component/ebpf/tc/bpf_bpfeb.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||||
|
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
|
||||||
|
|
||||||
|
package tc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.TcParamsMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.TcTunFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//go:embed bpf_bpfeb.o
|
||||||
|
var _BpfBytes []byte
|
BIN
component/ebpf/tc/bpf_bpfeb.o
Normal file
BIN
component/ebpf/tc/bpf_bpfeb.o
Normal file
Binary file not shown.
119
component/ebpf/tc/bpf_bpfel.go
Normal file
119
component/ebpf/tc/bpf_bpfel.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
|
||||||
|
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64
|
||||||
|
|
||||||
|
package tc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.TcParamsMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.TcTunFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//go:embed bpf_bpfel.o
|
||||||
|
var _BpfBytes []byte
|
BIN
component/ebpf/tc/bpf_bpfel.o
Normal file
BIN
component/ebpf/tc/bpf_bpfel.o
Normal file
Binary file not shown.
147
component/ebpf/tc/redirect_to_tun.go
Normal file
147
component/ebpf/tc/redirect_to_tun.go
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package tc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/tc.c
|
||||||
|
|
||||||
|
const (
|
||||||
|
mapKey1 uint32 = 0
|
||||||
|
mapKey2 uint32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type EBpfTC struct {
|
||||||
|
objs io.Closer
|
||||||
|
qdisc netlink.Qdisc
|
||||||
|
filter netlink.Filter
|
||||||
|
|
||||||
|
ifName string
|
||||||
|
ifIndex int
|
||||||
|
ifMark uint32
|
||||||
|
tunIfIndex uint32
|
||||||
|
|
||||||
|
bpfPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEBpfTc(ifName string, ifIndex int, ifMark uint32, tunIfIndex uint32) *EBpfTC {
|
||||||
|
return &EBpfTC{
|
||||||
|
ifName: ifName,
|
||||||
|
ifIndex: ifIndex,
|
||||||
|
ifMark: ifMark,
|
||||||
|
tunIfIndex: tunIfIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfTC) Start() error {
|
||||||
|
if err := rlimit.RemoveMemlock(); err != nil {
|
||||||
|
return fmt.Errorf("remove memory lock: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
|
||||||
|
if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("failed to create bpf fs subpath: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var objs bpfObjects
|
||||||
|
if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
|
||||||
|
Maps: ebpf.MapOptions{
|
||||||
|
PinPath: e.bpfPath,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("loading objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.objs = &objs
|
||||||
|
|
||||||
|
if err := objs.bpfMaps.TcParamsMap.Update(mapKey1, e.ifMark, ebpf.UpdateAny); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("storing objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := objs.bpfMaps.TcParamsMap.Update(mapKey2, e.tunIfIndex, ebpf.UpdateAny); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("storing objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := netlink.QdiscAttrs{
|
||||||
|
LinkIndex: e.ifIndex,
|
||||||
|
Handle: netlink.MakeHandle(0xffff, 0),
|
||||||
|
Parent: netlink.HANDLE_CLSACT,
|
||||||
|
}
|
||||||
|
|
||||||
|
qdisc := &netlink.GenericQdisc{
|
||||||
|
QdiscAttrs: attrs,
|
||||||
|
QdiscType: "clsact",
|
||||||
|
}
|
||||||
|
|
||||||
|
e.qdisc = qdisc
|
||||||
|
|
||||||
|
if err := netlink.QdiscAdd(qdisc); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
_ = netlink.QdiscDel(qdisc)
|
||||||
|
err = netlink.QdiscAdd(qdisc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterAttrs := netlink.FilterAttrs{
|
||||||
|
LinkIndex: e.ifIndex,
|
||||||
|
Parent: netlink.HANDLE_MIN_EGRESS,
|
||||||
|
Handle: netlink.MakeHandle(0, 1),
|
||||||
|
Protocol: unix.ETH_P_ALL,
|
||||||
|
Priority: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &netlink.BpfFilter{
|
||||||
|
FilterAttrs: filterAttrs,
|
||||||
|
Fd: objs.bpfPrograms.TcTunFunc.FD(),
|
||||||
|
Name: "clash-tc-" + e.ifName,
|
||||||
|
DirectAction: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.FilterAdd(filter); err != nil {
|
||||||
|
e.Close()
|
||||||
|
return fmt.Errorf("cannot attach ebpf object to filter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.filter = filter
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfTC) Close() {
|
||||||
|
if e.filter != nil {
|
||||||
|
_ = netlink.FilterDel(e.filter)
|
||||||
|
}
|
||||||
|
if e.qdisc != nil {
|
||||||
|
_ = netlink.QdiscDel(e.qdisc)
|
||||||
|
}
|
||||||
|
if e.objs != nil {
|
||||||
|
_ = e.objs.Close()
|
||||||
|
}
|
||||||
|
_ = os.Remove(filepath.Join(e.bpfPath, "tc_params_map"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EBpfTC) Lookup(_ netip.AddrPort) (socks5.Addr, error) {
|
||||||
|
return nil, fmt.Errorf("not supported")
|
||||||
|
}
|
|
@ -55,6 +55,7 @@ type General struct {
|
||||||
EnableProcess bool `json:"enable-process"`
|
EnableProcess bool `json:"enable-process"`
|
||||||
Tun Tun `json:"tun"`
|
Tun Tun `json:"tun"`
|
||||||
Sniffing bool `json:"sniffing"`
|
Sniffing bool `json:"sniffing"`
|
||||||
|
EBpf EBpf `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound config
|
// Inbound config
|
||||||
|
@ -190,6 +191,7 @@ type RawTun struct {
|
||||||
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
||||||
|
RedirectToTun []string `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
|
@ -222,6 +224,7 @@ type RawConfig struct {
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
DNS RawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Tun RawTun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
|
EBpf EBpf `yaml:"ebpf"`
|
||||||
IPTables IPTables `yaml:"iptables"`
|
IPTables IPTables `yaml:"iptables"`
|
||||||
Experimental Experimental `yaml:"experimental"`
|
Experimental Experimental `yaml:"experimental"`
|
||||||
Profile Profile `yaml:"profile"`
|
Profile Profile `yaml:"profile"`
|
||||||
|
@ -245,6 +248,12 @@ type RawSniffer struct {
|
||||||
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
|
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EBpf config
|
||||||
|
type EBpf struct {
|
||||||
|
RedirectToTun []string `yaml:"redirect-to-tun" json:"redirect-to-tun"`
|
||||||
|
AutoRedir []string `yaml:"auto-redir" json:"auto-redir"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
GroupsList = list.New()
|
GroupsList = list.New()
|
||||||
ProxiesList = list.New()
|
ProxiesList = list.New()
|
||||||
|
@ -415,7 +424,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
|
||||||
return &General{
|
return &General{
|
||||||
Inbound: Inbound{
|
Inbound: Inbound{
|
||||||
Port: cfg.Port,
|
Port: cfg.Port,
|
||||||
|
@ -442,6 +451,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
GeodataLoader: cfg.GeodataLoader,
|
GeodataLoader: cfg.GeodataLoader,
|
||||||
TCPConcurrent: cfg.TCPConcurrent,
|
TCPConcurrent: cfg.TCPConcurrent,
|
||||||
EnableProcess: cfg.EnableProcess,
|
EnableProcess: cfg.EnableProcess,
|
||||||
|
EBpf: cfg.EBpf,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
constant/ebpf.go
Normal file
20
constant/ebpf.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BpfFSPath = "/sys/fs/bpf/clash"
|
||||||
|
|
||||||
|
TcpAutoRedirPort = 't'<<8 | 'r'<<0
|
||||||
|
ClashTrafficMark = 'c'<<24 | 'l'<<16 | 't'<<8 | 'm'<<0
|
||||||
|
)
|
||||||
|
|
||||||
|
type EBpf interface {
|
||||||
|
Start() error
|
||||||
|
Close()
|
||||||
|
Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error)
|
||||||
|
}
|
|
@ -46,6 +46,13 @@ tun:
|
||||||
# auto-detect-interface: true # 自动识别interface
|
# auto-detect-interface: true # 自动识别interface
|
||||||
# auto-route: true # 配置
|
# auto-route: true # 配置
|
||||||
|
|
||||||
|
#ebpf配置
|
||||||
|
ebpf:
|
||||||
|
auto-redir: #redirect模式,仅支持TCP
|
||||||
|
- eth0
|
||||||
|
redirect-to-tun: #UDP+TCP,使用该功能请勿启用auto-route
|
||||||
|
- eth0
|
||||||
|
|
||||||
# 嗅探域名 可选配置
|
# 嗅探域名 可选配置
|
||||||
sniffer:
|
sniffer:
|
||||||
enable: false
|
enable: false
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -47,12 +47,13 @@ replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.28.1-
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
github.com/cheekybits/genny v1.0.0 // indirect
|
||||||
|
github.com/cilium/ebpf v0.9.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -16,10 +16,13 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
|
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
|
||||||
|
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/database64128/tfo-go v1.1.0 h1:VO0polyGNSAmr99nYw9GQeMz7ZOcQ/QbjlTwniHwfTQ=
|
github.com/database64128/tfo-go v1.1.0 h1:VO0polyGNSAmr99nYw9GQeMz7ZOcQ/QbjlTwniHwfTQ=
|
||||||
github.com/database64128/tfo-go v1.1.0/go.mod h1:95pOT8bnV3P2Lmu9upHNWFHz6dYGJ9cr7pnb0tGQAG8=
|
github.com/database64128/tfo-go v1.1.0/go.mod h1:95pOT8bnV3P2Lmu9upHNWFHz6dYGJ9cr7pnb0tGQAG8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -111,10 +114,14 @@ github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
|
@ -171,6 +178,7 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y=
|
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y=
|
||||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E=
|
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E=
|
||||||
|
@ -406,6 +414,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
|
|
@ -334,6 +334,8 @@ func updateGeneral(general *config.General, force bool) {
|
||||||
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
||||||
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||||
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||||
|
P.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||||
|
P.ReCreateRedirToTun(general.EBpf.RedirectToTun)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUsers(users []auth.AuthUser) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
|
86
listener/autoredir/tcp.go
Normal file
86
listener/autoredir/tcp.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package autoredir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
listener net.Listener
|
||||||
|
addr string
|
||||||
|
closed bool
|
||||||
|
lookupFunc func(netip.AddrPort) (socks5.Addr, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) TCPAddr() netip.AddrPort {
|
||||||
|
return l.listener.Addr().(*net.TCPAddr).AddrPort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) SetLookupFunc(lookupFunc func(netip.AddrPort) (socks5.Addr, error)) {
|
||||||
|
l.lookupFunc = lookupFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
|
if l.lookupFunc == nil {
|
||||||
|
log.Errorln("[Auto Redirect] lookup function is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := l.lookupFunc(conn.RemoteAddr().(*net.TCPAddr).AddrPort())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[Auto Redirect] %v", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
|
||||||
|
in <- inbound.NewSocket(target, conn, C.REDIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rl := &Listener{
|
||||||
|
listener: l,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if rl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go rl.handleRedir(c, in)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rl, nil
|
||||||
|
}
|
|
@ -2,8 +2,11 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/component/ebpf"
|
||||||
|
"github.com/Dreamacro/clash/listener/autoredir"
|
||||||
"github.com/Dreamacro/clash/listener/inner"
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -38,6 +41,9 @@ var (
|
||||||
mixedListener *mixed.Listener
|
mixedListener *mixed.Listener
|
||||||
mixedUDPLister *socks.UDPListener
|
mixedUDPLister *socks.UDPListener
|
||||||
tunStackListener ipstack.Stack
|
tunStackListener ipstack.Stack
|
||||||
|
tcProgram *ebpf.TcEBpfProgram
|
||||||
|
autoRedirListener *autoredir.Listener
|
||||||
|
autoRedirProgram *ebpf.TcEBpfProgram
|
||||||
|
|
||||||
// lock for recreate function
|
// lock for recreate function
|
||||||
socksMux sync.Mutex
|
socksMux sync.Mutex
|
||||||
|
@ -46,6 +52,8 @@ var (
|
||||||
tproxyMux sync.Mutex
|
tproxyMux sync.Mutex
|
||||||
mixedMux sync.Mutex
|
mixedMux sync.Mutex
|
||||||
tunMux sync.Mutex
|
tunMux sync.Mutex
|
||||||
|
autoRedirMux sync.Mutex
|
||||||
|
tcMux sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ports struct {
|
type Ports struct {
|
||||||
|
@ -356,6 +364,93 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||||
lastTunConf = tunConf
|
lastTunConf = tunConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- *inbound.PacketAdapter) {
|
||||||
|
autoRedirMux.Lock()
|
||||||
|
defer autoRedirMux.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if redirListener != nil {
|
||||||
|
_ = redirListener.Close()
|
||||||
|
redirListener = nil
|
||||||
|
}
|
||||||
|
if autoRedirProgram != nil {
|
||||||
|
autoRedirProgram.Close()
|
||||||
|
autoRedirProgram = nil
|
||||||
|
}
|
||||||
|
log.Errorln("Start auto redirect server error: %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
nicArr := ifaceNames
|
||||||
|
slices.Sort(nicArr)
|
||||||
|
nicArr = slices.Compact(nicArr)
|
||||||
|
|
||||||
|
if redirListener != nil && autoRedirProgram != nil {
|
||||||
|
_ = redirListener.Close()
|
||||||
|
autoRedirProgram.Close()
|
||||||
|
redirListener = nil
|
||||||
|
autoRedirProgram = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nicArr) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRouteInterfaceName, err := commons.GetAutoDetectInterface()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := genAddr("*", C.TcpAutoRedirPort, true)
|
||||||
|
|
||||||
|
autoRedirListener, err = autoredir.New(addr, tcpIn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autoRedirProgram, err = ebpf.NewRedirEBpfProgram(nicArr, autoRedirListener.TCPAddr().Port(), defaultRouteInterfaceName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autoRedirListener.SetLookupFunc(autoRedirProgram.Lookup)
|
||||||
|
|
||||||
|
log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReCreateRedirToTun(ifaceNames []string) {
|
||||||
|
tcMux.Lock()
|
||||||
|
defer tcMux.Unlock()
|
||||||
|
|
||||||
|
nicArr := ifaceNames
|
||||||
|
slices.Sort(nicArr)
|
||||||
|
nicArr = slices.Compact(nicArr)
|
||||||
|
|
||||||
|
if tcProgram != nil {
|
||||||
|
tcProgram.Close()
|
||||||
|
tcProgram = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nicArr) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastTunConf == nil || !lastTunConf.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
program, err := ebpf.NewTcEBpfProgram(nicArr, lastTunConf.Device)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Attached tc ebpf program error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tcProgram = program
|
||||||
|
|
||||||
|
log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs())
|
||||||
|
}
|
||||||
|
|
||||||
// GetPorts return the ports of proxy servers
|
// GetPorts return the ports of proxy servers
|
||||||
func GetPorts() *Ports {
|
func GetPorts() *Ports {
|
||||||
ports := &Ports{}
|
ports := &Ports{}
|
||||||
|
|
Loading…
Reference in a new issue