2023-01-23 18:54:21 +07:00
/ *
* SPDX - License - Identifier : AGPL - 3.0 - only
2024-01-04 16:28:16 +07:00
* Copyright ( c ) 2022 - 2024 , daeuniverse Organization < dae @ v2raya . org >
2023-01-23 18:54:21 +07:00
* /
package control
import (
2023-02-02 20:22:18 +07:00
"bufio"
2023-01-29 06:31:52 +07:00
"encoding/binary"
2023-02-02 20:22:18 +07:00
"errors"
2023-01-28 12:27:54 +07:00
"fmt"
2023-01-23 18:54:21 +07:00
"net/netip"
2023-02-01 20:15:58 +07:00
"os"
2023-02-08 14:38:13 +07:00
"path/filepath"
2023-01-28 12:27:54 +07:00
"reflect"
2023-02-01 20:15:58 +07:00
"strings"
2023-02-04 17:18:06 +07:00
"sync"
2023-04-23 12:27:29 +07:00
"github.com/cilium/ebpf"
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/common/consts"
internal "github.com/daeuniverse/dae/pkg/ebpf_internal"
"github.com/sirupsen/logrus"
2023-01-23 18:54:21 +07:00
)
2023-02-13 02:41:59 +07:00
type _bpfTuples struct {
Sip [ 4 ] uint32
Dip [ 4 ] uint32
Sport uint16
Dport uint16
L4proto uint8
_ [ 3 ] byte
}
2023-01-28 10:47:02 +07:00
type _bpfLpmKey struct {
2023-01-23 18:54:21 +07:00
PrefixLen uint32
Data [ 4 ] uint32
}
2023-01-29 06:31:52 +07:00
type _bpfPortRange struct {
PortStart uint16
PortEnd uint16
}
2023-02-04 10:24:03 +07:00
func ( r _bpfPortRange ) Encode ( ) ( b [ 16 ] byte ) {
binary . LittleEndian . PutUint16 ( b [ : 2 ] , r . PortStart )
binary . LittleEndian . PutUint16 ( b [ 2 : ] , r . PortEnd )
return b
2023-01-29 06:31:52 +07:00
}
2023-02-18 17:27:28 +07:00
func ParsePortRange ( b [ ] byte ) ( portStart , portEnd uint16 ) {
portStart = binary . LittleEndian . Uint16 ( b [ : 2 ] )
portEnd = binary . LittleEndian . Uint16 ( b [ 2 : ] )
return portStart , portEnd
}
2023-01-28 10:47:02 +07:00
func ( o * bpfObjects ) newLpmMap ( keys [ ] _bpfLpmKey , values [ ] uint32 ) ( m * ebpf . Map , err error ) {
2023-01-24 15:27:19 +07:00
m , err = ebpf . NewMap ( & ebpf . MapSpec {
Type : ebpf . LPMTrie ,
Flags : o . UnusedLpmType . Flags ( ) ,
MaxEntries : o . UnusedLpmType . MaxEntries ( ) ,
KeySize : o . UnusedLpmType . KeySize ( ) ,
ValueSize : o . UnusedLpmType . ValueSize ( ) ,
} )
2023-01-23 18:54:21 +07:00
if err != nil {
return nil , err
}
2023-02-09 11:26:44 +07:00
if _ , err = BpfMapBatchUpdate ( m , keys , values , & ebpf . BatchOptions {
2023-01-23 18:54:21 +07:00
ElemFlags : uint64 ( ebpf . UpdateAny ) ,
} ) ; err != nil {
return nil , err
}
return m , nil
}
2023-01-28 10:47:02 +07:00
func cidrToBpfLpmKey ( prefix netip . Prefix ) _bpfLpmKey {
2023-01-23 18:54:21 +07:00
bits := prefix . Bits ( )
if prefix . Addr ( ) . Is4 ( ) {
bits += 96
}
2023-01-24 15:27:19 +07:00
ip := prefix . Addr ( ) . As16 ( )
2023-01-28 10:47:02 +07:00
return _bpfLpmKey {
2023-01-23 18:54:21 +07:00
PrefixLen : uint32 ( bits ) ,
Data : common . Ipv6ByteSliceToUint32Array ( ip [ : ] ) ,
}
}
2023-01-28 12:27:54 +07:00
2023-02-04 17:18:06 +07:00
var (
CheckBatchUpdateFeatureOnce sync . Once
SimulateBatchUpdate bool
2023-02-04 17:27:13 +07:00
SimulateBatchUpdateLpmTrie bool
2023-02-04 17:18:06 +07:00
)
2023-02-09 11:26:44 +07:00
func BpfMapBatchUpdate ( m * ebpf . Map , keys interface { } , values interface { } , opts * ebpf . BatchOptions ) ( n int , err error ) {
2023-02-04 17:18:06 +07:00
CheckBatchUpdateFeatureOnce . Do ( func ( ) {
version , e := internal . KernelVersion ( )
2023-02-04 17:27:13 +07:00
if e != nil {
2023-02-04 17:18:06 +07:00
SimulateBatchUpdate = true
2023-02-04 17:27:13 +07:00
SimulateBatchUpdateLpmTrie = true
return
2023-02-04 17:18:06 +07:00
}
2023-02-04 17:27:13 +07:00
if version . Less ( consts . UserspaceBatchUpdateFeatureVersion ) {
2023-02-04 17:18:06 +07:00
SimulateBatchUpdate = true
}
2023-02-04 17:27:13 +07:00
if version . Less ( consts . UserspaceBatchUpdateLpmTrieFeatureVersion ) {
SimulateBatchUpdateLpmTrie = true
}
2023-02-04 17:18:06 +07:00
} )
2023-02-04 17:27:13 +07:00
simulate := SimulateBatchUpdate
if m . Type ( ) == ebpf . LPMTrie {
simulate = SimulateBatchUpdateLpmTrie
}
if ! simulate {
2023-02-09 11:26:44 +07:00
// Genuine BpfMapBatchUpdate
2023-01-28 12:27:54 +07:00
return m . BatchUpdate ( keys , values , opts )
2023-02-04 17:27:13 +07:00
}
// Simulate
vKeys := reflect . ValueOf ( keys )
if vKeys . Kind ( ) != reflect . Slice {
return 0 , fmt . Errorf ( "keys must be slice" )
}
vVals := reflect . ValueOf ( values )
if vVals . Kind ( ) != reflect . Slice {
return 0 , fmt . Errorf ( "values must be slice" )
}
length := vKeys . Len ( )
if vVals . Len ( ) != length {
return 0 , fmt . Errorf ( "keys and values must have same length" )
}
2023-01-28 12:27:54 +07:00
2023-02-04 17:27:13 +07:00
for i := 0 ; i < length ; i ++ {
vKey := vKeys . Index ( i )
vVal := vVals . Index ( i )
if err = m . Update ( vKey . Interface ( ) , vVal . Interface ( ) , ebpf . MapUpdateFlags ( opts . ElemFlags ) ) ; err != nil {
return i , err
2023-01-28 12:27:54 +07:00
}
}
2023-02-04 17:27:13 +07:00
return vKeys . Len ( ) , nil
2023-05-30 21:10:32 +07:00
}
// BpfMapBatchDelete deletes keys and ignores ErrKeyNotExist.
func BpfMapBatchDelete ( m * ebpf . Map , keys interface { } ) ( n int , err error ) {
// Simulate
vKeys := reflect . ValueOf ( keys )
if vKeys . Kind ( ) != reflect . Slice {
return 0 , fmt . Errorf ( "keys must be slice" )
}
length := vKeys . Len ( )
for i := 0 ; i < length ; i ++ {
vKey := vKeys . Index ( i )
if err = m . Delete ( vKey . Interface ( ) ) ; err != nil && ! errors . Is ( err , ebpf . ErrKeyNotExist ) {
return i , err
}
}
return vKeys . Len ( ) , nil
2023-01-28 12:27:54 +07:00
}
2023-02-01 00:34:50 +07:00
2023-02-02 20:22:18 +07:00
// detectCgroupPath returns the first-found mount point of type cgroup2
// and stores it in the cgroupPath global variable.
// Copied from https://github.com/cilium/ebpf/blob/v0.10.0/examples/cgroup_skb/main.go
func detectCgroupPath ( ) ( string , error ) {
f , err := os . Open ( "/proc/mounts" )
if err != nil {
return "" , err
}
defer f . Close ( )
scanner := bufio . NewScanner ( f )
for scanner . Scan ( ) {
// example fields: cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime 0 0
fields := strings . Split ( scanner . Text ( ) , " " )
if len ( fields ) >= 3 && fields [ 2 ] == "cgroup2" {
return fields [ 1 ] , nil
}
}
return "" , errors . New ( "cgroup2 not mounted" )
}
2023-02-04 10:38:01 +07:00
func ( p bpfIfParams ) CheckVersionRequirement ( version * internal . Version ) ( err error ) {
if ! p . TxL4CksmIp4Offload ||
! p . TxL4CksmIp6Offload {
// Need calc checksum on CPU. And need BPF_F_ADJ_ROOM_NO_CSUM_RESET.
if version . Less ( consts . ChecksumFeatureVersion ) {
return fmt . Errorf ( "your NIC does not support checksum offload and your kernel version %v does not support related BPF features; expect >=%v; upgrade your kernel and try again" , version . String ( ) ,
consts . ChecksumFeatureVersion . String ( ) )
}
}
return nil
}
2023-02-08 14:38:13 +07:00
type loadBpfOptions struct {
2024-01-01 16:20:28 +07:00
PinPath string
BigEndianTproxyPort uint32
CollectionOptions * ebpf . CollectionOptions
}
func loadBpfObjectsWithConstants ( obj interface { } , opts * ebpf . CollectionOptions , constants map [ string ] interface { } ) error {
spec , err := loadBpf ( )
if err != nil {
return err
}
if err := spec . RewriteConstants ( constants ) ; err != nil {
return err
}
return spec . LoadAndAssign ( obj , opts )
2023-02-08 14:38:13 +07:00
}
2023-02-27 08:56:00 +07:00
func fullLoadBpfObjects (
2023-02-08 14:38:13 +07:00
log * logrus . Logger ,
bpf * bpfObjects ,
opts * loadBpfOptions ,
) ( err error ) {
retryLoadBpf :
2024-03-01 17:27:02 +07:00
netnsID , err := GetDaeNetns ( ) . NetnsID ( )
if err != nil {
return fmt . Errorf ( "failed to get netns id: %w" , err )
}
2024-01-01 16:20:28 +07:00
constants := map [ string ] interface { } {
"PARAM" : struct {
tproxyPort uint32
controlPlanePid uint32
2024-03-01 17:27:02 +07:00
dae0Ifindex uint32
dae0NetnsId uint32
dae0peerMac [ 6 ] byte
padding [ 2 ] byte
2024-01-01 16:20:28 +07:00
} {
tproxyPort : uint32 ( opts . BigEndianTproxyPort ) ,
controlPlanePid : uint32 ( os . Getpid ( ) ) ,
2024-03-01 17:27:02 +07:00
dae0Ifindex : uint32 ( GetDaeNetns ( ) . Dae0 ( ) . Attrs ( ) . Index ) ,
dae0NetnsId : uint32 ( netnsID ) ,
dae0peerMac : [ 6 ] byte ( GetDaeNetns ( ) . Dae0Peer ( ) . Attrs ( ) . HardwareAddr ) ,
2024-01-01 16:20:28 +07:00
} ,
}
if err = loadBpfObjectsWithConstants ( bpf , opts . CollectionOptions , constants ) ; err != nil {
2023-02-08 14:38:13 +07:00
if errors . Is ( err , ebpf . ErrMapIncompatible ) {
// Map property is incompatible. Remove the old map and try again.
prefix := "use pinned map "
_ , after , ok := strings . Cut ( err . Error ( ) , prefix )
if ! ok {
return fmt . Errorf ( "loading objects: bad format: %w" , err )
}
mapName , _ , _ := strings . Cut ( after , ":" )
_ = os . Remove ( filepath . Join ( opts . PinPath , mapName ) )
log . Infof ( "Incompatible new map format with existing map %v detected; removed the old one." , mapName )
goto retryLoadBpf
}
// Get detailed log from ebpf.internal.(*VerifierError)
if log . Level == logrus . FatalLevel {
if v := reflect . Indirect ( reflect . ValueOf ( errors . Unwrap ( errors . Unwrap ( err ) ) ) ) ; v . Kind ( ) == reflect . Struct {
if _log := v . FieldByName ( "Log" ) ; _log . IsValid ( ) {
if strSlice , ok := _log . Interface ( ) . ( [ ] string ) ; ok {
log . Fatalln ( strings . Join ( strSlice , "\n" ) )
}
}
}
}
if strings . Contains ( err . Error ( ) , "no BTF found for kernel version" ) {
2023-03-15 17:15:07 +07:00
err = fmt . Errorf ( "%w: you should re-compile linux kernel with BTF configurations; see docs for more information" , err )
} else if strings . Contains ( err . Error ( ) , "unknown func bpf_trace_printk" ) {
2023-08-06 00:43:17 +07:00
err = fmt . Errorf ( ` %w: please try to compile dae without bpf_printk" ` , err )
2023-03-21 15:07:56 +07:00
} else if strings . Contains ( err . Error ( ) , "unknown func bpf_probe_read" ) {
2023-03-21 15:11:11 +07:00
err = fmt . Errorf ( ` %w: please re-compile linux kernel with CONFIG_BPF_EVENTS=y and CONFIG_KPROBE_EVENTS=y" ` , err )
2023-02-08 14:38:13 +07:00
}
return err
}
return nil
}