Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >基于 faas、http 的 tcp 隧道

基于 faas、http 的 tcp 隧道

原创
作者头像
王磊-字节跳动
发布于 2021-09-25 13:37:03
发布于 2021-09-25 13:37:03
1.6K0
举报
文章被收录于专栏:01ZOO01ZOO

背景

tcp 隧道我们见得比较多了,在 这篇文章 就给了一些来例子,其中有一些 tcp 隧道是用来穿越防火墙,或者 "科学上网"; 但是如果去看这些隧道的实现,本质上都是基于 http 的 connect 方法,具体区别可以看这个 wiki, 即实现其实是使用 http 的连接方法,然后 reuse http 底层的 conncetion,比如 websocket 等也是基于类似的实现

代码语言:txt
AI代码解释
复制
Example negotiation
The client connects to the proxy server and requests tunneling by specifying the port and the host computer to which it would like to connect. The port is used to indicate the protocol being requested.[3]

CONNECT streamline.t-mobile.com:22 HTTP/1.1
Proxy-Authorization: Basic encoded-credentials
If the connection was allowed and the proxy has connected to the specified host then the proxy will return a 2XX success response.[3]

HTTP/1.1 200 OK

但是很多时候 http 底层的 connection 我们都不能使用,即无法基于 connect 实现,只能只用 put, get, delete, post 方法,甚至,如果我们使用 faas 实现,比如腾讯云上的 scf,我们甚至连这几种方法都没有,我们只能假设所有的方法都是 post.

如果是这种情况,我们该如何实现呢。

实现

注意:本实现仅仅是 poc(prove of concept)并没有考虑性能优化,实际上,很多点性能可以大幅优化

  1. 首先我们要实现一个 client, 一个 server,client 监听本地的 sock5 端口,转发 tcp 请求到 server 端,这时候 tcp 请求被转化为 http 请求;
  2. server 端收到请求之后代替 client 像远端建立 tcp 连接,将 tcp 连接中的数据返回到 client
  3. client 使用 http 请求模拟一个 tcp 连接,因此,我们要有三种请求 "connect", "write", "read"
  4. server 端需要保持对远端的 连接,即一个 conncetion,这点很重要,如果用 faas 实现,那么 faas 的实例数量要限制为 1(即使用单实例并发,这点 腾讯云的 scf 还没有支持,阿里云已经支持)
代码语言:txt
AI代码解释
复制
sequenceDiagram
local->>client: tcp 代理本地的请求
client->>server: http 请求,类型: connect 
server->>remote: tcp 连接到远端, 读写数据
client->>server: http 请求,类型: write 
client->>server: http 请求,类型: read 
client->>local: tcp 请求返回

为了快速开始,我们 fork 了一个基础的项目: https://github.com/jarvisgally/v2simple, 这个项目实现了一套基础设施(即协议),我们在这上面实现基于 http/faas 的两套实现【再一次声明,这套 http 实现没有使用 connect 方法】

其中 http 的实现主体部分如下(faas 的实现也是类似的,注意代码里面省略了很多,仅仅演示了核心的部分)

代码语言:txt
AI代码解释
复制
const Name = "http"


type HttpClient struct {
	client *resty.Client
	addr   string
}

func NewHttpClient(url *url.URL) (proxy.Client, error) {
	return &HttpClient{
		client: resty.New(),
		addr:   url.String(),
	}, nil
}

func (c *HttpClient) Handshake(_ net.Conn, target string) (io.ReadWriter, error) {
	conn := &httpConnection{
		client:       c,
		target:       target,
		connectionId: RandStringRunes(8),
	}
	return conn, conn.Connect()
}

func (c *HttpClient) post(r *TunnelRequest) (*TunnelResponse, error) {
	ret := &TunnelResponse{}
	_, err := c.client.NewRequest().SetResult(ret).SetBody(r).SetHeader("Content-Type", "application/json").Post(c.addr)
	return ret, err
}

type TunnelRequest struct {
	Target       string
	Action       string // create, read, write
	Data         []byte
	ConnectionId string
}

type TunnelResponse struct {
	Target       string
	Action       string
	Data         []byte
	ConnectionId string
	Eof          bool
	Code         int
	Message      string
}

type httpConnection struct {
	client       *HttpClient
	target       string
	readBuffer   []byte
	writeBuffer  []byte
	connectionId string
	eof          bool
	lastWrite    time.Time
}

func (c *httpConnection) Connect() (err error) {
	_, err = c.client.post(&TunnelRequest{
		Target:       c.target,
		Action:       "connect",
		ConnectionId: c.connectionId,
	})
	return err
}

