diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bf2c47a..b25c8b5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,8 @@ on: push: branches: - master + tags: + - '*' paths-ignore: - '.github/**' - 'assets/**' diff --git a/Makefile b/Makefile index 40a9cf0..ca21756 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,12 @@ GO_BUILD = CGO_ENABLED=0 go build $(BUILD_FLAGS) -ldflags '$(LDFLAGS)' -tags '$( PLATFORM_LIST = \ darwin-amd64 \ + freebsd-amd64 \ + freebsd-arm64 \ linux-amd64 \ linux-arm64 \ + openbsd-amd64 \ + openbsd-arm64 \ .PHONY: all docker $(PLATFORM_LIST) @@ -27,12 +31,24 @@ docker: darwin-amd64: GOARCH=amd64 GOOS=darwin $(GO_BUILD) -o $(DIR)/$(NAME)-$@ +freebsd-amd64: + GOARCH=amd64 GOOS=freebsd $(GO_BUILD) -o $(DIR)/$(NAME)-$@ + +freebsd-arm64: + GOARCH=arm64 GOOS=freebsd $(GO_BUILD) -o $(DIR)/$(NAME)-$@ + linux-amd64: GOARCH=amd64 GOOS=linux $(GO_BUILD) -o $(DIR)/$(NAME)-$@ linux-arm64: GOARCH=arm64 GOOS=linux $(GO_BUILD) -o $(DIR)/$(NAME)-$@ +openbsd-amd64: + GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(DIR)/$(NAME)-$@ + +openbsd-arm64: + GOARCH=arm64 GOOS=openbsd $(GO_BUILD) -o $(DIR)/$(NAME)-$@ + zip_releases=$(addsuffix .zip, $(PLATFORM_LIST)) $(zip_releases): %.zip : % diff --git a/README.md b/README.md index 33f7e7b..495135c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ | Target | Minimum | Recommended | | :----- | :-----: | :---------: | -| System | linux darwin | linux | +| System | linux darwin freebsd openbsd | linux | | Memory | >20MB | >128MB | | CPU | amd64 arm64 | amd64 | @@ -296,5 +296,5 @@ If you are sensitive to memory, please go back to [v1](https://github.com/xjason ## TODO - [ ] Windows support -- [ ] FreeBSD support -- [ ] OpenBSD support +- [x] FreeBSD support +- [x] OpenBSD support diff --git a/go.mod b/go.mod index c00adfb..aa89d8b 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e + golang.zx2c4.com/wireguard v0.0.20200320 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect gvisor.dev/gvisor v0.0.0-20201107072535-9e848922ed33 ) diff --git a/go.sum b/go.sum index 932a7d8..0375605 100644 --- a/go.sum +++ b/go.sum @@ -247,6 +247,7 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -286,6 +287,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= @@ -334,6 +336,7 @@ golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= @@ -382,6 +385,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g= +golang.zx2c4.com/wireguard v0.0.20200320/go.mod h1:lDian4Sw4poJ04SgHh35nzMVwGSYlPumkdnHcucAQoY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 75cf3fd..e31dc44 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -13,11 +13,11 @@ import ( "github.com/xjasonlyu/clash/component/dialer" "github.com/xjasonlyu/tun2socks/internal/api" "github.com/xjasonlyu/tun2socks/internal/core" - "github.com/xjasonlyu/tun2socks/internal/dev" "github.com/xjasonlyu/tun2socks/internal/dns" "github.com/xjasonlyu/tun2socks/internal/proxy" "github.com/xjasonlyu/tun2socks/internal/tunnel" "github.com/xjasonlyu/tun2socks/pkg/log" + "github.com/xjasonlyu/tun2socks/pkg/tun" ) func bindToInterface(name string) { @@ -51,7 +51,7 @@ func Main(c *cli.Context) error { if c.IsSet("interface") { name := c.String("interface") bindToInterface(name) - log.Infof("[IFCE] bind to interface: %s", name) + log.Infof("[DIALER] bind to interface: %s", name) } if c.IsSet("api") { /* initiate API */ @@ -71,16 +71,11 @@ func Main(c *cli.Context) error { } deviceURL := c.String("device") - device, err := dev.Open(deviceURL) + device, err := tun.Open(deviceURL) if err != nil { return fmt.Errorf("open device %s: %w", deviceURL, err) } - defer func() { - err := device.Close() - if err != nil { - log.Errorf("close device %s error: %v", deviceURL, err) - } - }() + defer device.Close() proxyURL := c.String("proxy") if err := proxy.Register(proxyURL); err != nil { @@ -90,7 +85,7 @@ func Main(c *cli.Context) error { if _, err := core.NewDefaultStack(device, tunnel.Add, tunnel.AddPacket); err != nil { return fmt.Errorf("initiate stack: %w", err) } - log.Infof("[STACK] %s --> %s", device.String(), proxy.String()) + log.Infof("[STACK] %s --> %s", deviceURL, proxyURL) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) diff --git a/internal/dev/dev.go b/internal/dev/dev.go deleted file mode 100644 index ea4f8f8..0000000 --- a/internal/dev/dev.go +++ /dev/null @@ -1,74 +0,0 @@ -package dev - -import ( - "errors" - "io" - "net/url" - "strings" - - "gvisor.dev/gvisor/pkg/tcpip/stack" - - "github.com/xjasonlyu/tun2socks/internal/dev/tun" -) - -const defaultScheme = "tun" - -type Device struct { - url *url.URL - io.Closer - stack.LinkEndpoint -} - -func Open(deviceURL string) (device *Device, err error) { - if !strings.Contains(deviceURL, "://") { - deviceURL = defaultScheme + "://" + deviceURL - } - - var u *url.URL - if u, err = url.Parse(deviceURL); err != nil { - return - } - - var ( - ep stack.LinkEndpoint - c io.Closer - ) - switch strings.ToLower(u.Scheme) { - case "tun": - name := u.Host - ep, c, err = tun.Open(name) - default: - err = errors.New("unsupported device type") - } - - if err != nil { - return - } - - device = &Device{ - url: u, - Closer: c, - LinkEndpoint: ep, - } - return -} - -// Close closes device. -func (d *Device) Close() error { - return d.Closer.Close() -} - -// Name returns name of device. -func (d *Device) Name() string { - return d.url.Host -} - -// Type returns type of device. -func (d *Device) Type() string { - return strings.ToLower(d.url.Scheme) -} - -// String returns full URL string. -func (d *Device) String() string { - return d.url.String() -} diff --git a/internal/dev/tun/tun.go b/internal/dev/tun/tun.go deleted file mode 100644 index a412197..0000000 --- a/internal/dev/tun/tun.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !darwin,!linux - -package tun - -import ( - "fmt" - "io" - "runtime" - - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -func Open(_ string) (stack.LinkEndpoint, io.Closer, error) { - return nil, nil, fmt.Errorf("operation was not supported on %s", runtime.GOOS) -} diff --git a/internal/dev/tun/tun_darwin.go b/internal/dev/tun/tun_darwin.go deleted file mode 100644 index 1ec47da..0000000 --- a/internal/dev/tun/tun_darwin.go +++ /dev/null @@ -1,57 +0,0 @@ -package tun - -import ( - "fmt" - "io" - - "github.com/songgao/water" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/stack" - - "github.com/xjasonlyu/tun2socks/pkg/link/rwc" -) - -func Open(name string) (ep stack.LinkEndpoint, c io.Closer, err error) { - config := water.Config{ - DeviceType: water.TUN, - } - config.Name = name - - var ifce *water.Interface - ifce, err = water.New(config) - if err != nil { - return - } - - var mtu uint32 - mtu, err = getMTU(name) - if err != nil { - return - } - - ep, err = rwc.New(ifce, mtu) - c = ifce - - return -} - -func getMTU(name string) (uint32, error) { - fd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - - if err != nil { - return 0, err - } - - defer unix.Close(fd) - - ifr, err := unix.IoctlGetIfreqMTU(fd, name) - if err != nil { - return 0, fmt.Errorf("get MTU on %s: %w", name, err) - } - - return uint32(ifr.MTU), nil -} diff --git a/internal/dev/tun/tun_linux.go b/internal/dev/tun/tun_linux.go deleted file mode 100644 index 1f76963..0000000 --- a/internal/dev/tun/tun_linux.go +++ /dev/null @@ -1,43 +0,0 @@ -package tun - -import ( - "io" - "syscall" - - "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" - "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" - "gvisor.dev/gvisor/pkg/tcpip/link/tun" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -type closeFunc func() error - -func (f closeFunc) Close() error { - return f() -} - -func Open(name string) (ep stack.LinkEndpoint, c io.Closer, err error) { - var fd int - fd, err = tun.Open(name) - if err != nil { - return - } - - var mtu uint32 - mtu, err = rawfile.GetMTU(name) - if err != nil { - return - } - - ep, err = fdbased.New(&fdbased.Options{ - FDs: []int{fd}, - MTU: mtu, - EthernetHeader: false, - }) - - c = closeFunc(func() error { - return syscall.Close(fd) - }) - - return -} diff --git a/pkg/tun/tun.go b/pkg/tun/tun.go new file mode 100644 index 0000000..d9b3fa5 --- /dev/null +++ b/pkg/tun/tun.go @@ -0,0 +1,44 @@ +package tun + +import ( + "errors" + "net/url" + "strconv" + "strings" + + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +const defaultScheme = "tun" + +type Device interface { + stack.LinkEndpoint + + Name() string // returns the current name + Close() error // stops and closes the tun +} + +// Open opens TUN Device with given URL. +func Open(rawURL string) (Device, error) { + if !strings.Contains(rawURL, "://") { + rawURL = defaultScheme + "://" + rawURL + } + + var u *url.URL + u, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + if strings.ToLower(u.Scheme) != defaultScheme { + return nil, errors.New("unsupported TUN scheme") + } + + var n uint64 + if mtu := u.Query().Get("mtu"); mtu != "" { + n, _ = strconv.ParseUint(mtu, 10, 32) + } + + name := u.Host + return CreateTUN(name, uint32(n)) +} diff --git a/pkg/tun/tun_default.go b/pkg/tun/tun_default.go new file mode 100644 index 0000000..f10aecc --- /dev/null +++ b/pkg/tun/tun_default.go @@ -0,0 +1,12 @@ +// +build !darwin,!freebsd,!linux,!openbsd + +package tun + +import ( + "fmt" + "runtime" +) + +func CreateTUN(_ string, _ uint32) (Device, error) { + return nil, fmt.Errorf("operation was not supported on %s", runtime.GOOS) +} diff --git a/pkg/tun/tun_linux.go b/pkg/tun/tun_linux.go new file mode 100644 index 0000000..37d47ec --- /dev/null +++ b/pkg/tun/tun_linux.go @@ -0,0 +1,96 @@ +package tun + +import ( + "errors" + "os" + "unsafe" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" + "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" + "gvisor.dev/gvisor/pkg/tcpip/link/tun" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +type linuxTun struct { + stack.LinkEndpoint + + tunName string + tunFile *os.File +} + +func CreateTUN(name string, n uint32) (Device, error) { + fd, err := tun.Open(name) + if err != nil { + return nil, err + } + + if n > 0 { + if err := setMTU(name, n); err != nil { + return nil, err + } + } + + var mtu uint32 + if mtu, err = rawfile.GetMTU(name); err != nil { + return nil, err + } + + var ep stack.LinkEndpoint + if ep, err = fdbased.New(&fdbased.Options{ + FDs: []int{fd}, + MTU: mtu, + // TUN only + EthernetHeader: false, + }); err != nil { + return nil, err + } + + return &linuxTun{ + LinkEndpoint: ep, + tunName: name, + tunFile: os.NewFile(uintptr(fd), "tun"), + }, nil +} + +func (t *linuxTun) Name() string { + return t.tunName +} + +func (t *linuxTun) Close() error { + return t.tunFile.Close() +} + +func setMTU(name string, n uint32) error { + // open datagram socket + fd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + + if err != nil { + return err + } + + defer unix.Close(fd) + + const ifReqSize = unix.IFNAMSIZ + 64 + + // do ioctl call + var ifr [ifReqSize]byte + copy(ifr[:], name) + *(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(fd), + uintptr(unix.SIOCSIFMTU), + uintptr(unsafe.Pointer(&ifr[0])), + ) + + if errno != 0 { + return errors.New("failed to set MTU of TUN device") + } + + return nil +} diff --git a/pkg/tun/tun_unix.go b/pkg/tun/tun_unix.go new file mode 100644 index 0000000..3f61bd9 --- /dev/null +++ b/pkg/tun/tun_unix.go @@ -0,0 +1,69 @@ +// +build darwin freebsd openbsd + +package tun + +import ( + "golang.zx2c4.com/wireguard/tun" + + "github.com/xjasonlyu/clash/common/pool" + "github.com/xjasonlyu/tun2socks/pkg/link/rwc" +) + +const offset = 4 + +type unixTun struct { + *rwc.Endpoint + + device tun.Device +} + +func CreateTUN(name string, n uint32) (Device, error) { + device, err := tun.CreateTUN(name, int(n)) + if err != nil { + return nil, err + } + + mtu, err := device.MTU() + if err != nil { + return nil, err + } + + ut := &unixTun{ + device: device, + } + + if ut.Endpoint, err = rwc.New(ut, uint32(mtu)); err != nil { + return nil, err + } + + return ut, nil +} + +func (t *unixTun) Read(packet []byte) (n int, err error) { + buf := pool.Get(offset + len(packet)) + defer pool.Put(buf) + + if n, err = t.device.Read(buf, offset); err != nil { + return + } + + copy(packet, buf[offset:offset+n]) + return +} + +func (t *unixTun) Write(packet []byte) (int, error) { + buf := pool.Get(offset + len(packet)) + defer pool.Put(buf) + + copy(buf[offset:], packet) + return t.device.Write(buf[:offset+len(packet)], offset) +} + +func (t *unixTun) Name() string { + name, _ := t.device.Name() + return name +} + +func (t *unixTun) Close() error { + return t.device.Close() +}