frp改版-支持域前置
字数 917 2025-08-27 12:33:37

FRP改版支持域前置与WSS实现教学文档

1. 背景介绍

FRP是一个高性能的反向代理应用,可以帮助用户轻松地进行内网穿透。本教程将详细介绍如何修改FRP以支持域前置(domain fronting)和WebSocket Secure(WSS)协议。

2. 核心修改点

2.1 WSS协议支持

FRP原生不支持WSS协议,需要以下修改:

  1. CDN配置:需要将CDN回源协议配置为HTTP
  2. 错误处理:直接使用WSS会导致FRP服务端报错"invalid protocol version"

2.2 代码修改位置

2.2.1 WebSocket Host配置修改

文件路径:golang.org/x/net/websocket/hybi.go

修改hybiClientHandshake函数:

func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
    bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
    host := config.Location.Host
    if tmpHost := config.Header.Get("Host"); tmpHost != "" {
        host = tmpHost
    }
    bw.WriteString("Host: " + removeZone(host) + "\r\n")
    // ...其他代码不变...
}

2.2.2 NewConfig函数修改

文件路径:golang.org/x/net/websocket/client.go

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
}

2.2.3 ConnectWebsocketServer函数修改

文件路径:util/net/websocket.go

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
}

2.2.4 ConnectServerByProxy函数修改

文件路径:util/net/conn.go

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)
    }
}

2.3 配置文件修改

2.3.1 解析配置文件

文件路径:models/config/client_common.go

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
    }
}

2.4 修复证书错误

文件路径:websocket/dial.go

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
}

2.5 修改FRP特征

文件路径:utils/net/websocket.go

修改常量FrpWebsocketPath以改变FRP的默认特征。

3. 配置示例

3.1 客户端配置

[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

3.2 服务端配置

[common]
#绑定地址
bind_addr = 0.0.0.0
#TCP绑定端口
bind_port = 443
#连接密码
token = xxx

4. 注意事项

  1. 域前置使用:在使用域前置时,server_addr应使用IP地址而非域名,否则无法正常回源
  2. 证书问题:使用WSS协议时会存在证书报错,已通过设置InsecureSkipVerify: true解决
  3. CDN配置:必须将CDN回源协议配置为HTTP
  4. 协议支持:修改后支持tcp、kcp、websocket和wss协议

5. 参考链接

FRP改造计划续

FRP改版支持域前置与WSS实现教学文档 1. 背景介绍 FRP是一个高性能的反向代理应用,可以帮助用户轻松地进行内网穿透。本教程将详细介绍如何修改FRP以支持域前置(domain fronting)和WebSocket Secure(WSS)协议。 2. 核心修改点 2.1 WSS协议支持 FRP原生不支持WSS协议,需要以下修改: CDN配置 :需要将CDN回源协议配置为HTTP 错误处理 :直接使用WSS会导致FRP服务端报错"invalid protocol version" 2.2 代码修改位置 2.2.1 WebSocket Host配置修改 文件路径: golang.org/x/net/websocket/hybi.go 修改 hybiClientHandshake 函数: 2.2.2 NewConfig函数修改 文件路径: golang.org/x/net/websocket/client.go 2.2.3 ConnectWebsocketServer函数修改 文件路径: util/net/websocket.go 2.2.4 ConnectServerByProxy函数修改 文件路径: util/net/conn.go 2.3 配置文件修改 2.3.1 解析配置文件 文件路径: models/config/client_common.go 2.4 修复证书错误 文件路径: websocket/dial.go 2.5 修改FRP特征 文件路径: utils/net/websocket.go 修改常量 FrpWebsocketPath 以改变FRP的默认特征。 3. 配置示例 3.1 客户端配置 3.2 服务端配置 4. 注意事项 域前置使用 :在使用域前置时, server_addr 应使用IP地址而非域名,否则无法正常回源 证书问题 :使用WSS协议时会存在证书报错,已通过设置 InsecureSkipVerify: true 解决 CDN配置 :必须将CDN回源协议配置为HTTP 协议支持 :修改后支持tcp、kcp、websocket和wss协议 5. 参考链接 FRP改造计划续