diff --git a/linux-tool/glue/glue.go b/linux-tool/glue/glue.go new file mode 100644 index 00000000..e3112d57 --- /dev/null +++ b/linux-tool/glue/glue.go @@ -0,0 +1,121 @@ +package glue + +import ( + "encoding/binary" + "log" + "syscall" + "unsafe" + + "github.com/celzero/firestack/intra" + "github.com/celzero/firestack/intra/backend" +) + +type MyBridge struct { + PIDCSV string + TIDCSV string +} + +// implement intra.log.Console +func (bridge *MyBridge) Log(level int32, msg string) { + log.Printf("log level %d, %s", level, msg) +} + +// implement intra.backend.Controller +func (bridge *MyBridge) Bind4(who, addrport string, fd int) {} +func (bridge *MyBridge) Bind6(who, addrport string, fd int) {} +func (bridge *MyBridge) Protect(who string, fd int) {} + +// implement intra.SocketListener +func (bridge *MyBridge) Preflow(protocol, uid int32, src, dst, domains string) *intra.PreMark { + return &intra.PreMark{ + UID: "-1", + IsUidSelf: false, + } +} +func (bridge *MyBridge) Flow(protocol, uid int32, src, dst, origdsts, domains, probableDomains, blocklists string) *intra.Mark { + return &intra.Mark{ + PIDCSV: bridge.PIDCSV, + CID: "0", + UID: "-1", + } +} +func (bridge *MyBridge) Inflow(protocol, uid int32, src, dst string) *intra.Mark { + return &intra.Mark{ + PIDCSV: bridge.PIDCSV, + CID: "0", + UID: "-1", + } +} +func (bridge *MyBridge) OnSocketClosed(*intra.SocketSummary) {} + +// implement intra.backend.ResolverListener +func (bridge *MyBridge) OnDNSAdded(id string) {} +func (bridge *MyBridge) OnDNSRemoved(id string) {} +func (bridge *MyBridge) OnDNSStopped() {} + +// implement intra.backend.DNSListener +func (bridge *MyBridge) OnQuery(uid, domain string, qtyp int) *backend.DNSOpts { + return &backend.DNSOpts{ + PIDCSV: bridge.PIDCSV, + IPCSV: "", + TIDCSV: bridge.TIDCSV, + TIDSECCSV: "", + NOBLOCK: false, + } +} +func (bridge *MyBridge) OnResponse(*backend.DNSSummary) {} + +// implement intra.backend.ServerListener +func (bridge *MyBridge) SvcRoute(sid, pid, network, sipport, dipport string) *backend.Tab { + return &backend.Tab{ + CID: "0", + Block: false, + } +} +func (bridge *MyBridge) OnSvcComplete(*backend.ServerSummary) {} + +// implement intra.backend.ProxyListener +func (bridge *MyBridge) OnProxyAdded(id string) {} +func (bridge *MyBridge) OnProxyRemoved(id string) {} +func (bridge *MyBridge) OnProxyStopped(id string) {} +func (bridge *MyBridge) OnProxiesStopped() {} + +func Ioctl(fd int, req uint64, data []byte) (int, error) { + p := unsafe.Pointer(&data[0]) + r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(p)) + if errno == 0 { + return int(r1), nil + } else { + return int(r1), errno + } +} +func OpenTUN(name string) (int, error) { + fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 600) + if err != nil { + return fd, err + } + var req [64]byte + copy(req[:15], []byte(name)) + flags := uint16(syscall.IFF_TUN | syscall.IFF_NO_PI) + binary.NativeEndian.PutUint16(req[16:], flags) + _, err = Ioctl(fd, syscall.TUNSETIFF, req[:]) + if err != nil { + defer syscall.Close(fd) + return -1, err + } + return fd, nil +} +func GenFDCmsg(fds []uint32) []byte { + cmsgLen := 16 + len(fds)*4 + cmsg := make([]byte, cmsgLen) + binary.NativeEndian.PutUint64(cmsg[:], uint64(cmsgLen)) + binary.NativeEndian.PutUint32(cmsg[8:], syscall.SOL_SOCKET) + binary.NativeEndian.PutUint32(cmsg[12:], syscall.SCM_RIGHTS) + + offset := 16 + for _, v := range fds { + binary.NativeEndian.PutUint32(cmsg[offset:], v) + offset += 4 + } + return cmsg +} diff --git a/linux-tool/toy/main.go b/linux-tool/toy/main.go new file mode 100644 index 00000000..8ee3cd71 --- /dev/null +++ b/linux-tool/toy/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "encoding/binary" + "flag" + "fmt" + "net" + "os" + "os/exec" + "os/signal" + "syscall" + + "github.com/celzero/firestack/intra" + "github.com/celzero/firestack/intra/backend" + "github.com/celzero/firestack/intra/settings" + "github.com/celzero/firestack/linux-tool/glue" +) + +func p(e error) { + if e != nil { + panic(e) + } +} +func sendTUN(name string) { + sock := int(3) + defer syscall.Close(sock) + syscall.SetNonblock(sock, true) + tun, err := glue.OpenTUN(name) + p(err) + defer syscall.Close(tun) + + ifInfo, err := net.InterfaceByName(name) + p(err) + var msg [4]byte + binary.NativeEndian.PutUint32(msg[:], uint32(ifInfo.MTU)) + cmsg := glue.GenFDCmsg([]uint32{uint32(tun)}) + err = syscall.Sendmsg(sock, msg[:], cmsg, nil, 0) + p(err) +} +func demo(targetPid int, tunName string, fakeDNS string) { + pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0) + p(err) + defer func() { + syscall.Close(pair[0]) + syscall.Close(pair[1]) + }() + syscall.SetNonblock(pair[0], true) + + elfPath, err := os.Executable() + p(err) + c1 := exec.Command("/usr/bin/nsenter", "--target", fmt.Sprintf("%d", targetPid), "--user", "--net", + "--preserve-credentials", "--keep-caps", + elfPath, "-mode", "sendfd", "-tun", tunName) + c1.ExtraFiles = []*os.File{os.NewFile(uintptr(pair[1]), "")} + fmt.Println("starting child...") + output, err := c1.CombinedOutput() + if len(output) > 0 { + fmt.Printf("child's output:\n%s\n", string(output)) + } + p(err) + + var buf [4]byte + var cmsgBuf [20]byte + n, cmsgN, msgFlag, _, err := syscall.Recvmsg(pair[0], buf[:], cmsgBuf[:], 0) + p(err) + fmt.Printf("msg: %v\ncmsg: %v\nflag: %d\n", buf[:n], cmsgBuf[:cmsgN], msgFlag) + tun := int(binary.NativeEndian.Uint32(cmsgBuf[16:])) + defer syscall.Close(tun) + mtu := binary.NativeEndian.Uint32(buf[:]) + + settings.SetDialerOpts(settings.SplitDesync, settings.RetryNever, 0, false) + bridge := glue.MyBridge{ + PIDCSV: backend.Base, + TIDCSV: backend.Preferred, + } + tunnel, err := intra.NewTunnel(tun, int(mtu), fakeDNS, nil, &bridge) + p(err) + defer tunnel.Disconnect() + + err = intra.AddDoHTransport(tunnel, backend.Preferred, "https://[2620:fe::12]/dns-query", "2620:fe::12") + p(err) + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + wait1 := <-ch + fmt.Printf("received %v signal, exiting\n", wait1) +} +func main() { + var mode string + flag.StringVar(&mode, "mode", "main", "main or sendfd") + var tunName string + flag.StringVar(&tunName, "tun", "tun0", "") + var pid int + flag.IntVar(&pid, "target", -1, "target process to get namespaces from") + var fakeDNS string + flag.StringVar(&fakeDNS, "dns", "10.0.2.3:53", "DNS passed to intra.NewTunnel()") + flag.Parse() + + switch mode { + case "main": + demo(pid, tunName, fakeDNS) + case "sendfd": + sendTUN(tunName) + default: + fmt.Printf("unknown mode: %s\n", mode) + } +}