github上面有人push了wss实现
https://github.com/fatedier/frp/pull/1919/files
注意:
wss实现了就已经实现了配置域前置的第一步。
后面就是回源HOST了。由于默认frp的HOST就是连接的地址,导致无法使用域前置。
先分析WSS的HOST实在哪配置的。
D:\go\src\pkg\mod\golang.org\x\[email protected]\websocket\hybi.go
在hybiClientHandshake函数中,原先的代码是
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
下图是我修改后的
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n") // According to RFC 6874, an HTTP client, proxy, or other // intermediary must remove any IPv6 zone identifier attached // to an outgoing URI. host :=config.Location.Host if tmpHost :=config.Header.Get("Host");tmpHost !=""{ host=tmpHost } bw.WriteString("Host: " + removeZone(host) + "\r\n")
在
D:\go\src\pkg\mod\golang.org\x\[email protected]\websocket\client.go
NewConfig函数中可向config.Header传递值,需要新加一个参数websocket_domain。
func NewConfig(server, origin string,websocket_domain string) (config *Config, err error) { config = new(Config) config.Version = ProtocolVersionHybi13 config.Location, err = url.ParseRequestURI(server) if err != nil { return } config.Origin, err = url.ParseRequestURI(origin) if err != nil { return } config.Header = http.Header(make(map[string][]string)) config.Header.Set("Host",websocket_domain) return }
NewConfig在util/net/websocket.go 中的ConnectWebsocketServer被调用。
由于在域前置里,用wss协议的情况下,server_addr用域名会不能正常回源,只能用ip。且会存在证书报错。所以需要做以下修改。
func ConnectWebsocketServer(addr string,websocket_domain string,isSecure bool) (net.Conn, error) { if isSecure { ho := strings.Split(addr, ":") ip, err := net.ResolveIPAddr("ip", ho[0]) ip_addr := ip.String() + ":" + ho[1] if err != nil { return nil, err } addr = "wss://" + ip_addr + FrpWebsocketPath } else { addr = "ws://" + addr + FrpWebsocketPath } uri, err := url.Parse(addr) if err != nil { return nil, err } var origin string if isSecure { ho := strings.Split(uri.Host, ":") ip, err := net.ResolveIPAddr("ip", ho[0]) ip_addr := ip.String() + ":" + ho[1] if err != nil { return nil, err } origin = "https://" + ip_addr } else { origin = "http://" + uri.Host } fmt.Println("addr:"+addr) fmt.Println("origin:"+origin) cfg, err := websocket.NewConfig(addr, origin, websocket_domain) if err != nil { return nil, err } cfg.Dialer = &net.Dialer{ Timeout: 10 * time.Second, } conn, err := websocket.DialConfig(cfg) if err != nil { return nil, err } return conn, nil }
ConnectWebsocketServer在util/net/conn.go中ConnectServerByProxy被调用,添加新增参数。
func ConnectServerByProxy(proxyURL string,websocket_domain string, protocol string, addr string) (c net.Conn, err error) { switch protocol { case "tcp": return gnet.DialTcpByProxy(proxyURL, addr) case "kcp": // http proxy is not supported for kcp return ConnectServer(protocol, addr) case "websocket": return ConnectWebsocketServer(addr, websocket_domain,false) case "wss": return ConnectWebsocketServer(addr, websocket_domain,true) default: return nil, fmt.Errorf("unsupport protocol: %s", protocol) } }
ConnectServerByProxyWithTLS调用ConnectWebsocketServer
ConnectServerByProxyWithTLS被两处调用。
我这里是新加了功能,所以写了两种service.go。
分别是client/control.go、client/service.go
后面说一下,如何在frp的配置文件新增参数。
我是直接将配置文件写在变量里面。
调用流程parseClientCommonCfg--->parseClientCommonCfgFromIni---->UnmarshalClientConfFromIni
重点在UnmarshalClientConfFromIni函数中。
可以看到frp解析配置文件,通过conf.Get("common", "server_addr")获取参数,放入cfg.ServerAddr。
conf, err := ini.Load(strings.NewReader(content)) if err != nil { return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err) } cfg.AuthClientConfig = auth.UnmarshalAuthClientConfFromIni(conf) var ( tmpStr string ok bool v int64 ) if tmpStr, ok = conf.Get("common", "server_addr"); ok { cfg.ServerAddr = tmpStr } if tmpStr, ok = conf.Get("common", "server_port"); ok { v, err = strconv.ParseInt(tmpStr, 10, 64) if err != nil { err = fmt.Errorf("Parse conf error: invalid server_port") return } cfg.ServerPort = int(v) }
所以我们只需要models/config/client_common.go 的ClientCommonConf新增结构。
在UnmarshalClientConfFromIni里面,获取protocol分支里面增加获取websocket_domain参数即可
if tmpStr, ok = conf.Get("common", "protocol"); ok { // Now it only support tcp and kcp and websocket. if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket"&& tmpStr != "wss" { err = fmt.Errorf("Parse conf error: invalid protocol") return } cfg.Protocol = tmpStr if tmpStr, ok = conf.Get("common", "websocket_domain"); ok { cfg.Websocket_domain = tmpStr } }
而client/control.go、client/service.go中都是通过ClientCommonConf获取参数。
最后,修复证书错误问题。
修改websocket/dial.go
里的dialWithDialer
方法。
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) { switch config.Location.Scheme { case "ws": conn, err = dialer.Dial("tcp", parseAuthority(config.Location)) case "wss": config.TlsConfig = &tls.Config{ InsecureSkipVerify: true, } conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig) default: err = ErrBadScheme } return }
当使用wss协议的时候,将TlsConfig.InsecureSkipVerify设置为true,即可忽略证书错误了。
frp默认的特征比较明显,修改特征
utils/net/websocket.go
修改常量FrpWebsocketPath。
cdn配置回源http
客户端配置
[common] server_addr = xxxxxxxxxxxxx server_port = 443 token=xxx websocket_domain=xxxxxxx protocol = wss tls_enable=true [%s] type = tcp remote_port = %d plugin = socks5 plugin_user = a plugin_passwd = %s use_encryption = true use_compression = true
服务端配置
[common] #绑定地址 bind_addr = 0.0.0.0 #TCP绑定端口 bind_port = 443 #连接密码 token=xxx
FRP改造计划续-https://uknowsec.cn/posts/notes/FRP%E6%94%B9%E9%80%A0%E8%AE%A1%E5%88%92%E7%BB%AD.html