From fd60e03efd074940e590705072fe610184992aa3 Mon Sep 17 00:00:00 2001 From: FishFish Date: Fri, 3 Aug 2018 16:38:45 +0800 Subject: [PATCH] websocket protocol --- cmd/frps/root.go | 3 + conf/frpc_full.ini | 2 +- conf/frps_full.ini | 4 ++ models/config/client_common.go | 2 +- models/config/proxy.go | 49 ++++++++++++++ models/config/server_common.go | 20 ++++-- models/consts/consts.go | 13 ++-- server/service.go | 17 ++++- utils/net/conn.go | 2 + utils/net/websocket.go | 113 +++++++++++++++++++++++++++++++++ 10 files changed, 211 insertions(+), 14 deletions(-) create mode 100644 utils/net/websocket.go diff --git a/cmd/frps/root.go b/cmd/frps/root.go index fb19f532..b09d5459 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -41,6 +41,7 @@ var ( bindPort int bindUdpPort int kcpBindPort int + websocketBindPort int proxyBindAddr string vhostHttpPort int vhostHttpsPort int @@ -70,6 +71,7 @@ func init() { rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port") rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port") rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port") + rootCmd.PersistentFlags().IntVarP(&websocketBindPort, "websocket_bind_port", "", 0, "websocket bind tcp port") rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address") rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port") rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port") @@ -162,6 +164,7 @@ func parseServerCommonCfgFromCmd() (err error) { g.GlbServerCfg.BindPort = bindPort g.GlbServerCfg.BindUdpPort = bindUdpPort g.GlbServerCfg.KcpBindPort = kcpBindPort + g.GlbServerCfg.WebsocketBindPort = websocketBindPort g.GlbServerCfg.ProxyBindAddr = proxyBindAddr g.GlbServerCfg.VhostHttpPort = vhostHttpPort g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index c3d13ffd..5a2e95d9 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -41,7 +41,7 @@ user = your_name login_fail_exit = true # communication protocol used to connect to server -# now it supports tcp and kcp, default is tcp +# now it supports tcp and kcp and websocket, default is tcp protocol = tcp # specify a dns server, so frpc will use this instead of default one diff --git a/conf/frps_full.ini b/conf/frps_full.ini index 586b9677..0ac17853 100644 --- a/conf/frps_full.ini +++ b/conf/frps_full.ini @@ -12,6 +12,10 @@ bind_udp_port = 7001 # if not set, kcp is disabled in frps kcp_bind_port = 7000 +# tcp port used for websocket protocol, it can't be same with 'bind_port' +# if not set, websocket is disabled in frps +websocket_bind_port = 7002 + # specify which address proxy will listen for, default value is same with bind_addr # proxy_bind_addr = 127.0.0.1 diff --git a/models/config/client_common.go b/models/config/client_common.go index 95a383af..c1d61cb4 100644 --- a/models/config/client_common.go +++ b/models/config/client_common.go @@ -187,7 +187,7 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c if tmpStr, ok = conf.Get("common", "protocol"); ok { // Now it only support tcp and kcp. - if tmpStr != "kcp" { + if tmpStr != "kcp" && tmpStr != "websocket" { tmpStr = "tcp" } cfg.Protocol = tmpStr diff --git a/models/config/proxy.go b/models/config/proxy.go index 31b26e70..1950c3a3 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -825,6 +825,55 @@ func (cfg *XtcpProxyConf) CheckForSvr() (err error) { return } +// WEBSOCKET +type WebsocketProxyConf struct { + BaseProxyConf + BindInfoConf + + LocalSvrConf +} + +func (cfg *WebsocketProxyConf) Compare(cmp ProxyConf) bool { + cmpConf, ok := cmp.(*WebsocketProxyConf) + if !ok { + return false + } + + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || + !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || + !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) { + return false + } + return true +} + +func (cfg *WebsocketProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) + cfg.BindInfoConf.UnmarshalFromMsg(pMsg) +} + +func (cfg *WebsocketProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { + if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + return +} + +func (cfg *WebsocketProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.MarshalToMsg(pMsg) + cfg.BindInfoConf.MarshalToMsg(pMsg) +} + +func (cfg *WebsocketProxyConf) CheckForCli() error { return nil } + +func (cfg *WebsocketProxyConf) CheckForSvr() error { return nil } + func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) { localPorts, errRet := util.ParseRangeNumbers(section["local_port"]) if errRet != nil { diff --git a/models/config/server_common.go b/models/config/server_common.go index 19e1a1d2..d24f2ac3 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -41,11 +41,12 @@ func InitServerCfg(cfg *ServerCommonConf) { // common config type ServerCommonConf struct { - BindAddr string `json:"bind_addr"` - BindPort int `json:"bind_port"` - BindUdpPort int `json:"bind_udp_port"` - KcpBindPort int `json:"kcp_bind_port"` - ProxyBindAddr string `json:"proxy_bind_addr"` + BindAddr string `json:"bind_addr"` + BindPort int `json:"bind_port"` + BindUdpPort int `json:"bind_udp_port"` + KcpBindPort int `json:"kcp_bind_port"` + WebsocketBindPort int `json:"websocket_bind_port"` + ProxyBindAddr string `json:"proxy_bind_addr"` // If VhostHttpPort equals 0, don't listen a public port for http protocol. VhostHttpPort int `json:"vhost_http_port"` @@ -153,6 +154,15 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c } } + if tmpStr, ok = conf.Get("common", "websocket_bind_port"); ok { + if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { + err = fmt.Errorf("Parse conf error: invalid kcp_bind_port") + return + } else { + cfg.WebsocketBindPort = int(v) + } + } + if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok { cfg.ProxyBindAddr = tmpStr } else { diff --git a/models/consts/consts.go b/models/consts/consts.go index 9bf5880b..e5f7857e 100644 --- a/models/consts/consts.go +++ b/models/consts/consts.go @@ -23,10 +23,11 @@ var ( Offline string = "offline" // proxy type - TcpProxy string = "tcp" - UdpProxy string = "udp" - HttpProxy string = "http" - HttpsProxy string = "https" - StcpProxy string = "stcp" - XtcpProxy string = "xtcp" + TcpProxy string = "tcp" + UdpProxy string = "udp" + HttpProxy string = "http" + HttpsProxy string = "https" + StcpProxy string = "stcp" + XtcpProxy string = "xtcp" + WebsocketProxy string = "websocket" ) diff --git a/server/service.go b/server/service.go index a9b14a62..15e54414 100644 --- a/server/service.go +++ b/server/service.go @@ -53,6 +53,9 @@ type Service struct { // Accept connections using kcp kcpListener frpNet.Listener + // Accept connections using websocket + websocketListener frpNet.Listener + // For https proxies, route requests to different clients by hostname and other infomation VhostHttpsMuxer *vhost.HttpsMuxer @@ -137,6 +140,16 @@ func NewService() (svr *Service, err error) { log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort) } + if cfg.WebsocketBindPort > 0 { + svr.websocketListener, err = frpNet.ListenWebsocket(cfg.BindAddr, cfg.WebsocketBindPort) + if err != nil { + err = fmt.Errorf("Listen on websocket address tcp [%s:%d] error: %v", + cfg.BindAddr, cfg.WebsocketBindPort, err) + return + } + log.Info("frps websocket listen on tcp %s:%d", cfg.BindAddr, cfg.WebsocketBindPort) + } + // Create http vhost muxer. if cfg.VhostHttpPort > 0 { rp := vhost.NewHttpReverseProxy() @@ -214,6 +227,9 @@ func (svr *Service) Run() { if g.GlbServerCfg.KcpBindPort > 0 { go svr.HandleListener(svr.kcpListener) } + if g.GlbServerCfg.WebsocketBindPort > 0 { + go svr.HandleListener(svr.websocketListener) + } svr.HandleListener(svr.listener) } @@ -226,7 +242,6 @@ func (svr *Service) HandleListener(l frpNet.Listener) { log.Warn("Listener for incoming connections from client closed") return } - // Start a new goroutine for dealing connections. go func(frpConn frpNet.Conn) { dealFn := func(conn frpNet.Conn) { diff --git a/utils/net/conn.go b/utils/net/conn.go index 81dc82bb..825a9896 100644 --- a/utils/net/conn.go +++ b/utils/net/conn.go @@ -132,6 +132,8 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn case "kcp": // http proxy is not supported for kcp return ConnectServer(protocol, addr) + case "websocket": + return ConnectWebsocketServer(addr) default: return nil, fmt.Errorf("unsupport protocol: %s", protocol) } diff --git a/utils/net/websocket.go b/utils/net/websocket.go new file mode 100644 index 00000000..de58570f --- /dev/null +++ b/utils/net/websocket.go @@ -0,0 +1,113 @@ +package net + +import ( + "fmt" + "net" + "net/http" + "net/url" + "sync/atomic" + "time" + + "github.com/fatedier/frp/utils/log" + "golang.org/x/net/websocket" +) + +type WebsocketListener struct { + log.Logger + server *http.Server + httpMutex *http.ServeMux + connChan chan *WebsocketConn + closeFlag bool +} + +func ListenWebsocket(bindAddr string, bindPort int) (l *WebsocketListener, err error) { + l = &WebsocketListener{ + httpMutex: http.NewServeMux(), + connChan: make(chan *WebsocketConn), + Logger: log.NewPrefixLogger(""), + } + l.httpMutex.Handle("/", websocket.Handler(func(c *websocket.Conn) { + conn := NewWebScoketConn(c) + l.connChan <- conn + conn.waitClose() + })) + l.server = &http.Server{ + Addr: fmt.Sprintf("%s:%d", bindAddr, bindPort), + Handler: l.httpMutex, + } + ch := make(chan struct{}) + go func() { + close(ch) + err = l.server.ListenAndServe() + }() + <-ch + <-time.After(time.Millisecond) + return +} + +func (p *WebsocketListener) Accept() (Conn, error) { + c := <-p.connChan + return c, nil +} + +func (p *WebsocketListener) Close() error { + if !p.closeFlag { + p.closeFlag = true + p.server.Close() + } + return nil +} + +type WebsocketConn struct { + net.Conn + log.Logger + closed int32 + wait chan struct{} +} + +func NewWebScoketConn(conn net.Conn) (c *WebsocketConn) { + c = &WebsocketConn{ + Conn: conn, + Logger: log.NewPrefixLogger(""), + wait: make(chan struct{}), + } + return +} + +func (p *WebsocketConn) Close() error { + if atomic.LoadInt32(&p.closed) == 1 { + return nil + } + close(p.wait) + return p.Conn.Close() +} + +func (p *WebsocketConn) waitClose() { + <-p.wait +} + +// ConnectWebsocketServer : +// addr: ws://domain:port +func ConnectWebsocketServer(addr string) (c Conn, err error) { + addr = "ws://" + addr + uri, err := url.Parse(addr) + if err != nil { + return + } + + origin := "http://" + uri.Host + cfg, err := websocket.NewConfig(addr, origin) + if err != nil { + return + } + cfg.Dialer = &net.Dialer{ + Timeout: time.Second * 10, + } + + conn, err := websocket.DialConfig(cfg) + if err != nil { + return + } + c = NewWebScoketConn(conn) + return +}