mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-12-24 13:27:56 +08:00
修订文档,代码;令关闭tun时不卡住;-d自动下载wintun.dll;notun编译tag
新增 notun build tag,取消 tun和 gui之间的关联。
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -30,4 +30,6 @@ fakeca.key
|
||||
fakeca.crt
|
||||
.vscode
|
||||
.verysimple_preferences
|
||||
vs_qrcode.png
|
||||
vs_qrcode.png
|
||||
wintun/
|
||||
wintun.dll
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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("系统代理")
|
||||
|
||||
@@ -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
37
cmd/verysimple/tun.go
Normal 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
19
cmd/verysimple/tun_gui.go
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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
65
netLayer/interface.go
Normal 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
|
||||
}
|
||||
57
netLayer/interface_darwin.go
Normal file
57
netLayer/interface_darwin.go
Normal 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
141
netLayer/interface_linux.go
Normal 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
|
||||
}
|
||||
56
netLayer/interface_windows.go
Normal file
56
netLayer/interface_windows.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user