修订文档,代码;令关闭tun时不卡住;-d自动下载wintun.dll;notun编译tag

新增 notun  build tag,取消 tun和 gui之间的关联。
This commit is contained in:
e1732a364fed
2000-01-01 00:00:00 +00:00
parent 2ff907d335
commit 673bf2faf1
22 changed files with 599 additions and 331 deletions

4
.gitignore vendored
View File

@@ -30,4 +30,6 @@ fakeca.key
fakeca.crt
.vscode
.verysimple_preferences
vs_qrcode.png
vs_qrcode.png
wintun/
wintun.dll

View File

@@ -37,7 +37,7 @@ ws(以及earlydata)/grpc(以及multiMode,uTls以及 **支持回落的 grpcSim
dns(udp/tls)/route(geoip/geosite,分流功能完全与v2ray等价)/fallback(path/sni/alpn/PROXY protocol v1/v2), sniffing(tls)
tcp/udp(以及fullcone)/unix domain socket, tls(包括生成随机证书;客户端证书验证;rejectUnknownSni), uTls,**【tls lazy encrypt】**, http伪装头**可支持回落**,PROXY protocol v1/v2 监听,
[win/mac/linux的sockopt.device(bindToDevice)]/tcp/udp(以及fullcone)/unix domain socket, tls(包括生成随机证书;客户端证书验证;rejectUnknownSni), uTls,**【tls lazy encrypt】**, http伪装头**可支持回落**,PROXY protocol v1/v2 监听,
cli(**交互模式**)/**gui/[vsb计划](https://github.com/e1732a364fed/vsb)(flutter写的面板)**/apiServer, Docker, docker-compose.

View File

@@ -29,6 +29,8 @@ var (
download bool
defaultApiServerConf machine.ApiServerConf
extra_preCommands []func()
)
func init() {
@@ -68,8 +70,17 @@ func runExitCommands() (atLeastOneCalled bool) {
return
}
// 在开始正式代理前, 先运行一些需要运行的命令与函数
func runPreCommands() {
if len(extra_preCommands) > 0 {
for _, f := range extra_preCommands {
f()
}
}
}
// 在开始正式代理前, 先运行一些需要运行的命令与函数
func runPreCommandsAfterLoadConf() {
if download {
tryDownloadMMDB()

View File

@@ -13,21 +13,19 @@ import (
"os"
"strings"
"syscall"
"time"
"github.com/e1732a364fed/ui"
_ "github.com/e1732a364fed/ui/winmanifest"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
"github.com/e1732a364fed/v2ray_simple/proxy/tun"
qrcode "github.com/skip2/go-qrcode"
)
var mainwin *ui.Window
var testFunc func()
var theTunStartCmds []string
var multilineEntry *ui.MultilineEntry //用于向用户提供一些随机的有用的需要复制的字符串
var entriesGroup *ui.Group //父 of multilineEntry
@@ -91,13 +89,6 @@ func init() {
ui.Main(setupUI)
}
tun.AddManualRunCmdsListFunc = func(s []string) {
theTunStartCmds = s
if multilineEntry != nil {
entriesGroup.Show()
multilineEntry.SetText(strings.Join(theTunStartCmds, "\n"))
}
}
}
func setupDefaultPref() {
@@ -117,6 +108,7 @@ func setupDefaultPref() {
func makeBasicControlsPage() ui.Control {
vbox := ui.NewVerticalBox()
vbox.SetPadded(true)
vbox.Append(ui.NewLabel("开启或关闭vs代理"), false)
hbox := ui.NewHorizontalBox()
hbox.SetPadded(true)
@@ -131,40 +123,62 @@ func makeBasicControlsPage() ui.Control {
hbox.Append(startBtn, false)
{
isR := mainM.IsRunning()
var updateState = func(btn, cbx bool) {
isR := mainM.IsRunning()
if cbx {
toggleCheckbox.SetChecked(isR)
}
if btn {
//这里在darwin发现startBtn在被启用后显示不出来变化除非切换一下tab再换回来才能看出。不知何故
if isR {
startBtn.Disable()
stopBtn.Enable()
} else {
stopBtn.Disable()
startBtn.Enable()
}
}
toggleCheckbox.SetChecked(isR)
if isR {
startBtn.Disable()
} else {
stopBtn.Disable()
}
updateState(true, true)
mainM.AddToggleCallback(func(i int) {
if mainwin == nil {
return
}
ok := i == 1
toggleCheckbox.SetChecked(ok) //SetChecked不会触发OnToggled
if ok {
stopBtn.Enable()
startBtn.Disable()
} else {
stopBtn.Disable()
startBtn.Enable()
}
updateState(true, true)
})
var stopF = func() {
ch := make(chan struct{})
go func() {
mainM.Stop()
close(ch)
}()
tCh := time.After(time.Second * 2)
select {
case <-tCh:
log.Println("Close timeout")
//os.Exit(-1)
case <-ch:
break
}
}
toggleCheckbox.OnToggled(func(c *ui.Checkbox) {
if c.Checked() {
mainM.Start()
} else {
mainM.Stop()
//mainM.Stop()
stopF()
}
})
stopBtn.OnClicked(func(b *ui.Button) {
mainM.Stop()
//mainM.Stop()
stopF()
})
startBtn.OnClicked(func(b *ui.Button) {
@@ -173,8 +187,6 @@ func makeBasicControlsPage() ui.Control {
}
vbox.Append(ui.NewLabel("开启或关闭vs代理"), false)
vbox.Append(ui.NewHorizontalSeparator(), false)
systemProxyGroup := ui.NewGroup("系统代理")

View File

@@ -122,6 +122,8 @@ func mainFunc() (result int) {
// config params step
setupSystemParemeters()
runPreCommands()
fpath := utils.GetFilePath(configFileName)
if !utils.FileExist(fpath) {
@@ -200,7 +202,7 @@ func mainFunc() (result int) {
mainM.SetupDial()
}
runPreCommands()
runPreCommandsAfterLoadConf()
stopGorouteCaptureSignalChan := make(chan struct{})

37
cmd/verysimple/tun.go Normal file
View File

@@ -0,0 +1,37 @@
//go:build !notun
package main
import (
"runtime"
"github.com/e1732a364fed/v2ray_simple/utils"
_ "github.com/e1732a364fed/v2ray_simple/proxy/tun"
)
func init() {
extra_preCommands = append(extra_preCommands, func() {
if download {
if runtime.GOOS == "windows" {
//自动下载wintun.dll
if utils.FileExist("wintun.dll") {
return
}
if !utils.DownloadAndUnzip("wintun.zip", "https://www.wintun.net/builds/wintun-0.14.1.zip", "") {
return
}
dir := ""
switch a := runtime.GOARCH; a {
case "386":
dir = "x86"
default:
dir = a
}
utils.LogRunCmd("copy", "wintun\\bin\\"+dir+"\\wintun.dll", ".")
}
}
})
}

19
cmd/verysimple/tun_gui.go Normal file
View File

@@ -0,0 +1,19 @@
//go:build gui && !notun
package main
import (
"strings"
"github.com/e1732a364fed/v2ray_simple/proxy/tun"
)
func init() {
tun.AddManualRunCmdsListFunc = func(s []string) {
theGuiTunStartCmds := s
if multilineEntry != nil {
entriesGroup.Show()
multilineEntry.SetText(strings.Join(theGuiTunStartCmds, "\n"))
}
}
}

View File

@@ -1,4 +1,4 @@
# tun; vs的tun功能仅在 vs_gui 系列中存在。
# tun
###############################################################
@@ -128,5 +128,5 @@ utls = true
# 最新的 xjasonlyu/tun2socks 代码也都支持了,但是 其文档指导还停留在旧代码的阶段,特此指出。
# 2. linux 上它需要手动启动tun设备而其他两个平台不需要
# 这是因为它在linux上和在其他平台上开启tun设备的逻辑不同。本作作者也不清楚原因,但是还是觉得原作者肯定对,就模仿了。
# 这是因为它在linux上和在其他平台上开启tun设备的逻辑不同。
# 不过在本作如果你开启 auto_route, 会自动帮你开启tun设备无需额外操作。

1
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/biter777/countries v1.5.6
github.com/dustin/go-humanize v1.0.0
github.com/e1732a364fed/ui v0.0.1-alpha.12
github.com/gobwas/ws v1.1.0
github.com/lucas-clemente/quic-go v0.0.0-00010101000000-000000000000

2
go.sum
View File

@@ -19,6 +19,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/e1732a364fed/ui v0.0.1-alpha.12 h1:XAVcgQlF8l1VCxlMexX0UJkShFBzSI1m1bFDuX9nfOA=
github.com/e1732a364fed/ui v0.0.1-alpha.12/go.mod h1:uK9ryjwA0+3KdICbeXm5IjhKZ+1ZooMVDdTuLaQHpwM=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=

65
netLayer/interface.go Normal file
View File

@@ -0,0 +1,65 @@
package netLayer
import (
"errors"
"log"
"net"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
)
var (
// 如果机器没有ipv6地址, 就无法联通ipv6, 此时可以在dial时更快拒绝ipv6地址,
// 避免打印过多错误输出.
//weKnowThatWeDontHaveIPV6 bool
ErrMachineCantConnectToIpv6 = errors.New("ErrMachineCanConnectToIpv6")
)
// 做一些网络层的资料准备工作, 可以优化本包其它函数的调用。
func PrepareInterfaces() {
//weKnowThatWeDontHaveIPV6 = !HasIpv6Interface()
}
func GetDeviceNameIndex(idx int) string {
intf, err := net.InterfaceByIndex(idx)
if err != nil {
utils.Error(err.Error())
}
return intf.Name
}
func HasIpv6Interface() bool {
addrs, err := net.InterfaceAddrs()
if err != nil {
if ce := utils.CanLogErr("call net.InterfaceAddrs failed"); ce != nil {
ce.Write(zap.Error(err))
} else {
log.Println("call net.InterfaceAddrs failed", err)
}
return false
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsPrivate() && !ipnet.IP.IsLinkLocalUnicast() {
// IsLinkLocalUnicast: something starts with fe80:
// According to godoc, If ip is not an IPv4 address, To4 returns nil.
// This means it's ipv6
if ipnet.IP.To4() == nil {
if ce := utils.CanLogDebug("Has Ipv6Interface!"); ce != nil {
ce.Write()
} else {
log.Println("Has Ipv6Interface!")
}
return true
}
}
}
return false
}

View File

@@ -0,0 +1,57 @@
package netLayer
import (
"errors"
"net"
"syscall"
"golang.org/x/net/route"
)
/*
我们的auto route使用纯命令行方式。
sing-box 使用了另一种系统级别的方式。使用了
golang.org/x/net/route
下面给出一些参考
https://github.com/libp2p/go-netroute
https://github.com/jackpal/gateway/issues/27
https://github.com/GameXG/gonet/blob/master/route/route_windows.go
除了 GetGateway之外还可以使用更多其他代码
*/
func GetGateway() (ip net.IP, index int, err error) {
var rib []byte
rib, err = route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0)
if err != nil {
return
}
var msgs []route.Message
msgs, err = route.ParseRIB(syscall.NET_RT_DUMP, rib)
if err != nil {
return
}
for _, m := range msgs {
switch m := m.(type) {
case *route.RouteMessage:
switch sa := m.Addrs[syscall.RTAX_GATEWAY].(type) {
case *route.Inet4Addr:
ip = net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3])
case *route.Inet6Addr:
ip = make(net.IP, net.IPv6len)
copy(ip, sa.IP[:])
}
index = m.Index
return
}
}
err = errors.New("no gateway")
return
}

141
netLayer/interface_linux.go Normal file
View File

@@ -0,0 +1,141 @@
package netLayer
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strconv"
"strings"
)
// https://github.com/jackpal/gateway/blob/master/gateway_parsers.go
func GetGateway() (ip net.IP, ifName string, err error) {
// See http://man7.org/linux/man-pages/man8/route.8.html
const file = "/proc/net/route"
var f *os.File
f, err = os.Open(file)
if err != nil {
err = fmt.Errorf("Can't access %s", file)
return
}
defer f.Close()
bytes, err := ioutil.ReadAll(f)
if err != nil {
err = fmt.Errorf("Can't read %s", file)
return
}
parsedStruct, err := parseToLinuxRouteStruct(bytes)
if err != nil {
return
}
ifName = parsedStruct.Iface
destinationHex := "0x" + parsedStruct.Destination
gatewayHex := "0x" + parsedStruct.Gateway
// cast hex address to uint32
d, err := strconv.ParseInt(gatewayHex, 0, 64)
if err != nil {
err = fmt.Errorf(
"parsing default interface address field hex '%s': %w",
destinationHex,
err,
)
return
}
// make net.IP address from uint32
ipd32 := make(net.IP, 4)
binary.LittleEndian.PutUint32(ipd32, uint32(d))
// format net.IP to dotted ipV4 string
ip = net.IP(ipd32)
return
}
func parseToLinuxRouteStruct(output []byte) (linuxRouteStruct, error) {
// parseLinuxProcNetRoute parses the route file located at /proc/net/route
// and returns the IP address of the default gateway. The default gateway
// is the one with Destination value of 0.0.0.0.
//
// The Linux route file has the following format:
//
// $ cat /proc/net/route
//
// Iface Destination Gateway Flags RefCnt Use Metric Mask
// eno1 00000000 C900A8C0 0003 0 0 100 00000000 0 00
// eno1 0000A8C0 00000000 0001 0 0 100 00FFFFFF 0 00
const (
sep = "\t" // field separator
destinationField = 1 // field containing hex destination address
gatewayField = 2 // field containing hex gateway address
)
scanner := bufio.NewScanner(bytes.NewReader(output))
// Skip header line
if !scanner.Scan() {
return linuxRouteStruct{}, errors.New("Invalid linux route file")
}
for scanner.Scan() {
row := scanner.Text()
tokens := strings.Split(row, sep)
if len(tokens) < 11 {
return linuxRouteStruct{}, fmt.Errorf("invalid row '%s' in route file: doesn't have 11 fields", row)
}
// Cast hex destination address to int
destinationHex := "0x" + tokens[destinationField]
destination, err := strconv.ParseInt(destinationHex, 0, 64)
if err != nil {
return linuxRouteStruct{}, fmt.Errorf(
"parsing destination field hex '%s' in row '%s': %w",
destinationHex,
row,
err,
)
}
// The default interface is the one that's 0
if destination != 0 {
continue
}
return linuxRouteStruct{
Iface: tokens[0],
Destination: tokens[1],
Gateway: tokens[2],
Flags: tokens[3],
RefCnt: tokens[4],
Use: tokens[5],
Metric: tokens[6],
Mask: tokens[7],
MTU: tokens[8],
Window: tokens[9],
IRTT: tokens[10],
}, nil
}
return linuxRouteStruct{}, errors.New("interface with default destination not found")
}
type linuxRouteStruct struct {
Iface string
Destination string
Gateway string
Flags string
RefCnt string
Use string
Metric string
Mask string
MTU string
Window string
IRTT string
}

View File

@@ -0,0 +1,56 @@
package netLayer
import (
"os/exec"
"strings"
"github.com/e1732a364fed/v2ray_simple/utils"
)
func GetGateway() (ip string, err error) {
var out []byte
out, err = exec.Command("netstat", "-nr").Output()
if err != nil {
return
}
lines := strings.Split(string(out), "\n")
startLineIndex := -1
for i, l := range lines {
if strings.HasPrefix(l, "IPv4 Route Table") {
if i < len(lines)-3 && strings.HasPrefix(lines[i+3], "Network") {
//应该第一行就是默认的路由
startLineIndex = i + 4
}
break
}
}
if startLineIndex < 0 {
utils.Warn("auto route failed, parse netstat output failed,1")
err = utils.ErrFailed
return
}
str := utils.StandardizeSpaces(lines[startLineIndex])
fields := strings.Split(str, " ")
if len(fields) <= 3 {
utils.Warn("auto route failed, parse netstat output failed,2")
err = utils.ErrFailed
return
}
ip = fields[2]
//为了简单起见只认为192开头的是我们的本地路由地址;
if ip == "On-link" || !strings.HasPrefix(ip, "192") {
utils.Warn("auto route failed, routerIP parse failed, got " + ip)
err = utils.ErrFailed
return
}
return
}

View File

@@ -16,7 +16,6 @@ package netLayer
import (
"errors"
"io"
"log"
"net"
"reflect"
"syscall"
@@ -27,19 +26,9 @@ import (
)
var (
// 如果机器没有ipv6地址, 就无法联通ipv6, 此时可以在dial时更快拒绝ipv6地址,
// 避免打印过多错误输出.
//weKnowThatWeDontHaveIPV6 bool
ErrMachineCantConnectToIpv6 = errors.New("ErrMachineCanConnectToIpv6")
ErrTimeout = errors.New("timeout")
ErrTimeout = errors.New("timeout")
)
// 做一些网络层的资料准备工作, 可以优化本包其它函数的调用。
func PrepareInterfaces() {
//weKnowThatWeDontHaveIPV6 = !HasIpv6Interface()
}
// c.SetDeadline(time.Time{})
func PersistConn(c net.Conn) {
c.SetDeadline(time.Time{})
@@ -124,40 +113,6 @@ type EasyNetAddresser struct {
func (iw *EasyNetAddresser) LocalAddr() net.Addr { return iw.LA }
func (iw *EasyNetAddresser) RemoteAddr() net.Addr { return iw.RA }
func HasIpv6Interface() bool {
addrs, err := net.InterfaceAddrs()
if err != nil {
if ce := utils.CanLogErr("call net.InterfaceAddrs failed"); ce != nil {
ce.Write(zap.Error(err))
} else {
log.Println("call net.InterfaceAddrs failed", err)
}
return false
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsPrivate() && !ipnet.IP.IsLinkLocalUnicast() {
// IsLinkLocalUnicast: something starts with fe80:
// According to godoc, If ip is not an IPv4 address, To4 returns nil.
// This means it's ipv6
if ipnet.IP.To4() == nil {
if ce := utils.CanLogDebug("Has Ipv6Interface!"); ce != nil {
ce.Write()
} else {
log.Println("Has Ipv6Interface!")
}
return true
}
}
}
return false
}
// 用于定义拒绝响应的行为;可参考 httpLayer.RejectConn
type RejectConn interface {
RejectBehaviorDefined() bool //若为false则只能直接Close

View File

@@ -5,10 +5,10 @@ import (
"os"
)
//用于 listen和 dial 配置一些底层参数.
// 用于 listen和 dial 配置一些底层参数.
type Sockopt struct {
TProxy bool `toml:"tproxy"`
Somark int `toml:"mark"`
TProxy bool `toml:"tproxy"` //only linux
Somark int `toml:"mark"` //only linux
Device string `toml:"device"`
//fastopen 不予支持, 因为自己客户端在重重网关之下不可能让层层网关都支持tcp fast open
@@ -16,13 +16,13 @@ type Sockopt struct {
}
//net.TCPListener, net.UnixListener
// net.TCPListener, net.UnixListener
type ListenerWithFile interface {
net.Listener
File() (f *os.File, err error)
}
//net.UnixConn, net.UDPConn, net.TCPConn, net.IPConn
// net.UnixConn, net.UDPConn, net.TCPConn, net.IPConn
type ConnWithFile interface {
net.Conn
File() (f *os.File, err error)

View File

@@ -45,10 +45,9 @@ type StackCloser struct {
*stack.Stack
}
// Close() and Wait()
func (sc *StackCloser) Close() error {
sc.Stack.Close()
sc.Stack.Wait() //这个会卡住
//sc.Stack.Wait() //这个会卡住; 经测试,不调用它也不影响什么
return nil
}

View File

@@ -1,74 +1,13 @@
package tun
import (
"errors"
"net"
"os/exec"
"syscall"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
"golang.org/x/net/route"
)
/*
我们的auto route使用纯命令行方式。
sing-box 使用了另一种系统级别的方式。使用了
golang.org/x/net/route
下面给出一些参考
https://github.com/libp2p/go-netroute
https://github.com/jackpal/gateway/issues/27
https://github.com/GameXG/gonet/blob/master/route/route_windows.go
除了 GetGateway之外还可以使用更多其他代码
*/
func GetGateway() (ip net.IP, index int, err error) {
var rib []byte
rib, err = route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0)
if err != nil {
return
}
var msgs []route.Message
msgs, err = route.ParseRIB(syscall.NET_RT_DUMP, rib)
if err != nil {
return
}
for _, m := range msgs {
switch m := m.(type) {
case *route.RouteMessage:
switch sa := m.Addrs[syscall.RTAX_GATEWAY].(type) {
case *route.Inet4Addr:
ip = net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3])
case *route.Inet6Addr:
ip = make(net.IP, net.IPv6len)
copy(ip, sa.IP[:])
}
index = m.Index
return
}
}
err = errors.New("no gateway")
return
}
func GetDeviceNameIndex(idx int) string {
intf, err := net.InterfaceByIndex(idx)
if err != nil {
utils.Error(err.Error())
}
return intf.Name
}
var rememberedRouterName string
func init() {
autoRouteFunc = func(tunDevName, tunGateway, tunIP string, directList []string) {
if len(directList) == 0 {
@@ -120,7 +59,7 @@ func init() {
// }
// routerIP := fields[1]
rip, ridx, err := GetGateway() //oops, accidentally rest in peace
rip, ridx, err := netLayer.GetGateway() //oops, accidentally rest in peace
if err != nil {
if ce := utils.CanLogErr("auto route failed when get gateway"); ce != nil {
@@ -130,7 +69,7 @@ func init() {
}
rememberedRouterIP = rip.String()
rname := GetDeviceNameIndex(ridx)
rname := netLayer.GetDeviceNameIndex(ridx)
rememberedRouterName = rname
if ce := utils.CanLogInfo("auto route: Your router should be"); ce != nil {

View File

@@ -1,23 +1,11 @@
package tun
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strconv"
"strings"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
)
var rememberedRouterName string
func init() {
//https://github.com/xjasonlyu/tun2socks/wiki/Examples
@@ -54,10 +42,14 @@ func init() {
}
autoRouteFunc = func(tunDevName, tunGateway, tunIP string, directList []string) {
routerip, routerName, err := discoverLinuxGateway()
routerip, routerName, err := netLayer.GetGateway()
if err != nil {
utils.Error(err.Error())
}
if routerip == nil || len(routerip) == 0 {
utils.Error("got router ip but is nil / emtpy")
return
}
rememberedRouterName = routerName
rememberedRouterIP = routerip.String()
@@ -131,130 +123,3 @@ func init() {
}
}
}
// https://github.com/jackpal/gateway/blob/master/gateway_parsers.go
func discoverLinuxGateway() (ip net.IP, ifName string, err error) {
// See http://man7.org/linux/man-pages/man8/route.8.html
const file = "/proc/net/route"
var f *os.File
f, err = os.Open(file)
if err != nil {
err = fmt.Errorf("Can't access %s", file)
return
}
defer f.Close()
bytes, err := ioutil.ReadAll(f)
if err != nil {
err = fmt.Errorf("Can't read %s", file)
return
}
parsedStruct, err := parseToLinuxRouteStruct(bytes)
if err != nil {
return
}
ifName = parsedStruct.Iface
destinationHex := "0x" + parsedStruct.Destination
gatewayHex := "0x" + parsedStruct.Gateway
// cast hex address to uint32
d, err := strconv.ParseInt(gatewayHex, 0, 64)
if err != nil {
err = fmt.Errorf(
"parsing default interface address field hex '%s': %w",
destinationHex,
err,
)
return
}
// make net.IP address from uint32
ipd32 := make(net.IP, 4)
binary.LittleEndian.PutUint32(ipd32, uint32(d))
// format net.IP to dotted ipV4 string
ip = net.IP(ipd32)
return
}
func parseToLinuxRouteStruct(output []byte) (linuxRouteStruct, error) {
// parseLinuxProcNetRoute parses the route file located at /proc/net/route
// and returns the IP address of the default gateway. The default gateway
// is the one with Destination value of 0.0.0.0.
//
// The Linux route file has the following format:
//
// $ cat /proc/net/route
//
// Iface Destination Gateway Flags RefCnt Use Metric Mask
// eno1 00000000 C900A8C0 0003 0 0 100 00000000 0 00
// eno1 0000A8C0 00000000 0001 0 0 100 00FFFFFF 0 00
const (
sep = "\t" // field separator
destinationField = 1 // field containing hex destination address
gatewayField = 2 // field containing hex gateway address
)
scanner := bufio.NewScanner(bytes.NewReader(output))
// Skip header line
if !scanner.Scan() {
return linuxRouteStruct{}, errors.New("Invalid linux route file")
}
for scanner.Scan() {
row := scanner.Text()
tokens := strings.Split(row, sep)
if len(tokens) < 11 {
return linuxRouteStruct{}, fmt.Errorf("invalid row '%s' in route file: doesn't have 11 fields", row)
}
// Cast hex destination address to int
destinationHex := "0x" + tokens[destinationField]
destination, err := strconv.ParseInt(destinationHex, 0, 64)
if err != nil {
return linuxRouteStruct{}, fmt.Errorf(
"parsing destination field hex '%s' in row '%s': %w",
destinationHex,
row,
err,
)
}
// The default interface is the one that's 0
if destination != 0 {
continue
}
return linuxRouteStruct{
Iface: tokens[0],
Destination: tokens[1],
Gateway: tokens[2],
Flags: tokens[3],
RefCnt: tokens[4],
Use: tokens[5],
Metric: tokens[6],
Mask: tokens[7],
MTU: tokens[8],
Window: tokens[9],
IRTT: tokens[10],
}, nil
}
return linuxRouteStruct{}, errors.New("interface with default destination not found")
}
type linuxRouteStruct struct {
Iface string
Destination string
Gateway string
Flags string
RefCnt string
Use string
Metric string
Mask string
MTU string
Window string
IRTT string
}

View File

@@ -2,10 +2,9 @@ package tun
import (
"fmt"
"os/exec"
"strings"
"time"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
)
@@ -35,42 +34,11 @@ func init() {
utils.Warn(auto_route_bindToDeviceWarn)
}
out, err := exec.Command("netstat", "-nr").Output()
routerIP, err := netLayer.GetGateway()
if err != nil {
return
}
lines := strings.Split(string(out), "\n")
startLineIndex := -1
for i, l := range lines {
if strings.HasPrefix(l, "IPv4 Route Table") {
if i < len(lines)-3 && strings.HasPrefix(lines[i+3], "Network") {
//应该第一行就是默认的路由
startLineIndex = i + 4
}
break
}
}
if startLineIndex < 0 {
utils.Warn("auto route failed, parse netstat output failed,1")
return
}
str := utils.StandardizeSpaces(lines[startLineIndex])
fields := strings.Split(str, " ")
if len(fields) <= 3 {
utils.Warn("auto route failed, parse netstat output failed,2")
return
}
routerIP := fields[2]
//为了简单起见只认为192开头的是我们的本地路由地址;
if routerIP == "On-link" || !strings.HasPrefix(routerIP, "192") {
utils.Warn("auto route failed, routerIP parse failed, got " + routerIP)
return
}
if ce := utils.CanLogInfo("auto route: Your router's ip should be"); ce != nil {
ce.Write(zap.String("ip", routerIP))
}

View File

@@ -30,8 +30,10 @@ const (
)
var (
AddManualRunCmdsListFunc func([]string)
rememberedRouterIP string
AddManualRunCmdsListFunc func([]string)
rememberedRouterIP string
rememberedRouterName string
manualRoute bool
autoRoutePreFunc func(tunDevName, tunGateway, tunIP string, directlist []string) bool
autoRouteFunc func(tunDevName, tunGateway, tunIP string, directlist []string)
@@ -187,6 +189,12 @@ func (s *Server) Stop() {
autoRouteDownAfterCloseFunc(s.devName, s.realIP, s.selfip, s.autoRouteDirectList)
}
s.stackCloser = nil
s.tunDev = nil
s.infoChan = nil
s.udpRequestChan = nil
rememberedRouterIP = ""
rememberedRouterName = ""
}
}
@@ -219,7 +227,7 @@ func (s *Server) StartListen(tcpRequestChan chan<- netLayer.TCPRequestInfo, udpR
// 不过这就导致了 linux 和其他系统的一点不同那就是在linux上我们要先手动用命令创建tun然后再 运行本程序,
// 可以参考 https://github.com/xjasonlyu/tun2socks/wiki/Examples
//所以这里如果开启了 auto_route, 我们应该自动帮用户提前创建 tun, 减轻用户使用负担
//所以这里 auto_route, 在此自动帮用户提前创建 tun, 减轻用户使用负担
if s.autoRoute && autoRoutePreFunc != nil {
autoRoutePreFunc(s.devName, s.realIP, s.selfip, s.autoRouteDirectList)

View File

@@ -1,15 +1,24 @@
package utils
import (
"archive/zip"
"bytes"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/dustin/go-humanize"
)
// TryDownloadWithProxyUrl try to download from a link with the given proxy url.
// thehttpClient is the client created, could be http.DefaultClient or a newly created one.
//
//If proxyUrl is empty, the function will call http.DefaultClient.Get, or it will create with a client with a transport with proxy set to proxyUrl. If err==nil, then thehttpClient!=nil .
// If proxyUrl is empty, the function will call http.DefaultClient.Get, else it will create with a client with a transport with proxy set to proxyUrl. If err==nil, then thehttpClient!=nil .
func TryDownloadWithProxyUrl(proxyUrl, downloadLink string) (thehttpClient *http.Client, resp *http.Response, err error) {
thehttpClient = http.DefaultClient
@@ -40,3 +49,123 @@ func TryDownloadWithProxyUrl(proxyUrl, downloadLink string) (thehttpClient *http
return
}
func SimpleDownloadFile(fname, downloadLink string) (ok bool) {
PrintStr("Downloading ")
PrintStr(fname)
PrintStr(" ...\n")
_, resp, err := TryDownloadWithProxyUrl("", downloadLink)
if err != nil {
fmt.Printf("Download failed %s\n", err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("Download got bad status: %s\n", resp.Status)
return
}
out, err := os.Create(fname)
if err != nil {
fmt.Printf("Can Download but Can't Create File,%s \n", err.Error())
return
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
fmt.Printf("Write downloaded to file failed: %s\n", err.Error())
return
}
PrintStr("Download success!\n")
return
}
// https://golangcode.com/download-a-file-with-progress/
type downloadPrintCounter struct {
Total uint64
}
func (wc *downloadPrintCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
wc.PrintProgress()
return n, nil
}
func (wc downloadPrintCounter) PrintProgress() {
fmt.Printf("\r%s", strings.Repeat(" ", 35))
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total))
}
func DownloadAndUnzip(fname, downloadLink, dst string) (ok bool) {
PrintStr("Downloading ")
PrintStr(fname)
PrintStr(" ...\n")
_, resp, err := TryDownloadWithProxyUrl("", downloadLink)
if err != nil {
fmt.Printf("Download failed %s\n", err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("Download got bad status: %s\n", resp.Status)
return
}
buf := new(bytes.Buffer)
counter := &downloadPrintCounter{}
io.Copy(buf, io.TeeReader(resp.Body, counter))
out := bytes.NewReader(buf.Bytes())
PrintStr("\nDownload success!\n")
reader, _ := zip.NewReader(out, int64(out.Len()))
for _, f := range reader.File {
filePath := filepath.Join(dst, f.Name)
fmt.Println("unzipping file ", filePath)
if f.FileInfo().IsDir() {
fmt.Println("creating directory...")
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
fmt.Println(err)
return
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
fmt.Println(err)
return
}
fileInArchive, err := f.Open()
if err != nil {
fmt.Println(err)
return
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
fmt.Println(err)
return
}
dstFile.Close()
fileInArchive.Close()
}
ok = true
return
}