diff --git a/.travis.yml b/.travis.yml index f66096e..44a0483 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ sudo: false language: go go: - - 1.10.x - 1.11.x + - 1.12.x install: - make diff --git a/README.md b/README.md index d119713..29d63cb 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ Now it also try to support p2p connect. * [Configuration File](#configuration-file) * [Configuration file template](#configuration-file-template) * [Dashboard](#dashboard) + * [Admin UI](#admin-ui) * [Authentication](#authentication) * [Encryption and Compression](#encryption-and-compression) + * [TLS](#tls) * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) * [Get proxy status from client](#get-proxy-status-from-client) * [Port White List](#port-white-list) @@ -389,6 +391,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa ![dashboard](/doc/pic/dashboard.png) +### Admin UI + +Admin UI help you check and manage frpc's configure. + +Configure a address for admin UI to enable this feature: + +```ini +[common] +admin_addr = 127.0.0.1 +admin_port = 7400 +admin_user = admin +admin_pwd = admin +``` + +Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`. + ### Authentication `token` in frps.ini and frpc.ini should be same. @@ -407,6 +425,14 @@ use_encryption = true use_compression = true ``` +#### TLS + +frp support TLS protocol between frpc and frps since v0.25.0. + +Config `tls_enable = true` in `common` section to frpc.ini to enable this feature. + +For port multiplexing, frp send a first byte 0x17 to dial a TLS connection. + ### Hot-Reload frpc configuration First you need to set admin port in frpc's configure file to let it provide HTTP API for more features. @@ -592,7 +618,7 @@ custom_domains = test.yourdomain.com host_header_rewrite = dev.yourdomain.com ``` -If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address. +The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server. ### Set Headers In HTTP Request @@ -736,8 +762,6 @@ plugin_http_passwd = abc ## Development Plan * Log http request information in frps. -* Direct reverse proxy, like haproxy. -* kubernetes ingress support. ## Contributing diff --git a/README_zh.md b/README_zh.md index cd705b7..952aeb6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -24,8 +24,10 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp * [配置文件](#配置文件) * [配置文件模版渲染](#配置文件模版渲染) * [Dashboard](#dashboard) + * [Admin UI](#admin-ui) * [身份验证](#身份验证) * [加密与压缩](#加密与压缩) + * [TLS](#tls) * [客户端热加载配置文件](#客户端热加载配置文件) * [客户端查看代理状态](#客户端查看代理状态) * [端口白名单](#端口白名单) @@ -47,6 +49,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp * [开发计划](#开发计划) * [为 frp 做贡献](#为-frp-做贡献) * [捐助](#捐助) + * [知识星球](#知识星球) * [支付宝扫码捐赠](#支付宝扫码捐赠) * [微信支付捐赠](#微信支付捐赠) * [Paypal 捐赠](#paypal-捐赠) @@ -404,6 +407,24 @@ dashboard_pwd = admin ![dashboard](/doc/pic/dashboard.png) +### Admin UI + +Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。 + +需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能: + +```ini +[common] +admin_addr = 127.0.0.1 +admin_port = 7400 +admin_user = admin +admin_pwd = admin +``` + +打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI,用户名密码默认为 `admin`。 + +如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。 + ### 身份验证 服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。 @@ -426,6 +447,14 @@ use_compression = true 如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。 +#### TLS + +从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。 + +为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。 + +**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。** + ### 客户端热加载配置文件 当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。 diff --git a/client/control.go b/client/control.go index 750803f..bbcece6 100644 --- a/client/control.go +++ b/client/control.go @@ -15,6 +15,7 @@ package client import ( + "crypto/tls" "fmt" "io" "runtime/debug" @@ -166,8 +167,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { } conn = frpNet.WrapConn(stream) } else { - conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, - fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) + var tlsConfig *tls.Config + if g.GlbClientCfg.TLSEnable { + tlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } + conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, + fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig) if err != nil { ctl.Warn("start new connection to server error: %v", err) return diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index 4b721aa..55e87a7 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -18,7 +18,10 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net" + "strconv" + "strings" "sync" "time" @@ -33,6 +36,7 @@ import ( "github.com/fatedier/golib/errors" frpIo "github.com/fatedier/golib/io" "github.com/fatedier/golib/pool" + fmux "github.com/hashicorp/yamux" ) // Proxy defines how to handle work connections for different proxy type. @@ -278,32 +282,97 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) { return } - pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) + pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr) - // Send sid to visitor udp address. - time.Sleep(time.Second) + // Send detect message + array := strings.Split(natHoleRespMsg.VisitorAddr, ":") + if len(array) <= 1 { + pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) + } laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String()) - daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr) + /* + for i := 1000; i < 65000; i++ { + pxy.sendDetectMsg(array[0], int64(i), laddr, "a") + } + */ + port, err := strconv.ParseInt(array[1], 10, 64) if err != nil { - pxy.Error("resolve visitor udp address error: %v", err) + pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) return } + pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) + pxy.Trace("send all detect msg done") - lConn, err := net.DialUDP("udp", laddr, daddr) + msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}) + + // Listen for clientConn's address and wait for visitor connection + lConn, err := net.ListenUDP("udp", laddr) if err != nil { - pxy.Error("dial visitor udp address error: %v", err) + pxy.Error("listen on visitorConn's local adress error: %v", err) return } - lConn.Write([]byte(natHoleRespMsg.Sid)) + defer lConn.Close() - kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr) + lConn.SetReadDeadline(time.Now().Add(8 * time.Second)) + sidBuf := pool.GetBuf(1024) + var uAddr *net.UDPAddr + n, uAddr, err = lConn.ReadFromUDP(sidBuf) + if err != nil { + pxy.Warn("get sid from visitor error: %v", err) + return + } + lConn.SetReadDeadline(time.Time{}) + if string(sidBuf[:n]) != natHoleRespMsg.Sid { + pxy.Warn("incorrect sid from visitor") + return + } + pool.PutBuf(sidBuf) + pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid) + + lConn.WriteToUDP(sidBuf[:n], uAddr) + + kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr) if err != nil { pxy.Error("create kcp connection from udp connection error: %v", err) return } + fmuxCfg := fmux.DefaultConfig() + fmuxCfg.KeepAliveInterval = 5 * time.Second + fmuxCfg.LogOutput = ioutil.Discard + sess, err := fmux.Server(kcpConn, fmuxCfg) + if err != nil { + pxy.Error("create yamux server from kcp connection error: %v", err) + return + } + defer sess.Close() + muxConn, err := sess.Accept() + if err != nil { + pxy.Error("accept for yamux connection error: %v", err) + return + } + HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, - frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk)) + frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk)) +} + +func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) { + daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + return err + } + + tConn, err := net.DialUDP("udp", laddr, daddr) + if err != nil { + return err + } + + //uConn := ipv4.NewConn(tConn) + //uConn.SetTTL(3) + + tConn.Write(content) + tConn.Close() + return nil } // UDP diff --git a/client/service.go b/client/service.go index d439705..32106ca 100644 --- a/client/service.go +++ b/client/service.go @@ -15,6 +15,7 @@ package client import ( + "crypto/tls" "fmt" "io/ioutil" "runtime" @@ -151,8 +152,14 @@ func (svr *Service) keepControllerWorking() { // conn: control connection // session: if it's not nil, using tcp mux func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { - conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, - fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) + var tlsConfig *tls.Config + if g.GlbClientCfg.TLSEnable { + tlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } + conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, + fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig) if err != nil { return } diff --git a/client/visitor.go b/client/visitor.go index 1e8ffec..6eb3688 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -18,14 +18,11 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net" - "strconv" - "strings" "sync" "time" - "golang.org/x/net/ipv4" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" @@ -35,6 +32,7 @@ import ( frpIo "github.com/fatedier/golib/io" "github.com/fatedier/golib/pool" + fmux "github.com/hashicorp/yamux" ) // Visitor is used for forward traffics from local port tot remote service. @@ -249,40 +247,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { return } - sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) + sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr) // Close visitorConn, so we can use it's local address. visitorConn.Close() - // Send detect message. - array := strings.Split(natHoleRespMsg.ClientAddr, ":") - if len(array) <= 1 { - sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) - return - } + // send sid message to client laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) - /* - for i := 1000; i < 65000; i++ { - sv.sendDetectMsg(array[0], int64(i), laddr, "a") - } - */ - port, err := strconv.ParseInt(array[1], 10, 64) + daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr) if err != nil { - sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) + sv.Error("resolve client udp address error: %v", err) return } - sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) - sv.Trace("send all detect msg done") + lConn, err := net.DialUDP("udp", laddr, daddr) + if err != nil { + sv.Error("dial client udp address error: %v", err) + return + } + defer lConn.Close() - // Listen for visitorConn's address and wait for client connection. - lConn, err := net.ListenUDP("udp", laddr) - if err != nil { - sv.Error("listen on visitorConn's local adress error: %v", err) - return - } - lConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + lConn.Write([]byte(natHoleRespMsg.Sid)) + + // read ack sid from client sidBuf := pool.GetBuf(1024) - n, _, err = lConn.ReadFromUDP(sidBuf) + lConn.SetReadDeadline(time.Now().Add(8 * time.Second)) + n, err = lConn.Read(sidBuf) if err != nil { sv.Warn("get sid from client error: %v", err) return @@ -292,11 +281,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { sv.Warn("incorrect sid from client") return } - sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n])) pool.PutBuf(sidBuf) + sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid) + + // wrap kcp connection var remote io.ReadWriteCloser - remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr) + remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr) if err != nil { sv.Error("create kcp connection from udp connection error: %v", err) return @@ -314,25 +305,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { remote = frpIo.WithCompression(remote) } - frpIo.Join(userConn, remote) + fmuxCfg := fmux.DefaultConfig() + fmuxCfg.KeepAliveInterval = 5 * time.Second + fmuxCfg.LogOutput = ioutil.Discard + sess, err := fmux.Client(remote, fmuxCfg) + if err != nil { + sv.Error("create yamux session error: %v", err) + return + } + defer sess.Close() + muxConn, err := sess.Open() + if err != nil { + sv.Error("open yamux stream error: %v", err) + return + } + + frpIo.Join(userConn, muxConn) sv.Debug("join connections closed") } - -func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) { - daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port)) - if err != nil { - return err - } - - tConn, err := net.DialUDP("udp", laddr, daddr) - if err != nil { - return err - } - - uConn := ipv4.NewConn(tConn) - uConn.SetTTL(3) - - tConn.Write(content) - tConn.Close() - return nil -} diff --git a/cmd/frpc/sub/xtcp.go b/cmd/frpc/sub/xtcp.go index 0a81e18..c15ac5a 100644 --- a/cmd/frpc/sub/xtcp.go +++ b/cmd/frpc/sub/xtcp.go @@ -68,7 +68,7 @@ var xtcpCmd = &cobra.Command{ if role == "server" { cfg := &config.XtcpProxyConf{} cfg.ProxyName = prefix + proxyName - cfg.ProxyType = consts.StcpProxy + cfg.ProxyType = consts.XtcpProxy cfg.UseEncryption = useEncryption cfg.UseCompression = useCompression cfg.Role = role @@ -84,7 +84,7 @@ var xtcpCmd = &cobra.Command{ } else if role == "visitor" { cfg := &config.XtcpVisitorConf{} cfg.ProxyName = prefix + proxyName - cfg.ProxyType = consts.StcpProxy + cfg.ProxyType = consts.XtcpProxy cfg.UseEncryption = useEncryption cfg.UseCompression = useCompression cfg.Role = role diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index 29ef0e1..60bf859 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -44,6 +44,9 @@ login_fail_exit = true # now it supports tcp and kcp and websocket, default is tcp protocol = tcp +# if tls_enable is true, frpc will connect frps by tls +tls_enable = true + # specify a dns server, so frpc will use this instead of default one # dns_server = 8.8.8.8 diff --git a/go.mod b/go.mod index 3870c2f..fe82c1f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 github.com/gorilla/websocket v1.2.0 - github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0 + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/pkg/errors v0.8.0 // indirect diff --git a/go.sum b/go.sum index 86992a1..e71b602 100644 --- a/go.sum +++ b/go.sum @@ -9,7 +9,8 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= diff --git a/models/config/client_common.go b/models/config/client_common.go index 5dc49aa..1cd9ffc 100644 --- a/models/config/client_common.go +++ b/models/config/client_common.go @@ -44,6 +44,7 @@ type ClientCommonConf struct { LoginFailExit bool `json:"login_fail_exit"` Start map[string]struct{} `json:"start"` Protocol string `json:"protocol"` + TLSEnable bool `json:"tls_enable"` HeartBeatInterval int64 `json:"heartbeat_interval"` HeartBeatTimeout int64 `json:"heartbeat_timeout"` } @@ -69,6 +70,7 @@ func GetDefaultClientConf() *ClientCommonConf { LoginFailExit: true, Start: make(map[string]struct{}), Protocol: "tcp", + TLSEnable: false, HeartBeatInterval: 30, HeartBeatTimeout: 90, } @@ -194,6 +196,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c cfg.Protocol = tmpStr } + if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" { + cfg.TLSEnable = true + } else { + cfg.TLSEnable = false + } + if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") diff --git a/models/msg/msg.go b/models/msg/msg.go index e06fa37..2d5985c 100644 --- a/models/msg/msg.go +++ b/models/msg/msg.go @@ -17,44 +17,46 @@ package msg import "net" const ( - TypeLogin = 'o' - TypeLoginResp = '1' - TypeNewProxy = 'p' - TypeNewProxyResp = '2' - TypeCloseProxy = 'c' - TypeNewWorkConn = 'w' - TypeReqWorkConn = 'r' - TypeStartWorkConn = 's' - TypeNewVisitorConn = 'v' - TypeNewVisitorConnResp = '3' - TypePing = 'h' - TypePong = '4' - TypeUdpPacket = 'u' - TypeNatHoleVisitor = 'i' - TypeNatHoleClient = 'n' - TypeNatHoleResp = 'm' - TypeNatHoleSid = '5' + TypeLogin = 'o' + TypeLoginResp = '1' + TypeNewProxy = 'p' + TypeNewProxyResp = '2' + TypeCloseProxy = 'c' + TypeNewWorkConn = 'w' + TypeReqWorkConn = 'r' + TypeStartWorkConn = 's' + TypeNewVisitorConn = 'v' + TypeNewVisitorConnResp = '3' + TypePing = 'h' + TypePong = '4' + TypeUdpPacket = 'u' + TypeNatHoleVisitor = 'i' + TypeNatHoleClient = 'n' + TypeNatHoleResp = 'm' + TypeNatHoleClientDetectOK = 'd' + TypeNatHoleSid = '5' ) var ( msgTypeMap = map[byte]interface{}{ - TypeLogin: Login{}, - TypeLoginResp: LoginResp{}, - TypeNewProxy: NewProxy{}, - TypeNewProxyResp: NewProxyResp{}, - TypeCloseProxy: CloseProxy{}, - TypeNewWorkConn: NewWorkConn{}, - TypeReqWorkConn: ReqWorkConn{}, - TypeStartWorkConn: StartWorkConn{}, - TypeNewVisitorConn: NewVisitorConn{}, - TypeNewVisitorConnResp: NewVisitorConnResp{}, - TypePing: Ping{}, - TypePong: Pong{}, - TypeUdpPacket: UdpPacket{}, - TypeNatHoleVisitor: NatHoleVisitor{}, - TypeNatHoleClient: NatHoleClient{}, - TypeNatHoleResp: NatHoleResp{}, - TypeNatHoleSid: NatHoleSid{}, + TypeLogin: Login{}, + TypeLoginResp: LoginResp{}, + TypeNewProxy: NewProxy{}, + TypeNewProxyResp: NewProxyResp{}, + TypeCloseProxy: CloseProxy{}, + TypeNewWorkConn: NewWorkConn{}, + TypeReqWorkConn: ReqWorkConn{}, + TypeStartWorkConn: StartWorkConn{}, + TypeNewVisitorConn: NewVisitorConn{}, + TypeNewVisitorConnResp: NewVisitorConnResp{}, + TypePing: Ping{}, + TypePong: Pong{}, + TypeUdpPacket: UdpPacket{}, + TypeNatHoleVisitor: NatHoleVisitor{}, + TypeNatHoleClient: NatHoleClient{}, + TypeNatHoleResp: NatHoleResp{}, + TypeNatHoleClientDetectOK: NatHoleClientDetectOK{}, + TypeNatHoleSid: NatHoleSid{}, } ) @@ -169,6 +171,9 @@ type NatHoleResp struct { Error string `json:"error"` } +type NatHoleClientDetectOK struct { +} + type NatHoleSid struct { Sid string `json:"sid"` } diff --git a/models/nathole/nathole.go b/models/nathole/nathole.go index 1e120ae..0c33dfe 100644 --- a/models/nathole/nathole.go +++ b/models/nathole/nathole.go @@ -18,6 +18,11 @@ import ( // Timeout seconds. var NatHoleTimeout int64 = 10 +type SidRequest struct { + Sid string + NotifyCh chan struct{} +} + type NatHoleController struct { listener *net.UDPConn @@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) return nc, nil } -func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) { +func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) { clientCfg := &NatHoleClientCfg{ Name: name, Sk: sk, - SidCh: make(chan string), + SidCh: make(chan *SidRequest), } nc.mu.Lock() nc.clientCfgs[name] = clientCfg @@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP }() err := errors.PanicToError(func() { - clientCfg.SidCh <- sid + clientCfg.SidCh <- &SidRequest{ + Sid: sid, + NotifyCh: session.NotifyCh, + } }) if err != nil { return @@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd } log.Trace("handle client message, sid [%s]", session.Sid) session.ClientAddr = raddr - session.NotifyCh <- struct{}{} resp := nc.GenNatHoleResponse(session, "") log.Trace("send nat hole response to client") @@ -201,5 +208,5 @@ type NatHoleSession struct { type NatHoleClientCfg struct { Name string Sk string - SidCh chan string + SidCh chan *SidRequest } diff --git a/server/proxy/xtcp.go b/server/proxy/xtcp.go index 9c5f911..8726666 100644 --- a/server/proxy/xtcp.go +++ b/server/proxy/xtcp.go @@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { select { case <-pxy.closeCh: break - case sid := <-sidCh: + case sidRequest := <-sidCh: + sr := sidRequest workConn, errRet := pxy.GetWorkConnFromPool() if errRet != nil { continue } m := &msg.NatHoleSid{ - Sid: sid, + Sid: sr.Sid, } errRet = msg.WriteMsg(workConn, m) if errRet != nil { pxy.Warn("write nat hole sid package error, %v", errRet) + workConn.Close() + break } + + go func() { + raw, errRet := msg.ReadMsg(workConn) + if errRet != nil { + pxy.Warn("read nat hole client ok package error: %v", errRet) + workConn.Close() + return + } + if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok { + pxy.Warn("read nat hole client ok package format error") + workConn.Close() + return + } + + select { + case sr.NotifyCh <- struct{}{}: + default: + } + }() } } }() diff --git a/server/service.go b/server/service.go index b40a1a2..ac5602a 100644 --- a/server/service.go +++ b/server/service.go @@ -16,8 +16,14 @@ package server import ( "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" "fmt" "io/ioutil" + "math/big" "net" "net/http" "time" @@ -61,6 +67,9 @@ type Service struct { // Accept connections using websocket websocketListener frpNet.Listener + // Accept frp tls connections + tlsListener frpNet.Listener + // Manage all controllers ctlManager *ControlManager @@ -72,6 +81,8 @@ type Service struct { // stats collector to store server and proxies stats info statsCollector stats.Collector + + tlsConfig *tls.Config } func NewService() (svr *Service, err error) { @@ -84,6 +95,7 @@ func NewService() (svr *Service, err error) { TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts), UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), }, + tlsConfig: generateTLSConfig(), } // Init group controller @@ -187,6 +199,12 @@ func NewService() (svr *Service, err error) { log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort) } + // frp tls listener + tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool { + return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE + }) + svr.tlsListener = frpNet.WrapLogListener(tlsListener) + // Create nat hole controller. if cfg.BindUdpPort > 0 { var nc *nathole.NatHoleController @@ -225,6 +243,7 @@ func (svr *Service) Run() { } go svr.HandleListener(svr.websocketListener) + go svr.HandleListener(svr.tlsListener) svr.HandleListener(svr.listener) } @@ -237,6 +256,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) { log.Warn("Listener for incoming connections from client closed") return } + c = frpNet.CheckAndEnableTLSServerConn(c, svr.tlsConfig) // Start a new goroutine for dealing connections. go func(frpConn frpNet.Conn) { @@ -373,3 +393,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, newMsg.UseEncryption, newMsg.UseCompression) } + +// Setup a bare-bones TLS config for the server +func generateTLSConfig() *tls.Config { + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + panic(err) + } + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + panic(err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + panic(err) + } + return &tls.Config{Certificates: []tls.Certificate{tlsCert}} +} diff --git a/tests/ci/cmd_test.go b/tests/ci/cmd_test.go index ea76e6d..44f2e9e 100644 --- a/tests/ci/cmd_test.go +++ b/tests/ci/cmd_test.go @@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) { if assert.NoError(err) { defer s.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", "-l", "10701", "-r", "20801", "-n", "tcp_test"}) @@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) { if assert.NoError(err) { defer c.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) assert.NoError(err) @@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) { if assert.NoError(err) { defer s.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", "-l", "10702", "-r", "20802", "-n", "udp_test"}) @@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) { if assert.NoError(err) { defer c.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR) assert.NoError(err) @@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) { if assert.NoError(err) { defer s.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", "-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"}) @@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) { if assert.NoError(err) { defer c.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "") if assert.NoError(err) { diff --git a/tests/ci/reconnect_test.go b/tests/ci/reconnect_test.go index 114567b..9fe53b5 100644 --- a/tests/ci/reconnect_test.go +++ b/tests/ci/reconnect_test.go @@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) { defer frpsProcess.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) err = frpcProcess.Start() if assert.NoError(err) { defer frpcProcess.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) // test tcp res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) @@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) { // stop frpc frpcProcess.Stop() - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) // test tcp, expect failed _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) @@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) { if assert.NoError(err) { defer newFrpcProcess.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) // test tcp res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) @@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) { // stop frps frpsProcess.Stop() - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) // test tcp, expect failed _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) diff --git a/tests/ci/reload_test.go b/tests/ci/reload_test.go index f05f2ed..4c5b4d4 100644 --- a/tests/ci/reload_test.go +++ b/tests/ci/reload_test.go @@ -94,7 +94,7 @@ func TestReload(t *testing.T) { defer frpsProcess.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) err = frpcProcess.Start() @@ -102,7 +102,7 @@ func TestReload(t *testing.T) { defer frpcProcess.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) // test tcp1 res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) diff --git a/tests/ci/template_test.go b/tests/ci/template_test.go index 09b8417..1e30af5 100644 --- a/tests/ci/template_test.go +++ b/tests/ci/template_test.go @@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) { defer frpsProcess.Stop() } - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath}) err = frpcProcess.Start() @@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) { defer frpcProcess.Stop() } - time.Sleep(250 * time.Millisecond) + time.Sleep(500 * time.Millisecond) // test tcp1 res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) diff --git a/tests/ci/tls_test.go b/tests/ci/tls_test.go new file mode 100644 index 0000000..d2ad801 --- /dev/null +++ b/tests/ci/tls_test.go @@ -0,0 +1,188 @@ +package ci + +import ( + "os" + "testing" + "time" + + "github.com/fatedier/frp/tests/config" + "github.com/fatedier/frp/tests/consts" + "github.com/fatedier/frp/tests/util" + + "github.com/stretchr/testify/assert" +) + +const FRPS_TLS_TCP_CONF = ` +[common] +bind_addr = 0.0.0.0 +bind_port = 20000 +log_file = console +log_level = debug +token = 123456 +` + +const FRPC_TLS_TCP_CONF = ` +[common] +server_addr = 127.0.0.1 +server_port = 20000 +log_file = console +log_level = debug +token = 123456 +protocol = tcp +tls_enable = true + +[tcp] +type = tcp +local_port = 10701 +remote_port = 20801 +` + +func TestTlsOverTCP(t *testing.T) { + assert := assert.New(t) + frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpsCfgPath) + } + + frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpcCfgPath) + } + + frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath}) + err = frpsProcess.Start() + if assert.NoError(err) { + defer frpsProcess.Stop() + } + + time.Sleep(200 * time.Millisecond) + + frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) + err = frpcProcess.Start() + if assert.NoError(err) { + defer frpcProcess.Stop() + } + time.Sleep(500 * time.Millisecond) + + // test tcp + res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) + assert.NoError(err) + assert.Equal(consts.TEST_TCP_ECHO_STR, res) +} + +const FRPS_TLS_KCP_CONF = ` +[common] +bind_addr = 0.0.0.0 +bind_port = 20000 +kcp_bind_port = 20000 +log_file = console +log_level = debug +token = 123456 +` + +const FRPC_TLS_KCP_CONF = ` +[common] +server_addr = 127.0.0.1 +server_port = 20000 +log_file = console +log_level = debug +token = 123456 +protocol = kcp +tls_enable = true + +[tcp] +type = tcp +local_port = 10701 +remote_port = 20801 +` + +func TestTLSOverKCP(t *testing.T) { + assert := assert.New(t) + frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpsCfgPath) + } + + frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpcCfgPath) + } + + frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath}) + err = frpsProcess.Start() + if assert.NoError(err) { + defer frpsProcess.Stop() + } + + time.Sleep(200 * time.Millisecond) + + frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) + err = frpcProcess.Start() + if assert.NoError(err) { + defer frpcProcess.Stop() + } + time.Sleep(500 * time.Millisecond) + + // test tcp + res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) + assert.NoError(err) + assert.Equal(consts.TEST_TCP_ECHO_STR, res) +} + +const FRPS_TLS_WS_CONF = ` +[common] +bind_addr = 0.0.0.0 +bind_port = 20000 +log_file = console +log_level = debug +token = 123456 +` + +const FRPC_TLS_WS_CONF = ` +[common] +server_addr = 127.0.0.1 +server_port = 20000 +log_file = console +log_level = debug +token = 123456 +protocol = websocket +tls_enable = true + +[tcp] +type = tcp +local_port = 10701 +remote_port = 20801 +` + +func TestTLSOverWebsocket(t *testing.T) { + assert := assert.New(t) + frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF) + if assert.NoError(err) { + defer os.Remove(frpsCfgPath) + } + + frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_CONF) + if assert.NoError(err) { + defer os.Remove(frpcCfgPath) + } + + frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath}) + err = frpsProcess.Start() + if assert.NoError(err) { + defer frpsProcess.Stop() + } + + time.Sleep(200 * time.Millisecond) + + frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) + err = frpcProcess.Start() + if assert.NoError(err) { + defer frpcProcess.Stop() + } + time.Sleep(500 * time.Millisecond) + + // test tcp + res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) + assert.NoError(err) + assert.Equal(consts.TEST_TCP_ECHO_STR, res) +} diff --git a/utils/net/conn.go b/utils/net/conn.go index 6dab2bd..e716457 100644 --- a/utils/net/conn.go +++ b/utils/net/conn.go @@ -15,6 +15,7 @@ package net import ( + "crypto/tls" "errors" "fmt" "io" @@ -207,3 +208,13 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn return nil, fmt.Errorf("unsupport protocol: %s", protocol) } } + +func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) { + c, err = ConnectServerByProxy(proxyUrl, protocol, addr) + if tlsConfig == nil { + return + } + + c = WrapTLSClientConn(c, tlsConfig) + return +} diff --git a/utils/net/tls.go b/utils/net/tls.go new file mode 100644 index 0000000..ae1bfc7 --- /dev/null +++ b/utils/net/tls.go @@ -0,0 +1,44 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net + +import ( + "crypto/tls" + "net" + + gnet "github.com/fatedier/golib/net" +) + +var ( + FRP_TLS_HEAD_BYTE = 0x17 +) + +func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) { + c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)}) + out = WrapConn(tls.Client(c, tlsConfig)) + return +} + +func CheckAndEnableTLSServerConn(c net.Conn, tlsConfig *tls.Config) (out Conn) { + sc, r := gnet.NewSharedConnSize(c, 1) + buf := make([]byte, 1) + n, _ := r.Read(buf) + if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE { + out = WrapConn(tls.Server(c, tlsConfig)) + } else { + out = WrapConn(sc) + } + return +} diff --git a/utils/version/version.go b/utils/version/version.go index 41897cb..6fb34ec 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.24.1" +var version string = "0.25.0" func Full() string { return version diff --git a/vendor/github.com/hashicorp/yamux/go.mod b/vendor/github.com/hashicorp/yamux/go.mod new file mode 100644 index 0000000..672a0e5 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/yamux diff --git a/vendor/github.com/hashicorp/yamux/mux.go b/vendor/github.com/hashicorp/yamux/mux.go index 7abc7c7..18a078c 100644 --- a/vendor/github.com/hashicorp/yamux/mux.go +++ b/vendor/github.com/hashicorp/yamux/mux.go @@ -3,6 +3,7 @@ package yamux import ( "fmt" "io" + "log" "os" "time" ) @@ -30,8 +31,13 @@ type Config struct { // window size that we allow for a stream. MaxStreamWindowSize uint32 - // LogOutput is used to control the log destination + // LogOutput is used to control the log destination. Either Logger or + // LogOutput can be set, not both. LogOutput io.Writer + + // Logger is used to pass in the logger to be used. Either Logger or + // LogOutput can be set, not both. + Logger *log.Logger } // DefaultConfig is used to return a default configuration @@ -57,6 +63,11 @@ func VerifyConfig(config *Config) error { if config.MaxStreamWindowSize < initialStreamWindow { return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow) } + if config.LogOutput != nil && config.Logger != nil { + return fmt.Errorf("both Logger and LogOutput may not be set, select one") + } else if config.LogOutput == nil && config.Logger == nil { + return fmt.Errorf("one of Logger or LogOutput must be set, select one") + } return nil } diff --git a/vendor/github.com/hashicorp/yamux/session.go b/vendor/github.com/hashicorp/yamux/session.go index d8446fa..a80ddec 100644 --- a/vendor/github.com/hashicorp/yamux/session.go +++ b/vendor/github.com/hashicorp/yamux/session.go @@ -86,9 +86,14 @@ type sendReady struct { // newSession is used to construct a new session func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session { + logger := config.Logger + if logger == nil { + logger = log.New(config.LogOutput, "", log.LstdFlags) + } + s := &Session{ config: config, - logger: log.New(config.LogOutput, "", log.LstdFlags), + logger: logger, conn: conn, bufRead: bufio.NewReader(conn), pings: make(map[uint32]chan struct{}), @@ -309,8 +314,10 @@ func (s *Session) keepalive() { case <-time.After(s.config.KeepAliveInterval): _, err := s.Ping() if err != nil { - s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) - s.exitErr(ErrKeepAliveTimeout) + if err != ErrSessionShutdown { + s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) + s.exitErr(ErrKeepAliveTimeout) + } return } case <-s.shutdownCh: diff --git a/vendor/modules.txt b/vendor/modules.txt index 58d2b73..733731c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -23,7 +23,7 @@ github.com/gorilla/context github.com/gorilla/mux # github.com/gorilla/websocket v1.2.0 github.com/gorilla/websocket -# github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0 +# github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap @@ -61,11 +61,11 @@ golang.org/x/crypto/twofish golang.org/x/crypto/xtea golang.org/x/crypto/salsa20/salsa # golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 -golang.org/x/net/ipv4 golang.org/x/net/websocket +golang.org/x/net/context +golang.org/x/net/proxy +golang.org/x/net/ipv4 +golang.org/x/net/internal/socks golang.org/x/net/bpf golang.org/x/net/internal/iana golang.org/x/net/internal/socket -golang.org/x/net/context -golang.org/x/net/proxy -golang.org/x/net/internal/socks