From bde4217ca979ec067a038d493df92ba8feb5251f Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Wed, 1 Jan 2020 21:30:36 +0800 Subject: [PATCH] add support for tap device --- cmd/gost/route.go | 10 + handler.go | 8 + node.go | 4 +- tun.go => tuntap.go | 307 ++++++++++++++++++++++++---- tun_unix.go => tuntap_darwin.go | 10 +- tun_linux.go => tuntap_linux.go | 66 +++++- tuntap_unix.go | 102 +++++++++ tun_windows.go => tuntap_windows.go | 47 ++++- 8 files changed, 504 insertions(+), 50 deletions(-) rename tun.go => tuntap.go (53%) rename tun_unix.go => tuntap_darwin.go (87%) rename tun_linux.go => tuntap_linux.go (52%) create mode 100644 tuntap_unix.go rename tun_windows.go => tuntap_windows.go (60%) diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 836c010..265bb74 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -396,6 +396,8 @@ func (r *route) GenRouters() ([]router, error) { ln, err = gost.ObfsHTTPListener(node.Addr) case "tun": ln, err = gost.TunListener(node.Addr) + case "tap": + ln, err = gost.TapListener(node.Addr) default: ln, err = gost.TCPListener(node.Addr) } @@ -441,6 +443,14 @@ func (r *route) GenRouters() ([]router, error) { Routes: strings.Split(node.Get("route"), ","), } handler = gost.TunHandler(node.Remote, gost.TunConfigHandlerOption(cfg)) + case "tap": + cfg := gost.TapConfig{ + Name: node.Get("name"), + Addr: node.Get("net"), + MTU: node.GetInt("mtu"), + Routes: strings.Split(node.Get("route"), ","), + } + handler = gost.TapHandler(node.Remote, gost.TapConfigHandlerOption(cfg)) default: // start from 2.5, if remote is not empty, then we assume that it is a forward tunnel. if node.Remote != "" { diff --git a/handler.go b/handler.go index 11c80e1..d7a8240 100644 --- a/handler.go +++ b/handler.go @@ -41,6 +41,7 @@ type HandlerOptions struct { Host string IPs []string TunConfig TunConfig + TapConfig TapConfig } // HandlerOption allows a common way to set handler options. @@ -203,6 +204,13 @@ func TunConfigHandlerOption(cfg TunConfig) HandlerOption { } } +// TapConfigHandlerOption sets the config for tap device. +func TapConfigHandlerOption(cfg TapConfig) HandlerOption { + return func(opts *HandlerOptions) { + opts.TapConfig = cfg + } +} + type autoHandler struct { options *HandlerOptions } diff --git a/node.go b/node.go index 3a94dd3..f5d8512 100644 --- a/node.go +++ b/node.go @@ -82,7 +82,7 @@ func ParseNode(s string) (node Node, err error) { case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding case "ohttp": // obfs-http - case "tun": //tun device + case "tun", "tap": // tun/tap device default: node.Transport = "tcp" } @@ -94,7 +94,7 @@ func ParseNode(s string) (node Node, err error) { case "tcp", "udp", "rtcp", "rudp": // port forwarding case "direct", "remote", "forward": // forwarding case "redirect": // TCP transparent proxy - case "tun": // tun device + case "tun", "tap": // tun/tap device default: node.Protocol = "" } diff --git a/tun.go b/tuntap.go similarity index 53% rename from tun.go rename to tuntap.go index f6252da..73ecace 100644 --- a/tun.go +++ b/tuntap.go @@ -11,6 +11,7 @@ import ( "github.com/go-log/log" "github.com/shadowsocks/go-shadowsocks2/core" "github.com/songgao/water" + "github.com/songgao/water/waterutil" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) @@ -227,43 +228,6 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A return err } -type tunConn struct { - ifce *water.Interface - addr net.Addr -} - -func (c *tunConn) Read(b []byte) (n int, err error) { - return c.ifce.Read(b) -} - -func (c *tunConn) Write(b []byte) (n int, err error) { - return c.ifce.Write(b) -} - -func (c *tunConn) Close() (err error) { - return c.ifce.Close() -} - -func (c *tunConn) LocalAddr() net.Addr { - return c.addr -} - -func (c *tunConn) RemoteAddr() net.Addr { - return &net.IPAddr{} -} - -func (c *tunConn) SetDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} -} - -func (c *tunConn) SetReadDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} -} - -func (c *tunConn) SetWriteDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} -} - type tunListener struct { addr net.Addr conns chan net.Conn @@ -318,3 +282,272 @@ func (l *tunListener) Close() error { } return nil } + +type TapConfig struct { + Name string + Addr string + MTU int + Routes []string +} + +type tapHandler struct { + raddr string + options *HandlerOptions + ipNet *net.IPNet + routes sync.Map +} + +// TapHandler creates a handler for tap tunnel. +func TapHandler(raddr string, opts ...HandlerOption) Handler { + h := &tapHandler{ + raddr: raddr, + options: &HandlerOptions{}, + } + for _, opt := range opts { + opt(h.options) + } + + return h +} + +func (h *tapHandler) Init(options ...HandlerOption) { + if h.options == nil { + h.options = &HandlerOptions{} + } + for _, opt := range options { + opt(h.options) + } +} + +func (h *tapHandler) Handle(conn net.Conn) { + defer os.Exit(0) + defer conn.Close() + + uc, ok := conn.(net.PacketConn) + if !ok { + log.Log("[tap] wrong connection type, must be PacketConn") + return + } + + tc, err := h.createTap() + if err != nil { + log.Logf("[tap] %s create tap: %v", conn.LocalAddr(), err) + return + } + defer tc.Close() + + log.Logf("[tap] %s - %s: tap creation successful", tc.LocalAddr(), conn.LocalAddr()) + + var raddr net.Addr + if h.raddr != "" { + raddr, err = net.ResolveUDPAddr("udp", h.raddr) + if err != nil { + log.Logf("[tap] %s - %s remote addr: %v", tc.LocalAddr(), conn.LocalAddr(), err) + return + } + } + + if len(h.options.Users) > 0 && h.options.Users[0] != nil { + passwd, _ := h.options.Users[0].Password() + cipher, err := core.PickCipher(h.options.Users[0].Username(), nil, passwd) + if err != nil { + log.Logf("[tap] %s - %s cipher: %v", tc.LocalAddr(), conn.LocalAddr(), err) + return + } + uc = cipher.PacketConn(uc) + } + + h.transportTap(tc, uc, raddr) +} + +func (h *tapHandler) createTap() (conn net.Conn, err error) { + conn, h.ipNet, err = createTap(h.options.TapConfig) + return +} + +func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.Addr) error { + errc := make(chan error, 1) + + go func() { + for { + err := func() error { + b := sPool.Get().([]byte) + defer sPool.Put(b) + + n, err := tap.Read(b) + if err != nil { + return err + } + + macSrc := waterutil.MACSource(b[:n]) + macDst := waterutil.MACDestination(b[:n]) + + addr := raddr + if v, ok := h.routes.Load(macDst.String()); ok { + addr = v.(net.Addr) + } + if addr == nil { + log.Logf("[tap] %s: no route for %s -> %s %d %d", + tap.LocalAddr(), macSrc, macDst, n, waterutil.MACEthertype(b[:n])) + return nil + } + + if Debug { + log.Logf("[tap] %s >>> %s: %s -> %s %d %d", + tap.LocalAddr(), addr, macSrc, macDst, n, waterutil.MACEthertype(b[:n])) + } + + if _, err := conn.WriteTo(b[:n], addr); err != nil { + return err + } + return nil + }() + + if err != nil { + errc <- err + return + } + } + }() + + go func() { + for { + err := func() error { + b := sPool.Get().([]byte) + defer mPool.Put(b) + + n, addr, err := conn.ReadFrom(b) + if err != nil { + return err + } + + macSrc := waterutil.MACSource(b[:n]) + macDst := waterutil.MACDestination(b[:n]) + + if Debug { + log.Logf("[tap] %s <<< %s: %s -> %s %d %d", + tap.LocalAddr(), addr, macSrc, macDst, n, waterutil.MACEthertype(b[:n])) + } + + if actual, loaded := h.routes.LoadOrStore(macSrc.String(), addr); loaded { + if actual.(net.Addr).String() != addr.String() { + log.Logf("[tap] %s <- %s: update route: %s -> %s (old %s)", + tap.LocalAddr(), addr, macSrc, addr, actual.(net.Addr)) + h.routes.Store(macSrc.String(), addr) + } + } else { + log.Logf("[tap] %s: new route: %s -> %s", tap.LocalAddr(), macSrc, addr) + } + + if _, err := tap.Write(b[:n]); err != nil { + return err + } + return nil + }() + + if err != nil { + errc <- err + return + } + } + }() + + err := <-errc + if err != nil && err == io.EOF { + err = nil + } + log.Logf("[tap] %s - %s: %v", tap.LocalAddr(), conn.LocalAddr(), err) + return err +} + +type tapListener struct { + addr net.Addr + conns chan net.Conn + closed chan struct{} +} + +// TapListener creates a listener for tap tunnel. +func TapListener(addr string) (Listener, error) { + laddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + + threads := 1 + ln := &tapListener{ + addr: laddr, + conns: make(chan net.Conn, threads), + closed: make(chan struct{}), + } + + for i := 0; i < threads; i++ { + conn, err := net.ListenUDP("udp", laddr) + if err != nil { + return nil, err + } + ln.conns <- conn + } + + return ln, nil +} + +func (l *tapListener) Accept() (net.Conn, error) { + select { + case conn := <-l.conns: + return conn, nil + case <-l.closed: + } + + return nil, errors.New("accept on closed listener") +} + +func (l *tapListener) Addr() net.Addr { + return l.addr +} + +func (l *tapListener) Close() error { + select { + case <-l.closed: + return errors.New("listener has been closed") + default: + close(l.closed) + } + return nil +} + +type tunTapConn struct { + ifce *water.Interface + addr net.Addr +} + +func (c *tunTapConn) Read(b []byte) (n int, err error) { + return c.ifce.Read(b) +} + +func (c *tunTapConn) Write(b []byte) (n int, err error) { + return c.ifce.Write(b) +} + +func (c *tunTapConn) Close() (err error) { + return c.ifce.Close() +} + +func (c *tunTapConn) LocalAddr() net.Addr { + return c.addr +} + +func (c *tunTapConn) RemoteAddr() net.Addr { + return &net.IPAddr{} +} + +func (c *tunTapConn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *tunTapConn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *tunTapConn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} diff --git a/tun_unix.go b/tuntap_darwin.go similarity index 87% rename from tun_unix.go rename to tuntap_darwin.go index 45e3706..4bf0ed2 100644 --- a/tun_unix.go +++ b/tuntap_darwin.go @@ -1,8 +1,7 @@ -// +build !linux,!windows - package gost import ( + "errors" "fmt" "net" "os/exec" @@ -42,13 +41,18 @@ func createTun(cfg TunConfig) (conn net.Conn, ipNet *net.IPNet, err error) { return } - conn = &tunConn{ + conn = &tunTapConn{ ifce: ifce, addr: &net.IPAddr{IP: ip}, } return } +func createTap(cfg TapConfig) (conn net.Conn, ipNet *net.IPNet, err error) { + err = errors.New("tap is not supported on darwin") + return +} + func addRoutes(ifName string, routes ...string) error { for _, route := range routes { if route == "" { diff --git a/tun_linux.go b/tuntap_linux.go similarity index 52% rename from tun_linux.go rename to tuntap_linux.go index e2764a6..135d265 100644 --- a/tun_linux.go +++ b/tuntap_linux.go @@ -57,24 +57,82 @@ func createTun(cfg TunConfig) (conn net.Conn, ipNet *net.IPNet, err error) { return } - if err = addRoutes(ifce.Name(), cfg.Routes...); err != nil { + if err = addRoutes("tun", ifce.Name(), cfg.Routes...); err != nil { return } - conn = &tunConn{ + conn = &tunTapConn{ ifce: ifce, addr: &net.IPAddr{IP: ip}, } return } -func addRoutes(ifName string, routes ...string) error { +func createTap(cfg TapConfig) (conn net.Conn, ipNet *net.IPNet, err error) { + ip, ipNet, err := net.ParseCIDR(cfg.Addr) + if err != nil { + return + } + + ifce, err := water.New(water.Config{ + DeviceType: water.TAP, + PlatformSpecificParams: water.PlatformSpecificParams{ + Name: cfg.Name, + }, + }) + if err != nil { + return + } + + link, err := tenus.NewLinkFrom(ifce.Name()) + if err != nil { + return + } + + mtu := cfg.MTU + if mtu <= 0 { + mtu = DefaultMTU + } + + cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu) + log.Log("[tap]", cmd) + if er := link.SetLinkMTU(mtu); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name()) + log.Log("[tap]", cmd) + if er := link.SetLinkIp(ip, ipNet); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name()) + log.Log("[tap]", cmd) + if er := link.SetLinkUp(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + if err = addRoutes("tap", ifce.Name(), cfg.Routes...); err != nil { + return + } + + conn = &tunTapConn{ + ifce: ifce, + addr: &net.IPAddr{IP: ip}, + } + return +} + +func addRoutes(ifType, ifName string, routes ...string) error { for _, route := range routes { if route == "" { continue } cmd := fmt.Sprintf("ip route add %s dev %s", route, ifName) - log.Log("[tun]", cmd) + log.Logf("[%s] %s", ifType, cmd) if err := netlink.AddRoute(route, "", "", ifName); err != nil { return fmt.Errorf("%s: %v", cmd, err) } diff --git a/tuntap_unix.go b/tuntap_unix.go new file mode 100644 index 0000000..e7a3f34 --- /dev/null +++ b/tuntap_unix.go @@ -0,0 +1,102 @@ +// +build !linux,!windows,!darwin + +package gost + +import ( + "fmt" + "net" + "os/exec" + "strings" + + "github.com/go-log/log" + "github.com/songgao/water" +) + +func createTun(cfg TunConfig) (conn net.Conn, ipNet *net.IPNet, err error) { + ip, ipNet, err := net.ParseCIDR(cfg.Addr) + if err != nil { + return + } + + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) + if err != nil { + return + } + + mtu := cfg.MTU + if mtu <= 0 { + mtu = DefaultMTU + } + + cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) + log.Log("[tun]", cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + if err = addRoutes("tun", ifce.Name(), cfg.Routes...); err != nil { + return + } + + conn = &tunTapConn{ + ifce: ifce, + addr: &net.IPAddr{IP: ip}, + } + return +} + +func createTap(cfg TapConfig) (conn net.Conn, ipNet *net.IPNet, err error) { + ip, ipNet, err := net.ParseCIDR(cfg.Addr) + if err != nil { + return + } + + ifce, err := water.New(water.Config{ + DeviceType: water.TAP, + }) + if err != nil { + return + } + + mtu := cfg.MTU + if mtu <= 0 { + mtu = DefaultMTU + } + + cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) + log.Log("[tap]", cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + if err = addRoutes("tap", ifce.Name(), cfg.Routes...); err != nil { + return + } + + conn = &tunTapConn{ + ifce: ifce, + addr: &net.IPAddr{IP: ip}, + } + return +} + +func addRoutes(ifType, ifName string, routes ...string) error { + for _, route := range routes { + if route == "" { + continue + } + cmd := fmt.Sprintf("route add -net %s -interface %s", route, ifName) + log.Logf("[%s] %s", ifType, cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + return fmt.Errorf("%s: %v", cmd, er) + } + } + return nil +} diff --git a/tun_windows.go b/tuntap_windows.go similarity index 60% rename from tun_windows.go rename to tuntap_windows.go index 3aa71ea..b3d447e 100644 --- a/tun_windows.go +++ b/tuntap_windows.go @@ -38,18 +38,57 @@ func createTun(cfg TunConfig) (conn net.Conn, ipNet *net.IPNet, err error) { return } - if err = addRoutes(ifce.Name(), cfg.Routes...); err != nil { + if err = addRoutes("tun", ifce.Name(), cfg.Routes...); err != nil { return } - conn = &tunConn{ + conn = &tunTapConn{ ifce: ifce, addr: &net.IPAddr{IP: ip}, } return } -func addRoutes(ifName string, routes ...string) error { +func createTap(cfg TapConfig) (conn net.Conn, ipNet *net.IPNet, err error) { + ip, ipNet, err := net.ParseCIDR(cfg.Addr) + if err != nil { + return + } + + ifce, err := water.New(water.Config{ + DeviceType: water.TAP, + PlatformSpecificParams: water.PlatformSpecificParams{ + ComponentID: "tap0901", + InterfaceName: cfg.Name, + Network: cfg.Addr, + }, + }) + if err != nil { + return + } + + cmd := fmt.Sprintf("netsh interface ip set address name=%s "+ + "source=static addr=%s mask=%s gateway=none", + ifce.Name(), ip.String(), ipMask(ipNet.Mask)) + log.Log("[tap]", cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + + if err = addRoutes("tap", ifce.Name(), cfg.Routes...); err != nil { + return + } + + conn = &tunTapConn{ + ifce: ifce, + addr: &net.IPAddr{IP: ip}, + } + return +} + +func addRoutes(ifType, ifName string, routes ...string) error { for _, route := range routes { if route == "" { continue @@ -59,7 +98,7 @@ func addRoutes(ifName string, routes ...string) error { cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", route, ifName) - log.Log("[tun]", cmd) + log.Logf("[%s] %s", ifType, cmd) args := strings.Split(cmd, " ") if er := exec.Command(args[0], args[1:]...).Run(); er != nil { return fmt.Errorf("%s: %v", cmd, er)