V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
cy97cool
V2EX  ›  问与答

问: 有没有基于 http 的 frp 实现

  •  
  •   cy97cool · 2017-11-23 11:23:09 +08:00 · 2251 次点击
    这是一个创建于 2586 天前的主题,其中的信息可能已经有所发展或是发生改变。

    frp 是啥

    类似反向代理,通过一台有公网 IP 的服务器中转使我们可以访问到 没有公网 IP 的服务

    放张容易理解的图:

    From: https://github.com/aploium/shootback

    现在的情景

    我自己的电脑 A 连入了内网,但不能直接连入互联网 或者说 网速有限制不能满足需求;

    但外界的服务器 B 可以连上 A 提供的 http 服务 (由内网其他服务器转发),但仅仅支持普通的 http,不支持 websocket

    需求是在这种情况下通过 B 向 A 发起 http 请求的方式实现 A 的连入互联网

    场景的特殊性

    A 想上网,但只能靠 B 向 A 主动发起 http 连接

    B 发起 http 请求 和 A 的回应 必须完整后 才能抵达对方(长连接无效)

    frp 不支持这种情景

    frp、上图的 shootback 的客户端和服务端都需要 tcp 连接,并不能支持 http 协议的连接

    reGeorg 也不支持这种情景

    https://github.com/sensepost/reGeorg

    reGeorg 可以把 web 服务变为 socks 代理,但本质上还是 B 通过 A 的方式去请求 A 能得到的资源(比如说 A 所在的内网资源),我这个需求是要反过来 A 通过 B 去访问外网

    我的想法:

    A 实现一个 socks5 代理 和 http 服务器,B 实现 http 客户端; 使用$$tap 来让上层应用走 A 的 socks5 代理上网

    B 轮询 A 问有没有请求,如果有就记下来,与真实目标建立 socket 连接,下次 http 请求的时候带上请求的相应内容

    目前我的实现是 B 开 100 个线程轮询 A,A 在 10s 内如果有请求就在 http 响应中给出请求内容(A 的 socket send 的内容)和 request id ; 10s 超时就让 B 继续轮询; B 收到回应后检查 request id 对应的 socket、往 socket 里面 send ; B 对每个 socket 循环 recv,有内容就发起对 A 的 http 请求,带上 request id 和 socket recv 得到内容

    但在高并发的时候 A 与 B 之间的 http 通讯并不一定稳定,可能请求失败(丢包)、后来的请求先到达对方(乱序);感觉就要实现一个 http 之上的 tcp 协议。。。

    多线程写起来也没把握,写炸了也不明不白;这种情景下的实现能不能用 select 或者 epoll

    还有一个性能上的问题:这种情景下 socket send 一次相对原生的 tcp 而言代价就很高,但 socks5 代理 recv 以及 socket 连真实服务器 recv 可能不需要交互接连两次 /多次,这时候可以合并多次 recv 得到的内容以减轻代价(如下载大文件);但在 https 握手这种交互情况下,为了减轻延迟 recv 了一次就需要马上从真实服务器得到回应 上层应用才会发起下一个包,如果设计成每隔 1s 才发一次包就意味着 https 握手就需要 3~4s 才能完成。 可不可能实现智能判断 socket 一次 recv 后下一次 recv 多久后会到达

    问问大佬们有啥项目实现了这种情景 或者 给点建议

    16 条回复    2017-11-24 09:47:41 +08:00
    cy97cool
        1
    cy97cool  
    OP
       2017-11-23 11:43:32 +08:00 via Android
    需求忘了说一点: B 向 A 的 http 请求的传输速度是高于百兆的,我目前瞎写的实现了 20M/s 的下载速度,但日常使用不稳定

    想实现低延迟稳定的日常使用和高带宽的下载速度
    pqee
        2
    pqee  
       2017-11-23 11:48:16 +08:00   ❤️ 1
    HTTP 协议的本质只是格式要求,即第一行写什么第二行写什么。没有 TCP 可以考虑 UDP,都没有就废了,做不到的。
    pqee
        3
    pqee  
       2017-11-23 11:54:40 +08:00   ❤️ 1
    仔细读了一下发现楼主已经走了很远了,佩服!

    帮你 google 了一下 tcp over http,找到了几个不错的:github.com/jpillora/chisel stackoverflow.com/a/14115584
    cy97cool
        4
    cy97cool  
    OP
       2017-11-23 12:35:02 +08:00 via Android
    @pqee 感谢回复,
    我看了看 chisel 本质上等同于 reGeorg,都是在让 B 走 http 请求 A(墙外)能访问的资源,

    我的需求不是这样 而在于让 A(内网不能上网)借助 B 的 http 请求来上网
    boyxupers
        5
    boyxupers  
       2017-11-23 13:05:15 +08:00 via iPhone
    ssh -R ?
    cy97cool
        6
    cy97cool  
    OP
       2017-11-23 13:27:40 +08:00 via Android
    @boyxupers
    这种情景不支持 tcp,只能对外提供短连接 http 服务
    所以 ssh 不可用
    jjianwen68
        7
    jjianwen68  
       2017-11-23 13:36:06 +08:00
    squid 可以不
    boyxupers
        8
    boyxupers  
       2017-11-23 19:16:04 +08:00 via iPhone   ❤️ 1
    @cy97cool 那么 http 不走 tcp 协议吗?

    这里必然是得看限制具体是什么,如果可以 https,那么可以走 TLS 的 session ticket,如果不可以,模拟个 get 头,剩下的走 session ticket
    iceheart
        9
    iceheart  
       2017-11-23 19:34:11 +08:00 via Android   ❤️ 1
    逆向端口转发,跟 http 没关系,就是 tcp over tcp, 所有基于 tcp 的协议都能支持。对应的是正向转发,链路对称加密传输的话,前边放个普通代理就翻 wall 了,类似 ss 功能。
    逆向加正向组合能在任何位置连进任何物理可达的网络。
    cy97cool
        10
    cy97cool  
    OP
       2017-11-23 20:44:15 +08:00
    @boyxupers
    限制是由于这个 http 服务是被反向代理的,这么说吧:
    B 可以访问到 A,是走服务器 C 中转的,而服务器 C 会先等到完成一个请求再把请求结果返回,所以说只支持 http 短连接

    我对 session ticket 的理解不多,我觉得自己用 uuid1()生成一个 request id 就行?
    cy97cool
        11
    cy97cool  
    OP
       2017-11-23 20:52:45 +08:00
    @iceheart 不是很理解大佬的想法,大佬要不要再给点关键词给我学习一个

    这种情景下的限制就是接触不到底层的 tcp 的,B 不能访问到 A 的 tcp 端口 只能走服务器 C 按 http 协议访问 A (所以 B 并不完全物理可达 A 只是 http 可达)
    在这种限制下一切基于 tcp 的端口转发都要自己重新实现

    正向的方式很简单,B 要访问 A 发个 socket 请求转为 http 主动请求等着回应就好;
    而反过来 A 是不能主动请求 B 的,只能 A 被动接受 B 的轮询
    iceheart
        12
    iceheart  
       2017-11-23 23:50:12 +08:00 via Android   ❤️ 1
    不好意思,又看了一遍需求才发现 B 到 A 是要经过反向代理的。大方向上没的选,只能是用 http 做载体。具体实现上我提些建议。我不建议你去实现 socks5 或者 http 代理协议,因为这些可以在 B 端搭一个现成的。你只需要实现在 A 端 listen 一个端口,A 向这个端口发起的 tcp 连接会被 B 转到一个指定的 ip:port 就好。至于这个端口跑 http 代理还是 socks 代理,哪个方便你就布哪个,何必自己写呢?你只需实现关键的 tcp 转发就完了呀。

    tcp over http 反向转发:
    建议 1.要有一个固定数量的 http 请求池,来接受 A 的数据请求。
    2.AB 两端维护一个 tcp 连接集合,有状态的。来源就是来自 A listen 的端口上的连接,状态的变更就源自 AB 两端的 TCP 连接。连接标识可以用 A 端的 fd。
    3.通信,A->B : http 等待请求池里取一个发应答回去,就到 B 那边了。
    B->A :新发一个 http post
    4.连接池维护:A 收到 http 请求扔到等待请求池里。
    A 每秒检测一次这个池,超一秒的就返回空 http 应答。
    Arnie97
        13
    Arnie97  
       2017-11-24 01:23:38 +08:00 via Android   ❤️ 1
    想说 chisel 来着,发现#4 已经否定了。我感觉你说的 A、B 方向问题可以通过再套一层来解决?我这里以 chisel 和 openvpn 举个例子,其他协议同理。
    A 部署 chisel server,openvpn server
    B 部署 chisel client,openvpn client
    B 通过 chisel 转发,和 A 建立起 openvpn 连接
    此时 A,B 之间已经可以互访了,在 B 上开 NAT,A 的路由指向 B 即可
    Arnie97
        14
    Arnie97  
       2017-11-24 01:27:49 +08:00 via Android   ❤️ 1
    另外,chisel 是 Go 写的,我不是很了解这门语言是怎么垃圾回收的,反正在我 128MB 内存的容器里没法长期稳定运行,跑了一段时间后会内存不足…
    cy97cool
        15
    cy97cool  
    OP
       2017-11-24 09:46:55 +08:00 via Android
    @iceheart 感谢大佬,确实我没必要自己实现 socks 代理
    但似乎还是要自己实现丢包重传(反向代理 C 在高并发下不靠谱)和多次请求之间的有序性(继续怪 C 不靠谱),就感觉是自己在实现 tcp
    cy97cool
        16
    cy97cool  
    OP
       2017-11-24 09:47:41 +08:00 via Android
    @Arnie97 我来试试这个方案
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1680 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:24 · PVG 00:24 · LAX 08:24 · JFK 11:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.