func (c *httpConnection) Read(p []byte) (n int, err error) {
	if c.eof {
		return 0, io.EOF
	}
	if len(c.readBuffer) == 0 {
		resp, err := c.client.post(&TunnelRequest{
			Target:       c.target,
			Action:       "read",
			ConnectionId: c.connectionId,
		})
		if err != nil {
			return 0, err
		}
		c.readBuffer = append(c.readBuffer, resp.Data...)
		if resp.Eof {
			c.eof = true
		}
	}

	n = copy(p, c.readBuffer)
	c.readBuffer = c.readBuffer[:len(c.readBuffer)-n]
	return n, nil
}

func (c *httpConnection) Write(p []byte) (n int, err error) {
	c.writeBuffer = append(c.writeBuffer, p...)
	if len(c.writeBuffer) > 1024 || time.Now().Sub(c.lastWrite) > time.Millisecond*100 {
		resp, err := c.client.post(&TunnelRequest{
			Target:       c.target,
			Action:       "write",
			Data:         c.writeBuffer,
			ConnectionId: c.connectionId,
		})
		if err != nil {
			return 0, err
		}
		_ = resp
		c.writeBuffer = c.writeBuffer[:0]
	}
	return len(p), nil
}

部署

由于 scf 暂时不支持单实例并发,我们暂时只部署 http 版本,但是 faas 版本我们有理由相信他是 work 的

  1. 部署 server 端代码到一台云主机并启动
  2. 启动 client,并且 client 设置对 google.com 走 http tunnel
  3. 设置本地代理走 sock5, 以我的 macos 为例,设置 网络>wifi>高级>代理>socks代理【填写 client 监听 ip】

演示

代码语言:txt
AI代码解释
复制
# client 端启动
➜ go build -o main .
v2simple/cmd/client on  master by 🐹 v1.16 ☪️  21:29:49
➜ ./main -f client.example.http.json
V2Simple 0.1.0 (V2Simple, a simple implementation of V2Ray 4.25.0), go1.16 darwin amd64
2021/09/25 21:30:02 using /Users/leiwang/Work/go-librarys/src/github.com/u2takey/v2simple/cmd/client/client.example.http.json
2021/09/25 21:30:02 using /Users/leiwang/Work/go-librarys/src/github.com/u2takey/v2simple/cmd/client/blacklist
2021/09/25 21:30:02 socks5 listening TCP on 127.0.0.1:1081
代码语言:txt
AI代码解释
复制
# server 端启动
ubuntu@VM-0-7-ubuntu:~$ ./main
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /                         --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8081

打开 google.com, 连接成功, tcp 隧道实现之后可以在上面做更多复杂的功能,接下来就可以发挥想象力了.

