feat: support real process name traffic split (#6)

This commit is contained in:
mzz
2023-02-04 11:24:03 +08:00
committed by GitHub
parent 0793534f72
commit b117dfa7d5
13 changed files with 178 additions and 74 deletions

View File

@ -29,11 +29,19 @@ type _bpfPortRange struct {
PortEnd uint16
}
func (r _bpfPortRange) Encode() uint32 {
var b [4]byte
binary.BigEndian.PutUint16(b[:2], r.PortStart)
binary.BigEndian.PutUint16(b[2:], r.PortEnd)
return binary.BigEndian.Uint32(b[:])
type _bpfMatchSet struct {
// TODO: Need sync with C code.
Value [16]byte
Type uint8
Not bool
Outbound uint8
_ [1]byte
}
func (r _bpfPortRange) Encode() (b [16]byte) {
binary.LittleEndian.PutUint16(b[:2], r.PortStart)
binary.LittleEndian.PutUint16(b[2:], r.PortEnd)
return b
}
func (o *bpfObjects) newLpmMap(keys []_bpfLpmKey, values []uint32) (m *ebpf.Map, err error) {

View File

@ -69,6 +69,10 @@ func NewControlPlane(
if kernelVersion.Less(consts.BasicFeatureVersion) {
return nil, fmt.Errorf("your kernel version %v does not satisfy basic requirement; expect >=%v", c.kernelVersion.String(), consts.BasicFeatureVersion.String())
}
if len(wanInterface) > 0 && kernelVersion.Less(consts.CgSocketCookieFeatureVersion) {
return nil, fmt.Errorf("your kernel version %v does not support bind to WAN; expect >=%v; remove wan_interface in config file and try again", kernelVersion.String(),
consts.CgSocketCookieFeatureVersion.String())
}
// Allow the current process to lock memory for eBPF resources.
if err = rlimit.RemoveMemlock(); err != nil {
@ -83,7 +87,8 @@ func NewControlPlane(
var ProgramOptions ebpf.ProgramOptions
if log.IsLevelEnabled(logrus.TraceLevel) {
ProgramOptions = ebpf.ProgramOptions{
LogLevel: ebpf.LogLevelInstruction | ebpf.LogLevelStats,
LogLevel: ebpf.LogLevelBranch | ebpf.LogLevelStats,
//LogLevel: ebpf.LogLevelInstruction | ebpf.LogLevelStats,
}
}
@ -92,7 +97,7 @@ func NewControlPlane(
if len(lanInterface) > 0 && len(wanInterface) == 0 {
// Only bind LAN.
obj = &bpfObjectsLan{}
} else if len(wanInterface) == 0 && len(wanInterface) > 0 {
} else if len(lanInterface) == 0 && len(wanInterface) > 0 {
// Only bind to WAN.
// Trick. Replace the beams with rotten timbers.
obj = &bpfObjectsWan{}

View File

@ -164,10 +164,6 @@ func (c *ControlPlaneCore) BindLan(ifname string) error {
}
func (c *ControlPlaneCore) BindWan(ifname string) error {
if c.kernelVersion.Less(consts.BasicFeatureVersion) {
return fmt.Errorf("your kernel version %v does not support bind to WAN; expect >=%v; remove wan_interface in config file and try again", c.kernelVersion.String(), consts.CgGetPidFeatureVersion.String())
}
c.log.Infof("Bind to WAN: %v", ifname)
link, err := netlink.LinkByName(ifname)
if err != nil {

View File

@ -3,20 +3,17 @@
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org>
*/
#include <asm-generic/errno-base.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <stdbool.h>
#include "headers/if_ether_defs.h"
#include "headers/pkt_cls_defs.h"
#include "headers/socket_defs.h"
#include "headers/vmlinux.h"
#include <asm-generic/errno-base.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
// #define __DEBUG_ROUTING
// #define __PRINT_ROUTING_RESULT
@ -53,6 +50,8 @@
#define MAX_DST_MAPPING_NUM (65536 * 2)
#define MAX_SRC_PID_PNAME_MAPPING_NUM (65536)
#define IPV6_MAX_EXTENSIONS 4
#define MAX_ARG_LEN_TO_PROBE 192
#define MAX_ARG_SCANNER_BUFFER_SIZE (TASK_COMM_LEN * 4)
#define OUTBOUND_DIRECT 0
#define OUTBOUND_BLOCK 1
@ -229,6 +228,7 @@ struct port_range {
*/
struct match_set {
union {
/// NOTICE: MUST sync with component/control/bpf_utils.go.
__u32 __value; // Placeholder for bpf2go.
__u32 index;
@ -827,13 +827,13 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
/// TODO: BPF_MAP_UPDATE_BATCH ?
__u32 key = MatchType_L4Proto;
if ((ret = bpf_map_update_elem(&l4proto_ipversion_map, &key, &_l4proto_type,
BPF_ANY))) {
if (unlikely((ret = bpf_map_update_elem(&l4proto_ipversion_map, &key,
&_l4proto_type, BPF_ANY)))) {
return ret;
};
key = MatchType_IpVersion;
if ((ret = bpf_map_update_elem(&l4proto_ipversion_map, &key, &_ipversion_type,
BPF_ANY))) {
if (unlikely((ret = bpf_map_update_elem(&l4proto_ipversion_map, &key,
&_ipversion_type, BPF_ANY)))) {
return ret;
};
@ -849,11 +849,13 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
}
key = MatchType_SourcePort;
if ((ret = bpf_map_update_elem(&h_port_map, &key, &h_sport, BPF_ANY))) {
if (unlikely(
(ret = bpf_map_update_elem(&h_port_map, &key, &h_sport, BPF_ANY)))) {
return ret;
};
key = MatchType_Port;
if ((ret = bpf_map_update_elem(&h_port_map, &key, &h_dport, BPF_ANY))) {
if (unlikely(
(ret = bpf_map_update_elem(&h_port_map, &key, &h_dport, BPF_ANY)))) {
return ret;
};
@ -862,7 +864,7 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
if (h_dport == 53 && _l4proto_type == L4ProtoType_UDP) {
struct ip_port *upstream =
bpf_map_lookup_elem(&dns_upstream_map, &zero_key);
if (!upstream) {
if (unlikely(!upstream)) {
return -EFAULT;
}
h_dport = bpf_ntohs(upstream->port);
@ -875,21 +877,21 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
__builtin_memcpy(lpm_key_instance.data, daddr, IPV6_BYTE_LENGTH);
// bpf_printk("mac: %pI6", mac);
key = MatchType_IpSet;
if ((ret = bpf_map_update_elem(&lpm_key_map, &key, &lpm_key_instance,
BPF_ANY))) {
if (unlikely((ret = bpf_map_update_elem(&lpm_key_map, &key, &lpm_key_instance,
BPF_ANY)))) {
return ret;
};
__builtin_memcpy(lpm_key_instance.data, saddr, IPV6_BYTE_LENGTH);
key = MatchType_SourceIpSet;
if ((ret = bpf_map_update_elem(&lpm_key_map, &key, &lpm_key_instance,
BPF_ANY))) {
if (unlikely((ret = bpf_map_update_elem(&lpm_key_map, &key, &lpm_key_instance,
BPF_ANY)))) {
return ret;
};
if (!_is_wan) {
__builtin_memcpy(lpm_key_instance.data, mac, IPV6_BYTE_LENGTH);
key = MatchType_Mac;
if ((ret = bpf_map_update_elem(&lpm_key_map, &key, &lpm_key_instance,
BPF_ANY))) {
if (unlikely((ret = bpf_map_update_elem(&lpm_key_map, &key,
&lpm_key_instance, BPF_ANY)))) {
return ret;
};
}
@ -909,7 +911,7 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
for (__u32 i = 0; i < MAX_MATCH_SET_LEN; i++) {
__u32 k = i; // Clone to pass code checker.
match_set = bpf_map_lookup_elem(&routing_map, &k);
if (!match_set) {
if (unlikely(!match_set)) {
return -EFAULT;
}
if (bad_rule || good_subrule) {
@ -980,8 +982,8 @@ routing(const __u32 flag[6], const void *l4_hdr, const __be32 saddr[4],
if ((domain_routing->bitmap[i / 32] >> (i % 32)) & 1) {
good_subrule = true;
}
} else if (_is_wan && match_set->type == MatchType_ProcessName) {
if ((equal_ipv6_format(match_set->pname, _pname))) {
} else if (match_set->type == MatchType_ProcessName) {
if (_is_wan && equal_ipv6_format(match_set->pname, _pname)) {
good_subrule = true;
}
} else if (match_set->type == MatchType_Final) {
@ -1298,7 +1300,7 @@ static __always_inline bool pid_is_control_plane(struct __sk_buff *skb,
if (p) {
*p = NULL;
}
if ((skb->mark & 0x80) == 0x80) {
if ((skb->mark & 0x100) == 0x100) {
bpf_printk("No pid_pname found. But it should not happen");
/*
if (l4proto == IPPROTO_TCP) {
@ -1934,7 +1936,7 @@ int tproxy_wan_ingress(struct __sk_buff *skb) {
}
static int __always_inline update_map_elem_by_cookie(const __u64 cookie) {
if (!cookie) {
if (unlikely(!cookie)) {
bpf_printk("zero cookie");
return -EINVAL;
}
@ -1943,15 +1945,67 @@ static int __always_inline update_map_elem_by_cookie(const __u64 cookie) {
// Build value.
struct pid_pname val;
__builtin_memset(&val, 0, sizeof(struct pid_pname));
val.pid = bpf_get_current_pid_tgid() >> 32;
// struct task_struct *t = (void *)bpf_get_current_task();
if ((ret = bpf_get_current_comm(val.pname, sizeof(val.pname)))) {
return ret;
char buf[MAX_ARG_SCANNER_BUFFER_SIZE] = {0};
struct task_struct *current = (void *)bpf_get_current_task();
unsigned long arg_start = BPF_CORE_READ(current, mm, arg_start);
unsigned long arg_end = BPF_CORE_READ(current, mm, arg_end);
unsigned long arg_len = arg_end - arg_start;
if (arg_len > MAX_ARG_LEN_TO_PROBE) {
arg_len = MAX_ARG_LEN_TO_PROBE;
}
/**
For string like: /usr/lib/sddm/sddm-helper --socket /tmp/sddm-auth1
We extract "sddm-helper" from it.
*/
unsigned long loc, j;
unsigned long last_slash = -1;
#pragma unroll
for (loc = 0, j = 0; j < MAX_ARG_LEN_TO_PROBE;
++j, loc = ((loc + 1) & (MAX_ARG_SCANNER_BUFFER_SIZE - 1))) {
// volatile unsigned long k = j; // Cheat to unroll.
// if (arg_start + k >= arg_end) {
if (unlikely(arg_start + j >= arg_end)) {
break;
}
if (unlikely(loc == 0)) {
/// WANRING: Do NOT use bpf_core_read_user_str, it will bring terminator
/// 0.
// __builtin_memset(&buf, 0, MAX_ARG_SCANNER_BUFFER_SIZE);
unsigned long to_read = arg_end - (arg_start + j);
if (to_read >= MAX_ARG_SCANNER_BUFFER_SIZE) {
to_read = MAX_ARG_SCANNER_BUFFER_SIZE;
} else {
buf[to_read] = 0;
}
if ((ret = bpf_core_read_user(&buf, to_read, arg_start + j))) {
bpf_printk("failed to read process name: %d", ret);
return ret;
}
}
if (unlikely(buf[loc] == '/')) {
last_slash = j;
} else if (unlikely(buf[loc] == ' ' || buf[loc] == 0)) {
break;
}
}
++last_slash;
unsigned long length_cpy = j - last_slash;
if (length_cpy > TASK_COMM_LEN) {
length_cpy = TASK_COMM_LEN;
}
if ((ret = bpf_core_read_user(&val.pname, length_cpy,
arg_start + last_slash))) {
bpf_printk("failed to read process name: %d", ret);
return ret;
}
val.pid = BPF_CORE_READ(current, tgid);
// bpf_printk("a start_end: %lu %lu", arg_start, arg_end);
// bpf_printk("b start_end: %lu %lu", arg_start + last_slash, arg_start + j);
// Update map.
if ((ret =
bpf_map_update_elem(&cookie_pid_map, &cookie, &val, BPF_NOEXIST))) {
if (unlikely(ret = bpf_map_update_elem(&cookie_pid_map, &cookie, &val,
BPF_NOEXIST))) {
// bpf_printk("setup_mapping_from_sk: failed update map: %d", ret);
return ret;
}
@ -1970,7 +2024,7 @@ int tproxy_wan_cg_sock_create(struct bpf_sock *sk) {
SEC("cgroup/sock_release")
int tproxy_wan_cg_sock_release(struct bpf_sock *sk) {
__u64 cookie = bpf_get_socket_cookie(sk);
if (!cookie) {
if (unlikely(!cookie)) {
bpf_printk("zero cookie");
return 1;
}

View File

@ -6,6 +6,7 @@
package control
import (
"encoding/binary"
"fmt"
"github.com/cilium/ebpf"
"github.com/v2rayA/dae/common"
@ -14,7 +15,6 @@ import (
"github.com/v2rayA/dae/pkg/config_parser"
"net/netip"
"strconv"
"unsafe"
)
type DomainSet struct {
@ -27,7 +27,7 @@ type RoutingMatcherBuilder struct {
*routing.DefaultMatcherBuilder
outboundName2Id map[string]uint8
bpf *bpfObjects
rules []bpfMatchSet
rules []_bpfMatchSet
SimulatedLpmTries [][]netip.Prefix
SimulatedDomainSet []DomainSet
Final string
@ -74,7 +74,7 @@ func (b *RoutingMatcherBuilder) AddDomain(f *config_parser.Function, key string,
RuleIndex: len(b.rules),
Domains: values,
})
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Type: uint8(consts.MatchType_DomainSet),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
@ -94,12 +94,14 @@ func (b *RoutingMatcherBuilder) AddSourceMac(f *config_parser.Function, macAddrs
}
lpmTrieIndex := len(b.SimulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values)
b.rules = append(b.rules, bpfMatchSet{
set := _bpfMatchSet{
Value: [16]byte{},
Type: uint8(consts.MatchType_Mac),
Value: uint32(lpmTrieIndex),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
})
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
}
@ -109,12 +111,14 @@ func (b *RoutingMatcherBuilder) AddIp(f *config_parser.Function, values []netip.
}
lpmTrieIndex := len(b.SimulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values)
b.rules = append(b.rules, bpfMatchSet{
set := _bpfMatchSet{
Value: [16]byte{},
Type: uint8(consts.MatchType_IpSet),
Value: uint32(lpmTrieIndex),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
})
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
}
func (b *RoutingMatcherBuilder) AddPort(f *config_parser.Function, values [][2]uint16, _outbound string) {
@ -123,7 +127,7 @@ func (b *RoutingMatcherBuilder) AddPort(f *config_parser.Function, values [][2]u
if i == len(values)-1 {
outbound = _outbound
}
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Type: uint8(consts.MatchType_Port),
Value: _bpfPortRange{
PortStart: value[0],
@ -141,12 +145,14 @@ func (b *RoutingMatcherBuilder) AddSourceIp(f *config_parser.Function, values []
}
lpmTrieIndex := len(b.SimulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values)
b.rules = append(b.rules, bpfMatchSet{
set := _bpfMatchSet{
Value: [16]byte{},
Type: uint8(consts.MatchType_SourceIpSet),
Value: uint32(lpmTrieIndex),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
})
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
}
func (b *RoutingMatcherBuilder) AddSourcePort(f *config_parser.Function, values [][2]uint16, _outbound string) {
@ -155,7 +161,7 @@ func (b *RoutingMatcherBuilder) AddSourcePort(f *config_parser.Function, values
if i == len(values)-1 {
outbound = _outbound
}
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Type: uint8(consts.MatchType_SourcePort),
Value: _bpfPortRange{
PortStart: value[0],
@ -171,9 +177,9 @@ func (b *RoutingMatcherBuilder) AddL4Proto(f *config_parser.Function, values con
if b.err != nil {
return
}
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Value: [16]byte{byte(values)},
Type: uint8(consts.MatchType_L4Proto),
Value: uint32(values),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
})
@ -183,9 +189,9 @@ func (b *RoutingMatcherBuilder) AddIpVersion(f *config_parser.Function, values c
if b.err != nil {
return
}
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Value: [16]byte{byte(values)},
Type: uint8(consts.MatchType_IpVersion),
Value: uint32(values),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
})
@ -197,12 +203,12 @@ func (b *RoutingMatcherBuilder) AddProcessName(f *config_parser.Function, values
if i == len(values)-1 {
outbound = _outbound
}
matchSet := bpfMatchSet{
matchSet := _bpfMatchSet{
Type: uint8(consts.MatchType_ProcessName),
Not: f.Not,
Outbound: b.OutboundToId(outbound),
}
copy((*(*[16]byte)(unsafe.Pointer(&matchSet.Value)))[:], value[:])
copy(matchSet.Value[:], value[:])
b.rules = append(b.rules, matchSet)
}
}
@ -212,7 +218,7 @@ func (b *RoutingMatcherBuilder) AddFinal(outbound string) {
return
}
b.Final = outbound
b.rules = append(b.rules, bpfMatchSet{
b.rules = append(b.rules, _bpfMatchSet{
Type: uint8(consts.MatchType_Final),
Outbound: b.OutboundToId(outbound),
})

View File

@ -106,7 +106,7 @@ func init() {
func SoMarkControl(c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
//TODO: force to set 0xff. any chances to customize this value?
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, fwmarkIoctl, 0x80)
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, fwmarkIoctl, 0x100)
if err != nil {
return
}