长亭百川云 - 文章详情

frp改造计划续

零队

56

2024-07-13

前言

之前@Wfox师傅在群里提到“通过websocket协议让FRP用上域前置,可以隐藏真实服务ip地址”。最近没有项目,重新进行一下frp改造计划。感谢@Wfox提出的修改思路~

可行性证明

先用dns域名解析来证明域前置方案在frp上是可行的,这里也可以直接修改本地hosts文件来实现dns域名解析的效果。

比如我们用如下frpc.ini

  1. [common]

  2. server_addr = dwnwdqndlnqwln2321321.com

  3. server_port = 23333

  4. token = uknowsec

  5. protocol = websocket

  6. tls_enable = true

  7. [http_proxy]

  8. type = tcp

  9. remote_port = 10002

  10. plugin = socks5

让frpc认证数据包走websocket协议。

可以看到认证是通过websocket协议,这里特别标注出来了Host头,要实现域前置,我们只要把host修改为我们的指定回源域名即可。所以证明了“通过websocket协议让FRP用上域前置”是可行的。

Websocket依赖修改

跟进frp源码,我们可以到websocket依赖包 websocket/hybi.go文件下的hybiClientHandshake函数。

  1. func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {

  2.  `bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")`
    
  3.  `// According to RFC 6874, an HTTP client, proxy, or other`
    
  4.  `// intermediary must remove any IPv6 zone identifier attached`
    
  5.  `// to an outgoing URI.`
    
  6.  `bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")`
    
  7.  `bw.WriteString("Upgrade: websocket\r\n")`
    
  8.  `bw.WriteString("Connection: Upgrade\r\n")`
    
  9.  `nonce := generateNonce()`
    
  10.  `if config.handshakeData != nil {`
    
  11.      `nonce = []byte(config.handshakeData["key"])`
    
  12.  `}`
    
  13.  `bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")`
    
  14.  `bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")`
    
  15.  `...`
    
  16.  `return nil`
    
  17. }

可以看到Host是通过 config.Location.Host进行赋值的,我们再一步一步的往回看调用即可。

同时frp调用 websocket依赖在 pkg/util/net/websocket.go里的 ConnectWebsocketServer方法

  1. func ConnectWebsocketServer(addr string) (net.Conn, error) {

  2.  `addr = "ws://" + addr + FrpWebsocketPath`
    
  3.  `uri, err := url.Parse(addr)`
    
  4.  `if err != nil {`
    
  5.      `return nil, err`
    
  6.  `}`
    
  7.  `origin := "http://" + uri.Host`
    
  8.  `cfg, err := websocket.NewConfig(addr, origin)`
    
  9.  `if err != nil {`
    
  10.      `return nil, err`
    
  11.  `}`
    
  12.  `cfg.Dialer = &net.Dialer{`
    
  13.      `Timeout: 10 * time.Second,`
    
  14.  `}`
    
  15.  `conn, err := websocket.DialConfig(cfg)`
    
  16.  `if err != nil {`
    
  17.      `return nil, err`
    
  18.  `}`
    
  19.  `return conn, nil`
    
  20. }

所以只需要在往 websocket.NewConfig多传入一个指定的host参数即可。

新加入的host参数只要在 cmd/frpc/sub/root.goRegisterCommonFlags里进行注册即可。

测试效果

这样我们就实现了 通过websocket协议让FRP用上域前置

WSS实现

由上图,可见用websocket还是特征比较明显的,比如 /~!frp。这里我们可以通过如下修改

pkg/util/net/websocket.go下的变量即可。

  1. const (

  2.  `FrpWebsocketPath = "/~!frp"`
    
  3. )

同时,我们也修改frp使之实现wss协议。

@Wfox师傅提醒frp有人pull了支持wss协议的修改代码。

https://github.com/fatedier/frp/pull/1919/files

通过pull里的修改就可以实现wss协议了

同时由于在某云域前置里,用wss协议的情况下,server_addr用域名会不能正常回源,只能用ip。且会存在证书报错。

