mirror of
https://github.com/daeuniverse/dae.git
synced 2025-01-07 05:51:20 +07:00
307 lines
8.5 KiB
Go
307 lines
8.5 KiB
Go
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io>
|
|
*/
|
|
|
|
package control
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/cilium/ebpf"
|
|
ciliumLink "github.com/cilium/ebpf/link"
|
|
"github.com/safchain/ethtool"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/v2rayA/dae/common/consts"
|
|
internal "github.com/v2rayA/dae/pkg/ebpf_internal"
|
|
"github.com/vishvananda/netlink"
|
|
"golang.org/x/sys/unix"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type ControlPlaneCore struct {
|
|
log *logrus.Logger
|
|
deferFuncs []func() error
|
|
bpf *bpfObjects
|
|
|
|
kernelVersion *internal.Version
|
|
}
|
|
|
|
func (c *ControlPlaneCore) Close() (err error) {
|
|
// Invoke defer funcs in reverse order.
|
|
for i := len(c.deferFuncs) - 1; i >= 0; i-- {
|
|
if e := c.deferFuncs[i](); e != nil {
|
|
// Combine errors.
|
|
if err != nil {
|
|
err = fmt.Errorf("%w; %v", err, e)
|
|
} else {
|
|
err = e
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func getIfParamsFromLink(link netlink.Link) (ifParams bpfIfParams, err error) {
|
|
// Get link offload features.
|
|
et, err := ethtool.NewEthtool()
|
|
if err != nil {
|
|
return bpfIfParams{}, err
|
|
}
|
|
defer et.Close()
|
|
features, err := et.Features(link.Attrs().Name)
|
|
if err != nil {
|
|
return bpfIfParams{}, err
|
|
}
|
|
if features["tx-checksum-ip-generic"] {
|
|
ifParams.TxL4CksmIp4Offload = true
|
|
ifParams.TxL4CksmIp6Offload = true
|
|
}
|
|
if features["tx-checksum-ipv4"] {
|
|
ifParams.TxL4CksmIp4Offload = true
|
|
}
|
|
if features["tx-checksum-ipv6"] {
|
|
ifParams.TxL4CksmIp6Offload = true
|
|
}
|
|
if features["rx-checksum"] {
|
|
ifParams.RxCksmOffload = true
|
|
}
|
|
switch {
|
|
case regexp.MustCompile(`^docker\d+$`).MatchString(link.Attrs().Name):
|
|
ifParams.UseNonstandardOffloadAlgorithm = true
|
|
default:
|
|
}
|
|
return ifParams, nil
|
|
}
|
|
|
|
func (c *ControlPlaneCore) addQdisc(ifname string) error {
|
|
link, err := netlink.LinkByName(ifname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
qdisc := &netlink.GenericQdisc{
|
|
QdiscAttrs: netlink.QdiscAttrs{
|
|
LinkIndex: link.Attrs().Index,
|
|
Handle: netlink.MakeHandle(0xffff, 0),
|
|
Parent: netlink.HANDLE_CLSACT,
|
|
},
|
|
QdiscType: "clsact",
|
|
}
|
|
if err := netlink.QdiscAdd(qdisc); err != nil {
|
|
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ControlPlaneCore) delQdisc(ifname string) error {
|
|
link, err := netlink.LinkByName(ifname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
qdisc := &netlink.GenericQdisc{
|
|
QdiscAttrs: netlink.QdiscAttrs{
|
|
LinkIndex: link.Attrs().Index,
|
|
Handle: netlink.MakeHandle(0xffff, 0),
|
|
Parent: netlink.HANDLE_CLSACT,
|
|
},
|
|
QdiscType: "clsact",
|
|
}
|
|
if err := netlink.QdiscDel(qdisc); err != nil {
|
|
if !os.IsExist(err) {
|
|
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ControlPlaneCore) setupRoutingPolicy() (err error) {
|
|
/// Insert ip rule / ip route.
|
|
// TODO: Refactor me with netlink.
|
|
var output []byte
|
|
cleanFunc := func() error {
|
|
return exec.Command("sh", "-c", `
|
|
ip rule del fwmark 0x80000000/0x80000000 table 2023
|
|
ip route del local default dev lo table 2023
|
|
ip -6 rule del fwmark 0x80000000/0x80000000 table 2023
|
|
ip -6 route del local default dev lo table 2023
|
|
`).Run()
|
|
}
|
|
tryAgain:
|
|
if output, err = exec.Command("sh", "-c", `
|
|
ip rule add fwmark 0x80000000/0x80000000 table 2023
|
|
ip route add local default dev lo table 2023
|
|
ip -6 rule add fwmark 0x80000000/0x80000000 table 2023
|
|
ip -6 route add local default dev lo table 2023
|
|
`).CombinedOutput(); err != nil {
|
|
if strings.Contains(string(output), "File exists") {
|
|
_ = cleanFunc()
|
|
goto tryAgain
|
|
}
|
|
return fmt.Errorf("%w: %v", err, string(bytes.TrimSpace(output)))
|
|
}
|
|
c.deferFuncs = append(c.deferFuncs, cleanFunc)
|
|
return nil
|
|
}
|
|
|
|
func (c *ControlPlaneCore) bindLan(ifname string) error {
|
|
c.log.Infof("Bind to LAN: %v", ifname)
|
|
link, err := netlink.LinkByName(ifname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
/// Insert an elem into IfindexParamsMap.
|
|
ifParams, err := getIfParamsFromLink(link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = ifParams.CheckVersionRequirement(c.kernelVersion); err != nil {
|
|
return err
|
|
}
|
|
if err := c.bpf.IfindexParamsMap.Update(uint32(link.Attrs().Index), ifParams, ebpf.UpdateAny); err != nil {
|
|
return fmt.Errorf("update IfindexIpsMap: %w", err)
|
|
}
|
|
|
|
// Insert filters.
|
|
filterIngress := &netlink.BpfFilter{
|
|
FilterAttrs: netlink.FilterAttrs{
|
|
LinkIndex: link.Attrs().Index,
|
|
Parent: netlink.HANDLE_MIN_INGRESS,
|
|
Handle: netlink.MakeHandle(0x2023, 2),
|
|
Protocol: unix.ETH_P_ALL,
|
|
// Priority should be behind of WAN's
|
|
Priority: 2,
|
|
},
|
|
Fd: c.bpf.bpfPrograms.TproxyLanIngress.FD(),
|
|
Name: consts.AppName + "_lan_ingress",
|
|
DirectAction: true,
|
|
}
|
|
// Remove and add.
|
|
_ = netlink.FilterDel(filterIngress)
|
|
if err := netlink.FilterAdd(filterIngress); err != nil {
|
|
return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
|
|
}
|
|
c.deferFuncs = append(c.deferFuncs, func() error {
|
|
if err := netlink.FilterDel(filterIngress); err != nil {
|
|
return fmt.Errorf("FilterDel(%v:%v): %w", ifname, filterIngress.Name, err)
|
|
}
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (c *ControlPlaneCore) setupSkPidMonitor() error {
|
|
/// Set-up SrcPidMapper.
|
|
/// Attach programs to support pname routing.
|
|
// Get the first-mounted cgroupv2 path.
|
|
cgroupPath, err := detectCgroupPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Bind cg programs
|
|
type cgProg struct {
|
|
Name string
|
|
Prog *ebpf.Program
|
|
Attach ebpf.AttachType
|
|
}
|
|
cgProgs := []cgProg{
|
|
{Prog: c.bpf.TproxyWanCgSockCreate, Attach: ebpf.AttachCGroupInetSockCreate},
|
|
{Prog: c.bpf.TproxyWanCgSockRelease, Attach: ebpf.AttachCgroupInetSockRelease},
|
|
//{Prog: c.bpf.TproxyWanCgConnect4, Attach: ebpf.AttachCGroupInet4Connect},
|
|
//{Prog: c.bpf.TproxyWanCgConnect6, Attach: ebpf.AttachCGroupInet6Connect},
|
|
//{Prog: c.bpf.TproxyWanCgSendmsg4, Attach: ebpf.AttachCGroupUDP4Sendmsg},
|
|
//{Prog: c.bpf.TproxyWanCgSendmsg6, Attach: ebpf.AttachCGroupUDP6Sendmsg},
|
|
}
|
|
for _, prog := range cgProgs {
|
|
attached, err := ciliumLink.AttachCgroup(ciliumLink.CgroupOptions{
|
|
Path: cgroupPath,
|
|
Attach: prog.Attach,
|
|
Program: prog.Prog,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("AttachTracing: %v: %w", prog.Prog.String(), err)
|
|
}
|
|
c.deferFuncs = append(c.deferFuncs, func() error {
|
|
if err := attached.Close(); err != nil {
|
|
return fmt.Errorf("inet6Bind.Close(): %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
func (c *ControlPlaneCore) bindWan(ifname string) error {
|
|
c.log.Infof("Bind to WAN: %v", ifname)
|
|
link, err := netlink.LinkByName(ifname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if link.Attrs().Index == consts.LoopbackIfIndex {
|
|
return fmt.Errorf("cannot bind to loopback interface")
|
|
}
|
|
/// Insert an elem into IfindexParamsMap.
|
|
ifParams, err := getIfParamsFromLink(link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = ifParams.CheckVersionRequirement(c.kernelVersion); err != nil {
|
|
return err
|
|
}
|
|
if err := c.bpf.IfindexParamsMap.Update(uint32(link.Attrs().Index), ifParams, ebpf.UpdateAny); err != nil {
|
|
return fmt.Errorf("update IfindexIpsMap: %w", err)
|
|
}
|
|
|
|
/// Set-up WAN ingress/egress TC programs.
|
|
// Insert TC filters
|
|
filterEgress := &netlink.BpfFilter{
|
|
FilterAttrs: netlink.FilterAttrs{
|
|
LinkIndex: link.Attrs().Index,
|
|
Parent: netlink.HANDLE_MIN_EGRESS,
|
|
Handle: netlink.MakeHandle(0x2023, 1),
|
|
Protocol: unix.ETH_P_ALL,
|
|
Priority: 1,
|
|
},
|
|
Fd: c.bpf.bpfPrograms.TproxyWanEgress.FD(),
|
|
Name: consts.AppName + "_wan_egress",
|
|
DirectAction: true,
|
|
}
|
|
// Remove and add.
|
|
_ = netlink.FilterDel(filterEgress)
|
|
if err := netlink.FilterAdd(filterEgress); err != nil {
|
|
return fmt.Errorf("cannot attach ebpf object to filter egress: %w", err)
|
|
}
|
|
c.deferFuncs = append(c.deferFuncs, func() error {
|
|
if err := netlink.FilterDel(filterEgress); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("FilterDel(%v:%v): %w", ifname, filterEgress.Name, err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
filterIngress := &netlink.BpfFilter{
|
|
FilterAttrs: netlink.FilterAttrs{
|
|
LinkIndex: link.Attrs().Index,
|
|
Parent: netlink.HANDLE_MIN_INGRESS,
|
|
Handle: netlink.MakeHandle(0x2023, 1),
|
|
Protocol: unix.ETH_P_ALL,
|
|
Priority: 1,
|
|
},
|
|
Fd: c.bpf.bpfPrograms.TproxyWanIngress.FD(),
|
|
Name: consts.AppName + "_wan_ingress",
|
|
DirectAction: true,
|
|
}
|
|
// Remove and add.
|
|
_ = netlink.FilterDel(filterIngress)
|
|
if err := netlink.FilterAdd(filterIngress); err != nil {
|
|
return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
|
|
}
|
|
c.deferFuncs = append(c.deferFuncs, func() error {
|
|
if err := netlink.FilterDel(filterIngress); err != nil {
|
|
return fmt.Errorf("FilterDel(%v:%v): %w", ifname, filterIngress.Name, err)
|
|
}
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|