English | 简体中文
状态: Stable
opennotr是一款开源的内网穿透软件,内部使用透明代理
实现流量劫持,同时,基于虚拟IP构建虚拟局域网,整体技术与k8s的service技术类似,虚拟IP与service ip类似,只在opennotr的服务端所在的机器可以访问,在其他任何位置都无法访问该虚拟ip。
opennotr支持多种协议,包括http,https,grpc,tcp,udp,为了实现http,https,grpc协议端口复用,opennotr引入了openresty作为网关,多个客户端不同域名可以共享http的80,https的443端口,不需要额外的端口。
opennotr支持定制化插件,我们内置了http, https, grpc, tcp, udp代理,可以覆盖大部分场景,同时,opennotr允许自己开发协议插件,比如说,如果您希望使用apisix来作为前置的网关,您可以开发opennotr的apisix插件,opennotr会把一些基本信息传递给插件,其余功能均由插件自己进行。
opennotr内置的协议也是以插件的形式存在的,只是默认导入到程序当中。
- 支持多种协议,可覆盖大部分内网穿透场景
- 支持定制化插件,opennotr提供一个虚拟局域网的基础功能,您开发的插件运行在这个局域网内
- 引入openresty作为网关,网络处理性能问题可以得到保证
- 支持动态域名解析,可支持引入coredns作为dns的nameserver。
最下层是客户端所在的机器/设备,同时也作为虚拟局域网当中的一台机器,具备该局域网的IP地址100.64.240.100
最上层是服务端所在的机器,同时也作为虚拟局域网当中的网关,具备该局域网的IP地址100.64.240.1
,opennotr提供能够在Public Cloud
层可通过100.64.240.100
访问Device/PC behind Routed
的能力。在这个能力的基础之上构建整个内网穿透体系。
上图中,虚拟IP不存在任何网卡,只是内部虚拟出来的一个ip,便于流量劫持,opennotr通过透明代理,将访问虚拟ip的流量转到本机监听的端口,然后查找该虚拟ip对应的客户端长连接,将流量通过长连接下发到客户端,从而实现穿透。
上图中,openresty是基于我openresty开发的http网关,目的是支持动态upstream,可通过接口增删upstream,感兴趣的可以查看resty-upstream,当有客户端连接上来时,会调用resty-upstream
提供的接口新增配置,当客户端断开连接时,会调用resty-upstream
提供的删除接口删除配置。
从整个系统的层面,openresty的upstream的地址为opennotr客户端分配的lan地址,至于如何与客户端lan地址通信,这是opennotr处理的事情,对于openresty而言是透明的。
上图中,针对tcp和udp,opennotr倒是没有使用openresty的功能,而是自己开发的代理程序,当前也是集成在opennotrd程序当中,具体可参考以下两个文件。
不使用openresty处理tcp和udp主要基于以下考虑:
-
openresty本身的stream功能并不能满足要求,这里的场景是事先不知道需要监听哪个端口,而openresty的server配置块需要预先知道监听的端口,否则需要重写配置再reload。
-
openresty的stream模块和http是分割的,原本使用http接口实现的动态upstream针对tcp并不生效,因此需要使用lua的tcp编程接受请求,作者自身对lua和openresty了解不深,不贸贸然行动。
-
不使用openresty构建tcp/udp代理难度并不大,http使用openresty是为了端口复用,根据host区分,而tcp则没有类似的机制,也就是说,即使用openresty,也是单独的端口。
后续有兴趣的同学可以考虑将openresty完全换成golang实现,或者将golang的tcp/udp部分换成openresty。笔者注意到市面上已经有类似的实现,比如apache apisix是使用的openresty实现的http,tcp,udp网关,这个是很不错的项目,我的resty-upstream项目大部分都是从该项目学习而来
opennotr包括服务端程序opennotrd
和客户端程序opennotr
强烈建议使用docker运行opennotrd
为了运行opennotrd,您需要准备好程序运行的配置文件和tls证书等信息,以下为笔者的一个示例
root@iZwz97kfjnf78copv1ae65Z:/opt/data/opennotrd# tree
.
|-- cert ---------------------> 证书,秘钥目录
| |-- upstream.crt
| `-- upstream.key
`-- notrd.yaml ---------------> opennotrd启动的配置文件
2 directories, 5 files
这里主要关注notrd.yaml
文件的内容,该文件是opennotrd运行时的配置文件.
server:
listen: ":10100"
authKey: "client server exchange key"
domain: "open.notr.tech"
tcpforward:
listen: ":4398"
udpforward:
listen: ":4399"
dhcp:
cidr: "100.64.242.1/24"
ip: "100.64.242.1"
plugin:
tcp: |
{}
udp: |
{}
http: |
{
"adminUrl": "http://127.0.0.1:81/upstreams"
}
https: |
{
"adminUrl": "http://127.0.0.1:81/upstreams"
}
h2c: |
{
"adminUrl": "http://127.0.0.1:81/upstreams"
}
大部分情况下,上述配置只需要调整server
区块相关配置,其他保留默认即可。
准备好配置之后运行以下命令即可开始启动:
docker run --privileged --net=host -v /opt/logs/opennotr:/opt/resty-upstream/logs -v /opt/data/opennotrd:/opt/conf -d ickelin/opennotr:v1.0.4
需要配置volume,将主机目录/opt/data/opennotrd
挂载到容器的/opt/conf
当中,/opt/data/opennotrd
为在上一步当中创建的配置文件,证书目录。
或者您也可以使用docker-compose启动
wget https://github.com/ICKelin/opennotr/blob/master/docker-build/docker-compose.yaml
docker-compose up -d opennotrd
opennotr的启动比较简单,首先需要准备配置.
serverAddr: "demo.notr.tech:10100"
key: "client server exchange key"
domain: "cloud.dahuizong.com"
# 转发表信息
# ports字段解析: 公网端口: 本机监听的端口
forwards:
- protocol: tcp
ports:
2222: 2222
- protocol: udp
ports:
53: 53
- protocol: http
ports:
0: 8080
- protocol: https
ports:
0: 8081
- protocol: h2c
ports:
0: 50052
配置准备好之后,启动opennotr即可
./opennotr -conf config.yaml
要开发opennotr支持的插件,您需要实现以下接口:
// IPlugin defines plugin interface
// Plugin should implements the IPlugin
type IPlugin interface {
// Setup calls at the begin of plugin system initialize
// plugin system will pass the raw message to plugin's Setup function
Setup(json.RawMessage) error
// Close a proxy, it may be called by client's connection close
StopProxy(item *PluginMeta)
// Run a proxy, it may be called by client's connection established
RunProxy(item *PluginMeta) error
}
Setup
函数负责初始化您的插件,由插件管理程序调用,开发者无需手动调用,参数是插件运行需要的配置,格式为json
格式
RunProxy
和StopProxy
也是由我们插件管理程序调用的,需要由开发者自己实现。
实现以上三个接口之后,需要在插件当中调用注册函数,将插件注册到系统当中,比如:
package tcpproxy
import (
"encoding/json"
"fmt"
"github.com/ICKelin/opennotr/opennotrd/plugin"
)
func init() {
plugin.Register("tcp", &TCPProxy{})
}
type TCPProxy struct{}
func (t *TCPProxy) Setup(config json.RawMessage) error { return nil }
func (t *TCPProxy) StopProxy(item *plugin.PluginMeta) {}
func (t *TCPProxy) RunProxy(item *plugin.PluginMeta) error {
return fmt.Errorf("TODO://")
}
最后,需要在opennotrd/plugins.go
当中导入您的插件所在的包。
import (
// plugin import
_ "github.com/ICKelin/opennotr/opennotrd/plugin/tcpproxy"
)
示例插件可参考tcpproxy.go
一个爱好编程的人,网名叫ICKelin。对于以下任何问题,包括
- 项目实现细节
- 项目使用问题
- 项目建议,代码问题
- 案例分享
- 技术交流
可加微信: zyj995139094