这里可以通过做如下修改 pkg/util/net/websocket.go里的 ConnectWebsocketServer函数

  1. // addr: domain:port

  2. func ConnectWebsocketServer(addr string, websocket_domain string, isSecure bool) (net.Conn, error) {

  3.  `if isSecure {`
    
  4.      `ho := strings.Split(addr, ":")`
    
  5.      `ip, err := net.ResolveIPAddr("ip", ho[0])`
    
  6.      `ip_addr := ip.String() + ":" + ho[1]`
    
  7.      `if err != nil {`
    
  8.          `return nil, err`
    
  9.      `}`
    
  10.      `addr = "wss://" + ip_addr + FrpWebsocketPath`
    
  11.  `} else {`
    
  12.      `addr = "ws://" + addr + FrpWebsocketPath`
    
  13.  `}`
    
  14.  `uri, err := url.Parse(addr)`
    
  15.  `if err != nil {`
    
  16.      `return nil, err`
    
  17.  `}`
    
  18.  `var origin string`
    
  19.  `if isSecure {`
    
  20.      `ho := strings.Split(uri.Host, ":")`
    
  21.      `ip, err := net.ResolveIPAddr("ip", ho[0])`
    
  22.      `ip_addr := ip.String() + ":" + ho[1]`
    
  23.      `if err != nil {`
    
  24.          `return nil, err`
    
  25.      `}`
    
  26.      `origin = "https://" + ip_addr`
    
  27.  `} else {`
    
  28.      `origin = "http://" + uri.Host`
    
  29.  `}`
    
  30.  `cfg, err := websocket.NewConfig(addr, origin, websocket_domain)`
    
  31.  `if err != nil {`
    
  32.      `return nil, err`
    
  33.  `}`
    
  34.  `cfg.Dialer = &net.Dialer{`
    
  35.      `Timeout: 10 * time.Second,`
    
  36.  `}`
    
  37.  `conn, err := websocket.DialConfig(cfg)`
    
  38.  `if err != nil {`
    
  39.      `return nil, err`
    
  40.  `}`
    
  41.  `return conn, nil`
    
  42. }

net.ResolveIPAddr先获取域名所对应ip地址,再进行wss和https协议的使用即可。

另外修复证书错误问题。

修改 websocket/dial.go里的 dialWithDialer方法。

  1. func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {

  2.  `switch config.Location.Scheme {`
    
  3.  `case "ws":`
    
  4.      `conn, err = dialer.Dial("tcp", parseAuthority(config.Location))`
    
  5.  `case "wss":`
    
  6.      `config.TlsConfig = &tls.Config{`
    
  7.          `InsecureSkipVerify: true,`
    
  8.      `}`
    
  9.      `conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)`
    
  10.  `default:`
    
  11.      `err = ErrBadScheme`
    
  12.  `}`
    
  13.  `return`
    
  14. }

当使用wss协议的时候,将 TlsConfig.InsecureSkipVerify设置为true,即可忽略证书错误了。

测试效果

可见图中的认证数据包已经以wss进行认证了。

配置文件自删除

在其中看@lz520520师傅的文章里看到

https://sec.lz520520.cn:4430/2020/11/566/#0x03

只要读取后删除配置文件就好了呀,这个就很简单,我多添加了一个配置文件参数delete,用于判断是否自动删除配置文件。

这一点还是不错的,添加参数,读取完配置文件启动frpc后,自动删除配置文件。

同样相同的方法在 cmd/frpc/sub/root.goRegisterCommonFlags里进行注册参数即可。

然后在 cmd/frpc/sub/root.go里的 startService方法里进行判断调用删除配置文件即可。

  1. func startService(

  2.  `cfg config.ClientCommonConf,`
    
  3.  `pxyCfgs map[string]config.ProxyConf,`
    
  4.  `visitorCfgs map[string]config.VisitorConf,`
    
  5.  `cfgFile string,`
    
  6. ) (err error) {

  7.  `log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,`
    
  8.      `cfg.LogMaxDays, cfg.DisableLogColor)`
    
  9.  `if cfg.DNSServer != "" {`
    
  10.      `s := cfg.DNSServer`
    
  11.      `if !strings.Contains(s, ":") {`
    
  12.          `s += ":53"`
    
  13.      `}`
    
  14.      `// Change default dns server for frpc`
    
  15.      `net.DefaultResolver = &net.Resolver{`
    
  16.          `PreferGo: true,`
    
  17.          `Dial: func(ctx context.Context, network, address string) (net.Conn, error) {`
    
  18.              `return net.Dial("udp", s)`
    
  19.          `},`
    
  20.      `}`
    
  21.  `}`
    
  22.  `svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)`
    
  23.  `if errRet != nil {`
    
  24.      `err = errRet`
    
  25.      `return`
    
  26.  `}`
    
  27.  `if cfg.DELEnable == true {`
    
  28.      `os.Remove(cfgFile)`
    
  29.  `}`
    
  30.  `// Capture the exit signal if we use kcp.`
    
  31.  `if cfg.Protocol == "kcp" {`
    
  32.      `go handleSignal(svr)`
    
  33.  `}`
    
  34.  `err = svr.Run()`
    
  35.  `if cfg.Protocol == "kcp" {`
    
  36.      `<-kcpDoneCh`
    
  37.  `}`
    
  38.  `return`
    
  39. }

这样就可以实现配置文件自动删除功能了。

Reference

https://github.com/fatedier/frp/pull/1919/files

https://sec.lz520520.cn:4430/2020/11/566/

代码排版转换有点乱,有需求点击查看原文

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2