原文始发于先知社区(an1m0re7):frp改版-支持域前置
frp改版-域前置
wss实现
github上面有人push了wss实现
https://github.com/fatedier/frp/pull/1919/files
注意:
- 由于frp不支持wss协议,所以需要cdn配置回源协议为http。
直接使用wss,frp服务端会报错:invalid protocol version。
具体代码修改
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。
测试
[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