Implement TUN, no ACL support yet

This commit is contained in:
Haruue Icymoon 2021-04-24 20:18:58 +08:00
parent 89b9ec2acf
commit 13ec19cbfe
No known key found for this signature in database
GPG key ID: F6083B28CBCBC148
9 changed files with 266 additions and 1 deletions

View file

@ -245,6 +245,15 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452
"cert": "/home/ubuntu/my_cert.crt", // Cert file (HTTPS proxy)
"key": "/home/ubuntu/my_key.crt" // Key file (HTTPS proxy)
},
"tun": {
"name": "tun-hy", // TUN interface name
"timeout": 300, // UDP timeout in seconds
"address": "192.0.2.2", // TUN interface address, not applicable for Linux
"gateway": "192.0.2.1", // TUN interface gateway, not applicable for Linux
"mask": "255.255.255.252", // TUN interface mask, not applicable for Linux
"dns": [ "8.8.8.8", "8.8.4.4" ], // TUN interface DNS, only applicable for Windows
"persist": false // Persist TUN interface after exit, only applicable for Linux
},
"relay_tcp": {
"listen": "127.0.0.1:2222", // TCP relay listen address
"remote": "123.123.123.123:22", // TCP relay remote address

View file

@ -232,6 +232,15 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452
"cert": "/home/ubuntu/my_cert.crt", // 证书 (变为 HTTPS 代理)
"key": "/home/ubuntu/my_key.crt" // 证书密钥 (变为 HTTPS 代理)
},
"tun": {
"name": "tun-hy", // TUN 接口名称
"timeout": 300, // UDP 超时秒数
"address": "192.0.2.2", // TUN 接口地址(不适用于 Linux)
"gateway": "192.0.2.1", // TUN 接口网关(不适用于 Linux)
"mask": "255.255.255.252", // TUN 接口子网掩码(不适用于 Linux)
"dns": [ "8.8.8.8", "8.8.4.4" ], // TUN 接口 DNS 服务器(仅适用于 Windows)
"persist": false // 在程序退出之后保留接口(仅适用于 Linux)
},
"relay_tcp": {
"listen": "127.0.0.1:2222", // TCP 转发监听地址
"remote": "123.123.123.123:22", // TCP 转发目标地址

View file

@ -15,6 +15,7 @@ import (
"github.com/tobyxdd/hysteria/pkg/socks5"
"github.com/tobyxdd/hysteria/pkg/tproxy"
"github.com/tobyxdd/hysteria/pkg/transport"
"github.com/tobyxdd/hysteria/pkg/tun"
"io"
"io/ioutil"
"net"
@ -188,6 +189,17 @@ func client(config *clientConfig) {
}()
}
if len(config.TUN.Name) != 0 {
go func() {
tunServer, err := tun.NewServer(client, time.Duration(config.TUN.Timeout)*time.Second,
config.TUN.Name, config.TUN.Address, config.TUN.Gateway, config.TUN.Mask, config.TUN.DNS, config.TUN.Persist)
if err != nil {
logrus.WithField("error", err).Fatal("Failed to initialize TUN server")
}
errChan <- tunServer.ListenAndServe()
}()
}
if len(config.TCPRelay.Listen) > 0 {
go func() {
rl, err := relay.NewTCPRelay(client, transport.DefaultTransport,

View file

@ -88,6 +88,15 @@ type clientConfig struct {
Cert string `json:"cert"`
Key string `json:"key"`
} `json:"http"`
TUN struct {
Name string `json:"name"`
Timeout int `json:"timeout"`
Address string `json:"address"`
Gateway string `json:"gateway"`
Mask string `json:"mask"`
DNS []string `json:"dns"`
Persist bool `json:"persist"`
} `json:"tun"`
TCPRelay struct {
Listen string `json:"listen"`
Remote string `json:"remote"`
@ -117,7 +126,7 @@ type clientConfig struct {
}
func (c *clientConfig) Check() error {
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 &&
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.TUN.Name) == 0 &&
len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 &&
len(c.TCPTProxy.Listen) == 0 && len(c.UDPTProxy.Listen) == 0 {
return errors.New("no SOCKS5, HTTP, relay or TProxy listen address")
@ -134,6 +143,9 @@ func (c *clientConfig) Check() error {
if c.HTTP.Timeout != 0 && c.HTTP.Timeout <= 4 {
return errors.New("invalid HTTP timeout")
}
if c.TUN.Timeout != 0 && c.TUN.Timeout < 4 {
return errors.New("invalid TUN timeout")
}
if c.TCPRelay.Timeout != 0 && c.TCPRelay.Timeout <= 4 {
return errors.New("invalid TCP relay timeout")
}

1
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/caddyserver/certmagic v0.13.0
github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e
github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e
github.com/eycorsican/go-tun2socks v1.16.11
github.com/hashicorp/golang-lru v0.5.4
github.com/lucas-clemente/quic-go v0.20.1
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40

5
go.sum
View file

@ -78,6 +78,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/eycorsican/go-tun2socks v1.16.11 h1:+hJDNgisrYaGEqoSxhdikMgMJ4Ilfwm/IZDrWRrbaH8=
github.com/eycorsican/go-tun2socks v1.16.11/go.mod h1:wgB2BFT8ZaPKyKOQ/5dljMG/YIow+AIXyq4KBwJ5sGQ=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
@ -369,6 +371,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
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/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
@ -469,6 +473,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=

59
pkg/tun/server.go Normal file
View file

@ -0,0 +1,59 @@
package tun
import (
tun2socks "github.com/eycorsican/go-tun2socks/core"
"github.com/eycorsican/go-tun2socks/tun"
"github.com/tobyxdd/hysteria/pkg/core"
"io"
"sync"
"time"
)
type Server struct {
HyClient *core.Client
Timeout time.Duration
TunDev io.ReadWriteCloser
udpConnMap map[tun2socks.UDPConn]*UDPConnInfo
udpConnMapLock sync.RWMutex
}
const (
MTU = 1500
)
func NewServerWithTunDev(hyClient *core.Client, timeout time.Duration,
tunDev io.ReadWriteCloser) (*Server, error) {
s := &Server{
HyClient: hyClient,
Timeout: timeout,
TunDev: tunDev,
udpConnMap: make(map[tun2socks.UDPConn]*UDPConnInfo),
}
return s, nil
}
func NewServer(hyClient *core.Client, timeout time.Duration,
name, address, gateway, mask string, dnsServers []string, persist bool) (*Server, error) {
tunDev, err := tun.OpenTunDevice(name, address, gateway, mask, dnsServers, persist)
if err != nil {
return nil, err
}
return NewServerWithTunDev(hyClient, timeout, tunDev)
}
func (s *Server) ListenAndServe() error {
lwipWriter := tun2socks.NewLWIPStack().(io.Writer)
tun2socks.RegisterTCPConnHandler(s)
tun2socks.RegisterUDPConnHandler(s)
tun2socks.RegisterOutputFn(func(data []byte) (int, error) {
return s.TunDev.Write(data)
})
_, err := io.CopyBuffer(lwipWriter, s.TunDev, make([]byte, MTU))
if err != nil {
return err
}
return nil
}

73
pkg/tun/tcp.go Normal file
View file

@ -0,0 +1,73 @@
package tun
import (
"io"
"net"
)
func (s *Server) Handle(conn net.Conn, target *net.TCPAddr) error {
hyConn, err := s.HyClient.DialTCP(target.String())
if err != nil {
return err
}
go s.relay(conn, hyConn)
return nil
}
type direction byte
const (
directionUplink direction = iota
directionDownlink
)
type duplexConn interface {
net.Conn
CloseRead() error
CloseWrite() error
}
func (s *Server) relay(clientConn, relayConn net.Conn) {
uplinkDone := make(chan struct{})
halfCloseConn := func(dir direction, interrupt bool) {
clientDuplexConn, ok1 := clientConn.(duplexConn)
relayDuplexConn, ok2 := relayConn.(duplexConn)
if !interrupt && ok1 && ok2 {
switch dir {
case directionUplink:
clientDuplexConn.CloseRead()
relayDuplexConn.CloseWrite()
case directionDownlink:
clientDuplexConn.CloseWrite()
relayDuplexConn.CloseRead()
}
} else {
clientConn.Close()
relayConn.Close()
}
}
// Uplink
go func() {
var err error
_, err = io.Copy(relayConn, clientConn)
if err != nil {
halfCloseConn(directionUplink, true)
} else {
halfCloseConn(directionUplink, false)
}
uplinkDone <- struct{}{}
}()
// Downlink
var err error
_, err = io.Copy(clientConn, relayConn)
if err != nil {
halfCloseConn(directionDownlink, true)
} else {
halfCloseConn(directionDownlink, false)
}
<-uplinkDone
}

85
pkg/tun/udp.go Normal file
View file

@ -0,0 +1,85 @@
package tun
import (
"fmt"
tun2socks "github.com/eycorsican/go-tun2socks/core"
"github.com/tobyxdd/hysteria/pkg/core"
"log"
"net"
"sync/atomic"
"time"
)
type UDPConnInfo struct {
hyConn core.UDPConn
expire atomic.Value
}
func (s *Server) fetchUDPInput(conn tun2socks.UDPConn, ci *UDPConnInfo) {
defer func() {
s.closeUDPConn(conn)
}()
if s.Timeout > 0 {
go func() {
for {
life := ci.expire.Load().(time.Time).Sub(time.Now())
if life < 0 {
s.closeUDPConn(conn)
break
} else {
time.Sleep(life)
}
}
}()
}
for {
bs, from, err := ci.hyConn.ReadFrom()
if err != nil {
break
}
ci.expire.Store(time.Now().Add(s.Timeout))
udpAddr, _ := net.ResolveUDPAddr("udp", from)
_, _ = conn.WriteFrom(bs, udpAddr)
}
}
func (s *Server) Connect(conn tun2socks.UDPConn, target *net.UDPAddr) error {
c, err := s.HyClient.DialUDP()
if err != nil {
return err
}
ci := UDPConnInfo{
hyConn: c,
}
ci.expire.Store(time.Now().Add(s.Timeout))
s.udpConnMapLock.Lock()
s.udpConnMap[conn] = &ci
s.udpConnMapLock.Unlock()
go s.fetchUDPInput(conn, &ci)
return nil
}
func (s *Server) ReceiveTo(conn tun2socks.UDPConn, data []byte, addr *net.UDPAddr) error {
s.udpConnMapLock.RLock()
ci, ok := s.udpConnMap[conn]
s.udpConnMapLock.RUnlock()
if !ok {
log.Printf("not connected: %s <-> %s\n", conn.LocalAddr().String(), addr.String())
return fmt.Errorf("not connected: %s <-> %s", conn.LocalAddr().String(), addr.String())
}
ci.expire.Store(time.Now().Add(s.Timeout))
_ = ci.hyConn.WriteTo(data, addr.String())
return nil
}
func (s *Server) closeUDPConn(conn tun2socks.UDPConn) {
conn.Close()
s.udpConnMapLock.Lock()
defer s.udpConnMapLock.Unlock()
if c, ok := s.udpConnMap[conn]; ok {
c.hyConn.Close()
delete(s.udpConnMap, conn)
}
}