mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-09-08 01:42:34 +00:00
255 lines
5.1 KiB
Go
255 lines
5.1 KiB
Go
//go:build linux || android
|
|
// +build linux android
|
|
|
|
package dev
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
cloneDevicePath = "/dev/net/tun"
|
|
ifReqSize = unix.IFNAMSIZ + 64
|
|
)
|
|
|
|
type tunLinux struct {
|
|
url string
|
|
name string
|
|
tunAddress string
|
|
autoRoute bool
|
|
tunFile *os.File
|
|
mtu int
|
|
|
|
closed bool
|
|
stopOnce sync.Once
|
|
}
|
|
|
|
// OpenTunDevice return a TunDevice according a URL
|
|
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
deviceURL, _ := url.Parse("dev://clash0")
|
|
mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32)
|
|
|
|
t := &tunLinux{
|
|
url: deviceURL.String(),
|
|
mtu: int(mtu),
|
|
tunAddress: tunAddress,
|
|
autoRoute: autoRoute,
|
|
}
|
|
switch deviceURL.Scheme {
|
|
case "dev":
|
|
var err error
|
|
var dev TunDevice
|
|
dev, err = t.openDeviceByName(deviceURL.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if autoRoute {
|
|
SetLinuxAutoRoute()
|
|
}
|
|
return dev, nil
|
|
case "fd":
|
|
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dev TunDevice
|
|
dev, err = t.openDeviceByFd(int(fd))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if autoRoute {
|
|
SetLinuxAutoRoute()
|
|
}
|
|
return dev, nil
|
|
}
|
|
return nil, fmt.Errorf("unsupported device type `%s`", deviceURL.Scheme)
|
|
}
|
|
|
|
func (t *tunLinux) Name() string {
|
|
return t.name
|
|
}
|
|
|
|
func (t *tunLinux) URL() string {
|
|
return t.url
|
|
}
|
|
|
|
func (t *tunLinux) Write(buff []byte) (int, error) {
|
|
return t.tunFile.Write(buff)
|
|
}
|
|
|
|
func (t *tunLinux) Read(buff []byte) (int, error) {
|
|
return t.tunFile.Read(buff)
|
|
}
|
|
|
|
func (t *tunLinux) IsClose() bool {
|
|
return t.closed
|
|
}
|
|
|
|
func (t *tunLinux) Close() error {
|
|
t.stopOnce.Do(func() {
|
|
if t.autoRoute {
|
|
RemoveLinuxAutoRoute()
|
|
}
|
|
t.closed = true
|
|
t.tunFile.Close()
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (t *tunLinux) MTU() (int, error) {
|
|
// Sometime, we can't read MTU by SIOCGIFMTU. Then we should return the preset MTU
|
|
if t.mtu > 0 {
|
|
return t.mtu, nil
|
|
}
|
|
mtu, err := t.getInterfaceMtu()
|
|
return int(mtu), err
|
|
}
|
|
|
|
func (t *tunLinux) openDeviceByName(name string) (TunDevice, error) {
|
|
nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ifr [ifReqSize]byte
|
|
var flags uint16 = unix.IFF_TUN | unix.IFF_NO_PI
|
|
nameBytes := []byte(name)
|
|
if len(nameBytes) >= unix.IFNAMSIZ {
|
|
return nil, errors.New("interface name too long")
|
|
}
|
|
copy(ifr[:], nameBytes)
|
|
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
|
|
|
_, _, errno := unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
uintptr(nfd),
|
|
uintptr(unix.TUNSETIFF),
|
|
uintptr(unsafe.Pointer(&ifr[0])),
|
|
)
|
|
if errno != 0 {
|
|
return nil, errno
|
|
}
|
|
err = unix.SetNonblock(nfd, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line.
|
|
|
|
t.tunFile = os.NewFile(uintptr(nfd), cloneDevicePath)
|
|
t.name, err = t.getName()
|
|
if err != nil {
|
|
t.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (t *tunLinux) openDeviceByFd(fd int) (TunDevice, error) {
|
|
var ifr struct {
|
|
name [16]byte
|
|
flags uint16
|
|
_ [22]byte
|
|
}
|
|
|
|
fd, err := syscall.Dup(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNGETIFF, uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return nil, errno
|
|
}
|
|
|
|
if ifr.flags&syscall.IFF_TUN == 0 || ifr.flags&syscall.IFF_NO_PI == 0 {
|
|
return nil, errors.New("only tun device and no pi mode supported")
|
|
}
|
|
|
|
nullStr := ifr.name[:]
|
|
i := bytes.IndexByte(nullStr, 0)
|
|
if i != -1 {
|
|
nullStr = nullStr[:i]
|
|
}
|
|
t.name = string(nullStr)
|
|
t.tunFile = os.NewFile(uintptr(fd), "/dev/tun")
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (t *tunLinux) getInterfaceMtu() (uint32, error) {
|
|
fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
defer syscall.Close(fd)
|
|
|
|
var ifreq struct {
|
|
name [16]byte
|
|
mtu int32
|
|
_ [20]byte
|
|
}
|
|
|
|
copy(ifreq.name[:], t.name)
|
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCGIFMTU, uintptr(unsafe.Pointer(&ifreq)))
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
|
|
return uint32(ifreq.mtu), nil
|
|
}
|
|
|
|
func (t *tunLinux) getName() (string, error) {
|
|
sysconn, err := t.tunFile.SyscallConn()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var ifr [ifReqSize]byte
|
|
var errno syscall.Errno
|
|
err = sysconn.Control(func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
fd,
|
|
uintptr(unix.TUNGETIFF),
|
|
uintptr(unsafe.Pointer(&ifr[0])),
|
|
)
|
|
})
|
|
if err != nil {
|
|
return "", errors.New("failed to get name of TUN device: " + err.Error())
|
|
}
|
|
if errno != 0 {
|
|
return "", errors.New("failed to get name of TUN device: " + errno.Error())
|
|
}
|
|
nullStr := ifr[:]
|
|
i := bytes.IndexByte(nullStr, 0)
|
|
if i != -1 {
|
|
nullStr = nullStr[:i]
|
|
}
|
|
t.name = string(nullStr)
|
|
return t.name, nil
|
|
}
|
|
|
|
// GetAutoDetectInterface get ethernet interface
|
|
func GetAutoDetectInterface() (string, error) {
|
|
cmd := exec.Command("bash", "-c", "ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return out.String(), nil
|
|
}
|