完整的代码在这里

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go - httpclient 常用操作
httpclient 是基于 net/http 封装的 Go HTTP 客户端请求包,支持常用的请求方式、常用设置,比如:
新亮
2021/02/03
6830
开发外包--golang熟练之gin & net(二)
Godev
2023/07/26
6380
给Go的Gin web框架增加 WebSocket 功能
Gin 是一个 go 的 web 框架,它具有轻量级,高性能,运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件,rest api和json。
杨永贞
2021/01/18
8.3K0
有关[Http持久连接]的一切,卷给你看
上文中我的结论是: HTTP Keep-Alive 是在应用层对TCP连接进行滑动续约复用, 如果客户端/服务器稳定续约,就成了名副其实的长连接。
有态度的马甲
2021/12/05
4800
有关[Http持久连接]的一切,卷给你看
Go单测系列2—网络测试
这是Go语言单元测试从零到溜系列教程的第1篇,介绍了如何使用httptest和gock工具进行网络测试。
luckpunk
2023/09/10
4820
流量回放工具之GoReplay output_http 源码分析
GoReplay 对数据流的抽象出了两个概念,即用输入(input )和 输出(output )来表示数据来源与去向,统称为 plugin,用介于输入和输出模块之间的中间件实现拓展机制。
高楼Zee
2021/11/10
5900
Go http client 连接池不复用的问题
当 http client 返回值为不为空,只读取 response header,但不读 body 内容就执行 response.Body.Close(),那么连接会被主动关闭,得不到复用。
梦醒人间
2021/01/04
3.8K0
从零开发区块链应用(一)-golang配置文件管理工具viper
理解 HTTP 构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及 server 端的 response。所谓的 http 服务器,主要在于如何接受 client 的 request,并向 client 返回 response。
Tiny熊
2022/02/22
8370
Go-http编程
web工作流程 Web服务器的工作原理可以简单地归纳为 1.客户机通过TCP/IP协议建立到服务器的TCP连接 2.客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档 3.服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端 4.客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果 HTTP协议 超文本传输协议(HTTP,HyperText Transfer Protoc
ccf19881030
2020/10/30
3870
Go-http编程
一文说透 Go 语言 HTTP 标准库
基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以http服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。
luozhiyun
2021/06/09
1.4K1
【测试平台系列】第一章 手撸压力机(八)- 实现testobject接口
上一章中我们已经启动了一个/engine/run/testObject/接口,但是我们还没有具体的实现该接口,本章我们就来实现一下该接口。
被测试耽误的大厨
2023/11/27
2080
【测试平台系列】第一章 手撸压力机(八)- 实现testobject接口
k8sailor - 05 设计 RESTful API 和 HTTP 响应数据
可以看到, k8s api 中都有一个对应的 kind 描述资源类型, 这个正好符合 RESTful 中资源定位的需求。
老麦
2022/12/24
4380
分布式事务saga的实现
可以先看看理论:https://github.com/ikenchina/octopus/blob/master/README_saga.md
用户6879030
2024/06/05
1550
8.Go编程快速入门学习
[TOC] 0x00 Go语言基础之Socket网络编程 现在的我们几乎每天都在使用互联网,但是你知道程序是如果通过网络互相通信吗? 描述: 相信大部分人通常是一知半解的,作为一个程序员👨‍💻‍,对于网络模型你应该了解,知道网络到底是怎么进行通信的,进行工作的,为什么服务器能够接收到请求,做出响应。这里面的原理应该是每个 Web 程序员应该了解的。 本章我们就一起来学习下Go语言中的网络编程,关于网络编程其实是一个很庞大的领域,本文只是简单的演示了如何使用net包进行TCP和UDP通信。 1.基础概念介绍
全栈工程师修炼指南
2022/09/29
8020
8.Go编程快速入门学习
Golang语言 之网络
Go语言标准库里提供的net包,支持基于IP层、TCP/UDP层及更高层面(如HTTP、FTP、SMTP)的网络操作,其中用于IP层的称为Raw Socket。 net包的Dial()函数用于创建网络连接,函数原型如下: func Dial(net, addr string) (Conn, error) 其中net参数是网络协议的名字,addr参数是IP地址或域名;如果连接成功,返回连接对象,否则返回error。 目前,Dial()函数支持如下几种网络协议:"tcp"、"udp"、"ip"、"ip6"等,例
李海彬
2018/03/21
7980
gRPC如何在Golang和PHP中进行实战?7步教你上手!
导语 | gRPC也是RPC技术家族的一种,它由Google主导开发,是一个跨平台的调用框架,其中和go语言结合的是最紧密的,在go语言的开发和调用中占据主导地位。gRPC采用protobuf作为配置载体来实现通讯和调用。本文主要实战演示一下gRPC的几种调用通讯模式(普通、客户端流、服务端流、双向流)以及和PHP客户端的联通调用。 在学习gRPC之前,我们需要了解一下ptorobuf语法和protoc的命令,能帮助我们更加深入的学习和理解gRPC。 一、需求分析 我们这次只搞个很简单的需求,搞个用户
腾讯云开发者
2021/10/20
3K1
Go HTTP
在网络编程里多数情况下,我们都在处理HTTP请求,关于请求协议的部分我推荐阅读《TCP协议》这本书,绝对值得购买。Go语言也提供了一个标准库包net/http,让开发者可以快速的处理HTTP请求,其封装的易用性,足够简单。并且,整个net包里提供了多种多样的处理模块,比如rpc等等。http包提供了HTTP客户端和服务端的实现,这让我想到了Node.js提供的http模块。
icepy
2019/06/24
1.5K0
Go HTTP
go的net/http有哪些值得关注的细节?
golang的net/http库是我们平时写代码中,非常常用的标准库。由于go语言拥有goroutine,goroutine的上下文切换成本比普通线程低很多,net/http库充分利用了这个优势,因此,它的内部实现跟其他语言会有一些区别。
小白debug
2023/09/01
5350
go的net/http有哪些值得关注的细节?
golang http.Client 用户自定义重定向策略
其实, 还有一个, CheckRedirect 重定向检查条件。只有满足重定向规则条件, 才能继续执行。
老麦
2022/12/24
1.9K0
golang http.Client 用户自定义重定向策略
golang 源码分析(20)http/client
一、net/http的httpclient发起http请求 方法 get请求 func httpGet() { resp, err := http.Get("http://www.01happ
golangLeetcode
2022/08/02
7170
相关推荐
Go - httpclient 常用操作
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档