Skip to content

Commit

Permalink
Add an ability to manage tun routes
Browse files Browse the repository at this point in the history
  • Loading branch information
kayrus committed Dec 8, 2020
1 parent 301549c commit a693670
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ BUILD_CMD="cd $(CMDDIR) && $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BUILDDIR)
XBUILD_LINUX_CMD="cd $(BUILDDIR) && $(XGOCMD) -ldflags $(STATIC_RELEASE_LDFLAGS) -tags '$(BUILD_TAGS)' --targets=linux/* $(CMDDIR)"
XBUILD_OTHERS_CMD="cd $(BUILDDIR) && $(XGOCMD) -ldflags $(RELEASE_LDFLAGS) -tags '$(BUILD_TAGS)' --targets=darwin/*,windows/*,android/*,ios/* $(CMDDIR)"

.PHONY: build

all: build

build:
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ The following projects are using go-tun2socks:

- https://github.com/mellow-io/mellow
- https://github.com/eycorsican/kitsunebi-android

## Quick start

Create an ssh SOCKS proxy:

```sh
ssh -D 1234 -C -N [email protected]
```

Create a tunnel:

```sh
sudo tun2socks -proxyServer 127.0.0.1:1234 -routes 10.0.0.0/8,172.16.0.0/12 -exclude example.com,10.0.0.1 -loglevel debug
```
18 changes: 17 additions & 1 deletion cmd/tun2socks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/eycorsican/go-tun2socks/common/log"
_ "github.com/eycorsican/go-tun2socks/common/log/simple" // Register a simple logger.
"github.com/eycorsican/go-tun2socks/core"
"github.com/eycorsican/go-tun2socks/routes"
"github.com/eycorsican/go-tun2socks/tun"
)

Expand Down Expand Up @@ -48,6 +49,8 @@ type CmdArgs struct {
UdpTimeout *time.Duration
LogLevel *string
DnsFallback *bool
Routes *string
Exclude *string
}

type cmdFlag uint
Expand Down Expand Up @@ -91,12 +94,14 @@ func main() {
args.TunName = flag.String("tunName", "tun1", "TUN interface name")
args.TunAddr = flag.String("tunAddr", "10.255.0.2", "TUN interface address")
args.TunGw = flag.String("tunGw", "10.255.0.1", "TUN interface gateway")
args.TunMask = flag.String("tunMask", "255.255.255.0", "TUN interface netmask, it should be a prefixlen (a number) for IPv6 address")
args.TunMask = flag.String("tunMask", "255.255.255.255", "TUN interface netmask, it should be a prefixlen (a number) for IPv6 address")
args.TunDns = flag.String("tunDns", "8.8.8.8,8.8.4.4", "DNS resolvers for TUN interface (only need on Windows)")
args.TunPersist = flag.Bool("tunPersist", false, "Persist TUN interface after the program exits or the last open file descriptor is closed (Linux only)")
args.BlockOutsideDns = flag.Bool("blockOutsideDns", false, "Prevent DNS leaks by blocking plaintext DNS queries going out through non-TUN interface (may require admin privileges) (Windows only) ")
args.ProxyType = flag.String("proxyType", "socks", "Proxy handler type")
args.LogLevel = flag.String("loglevel", "info", "Logging level. (debug, info, warn, error, none)")
args.Routes = flag.String("routes", "", "Subnets to forward via TUN interface")
args.Exclude = flag.String("exclude", "", "Subnets or hostnames to exclude from forwarding via TUN interface")

flag.Parse()

Expand Down Expand Up @@ -128,13 +133,24 @@ func main() {
panic("unsupport logging level")
}

tunGw, tunRoutes, err := routes.Get(*args.Routes, *args.Exclude, *args.TunAddr, *args.TunGw, *args.TunMask)
if err != nil {
log.Fatalf("cannot parse config values: %v", err)
}

// Open the tun device.
dnsServers := strings.Split(*args.TunDns, ",")
tunDev, err := tun.OpenTunDevice(*args.TunName, *args.TunAddr, *args.TunGw, *args.TunMask, dnsServers, *args.TunPersist)
if err != nil {
log.Fatalf("failed to open tun device: %v", err)
}

// unset routes on exit, when provided
defer routes.Unset(*args.TunName, tunGw, tunRoutes)

// set routes, when provided
routes.Set(*args.TunName, tunGw, tunRoutes)

if runtime.GOOS == "windows" && *args.BlockOutsideDns {
if err := blocker.BlockOutsideDns(*args.TunName); err != nil {
log.Fatalf("failed to block outside DNS: %v", err)
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ module github.com/eycorsican/go-tun2socks
go 1.13

require (
github.com/IBM/netaddr v1.4.0
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b
golang.org/x/net v0.0.0-20191021144547-ec77196f6094
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
github.com/stretchr/testify v1.6.1 // indirect
github.com/vishvananda/netlink v1.1.0
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3
)
32 changes: 28 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
github.com/IBM/netaddr v1.4.0 h1:6T+fAXurIdgA9+nVlehOOkhhxmzU2MTxtok7qNldDVo=
github.com/IBM/netaddr v1.4.0/go.mod h1:eXOsTXDZemcAXuuddrfHcWdA0on76PJwaH37JnS5PKE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
115 changes: 115 additions & 0 deletions routes/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package routes

import (
"fmt"
"net"
"strings"

"github.com/IBM/netaddr"
"github.com/eycorsican/go-tun2socks/common/log"
)

func splitFunc(c rune) bool {
return c == ',' || c == ' '
}

func getNet(v interface{}) *net.IPNet {
switch v := v.(type) {
case net.IP:
return &net.IPNet{IP: v, Mask: net.CIDRMask(32, 32)}
case *net.IPNet:
return v
}
return nil
}

func Get(routes, excludeRoutes, addr, gw, mask string) (net.IP, []*net.IPNet, error) {
excludeAddrs, err := ParseAddresses(addr, gw, mask)
if err != nil {
return nil, nil, fmt.Errorf("invalid addresses: %v", err)
}

res := &netaddr.IPSet{}
for _, cidr := range strings.FieldsFunc(routes, splitFunc) {
if v := net.ParseIP(cidr).To4(); v != nil {
res.InsertNet(getNet(v))
continue
}

_, v, err := net.ParseCIDR(cidr)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse %s CIDR: %v", cidr, err)
}
res.InsertNet(v)
}

for _, cidr := range strings.FieldsFunc(excludeRoutes, splitFunc) {
if v := net.ParseIP(cidr).To4(); v != nil {
res.RemoveNet(getNet(v))
log.Debugf("excluding %s from routes", v)
continue
}

_, v, err := net.ParseCIDR(cidr)
if err != nil {
// trying to lookup a hostname
if ips, err := net.LookupIP(cidr); err == nil {
for _, v := range ips {
if v := v.To4(); v != nil {
log.Debugf("excluding %s (%s) from routes", cidr, v)
res.RemoveNet(getNet(v))
}
}
continue
} else {
return nil, nil, fmt.Errorf("failed to resolve %q: %v", cidr, err)
}
return nil, nil, fmt.Errorf("failed to parse %s CIDR: %v", cidr, err)
}
log.Debugf("excluding %s from routes", v)
res.RemoveNet(v)
}

for _, cidr := range excludeAddrs {
res.RemoveNet(cidr)
}

gateway := excludeAddrs[1]
return gateway.IP, res.GetNetworks(), nil
}

func Set(name string, gw net.IP, routes []*net.IPNet) {
for _, cidr := range routes {
if err := routeAdd(cidr, gw, 0, name); err != nil {
log.Errorf("failed to set %s routes: %v", name, err)
}
}
}

func Unset(name string, gw net.IP, routes []*net.IPNet) {
for _, cidr := range routes {
if err := routeDel(cidr, gw, 0, name); err != nil {
log.Errorf("failed to unset %s routes: %v", name, err)
}
}
}

func ParseAddresses(addr, gw, mask string) ([]*net.IPNet, error) {
local := net.ParseIP(addr)
if local == nil {
return nil, fmt.Errorf("invalid local IP address")
}
remote := net.ParseIP(gw)
if remote == nil {
return nil, fmt.Errorf("invalid server IP address")
}
remoteMask := net.ParseIP(mask)
if remoteMask == nil {
return nil, fmt.Errorf("invalid server IP mask")
}

return []*net.IPNet{
getNet(local),
&net.IPNet{IP: remote, Mask: net.IPMask(remoteMask.To4())},
}, nil
}
47 changes: 47 additions & 0 deletions routes/routes_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package routes

import (
"fmt"
"net"
"os/exec"
)

func routeAdd(dst interface{}, gw net.IP, priority int, iface string) error {
// an implementation of "replace"
routeDel(dst, gw, priority, iface)
args := []string{
"-n",
"add",
"-net",
getNet(dst).String(),
}
if gw == nil {
args = append(args, "-interface", iface)
} else {
args = append(args, gw.String())
}
v, err := exec.Command("route", args...).Output()
if err != nil {
return fmt.Errorf("failed to add %s route to %s interface: %s: %s", dst, iface, v, err)
}
return nil
}

func routeDel(dst interface{}, gw net.IP, priority int, iface string) error {
args := []string{
"-n",
"delete",
"-net",
getNet(dst).String(),
}
if gw == nil {
args = append(args, "-interface", iface)
} else {
args = append(args, gw.String())
}
v, err := exec.Command("route", args...).Output()
if err != nil {
return fmt.Errorf("failed to delete %s route from %s interface: %s: %s", dst, iface, v, err)
}
return nil
}
46 changes: 46 additions & 0 deletions routes/routes_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package routes

import (
"fmt"
"net"

"github.com/vishvananda/netlink"
)

func routeAdd(dst interface{}, gw net.IP, priority int, iface string) error {
route := netlink.Route{
Dst: getNet(dst),
Priority: priority,
Gw: gw,
}
if gw == nil {
link, err := netlink.LinkByName(iface)
if err != nil {
return fmt.Errorf("failed to get %q interface by name: %s", iface, err)
}
route.LinkIndex = link.Attrs().Index
}
if err := netlink.RouteReplace(&route); err != nil {
return fmt.Errorf("failed to add %s route to %q interface: %s", dst, iface, err)
}
return nil
}

func routeDel(dst interface{}, gw net.IP, priority int, iface string) error {
route := netlink.Route{
Dst: getNet(dst),
Priority: priority,
Gw: gw,
}
if gw == nil {
link, err := netlink.LinkByName(iface)
if err != nil {
return fmt.Errorf("failed to get %q interface by name: %s", iface, err)
}
route.LinkIndex = link.Attrs().Index
}
if err := netlink.RouteDel(&route); err != nil {
return fmt.Errorf("failed to delete %s route from %q interface: %s", dst, iface, err)
}
return nil
}
43 changes: 43 additions & 0 deletions routes/routes_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package routes

import (
"fmt"
"net"
"os/exec"
)

func routeAdd(dst interface{}, gw net.IP, priority int, iface string) error {
// an implementation of "replace"
routeDel(dst, gw, priority, iface)
d := getNet(dst)
args := []string{
"add",
d.IP.String(),
net.IP(d.Mask).To4().String(),
gw.String(),
"metric",
fmt.Sprintf("%d", priority),
}
v, err := exec.Command("route", args...).Output()
if err != nil {
return fmt.Errorf("failed to add %s route to %s interface: %s: %s", dst, iface, v, err)
}
return nil
}

func routeDel(dst interface{}, gw net.IP, priority int, iface string) error {
d := getNet(dst)
args := []string{
"delete",
d.IP.String(),
net.IP(d.Mask).To4().String(),
gw.String(),
"metric",
fmt.Sprintf("%d", priority),
}
v, err := exec.Command("route", args...).Output()
if err != nil {
return fmt.Errorf("failed to delete %s route from %s interface: %s: %s", dst, iface, v, err)
}
return nil
}
Loading

0 comments on commit a693670

Please sign in to comment.