Compare commits

..

23 Commits

Author SHA1 Message Date
TenderIronh
c733a2a4a1 doc 2022-02-11 18:33:50 +08:00
TenderIronh
b54fa2c6be doc 2022-02-10 23:20:16 +08:00
TenderIronh
133fe046f8 doc 2022-02-03 23:51:58 +08:00
TenderIronh
95b46f51d0 web console 2022-02-03 23:43:28 +08:00
TenderIronh
7686af39e0 Improve update mechanism 2022-01-04 15:34:29 +08:00
TenderIronh
16b937ebd7 config doc 2021-12-30 15:14:07 +08:00
TenderIronh
ac454ec694 fix parameters default value 2021-12-30 14:54:45 +08:00
TenderIronh
029d69869f refactor autorunApp and add api for web 2021-12-30 11:18:05 +08:00
TenderIronh
a528441342 install error 2021-12-21 14:41:07 +08:00
TenderIronh
2d6521be43 update readme 2021-12-15 15:42:27 +08:00
TenderIronh
2223634c83 update readme 2021-12-15 15:41:39 +08:00
TenderIronh
6c1551d951 auto adjust server and local timestamp for totp 2021-12-14 15:25:38 +08:00
TenderIronh
dd3d87c3d2 update doc 2021-12-13 18:31:31 +08:00
TenderIronh
a28fc33b66 fix nil pointer and testing public ip 2021-12-10 17:20:17 +08:00
TenderIronh
f12059d889 install mode and improve relay 2021-12-09 18:54:46 +08:00
TenderIronh
a2387fed68 fix build darwin 2021-12-09 11:26:59 +08:00
hhd
8353c1c3dd ifconfig retry 2021-12-01 18:00:23 +08:00
hhd
a9250737f1 report netinfo, improve relay model 2021-12-01 16:08:44 +08:00
hhd
c83a57f014 goal and principle for free 2021-11-30 15:57:41 +08:00
hhd
8551264a15 no weibo wechat 2021-11-25 18:08:05 +08:00
hhd
1ef0f919d9 lower case png 2021-11-25 18:03:56 +08:00
hhd
f01ca64dd9 readme 2021-11-25 11:00:17 +08:00
hhd
c6f50992c3 readme 2021-11-24 22:53:46 +08:00
54 changed files with 1300 additions and 665 deletions

View File

@@ -1,58 +1,80 @@
[English](/README.md)|中文
# [English](/README.md)|中文
网站: [openp2p.cn](https://openp2p.cn)
## OpenP2P是什么
它是一个开源、免费、轻量级的P2P共享网络。任何设备接入OpenP2P就可以随时随地访问它们
它是一个开源、免费、轻量级的P2P共享网络。你的设备将组成一个私有P2P网络里面的设备可以直接访问其它成员或者通过其它成员转发数据间接访问。如果私有网络无法完成通信将会到公有P2P网络寻找共享节点协助通信
相比BT网络用来共享文件OpenP2P网络用来共享带宽。
我们的目标是:充分利用带宽,利用共享节点转发数据,建设一个远程连接的通用基础设施。
## 为什么选择OpenP2P
### 免费
完全免费,满足大部分用户的核心白票需求。不像其它类似的产品,我们不需要有公网IP的服务器不需要花钱买服务。
### 安全
代码开源,接受各位大佬检验。下面详细展开
### 轻量
### 1. 免费
完全免费,满足大部分用户的核心白票需求。不像其它类似的产品,OpenP2P不需要有公网IP的服务器不需要花钱买服务。了解它原理即可理解为什么能做到免费。
### 2. 共享
你的设备会形成一个私有P2P网络它们之间共享带宽提供网络数据转发服务。
当你的私有P2P网络下没有可以提供转发服务的节点时会尝试在公共P2P网络寻找转发节点。
默认会开启共享限速10mbps只有你用户下提供了共享节点才能使用别人的共享节点。这非常公平也是这个项目的初衷。
我们建议你在带宽足够的地方(比如办公室,家里的百兆光纤)加入共享网络。
如果你不想共享任何节点,或设置共享带宽,请查看[详细使用说明](/USAGE-ZH.md)
### 3. 安全
代码开源P2P隧道使用TLS1.3+AES双重加密共享节点临时授权使用TOTP一次性密码
[查看详细](#安全性)
### 4. 轻量
文件大小2MB+运行内存2MB+;全部在应用层实现,没有虚拟网卡,没有内核程序
### 跨平台
### 5. 跨平台
因为轻量所以很容易支持各个平台。支持主流的操作系统Windows,Linux,MacOS和主流的cpu架构386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
### 高效
### 6. 高效
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境无论NAT1-4Cone或Symmetric都支持。依靠Quic协议优秀的拥塞算法能在糟糕的网络环境获得高带宽低延时。
### 二次开发
### 7. 二次开发
基于OpenP2P只需数行代码就能让原来只能局域网通信的程序变成任何内网都能通信
## 快速入门
以一个最常见的例子说明OpenP2P如何使用远程办公在家里连入办公室Windows电脑
相信很多人在疫情下远程办公是刚需。
1. 先确认办公室电脑已开启远程桌面功能如何开启参考官方说明https://docs.microsoft.com/zh-cn/windows-server/remote/remote-desktop-services/clients/remote-desktop-allow-access
2. 在办公室下载最新的OpenP2P(补上URL),解压出来,在命令行执行
```
openp2p.exe -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
```
`切记将标记大写的参数改成自己的`
仅需简单4步就能用起来
下面是一个远程办公例子在家里连入办公室Windows电脑。
### 1.注册
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
![image](/doc/images/register.png)
### 2.安装
分别在本地和远程电脑下载后双击运行,一键安装
![image](/doc/images/install.png)
Windows默认会阻止没有花钱买它家证书签名过的程序选择“仍要运行”即可。
![image](/doc/images/win10warn.png)
![image](/doc/images/stillrun.png)
### 3.新建P2P应用
![image](/doc/images/devices.png)
![image](/doc/images/newapp.png)
![image](/doc/images/newappedit.png)
### 4.使用P2P应用
在“MyHomePC”设备上能看到刚才创建的P2P应用连接下图显示的“本地监听端口”即可。
![image](/doc/images/p2pappok.png)
在家里Windows电脑按Win+R输入mstsc打开远程桌面输入127.0.0.1:23389 /admin
![image](/doc/images/officelisten.png)
3. 在家里下载最新的OpenP2P(补上URL),解压出来,在命令行执行
```
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 --peernode OFFICEPC1 --dstip 127.0.0.1 --dstport 3389 --srcport 23389 --protocol tcp
```
`切记将标记大写的参数改成自己的`
![image](/doc/images/homeconnect.png)
![image](/doc/images/mem.png)
`LISTEN ON PORT 23389 START` 看到这行日志表示P2PApp建立成功监听23389端口。只需连接本机的127.0.0.1:23389就相当于连接公司Windows电脑的3389端口。
4. 在家里Windows电脑按Win+R输入mstsc打开远程桌面输入127.0.0.1:23389 /admin
![image](/doc/images/mstscconnect.png)
![image](/doc/images/afterconnect.png)
## [详细使用说明](/USAGE-ZH.md)
## 详细使用说明
[这里](/USAGE-ZH.md)介绍如何手动运行
## 典型应用场景
特别适合大流量的内网访问
### 远程办公
Windows MSTSC、VNC等远程桌面SSH内网各种ERP系统
### 远程访问NAS
管理大量视频、图片
### 远程监控摄像头
### 远程刷机
### 远程数据备份
>* 远程办公: Windows MSTSC、VNC等远程桌面SSH内网各种ERP系统
>* 远程访问内网ERP系统
>* 远程访问NAS: 管理大量视频、图片
>* 远程监控摄像头
>* 远程刷机
>* 远程数据备份
---
## 概要设计
### 原型
@@ -62,18 +84,14 @@ Windows MSTSC、VNC等远程桌面SSH内网各种ERP系统
### P2PApp
它是项目里最重要的概念一个P2PApp就是把远程的一个服务mstsc/ssh等通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API主要工作就是管理P2PApp
![image](/doc/images/appdetail.png)
## 共享
默认会开启共享限速10mbps只有你用户下提供了共享节点才能使用别人的共享节点。这非常公平也是这个项目的初衷。
我们建议你在带宽足够的地方(比如办公室,家里的百兆光纤)加入共享网络。
如果你仍然不想共享任何节点,请查看运行参数
## 安全性
加入OpenP2P共享网络的节点只能凭授权访问。共享节点只会中转数据别人无法访问内网任何资源。
### TLS1.3+AES
### 1. TLS1.3+AES
两个节点间通信数据走业界最安全的TLS1.3通道。通信内容还会使用AES加密双重安全密钥是通过服务端作换。有效阻止中间人攻击
### 共享的中转节点是否会获得我的数据
### 2. 共享的中转节点是否会获得我的数据
没错中转节点天然就是一个中间人所以才加上AES加密通信内容保证安全。中转节点是无法获取明文的
### 中转节点是如何校验权限的
### 3. 中转节点是如何校验权限的
服务端有个调度模型根据带宽、ping值、稳定性、服务时长尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码hmac-sha256算法校验它是一次性密码和我们平时使用的手机验证码或银行密码器一样的原理。
## 编译
@@ -102,7 +120,7 @@ go build
14. 内网直连优化,用处不大,估计就用户测试时用到
远期计划:
1. 彻底地分布式去中心化设计
1. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。
2. 企业级支持,可以更好地管理大量设备,和更安全更细的权限控制
## 参与贡献
@@ -111,9 +129,7 @@ TODO或ISSUE里如果有你擅长的领域或者你有特别好的主意
它是一个中国人发起的项目,更懂国内网络环境,更懂用户需求,更好的企业级支持
## 技术交流
QQ群16947733
邮箱openp2p.cn@gmail.com 271357901@qq.com
第一时间获得最新版本消息以及一些最新IT业界动态
微信公众号openp2p
微博https://weibo.com/openp2p
邮箱openp2p.cn@gmail.com tenderiron@139.com
## 免责声明
本项目开源供大家学习和免费使用,禁止用于非法用途,任何不当使用本项目或意外造成的损失,本项目及相关人员不会承担任何责任。

129
README.md
View File

@@ -1,76 +1,84 @@
English|[中文](/README-ZH.md)
# English|[中文](/README-ZH.md)
Website: [openp2p.cn](https://openp2p.cn)
## What is OpenP2P
It is an open source, free, and lightweight P2P sharing network. As long as any device joins in, you can access them anywhere
It is an open source, free, and lightweight P2P sharing network. Your devices will form a private P2P network, in which devices can directly access other members, or indirectly access through other members forwarding data.
If the private network cannot complete the communication, it will go to the public P2P network to find a shared node to assist in the communication. Compared with the BT network used to share files, the OpenP2P network is used to share bandwidth.
Our goal is to make full use of bandwidth, use shared nodes to relay data, and build a common infrastructure for remote connections.
## Why OpenP2P
### Free
Totaly free, fullfills most of users(especially free-rider). Unlike other similar products, we don't need a server with public IP, and don't need to pay for services.
### 1. Free
Totaly free, fullfills most of users(especially free-rider). Unlike other similar products, OpenP2p doesn't need a server with public IP, and doesn't need to pay for services.By understanding its principle, you can understand why it can be done for free.
### 2. Share
Your devices will form a private P2P network, share bandwidth between them, and provide network data forwarding services.
When there is no node that can provide forwarding services in your private P2P network, you will try to find forwarding nodes in the public P2P network.
10mbps is its default setting of share speed limit. Only when you have shared their nodes, you are allowed to use others' shared nodes. This is very fair, and it is also the original intention of this project.
We recommend that you join a shared network in a place with sufficient bandwidth (such as an office or home with 100M optical fiber).
If you are not willing to contribute any node to the OpenP2P share network, please refer to the [usage](/USAGE.md) for your own setting.
### 3. Safe
The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and the shared node temporarily authorizes the use of the TOTP one-time password
[details](#Safety)
### Safe
Open source, trustable(see details below)
### Lightweight
### 4. Lightweight
2MB+ filesize, 2MB+ memory. It runs at appllication layer, no vitrual NIC, no kernel driver.
### Cross-platform
### 5. Cross-platform
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64.
### Efficient
### 6. Efficient
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric). Relying on the excellent congestion algorithm of the Quic protocol, high bandwidth and low latency can be obtained in a bad network environment.
### Integration
### 7. Integration
Your applicaiton can call OpenP2P with a few code to make any internal networks communicate with each other.
## Get Started
A common scenario to introduce OpenP2P: remote work. At home connects to office's Linux PC .
Under the outbreak of covid-19 pandemic, surely remote work becomes a fundamental demand.
Just 4 simple steps to use.
Here's an example of remote work: connecting to an office Windows computer at home.
### 1.Register
Go to <https://console.openp2p.cn> register a new user
1. Make sure your office device(Linux) has opened the access of ssh.
```
netstat -nl | grep 22
```
Output sample
![image](/doc/images/officelisten_linux.png)
![image](/doc/images/register_en.png)
### 2.Install
Download on local and remote computers and double-click to run, one-click installation
2. Download the latest version of OpenP2P(TBC),unzip the downloaded package, and execute below command line.
```
tar xvf openp2p0.95.3.linux-amd64.tar.gz
openp2p -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
```
`Must change the parameters marked in uppercase to your own`
![image](/doc/images/install_en.png)
Output sample
![image](/doc/images/officeexecute_linux.png)
By default, Windows will block programs that have not been signed by the Microsoft's certificate, and you can select "Run anyway".
3. Download the same package of OpenP2P(TBC) on your home deviceunzip and execute below command line.
```
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 --peernode OFFICEPC1 --dstip 127.0.0.1 --dstport 22 --srcport 22022 --protocol tcp
```
`Must change the parameters marked in uppercase to your own`
Output sample
![image](/doc/images/homeconnect_windows.png)
The log of `LISTEN ON PORT 22022 START` indicates P2PApp runs successfully on your home device, listing port is 22022. Once connects to local ip:port,127.0.0.1:22022, it means the home device has conneccted to the office device's port, 22.
![image](/doc/images/officelisten_2_linux.png)
![image](/doc/images/win10warn_en.png)
![image](/doc/images/stillrun_en.png)
### 3.New P2PApp
![image](/doc/images/devices_en.png)
![image](/doc/images/newapp_en.png)
![image](/doc/images/newappedit_en.png)
### 4.Use P2PApp
You can see the P2P application you just created on the "MyHomePC" device, just connect to the "local listening port" shown in the figure below.
![image](/doc/images/p2pappok_en.png)
On MyHomePC, press Win+R and enter MSTSC to open the remote desktop, input `127.0.0.1:23389 /admin`
![image](/doc/images/mstscconnect_en.png)
![image](/doc/images/afterconnect_en.png)
4. Test the connection between office device and home device.In your home deivce, run SSH to login the office device.
```
ssh -p22022 root@127.0.0.1:22022
```
![image](/doc/images/sshconnect.png)
## [Usage](/USAGE.md)
## Usage
[Here](/USAGE.md) describes how to run manually
## Scenarios
Especially suitable for large traffic intranet access.
### Remote work
Windows MSTSC, VNC and other remote desktops, SSH, various ERP systems in the intranet
### Remote Access NAS
Manage a large number of videos and pictures
### Remote Access Camera
### Remote Flashing Phone
### Remotely Data Backup
>* Remote work: Windows MSTSC, VNC and other remote desktops, SSH, various ERP systems in the intranet
>* Remote access ERP systems in the intranet
>* Remote access NAS: Manage a large number of videos and pictures
>* Remote access camera
>* Remote flashing phone
>* Remotely data backup
---
## Overview Design
### Prototype
@@ -81,19 +89,16 @@ Manage a large number of videos and pictures
P2PAPP is the most import concept in this project, one P2PApp is able to map the remote service(mstsc/ssh) to the local listening. The main job of re-development or restful API we provide is to manage P2PApp.
![image](/doc/images/appdetail.png)
## Share
10mbps is its default setting of share speed limit. Only when your users have shared their nodes, they are allowed to use others' shared nodes. This is very fair, and it is also the original intention of this project.
We recommend that you join a shared network in a place with sufficient bandwidth (such as an office or home with 100M optical fiber).
If you are still not willing to contribute any node to the OpenP2P share network, please refer to the operating parameters for your own setting.
## Safety
The nodes which have joined the OpenP2P share network can vist each other by authentications. Shared nodes will only relay data, and others cannot access any resources in the intranet.
### TLS1.3+AES
### 1. TLS1.3+AES
The communication data between the two nodes uses the industry's most secure TLS1.3 channel. The communication content will also use AES encryption, double security, the key is exchanged through the server. Effectively prevent man-in-the-middle attacks.
### Will the shared node capture my data?
### 2. Will the shared node capture my data?
That's right, the relay node is naturally an man-in-middle, so AES encryption is added to ensure the security of the communication content. The relay node cannot obtain the plaintext.
### How does the shared relay node verify the authority?
### 3. How does the shared relay node verify the authority?
The server side has a scheduling model, which calculate bandwith, ping value,stability and service duration to provide a well-proportioned service to every share node. It uses TOTP(Time-based One-time Password) with hmac-sha256 algorithem, its theory as same as the cellphone validation code or bank cipher coder.
## Build
@@ -123,7 +128,7 @@ Short-Term:
Long-Term:
1. Decentration and distribution.
1. Use blockchain technology to decentralize, so that users who share equipment have benefits, thereby promoting more users to share, and achieving a positive closed loop.
2. Enterprise-level product can well manage large scale equipment and ACL.
@@ -135,10 +140,6 @@ QQ Group: 16947733
Email: openp2p.cn@gmail.com tenderiron@139.com
Get the latest version news, and some of the latest IT industry trends
WeChat public account: openp2p
## Disclaimer
This project is open source for everyone to learn and use for free. It is forbidden to be used for illegal purposes. Any loss caused by improper use of this project or accident, this project and related personnel will not bear any responsibility.

View File

@@ -1,33 +1,75 @@
# 详细运行参数说明
# 手动运行说明
大部分情况通过<https://console.openp2p.cn> 操作即可。有些情况需要手动运行
> :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p"
## 监听
## 安装和监听
```
openp2p.exe -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
./openp2p install -node OFFICEPC1 -token TOKEN
./openp2p -d -node OFFICEPC1 -token TOKEN
# 注意Windows系统把“./openp2p” 换成“openp2p.exe”
```
>* -d daemon模式推荐使用。发现worker进程意外退出就会自动启动新的worker进程
>* -node 独一无二的节点名字,唯一标识
>* -user 独一无二的用户名字,该节点属于这个user
>* -password 密码
>* -sharebandwidth 作为共享节点时提供带宽默认10mbps. 如果是光纤大带宽,设置越大效果越好
>* -loglevel 需要查看更多调试日志设置0默认是1
>* -noshare 不共享该节点只在私有的P2P网络使用。不加入共享的P2P网络这样也意味着无法使用别人的共享节点
>* install: 安装模式推荐】,会安装成系统服务,这样它就能随系统自动启动
>* -d: daemon模式。发现worker进程意外退出就会自动启动新的worker进程
>* -node: 独一无二的节点名字,唯一标识
>* -token: 在<console.openp2p.cn>“我的”里面找到
>* -sharebandwidth: 作为共享节点时提供带宽默认10mbps. 如果是光纤大带宽,设置越大效果越好. 0表示不共享该节点只在私有的P2P网络使用。不加入共享的P2P网络这样也意味着无法使用别人的共享节点
>* -loglevel: 需要查看更多调试日志设置0默认是1
## 连接
```
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389
使用配置文件建立多个P2PApp
./openp2p -d
```
>* -appname: 这个P2P应用名字
>* -peernode: 目标节点名字
>* -dstip: 目标服务地址默认本机127.0.0.1
>* -dstport: 目标服务端口常见的如windows远程桌面3389Linux ssh 22
>* -protocol: 目标服务协议 tcp、udp
## 配置文件
一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json``/usr/local/openp2p/config.json`
希望修改参数或者配置多个P2PApp可手动修改配置文件
配置实例
```
{
"network": {
"Node": "hhd1207-222",
"Token": "TOKEN",
"ShareBandwidth": 0,
"ServerHost": "api.openp2p.cn",
"ServerPort": 27183,
"UDPPort1": 27182,
"UDPPort2": 27183
},
"apps": [
{
"AppName": "OfficeWindowsPC",
"Protocol": "tcp",
"SrcPort": 23389,
"PeerNode": "OFFICEPC1",
"DstPort": 3389,
"DstHost": "localhost",
},
{
"AppName": "OfficeServerSSH",
"Protocol": "tcp",
"SrcPort": 22,
"PeerNode": "OFFICEPC1",
"DstPort": 22,
"DstHost": "192.168.1.5",
}
]
}
```
>* -peernode 目标节点名字
>* -dstip 目标服务地址默认本机127.0.0.1
>* -dstport 目标服务端口常见的如windows远程桌面3389Linux ssh 22
>* -protocol 目标服务协议 tcp、udp
>* -peeruser 目标用户,如果是同一个用户下的节点,则无需设置
>* -peerpassword 目标密码,如果是同一个用户下的节点,则无需设置
>* -f 配置文件,如果希望配置多个P2PApp参考[config.json](/config.json)
## 升级客户端
```
# update local client
openp2p update
./openp2p update
# update remote client
curl --insecure 'https://openp2p.cn:27182/api/v1/device/YOUR-NODE-NAME/update?user=&password='
```
@@ -38,4 +80,9 @@ Linux系统Ubuntu和CentOS7的防火墙默认配置均不会有影响
systemctl stop firewalld.service
systemctl start firewalld.service
firewall-cmd --state
```
## 卸载
```
./openp2p uninstall
```

View File

@@ -1,33 +1,76 @@
# Parameters details
## Listen
# Parameters details
In most cases, you can operate it through <https://console.openp2p.cn>. In some cases it is necessary to run manually
> :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p"
## Install and Listen
```
openp2p.exe -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
./openp2p install -node OFFICEPC1 -token TOKEN
Or
./openp2p -d -node OFFICEPC1 -token TOKEN
```
>* -d daemon mode is recommand. When the worker process is found to exit unexpectedly, a new worker process will be automatically started
>* -node Unique node name, unique identification
>* -user Unique user name, the node belongs to this user
>* -password Password
>* -sharebandwidth Provides bandwidth when used as a shared node, the default is 10mbps. If it is a large bandwidth of optical fiber, the larger the setting, the better the effect
>* -loglevel Need to view more debug logs, set 0; the default is 1
>* -noshare Not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other peoples shared nodes
>* install: [recommand] will install as system service. So it will autorun when system booting.
>* -d: daemon mode run once. When the worker process is found to exit unexpectedly, a new worker process will be automatically started
>* -node: Unique node name, unique identification
>* -token: See <console.openp2p.cn> "Profile"
>* -sharebandwidth: Provides bandwidth when used as a shared node, the default is 10mbps. If it is a large bandwidth of optical fiber, the larger the setting, the better the effect. 0 means not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other peoples shared nodes
>* -loglevel: Need to view more debug logs, set 0; the default is 1
## Connect
```
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389
Create multiple P2PApp by config file
./openp2p -d
```
>* -peernode Target node name
>* -dstip Target service address, default local 127.0.0.1
>* -dstport Target service port, such as windows remote desktop 3389, Linux ssh 22
>* -protocol Target service protocol tcp, udp
>* -peeruser The target user, if it is a node under the same user, no need to set
>* -peerpassword The target password, if it is a node under the same user, no need to set
>* -f Configuration file, if you want to configure multiple P2PApp refer to [config.json](/config.json)
>* -appname: This P2PApp name
>* -peernode: Target node name
>* -dstip: Target service address, default local 127.0.0.1
>* -dstport: Target service port, such as windows remote desktop 3389, Linux ssh 22
>* -protocol: Target service protocol tcp, udp
## Config file
Generally saved in the current directory, in installation mode it will be saved to `C:\Program Files\OpenP2P\config.json` or `/usr/local/openp2p/config.json`
If you want to modify the parameters, or configure multiple P2PApps, you can manually modify the configuration file
Configuration example
```
{
"network": {
"Node": "hhd1207-222",
"Token": "TOKEN",
"ShareBandwidth": 0,
"ServerHost": "api.openp2p.cn",
"ServerPort": 27183,
"UDPPort1": 27182,
"UDPPort2": 27183
},
"apps": [
{
"AppName": "OfficeWindowsPC",
"Protocol": "tcp",
"SrcPort": 23389,
"PeerNode": "OFFICEPC1",
"DstPort": 3389,
"DstHost": "localhost",
},
{
"AppName": "OfficeServerSSH",
"Protocol": "tcp",
"SrcPort": 22,
"PeerNode": "OFFICEPC1",
"DstPort": 22,
"DstHost": "192.168.1.5",
}
]
}
```
## Client update
```
# update local client
openp2p update
./openp2p update
# update remote client
curl --insecure 'https://openp2p.cn:27182/api/v1/device/YOUR-NODE-NAME/update?user=&password='
```
@@ -38,4 +81,9 @@ The default firewall configuration of Linux system (Ubuntu and CentOS7) will not
systemctl stop firewalld.service
systemctl start firewalld.service
firewall-cmd --state
```
## Uninstall
```
./openp2p uninstall
```

View File

@@ -7,39 +7,39 @@ import (
// BandwidthLimiter ...
type BandwidthLimiter struct {
freeFlowTime time.Time
bandwidth int // mbps
freeFlow int // bytes
maxFreeFlow int // bytes
freeFlowMtx sync.Mutex
ts time.Time
bw int // mbps
freeBytes int // bytes
maxFreeBytes int // bytes
mtx sync.Mutex
}
// mbps
func newBandwidthLimiter(bw int) *BandwidthLimiter {
return &BandwidthLimiter{
bandwidth: bw,
freeFlowTime: time.Now(),
maxFreeFlow: bw * 1024 * 1024 / 8,
freeFlow: bw * 1024 * 1024 / 8,
bw: bw,
ts: time.Now(),
maxFreeBytes: bw * 1024 * 1024 / 8,
freeBytes: bw * 1024 * 1024 / 8,
}
}
// Add ...
func (bl *BandwidthLimiter) Add(bytes int) {
if bl.bandwidth <= 0 {
if bl.bw <= 0 {
return
}
bl.freeFlowMtx.Lock()
defer bl.freeFlowMtx.Unlock()
bl.mtx.Lock()
defer bl.mtx.Unlock()
// calc free flow 1000*1000/1024/1024=0.954; 1024*1024/1000/1000=1.048
bl.freeFlow += int(time.Now().Sub(bl.freeFlowTime) * time.Duration(bl.bandwidth) / 8 / 954)
if bl.freeFlow > bl.maxFreeFlow {
bl.freeFlow = bl.maxFreeFlow
bl.freeBytes += int(time.Since(bl.ts) * time.Duration(bl.bw) / 8 / 954)
if bl.freeBytes > bl.maxFreeBytes {
bl.freeBytes = bl.maxFreeBytes
}
bl.freeFlow -= bytes
bl.freeFlowTime = time.Now()
if bl.freeFlow < 0 {
bl.freeBytes -= bytes
bl.ts = time.Now()
if bl.freeBytes < 0 {
// sleep for the overflow
time.Sleep(time.Millisecond * time.Duration(-bl.freeFlow/(bl.bandwidth*1048/8)))
time.Sleep(time.Millisecond * time.Duration(-bl.freeBytes/(bl.bw*1048/8)))
}
}

View File

@@ -1,10 +1,18 @@
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/tls"
"encoding/json"
"fmt"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"time"
)
func getmac(ip string) string {
@@ -99,3 +107,54 @@ func decryptBytes(key []byte, out, in []byte, dataLen int) ([]byte, error) {
mode.CryptBlocks(out[:dataLen], in[:dataLen])
return pkcs7UnPadding(out, dataLen)
}
// {240e:3b7:622:3440:59ad:7fa1:170c:ef7f 47924975352157270363627191692449083263 China CN 0xc0000965c8 Guangdong GD 0 Guangzhou 23.1167 113.25 Asia/Shanghai AS4134 Chinanet }
func netInfo() *NetInfo {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
// DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// var d net.Dialer
// return d.DialContext(ctx, "tcp6", addr)
// },
}
// sometime will be failed, retry
for i := 0; i < 2; i++ {
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
r, err := client.Get("https://ifconfig.co/json")
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
continue
}
defer r.Body.Close()
buf := make([]byte, 1024*64)
n, err := r.Body.Read(buf)
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
continue
}
rsp := NetInfo{}
err = json.Unmarshal(buf[:n], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong NetInfo:%s", err)
continue
}
return &rsp
}
return nil
}
func execOutput(name string, args ...string) string {
cmdGetOsName := exec.Command(name, args...)
var cmdOut bytes.Buffer
cmdGetOsName.Stdout = &cmdOut
cmdGetOsName.Run()
return cmdOut.String()
}
func defaultNodeName() string {
name, _ := os.Hostname()
for len(name) < 8 {
name = fmt.Sprintf("%s%d", name, rand.Int()%10)
}
return name
}

View File

@@ -39,3 +39,7 @@ func TestAESCBC(t *testing.T) {
}
}
func TestNetInfo(t *testing.T) {
log.Println(netInfo())
}

168
config.go
View File

@@ -2,21 +2,27 @@ package main
import (
"encoding/json"
"flag"
"io/ioutil"
"os"
"sync"
"time"
)
var gConf Config
const IntValueNotSet int = -99999999
type AppConfig struct {
// required
Protocol string
SrcPort int
PeerNode string
DstPort int
DstHost string
PeerUser string
PeerPassword string
AppName string
Protocol string
SrcPort int
PeerNode string
DstPort int
DstHost string
PeerUser string
Enabled int // default:1
// runtime info
peerToken uint64
peerNatType int
@@ -27,33 +33,76 @@ type AppConfig struct {
shareBandwidth int
}
// TODO: add loglevel, maxlogfilesize
type Config struct {
Network NetworkConfig `json:"network"`
Apps []AppConfig `json:"apps"`
daemonMode bool
Network NetworkConfig `json:"network"`
Apps []AppConfig `json:"apps"`
LogLevel int
mtx sync.Mutex
}
func (c *Config) add(app AppConfig) {
func (c *Config) switchApp(app AppConfig, enabled int) {
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps[i].Enabled = enabled
return
}
}
}
func (c *Config) add(app AppConfig, force bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
if app.SrcPort == 0 || app.DstPort == 0 {
gLog.Println(LevelERROR, "invalid app ", app)
return
}
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
if force {
c.Apps[i] = app
}
return
}
}
c.Apps = append(c.Apps, app)
}
// func (c *Config) save() {
// data, _ := json.MarshalIndent(c, "", "")
// ioutil.WriteFile("config.json", data, 0644)
// }
func (c *Config) delete(app AppConfig) {
if app.SrcPort == 0 || app.DstPort == 0 {
return
}
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
return
}
}
}
func (c *Config) save() {
c.mtx.Lock()
defer c.mtx.Unlock()
data, _ := json.MarshalIndent(c, "", " ")
err := ioutil.WriteFile("config.json", data, 0644)
if err != nil {
gLog.Println(LevelERROR, "save config.json error:", err)
}
}
func (c *Config) load() error {
c.mtx.Lock()
c.LogLevel = IntValueNotSet
c.Network.ShareBandwidth = IntValueNotSet
defer c.mtx.Unlock()
data, err := ioutil.ReadFile("config.json")
if err != nil {
gLog.Println(LevelERROR, "read config.json error:", err)
// gLog.Println(LevelERROR, "read config.json error:", err)
return err
}
err = json.Unmarshal(data, &c)
@@ -65,21 +114,100 @@ func (c *Config) load() error {
type NetworkConfig struct {
// local info
Token uint64
Node string
User string
Password string
NoShare bool
localIP string
ipv6 string
hostName string
mac string
os string
publicIP string
natType int
shareBandwidth int
ShareBandwidth int
// server info
ServerHost string
ServerPort int
UDPPort1 int
UDPPort2 int
}
func parseParams() {
serverHost := flag.String("serverhost", "api.openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
node := flag.String("node", "", "node name. 8-31 characters")
token := flag.Uint64("token", 0, "token")
peerNode := flag.String("peernode", "", "peer node name that you want to connect")
dstIP := flag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := flag.Int("dstport", 0, "destination port ")
srcPort := flag.Int("srcport", 0, "source port ")
protocol := flag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
flag.Bool("noshare", false, "deprecated. uses -sharebandwidth 0") // Deprecated, rm later
shareBandwidth := flag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private node no limit")
flag.Bool("f", false, "deprecated. config file") // Deprecated, rm later
daemonMode := flag.Bool("d", false, "daemonMode")
flag.Bool("bydaemon", false, "start by daemon") // Deprecated, rm later
logLevel := flag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
flag.Parse()
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
gConf.load()
if config.SrcPort != 0 {
gConf.add(config, true)
}
gConf.mtx.Lock()
// spec paramters in commandline will always be used
flag.Visit(func(f *flag.Flag) {
if f.Name == "sharebandwidth" {
gConf.Network.ShareBandwidth = *shareBandwidth
}
if f.Name == "node" {
gConf.Network.Node = *node
}
if f.Name == "serverhost" {
gConf.Network.ServerHost = *serverHost
}
if f.Name == "loglevel" {
gConf.LogLevel = *logLevel
}
})
if gConf.Network.ServerHost == "" {
gConf.Network.ServerHost = *serverHost
}
if gConf.Network.Node == "" {
if *node == "" { // config and param's node both empty
hostname := defaultNodeName()
node = &hostname
}
gConf.Network.Node = *node
}
if *token != 0 {
gConf.Network.Token = *token
}
if gConf.LogLevel == IntValueNotSet {
gConf.LogLevel = *logLevel
}
if gConf.Network.ShareBandwidth == IntValueNotSet {
gConf.Network.ShareBandwidth = *shareBandwidth
}
gConf.Network.ServerPort = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gLog.setLevel(LogLevel(gConf.LogLevel))
gConf.mtx.Unlock()
gConf.save()
if *daemonMode {
d := daemon{}
d.run()
os.Exit(0)
}
}

View File

@@ -1,8 +1,8 @@
{
"network": {
"Node": "hhd1207-222",
"User": "tenderiron",
"Password": "13760636579",
"Node": "YOUR_NODE_NAME",
"User": "YOUR_USER_NAME",
"Password": "YOUR_PASSWORD",
"ServerHost": "openp2p.cn",
"ServerPort": 27182,
"UDPPort1": 27182,
@@ -11,21 +11,21 @@
"apps": [
{
"Protocol": "tcp",
"SrcPort": 53389,
"PeerNode": "dell720-902",
"DstPort": 3389,
"DstHost": "10.1.6.36",
"SrcPort": 22,
"PeerNode": "YOURNODE1",
"DstPort": 22,
"DstHost": "127.0.0.1",
"PeerUser": "",
"PeerPassword": ""
},
{
"Protocol": "tcp",
"SrcPort": 22,
"PeerNode": "dell720-902",
"DstPort": 22,
"SrcPort": 50022,
"PeerNode": "YOURNODE2",
"DstPort": 50022,
"DstHost": "127.0.0.1",
"PeerUser": "",
"PeerPassword": ""
}
]
}
}

235
daemon.go
View File

@@ -1,16 +1,61 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/kardianos/service"
)
type daemon struct {
running bool
proc *os.Process
}
func (d *daemon) Start(s service.Service) error {
gLog.Println(LevelINFO, "daemon start")
return nil
}
func (d *daemon) Stop(s service.Service) error {
gLog.Println(LevelINFO, "service stop")
d.running = false
if d.proc != nil {
gLog.Println(LevelINFO, "stop worker")
d.proc.Kill()
}
if service.Interactive() {
gLog.Println(LevelINFO, "stop daemon")
os.Exit(0)
}
return nil
}
func (d *daemon) run() {
gLog.Println(LevelINFO, "daemon start")
defer gLog.Println(LevelINFO, "daemon end")
gLog.Println(LevelINFO, "daemon run start")
defer gLog.Println(LevelINFO, "daemon run end")
d.running = true
binPath, _ := os.Executable()
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
gLog.Println(LevelINFO, mydir)
conf := &service.Config{
Name: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
Executable: binPath,
}
s, _ := service.New(d, conf)
go s.Run()
var args []string
// rm -d parameter
for i := 0; i < len(os.Args); i++ {
@@ -19,18 +64,198 @@ func (d *daemon) run() {
break
}
}
args = append(args, "-bydaemon")
for {
// start worker
gLog.Println(LevelINFO, "start worker process")
gLog.Println(LevelINFO, "start worker process, args:", args)
execSpec := &os.ProcAttr{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}
p, err := os.StartProcess(os.Args[0], args, execSpec)
p, err := os.StartProcess(binPath, args, execSpec)
if err != nil {
gLog.Printf(LevelERROR, "start worker error:%s", err)
return
}
d.proc = p
_, _ = p.Wait()
if !d.running {
return
}
gLog.Printf(LevelERROR, "worker stop, restart it after 10s")
time.Sleep(time.Second * 10)
}
}
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
svcConfig := &service.Config{
Name: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
Executable: exeAbsPath,
Arguments: args,
}
s, e := service.New(d, svcConfig)
if e != nil {
return e
}
e = service.Control(s, ctrlComm)
if e != nil {
return e
}
return nil
}
// examples:
// listen:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0
// listen and build p2papp:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
func install() {
gLog.Println(LevelINFO, "install start")
defer gLog.Println(LevelINFO, "install end")
// auto uninstall
uninstall()
// save config file
installFlag := flag.NewFlagSet("install", flag.ExitOnError)
serverHost := installFlag.String("serverhost", "api.openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
token := installFlag.Uint64("token", 0, "token")
node := installFlag.String("node", "", "node name. 8-31 characters. if not set, it will be hostname")
peerNode := installFlag.String("peernode", "", "peer node name that you want to connect")
dstIP := installFlag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := installFlag.Int("dstport", 0, "destination port ")
srcPort := installFlag.Int("srcport", 0, "source port ")
protocol := installFlag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
installFlag.Bool("noshare", false, "deprecated. uses -sharebandwidth 0")
shareBandwidth := installFlag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private node no limit")
logLevel := installFlag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
installFlag.Parse(os.Args[2:])
if *node != "" && len(*node) < 8 {
gLog.Println(LevelERROR, ErrNodeTooShort)
os.Exit(9)
}
if *node == "" { // if node name not set. use os.Hostname
hostname := defaultNodeName()
node = &hostname
}
gConf.load() // load old config. otherwise will clear all apps
gConf.LogLevel = *logLevel
gConf.Network.ServerHost = *serverHost
gConf.Network.Token = *token
gConf.Network.Node = *node
gConf.Network.ServerPort = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gConf.Network.ShareBandwidth = *shareBandwidth
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
if config.SrcPort != 0 {
gConf.add(config, true)
}
err := os.MkdirAll(defaultInstallPath, 0775)
if err != nil {
gLog.Printf(LevelERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
return
}
err = os.Chdir(defaultInstallPath)
if err != nil {
gLog.Println(LevelERROR, "cd error:", err)
return
}
gConf.save()
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
// copy files
binPath, _ := os.Executable()
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
if errFiles != nil {
gLog.Printf(LevelERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles)
return
}
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if errFiles != nil {
gLog.Printf(LevelERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
return
}
_, errFiles = io.Copy(dst, src)
if errFiles != nil {
gLog.Printf(LevelERROR, "io.Copy error:%s", errFiles)
return
}
src.Close()
dst.Close()
// install system service
// args := []string{""}
gLog.Println(LevelINFO, "targetPath:", targetPath)
err = d.Control("install", targetPath, []string{"-d"})
if err == nil {
gLog.Println(LevelINFO, "install system service ok.")
}
time.Sleep(time.Second * 2)
err = d.Control("start", targetPath, []string{"-d"})
if err != nil {
gLog.Println(LevelERROR, "start openp2p service error:", err)
} else {
gLog.Println(LevelINFO, "start openp2p service ok.")
}
}
func installByFilename() {
params := strings.Split(filepath.Base(os.Args[0]), "-")
if len(params) < 4 {
return
}
serverHost := params[1]
token := params[2]
gLog.Println(LevelINFO, "install start")
targetPath := os.Args[0]
args := []string{"install"}
args = append(args, "-serverhost")
args = append(args, serverHost)
args = append(args, "-token")
args = append(args, token)
env := os.Environ()
cmd := exec.Command(targetPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err != nil {
gLog.Println(LevelERROR, "install by filename, start process error:", err)
return
}
gLog.Println(LevelINFO, "install end")
fmt.Println("Press the Any Key to exit")
fmt.Scanln()
os.Exit(0)
}
func uninstall() {
gLog.Println(LevelINFO, "uninstall start")
defer gLog.Println(LevelINFO, "uninstall end")
d := daemon{}
err := d.Control("stop", "", nil)
if err != nil { // service maybe not install
return
}
err = d.Control("uninstall", "", nil)
if err != nil {
gLog.Println(LevelERROR, "uninstall system service error:", err)
} else {
gLog.Println(LevelINFO, "uninstall system service ok.")
}
binPath := filepath.Join(defaultInstallPath, defaultBinName)
os.Remove(binPath + "0")
os.Remove(binPath)
// os.RemoveAll(defaultInstallPath) // reserve config.json
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

BIN
doc/images/devices.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
doc/images/devices_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

BIN
doc/images/install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
doc/images/install_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
doc/images/newapp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
doc/images/newapp_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
doc/images/newappedit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
doc/images/p2pappok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
doc/images/p2pappok_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 98 KiB

BIN
doc/images/register.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
doc/images/register_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

BIN
doc/images/stillrun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
doc/images/stillrun_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
doc/images/win10warn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
doc/images/win10warn_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

16
errorcode.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"errors"
)
// error message
var (
// ErrorS2S string = "s2s is not supported"
// ErrorHandshake string = "handshake error"
ErrorS2S = errors.New("s2s is not supported")
ErrorHandshake = errors.New("handshake error")
ErrorNewUser = errors.New("new user")
ErrorLogin = errors.New("user or password not correct")
ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters")
)

1
go.mod
View File

@@ -4,6 +4,7 @@ go 1.16
require (
github.com/gorilla/websocket v1.4.2
github.com/kardianos/service v1.2.0
github.com/lucas-clemente/quic-go v0.24.0
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
)

208
handlepush.go Normal file
View File

@@ -0,0 +1,208 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
)
func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
pushHead := PushHeader{}
err := binary.Read(bytes.NewReader(msg[openP2PHeaderSize:openP2PHeaderSize+PushHeaderSize]), binary.LittleEndian, &pushHead)
if err != nil {
return err
}
gLog.Printf(LevelDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead)
switch subType {
case MsgPushConnectReq:
req := PushConnectReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushConnectReq:%s", err)
return err
}
gLog.Printf(LevelINFO, "%s is connecting...", req.From)
gLog.Println(LevelDEBUG, "push connect response to ", req.From)
// verify totp token or token
if VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()) ||
(req.FromToken == pn.config.Token) {
gLog.Printf(LevelINFO, "Access Granted\n")
config := AppConfig{}
config.peerNatType = req.NatType
config.peerConeNatPort = req.ConeNatPort
config.peerIP = req.FromIP
config.PeerNode = req.From
// share relay node will limit bandwidth
if req.FromToken != pn.config.Token {
gLog.Printf(LevelINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth)
config.shareBandwidth = pn.config.ShareBandwidth
}
// go pn.AddTunnel(config, req.ID)
go pn.addDirectTunnel(config, req.ID)
break
}
gLog.Println(LevelERROR, "Access Denied:", req.From)
rsp := PushConnectRsp{
Error: 1,
Detail: fmt.Sprintf("connect to %s error: Access Denied", pn.config.Node),
To: req.From,
From: pn.config.Node,
}
pn.push(req.From, MsgPushConnectRsp, rsp)
case MsgPushRsp:
rsp := PushRsp{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong pushRsp:%s", err)
return err
}
if rsp.Error == 0 {
gLog.Printf(LevelDEBUG, "push ok, detail:%s", rsp.Detail)
} else {
gLog.Printf(LevelERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail)
}
case MsgPushAddRelayTunnelReq:
req := AddRelayTunnelReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
return err
}
config := AppConfig{}
config.PeerNode = req.RelayName
config.peerToken = req.RelayToken
go func(r AddRelayTunnelReq) {
t, errDt := pn.addDirectTunnel(config, 0)
if errDt == nil {
// notify peer relay ready
msg := TunnelMsg{ID: t.id}
pn.push(r.From, MsgPushAddRelayTunnelRsp, msg)
SaveKey(req.AppID, req.AppKey)
}
}(req)
case MsgPushUpdate:
gLog.Println(LevelINFO, "MsgPushUpdate")
update() // download new version first, then exec ./openp2p update
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
args := []string{"update"}
env := os.Environ()
cmd := exec.Command(targetPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err == nil {
os.Exit(0)
}
return err
case MsgPushRestart:
gLog.Println(LevelINFO, "MsgPushRestart")
os.Exit(0)
return err
case MsgPushReportApps:
gLog.Println(LevelINFO, "MsgPushReportApps")
req := ReportApps{}
// TODO: add the retrying apps
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
for _, config := range gConf.Apps {
appActive := 0
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
if app.isActive() {
appActive = 1
}
}
appInfo := AppInfo{
AppName: config.AppName,
Protocol: config.Protocol,
SrcPort: config.SrcPort,
// RelayNode: relayNode,
PeerNode: config.PeerNode,
DstHost: config.DstHost,
DstPort: config.DstPort,
PeerUser: config.PeerUser,
PeerIP: config.peerIP,
PeerNatType: config.peerNatType,
RetryTime: config.retryTime.String(),
IsActive: appActive,
Enabled: config.Enabled,
}
req.Apps = append(req.Apps, appInfo)
}
pn.write(MsgReport, MsgReportApps, &req)
case MsgPushEditApp:
gLog.Println(LevelINFO, "MsgPushEditApp")
newApp := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &newApp)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
oldConf := AppConfig{Enabled: 1}
// protocol0+srcPort0 exist, delApp
oldConf.AppName = newApp.AppName
oldConf.Protocol = newApp.Protocol0
oldConf.SrcPort = newApp.SrcPort0
oldConf.PeerNode = newApp.PeerNode
oldConf.DstHost = newApp.DstHost
oldConf.DstPort = newApp.DstPort
gConf.delete(oldConf)
// AddApp
newConf := oldConf
newConf.Protocol = newApp.Protocol
newConf.SrcPort = newApp.SrcPort
gConf.add(newConf, false)
gConf.save() // save quickly for the next request reportApplist
pn.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end
// autoReconnect will auto AddApp
// pn.AddApp(config)
// TODO: report result
case MsgPushEditNode:
gLog.Println(LevelINFO, "MsgPushEditNode")
req := EditNode{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditNode:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
gConf.mtx.Lock()
gConf.Network.Node = req.NewName
gConf.Network.ShareBandwidth = req.Bandwidth
gConf.mtx.Unlock()
gConf.save()
// TODO: hot reload
os.Exit(0)
case MsgPushSwitchApp:
gLog.Println(LevelINFO, "MsgPushSwitchApp")
app := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &app)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushSwitchApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
gLog.Println(LevelINFO, app.AppName, " switch to ", app.Enabled)
gConf.switchApp(config, app.Enabled)
if app.Enabled == 0 {
// disable APP
pn.DeleteApp(config)
}
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[pushHead.From]
pn.msgMapMtx.Unlock()
ch <- msg
}
return nil
}

42
log.go
View File

@@ -1,7 +1,6 @@
package main
import (
"fmt"
"log"
"os"
"runtime"
@@ -48,14 +47,13 @@ const (
type V8log struct {
loggers map[LogLevel]*log.Logger
files map[LogLevel]*os.File
llevel LogLevel
level LogLevel
stopSig chan bool
logDir string
mtx *sync.Mutex
stoped bool
lineEnding string
pid int
lastError string
maxLogSize int64
mode int
}
@@ -89,22 +87,15 @@ func InitLogger(path string, filePrefix string, level LogLevel, maxLogSize int64
} else {
le = "\n"
}
pLog := &V8log{logger, openedfile, level, make(chan bool, 10), logdir, &sync.Mutex{}, false, le, os.Getpid(), "", maxLogSize, mode}
pLog := &V8log{logger, openedfile, level, make(chan bool, 10), logdir, &sync.Mutex{}, false, le, os.Getpid(), maxLogSize, mode}
go pLog.checkFile()
return pLog
}
// UninitLogger ...
func (vl *V8log) UninitLogger() {
if !vl.stoped {
vl.stoped = true
close(vl.stopSig)
for l := range logFileNames {
if l >= vl.llevel {
vl.files[l].Close()
}
}
}
func (vl *V8log) setLevel(level LogLevel) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
vl.level = level
}
func (vl *V8log) checkFile() {
@@ -152,18 +143,14 @@ func (vl *V8log) Printf(level LogLevel, format string, params ...interface{}) {
if vl.stoped {
return
}
if level < vl.llevel {
if level < vl.level {
return
}
if level == LevelERROR {
vl.lastError = fmt.Sprintf(format, params...)
}
pidAndLevel := []interface{}{vl.pid, loglevel[level]}
params = append(pidAndLevel, params...)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Printf("%d %s "+format+vl.lineEnding, params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Printf("%d %s "+format+vl.lineEnding, params...)
}
@@ -176,23 +163,16 @@ func (vl *V8log) Println(level LogLevel, params ...interface{}) {
if vl.stoped {
return
}
if level < vl.llevel {
if level < vl.level {
return
}
if level == LevelERROR {
vl.lastError = fmt.Sprint(params...)
}
pidAndLevel := []interface{}{vl.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...)
params = append(params, vl.lineEnding)
vl.loggers[0].Print(params...)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Print(params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Print(params...)
}
}
func (vl *V8log) getLastError() string {
vl.mtx.Lock()
defer vl.mtx.Unlock()
return vl.lastError
}

50
nat.go
View File

@@ -8,7 +8,7 @@ import (
"time"
)
func natTest(serverHost string, serverPort int, localPort int) (publicIP string, isPublicIP int, publicPort int, err error) {
func natTest(serverHost string, serverPort int, localPort int, echoPort int) (publicIP string, isPublicIP int, publicPort int, err error) {
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
if err != nil {
return "", 0, 0, err
@@ -21,7 +21,7 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
}
// The connection can write data to the desired address.
msg, err := newMessage(MsgNATDetect, 0, &NatDetectReq{SrcPort: localPort, EchoPort: EchoPort})
msg, err := newMessage(MsgNATDetect, 0, &NatDetectReq{SrcPort: localPort, EchoPort: echoPort})
_, err = conn.WriteTo(msg, dst)
if err != nil {
return "", 0, 0, err
@@ -39,14 +39,44 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
}
natRsp := NatDetectRsp{}
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
// testing for public ip
if echoPort != 0 {
for {
gLog.Printf(LevelDEBUG, "public ip test start %s:%d", natRsp.IP, echoPort)
conn, err := net.ListenUDP("udp", nil)
if err != nil {
break
}
defer conn.Close()
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", natRsp.IP, echoPort))
if err != nil {
break
}
conn.WriteTo([]byte("echo"), dst)
buf := make([]byte, 1600)
// wait for echo testing
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
_, _, err = conn.ReadFromUDP(buf)
if err == nil {
gLog.Println(LevelDEBUG, "public ip:YES")
natRsp.IsPublicIP = 1
} else {
gLog.Println(LevelDEBUG, "public ip:NO")
}
break
}
}
return natRsp.IP, natRsp.IsPublicIP, natRsp.Port, nil
}
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, err error) {
// the random local port may be used by other.
go echo()
localPort := int(rand.Uint32()%10000 + 50000)
ip1, isPublicIP, port1, err := natTest(host, udp1, localPort)
echoPort := int(rand.Uint32()%10000 + 50000)
go echo(echoPort)
ip1, isPublicIP, port1, err := natTest(host, udp1, localPort, echoPort)
gLog.Printf(LevelDEBUG, "local port:%d nat port:%d", localPort, port1)
if err != nil {
return "", 0, err
@@ -54,7 +84,7 @@ func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int,
if isPublicIP == 1 {
return ip1, NATNone, nil
}
ip2, _, port2, err := natTest(host, udp2, localPort)
ip2, _, port2, err := natTest(host, udp2, localPort, 0) // 2rd nat test not need testing publicip
gLog.Printf(LevelDEBUG, "local port:%d nat port:%d", localPort, port2)
if err != nil {
return "", 0, err
@@ -70,14 +100,8 @@ func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int,
return ip1, natType, nil
}
const (
UDPPort1 = 27182
UDPPort2 = 27183
EchoPort = 31415
)
func echo() {
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: EchoPort})
func echo(echoPort int) {
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
if err != nil {
gLog.Println(LevelERROR, "echo server listen error:", err)
return

View File

@@ -1,7 +1,6 @@
package main
import (
"flag"
"fmt"
"math/rand"
"os"
@@ -11,147 +10,46 @@ import (
func main() {
rand.Seed(time.Now().UnixNano())
binDir := filepath.Dir(os.Args[0])
os.Chdir(binDir) // for system service
gLog = InitLogger(binDir, "openp2p", LevelDEBUG, 1024*1024, LogFileAndConsole)
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
// TODO: install sub command, deamon process
// groups := flag.String("groups", "", "you could join in several groups. like: GroupName1:Password1;GroupName2:Password2; group name 8-31 characters")
if len(os.Args) > 1 {
switch os.Args[1] {
case "version", "-v", "--version":
fmt.Println(OpenP2PVersion)
return
case "update":
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p", LevelDEBUG, 1024*1024, LogConsole)
update()
return
}
}
user := flag.String("user", "", "user name. 8-31 characters")
node := flag.String("node", "", "node name. 8-31 characters")
password := flag.String("password", "", "user password. 8-31 characters")
peerNode := flag.String("peernode", "", "peer node name that you want to connect")
peerUser := flag.String("peeruser", "", "peer node user (default peeruser=user)")
peerPassword := flag.String("peerpassword", "", "peer node password (default peerpassword=password)")
dstIP := flag.String("dstip", "127.0.0.1", "destination ip ")
serverHost := flag.String("serverhost", "openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
dstPort := flag.Int("dstport", 0, "destination port ")
srcPort := flag.Int("srcport", 0, "source port ")
protocol := flag.String("protocol", "tcp", "tcp or udp")
noShare := flag.Bool("noshare", false, "disable using the huge numbers of shared nodes in OpenP2P network, your connectivity will be weak. also this node will not shared with others")
shareBandwidth := flag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private node no limit")
configFile := flag.Bool("f", false, "config file")
daemonMode := flag.Bool("d", false, "daemonMode")
byDaemon := flag.Bool("bydaemon", false, "start by daemon")
logLevel := flag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
flag.Parse()
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p", LogLevel(*logLevel), 1024*1024, LogConsole)
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
if *daemonMode {
d := daemon{}
d.run()
return
}
if !*configFile {
// validate cmd params
if *node == "" {
gLog.Println(LevelERROR, "node name not set", os.Args, len(os.Args), os.Args[0])
return
}
if *user == "" {
gLog.Println(LevelERROR, "user name not set")
return
}
if *password == "" {
gLog.Println(LevelERROR, "password not set")
return
}
if *peerNode != "" {
if *dstPort == 0 {
gLog.Println(LevelERROR, "dstPort not set")
return
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p", LevelDEBUG, 1024*1024, LogFileAndConsole)
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
err := d.Control("restart", targetPath, nil)
if err != nil {
gLog.Println(LevelERROR, "restart service error:", err)
} else {
gLog.Println(LevelINFO, "restart service ok.")
}
if *srcPort == 0 {
gLog.Println(LevelERROR, "srcPort not set")
return
}
}
}
config := AppConfig{}
config.PeerNode = *peerNode
config.PeerUser = *peerUser
config.PeerPassword = *peerPassword
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
gLog.Println(LevelINFO, config)
if *configFile {
if err := gConf.load(); err != nil {
gLog.Println(LevelERROR, "load config error. exit.")
return
case "install":
install()
return
case "uninstall":
uninstall()
return
}
} else {
gConf.add(config)
gConf.Network = NetworkConfig{
Node: *node,
User: *user,
Password: *password,
NoShare: *noShare,
ServerHost: *serverHost,
ServerPort: 27182,
UDPPort1: 27182,
UDPPort2: 27183,
ipv6: "240e:3b7:621:def0:fda4:dd7f:36a1:2803", // TODO: detect real ipv6
shareBandwidth: *shareBandwidth,
}
installByFilename()
}
// gConf.save() // not change config file
gConf.daemonMode = *byDaemon
gLog.Println(LevelINFO, gConf)
parseParams()
gLog.Println(LevelINFO, &gConf)
setFirewall()
network := P2PNetworkInstance(&gConf.Network)
if ok := network.Connect(30000); !ok {
gLog.Println(LevelERROR, "P2PNetwork login error")
return
}
for _, app := range gConf.Apps {
// set default peer user password
if app.PeerPassword == "" {
app.PeerPassword = gConf.Network.Password
}
if app.PeerUser == "" {
app.PeerUser = gConf.Network.User
}
err := network.AddApp(app)
if err != nil {
gLog.Println(LevelERROR, "addTunnel error")
}
}
// test
// go func() {
// time.Sleep(time.Second * 30)
// config := AppConfig{}
// config.PeerNode = *peerNode
// config.PeerUser = *peerUser
// config.PeerPassword = *peerPassword
// config.DstHost = *dstIP
// config.DstPort = *dstPort
// config.SrcPort = 32
// config.Protocol = *protocol
// network.AddApp(config)
// // time.Sleep(time.Second * 30)
// // network.DeleteTunnel(config)
// // time.Sleep(time.Second * 30)
// // network.DeleteTunnel(config)
// }()
// // TODO: http api
// api := ClientAPI{}
// go api.run()
gLog.Println(LevelINFO, "waiting for connection...")
forever := make(chan bool)
<-forever

View File

@@ -21,8 +21,8 @@ type overlayTCP struct {
}
func (otcp *overlayTCP) run() {
gLog.Printf(LevelINFO, "%d overlayTCP run start", otcp.id)
defer gLog.Printf(LevelINFO, "%d overlayTCP run end", otcp.id)
gLog.Printf(LevelDEBUG, "%d overlayTCP run start", otcp.id)
defer gLog.Printf(LevelDEBUG, "%d overlayTCP run end", otcp.id)
otcp.running = true
buffer := make([]byte, ReadBuffLen+PaddingSize)
readBuf := buffer[:ReadBuffLen]

View File

@@ -11,19 +11,23 @@ import (
)
type p2pApp struct {
config AppConfig
listener net.Listener
tunnel *P2PTunnel
rtid uint64
hbTime time.Time
hbMtx sync.Mutex
running bool
id uint64
key uint64
wg sync.WaitGroup
config AppConfig
listener net.Listener
tunnel *P2PTunnel
rtid uint64
relayNode string
hbTime time.Time
hbMtx sync.Mutex
running bool
id uint64
key uint64
wg sync.WaitGroup
}
func (app *p2pApp) isActive() bool {
if app.tunnel == nil {
return false
}
if app.rtid == 0 { // direct mode app heartbeat equals to tunnel heartbeat
return app.tunnel.isActive()
}
@@ -69,11 +73,10 @@ func (app *p2pApp) listenTCP() error {
otcp.appKeyBytes = encryptKey
}
app.tunnel.overlayConns.Store(otcp.id, &otcp)
gLog.Printf(LevelINFO, "Accept overlayID:%d", otcp.id)
gLog.Printf(LevelDEBUG, "Accept overlayID:%d", otcp.id)
// tell peer connect
req := OverlayConnectReq{ID: otcp.id,
User: app.config.PeerUser,
Password: app.config.PeerPassword,
Token: app.tunnel.pn.config.Token,
DstIP: app.config.DstHost,
DstPort: app.config.DstPort,
Protocol: app.config.Protocol,
@@ -105,7 +108,9 @@ func (app *p2pApp) listen() error {
go app.relayHeartbeatLoop()
}
for app.running {
if app.config.Protocol == "tcp" {
if app.config.Protocol == "udp" {
app.listenTCP()
} else {
app.listenTCP()
}
time.Sleep(time.Second * 5)
@@ -119,7 +124,9 @@ func (app *p2pApp) close() {
if app.listener != nil {
app.listener.Close()
}
app.tunnel.closeOverlayConns(app.id)
if app.tunnel != nil {
app.tunnel.closeOverlayConns(app.id)
}
app.wg.Wait()
}

View File

@@ -7,10 +7,9 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"net"
"net/url"
"os"
"strings"
"sync"
"time"
@@ -30,13 +29,14 @@ type P2PNetwork struct {
restartCh chan bool
wg sync.WaitGroup
writeMtx sync.Mutex
serverTs uint64
serverTs int64
localTs int64
// msgMap sync.Map
msgMap map[uint64]chan []byte //key: nodeID
msgMapMtx sync.Mutex
config NetworkConfig
allTunnels sync.Map
apps sync.Map
apps sync.Map //key: protocol+srcport; value: p2pApp
limiter *BandwidthLimiter
}
@@ -48,7 +48,7 @@ func P2PNetworkInstance(config *NetworkConfig) *P2PNetwork {
online: false,
running: true,
msgMap: make(map[uint64]chan []byte),
limiter: newBandwidthLimiter(config.shareBandwidth),
limiter: newBandwidthLimiter(config.ShareBandwidth),
}
instance.msgMap[0] = make(chan []byte) // for gateway
if config != nil {
@@ -62,7 +62,7 @@ func P2PNetworkInstance(config *NetworkConfig) *P2PNetwork {
}
func (pn *P2PNetwork) run() {
go pn.autoReconnectApp()
go pn.autorunApp()
heartbeatTimer := time.NewTicker(NetworkHeartbeatTime)
for pn.running {
select {
@@ -92,65 +92,63 @@ func (pn *P2PNetwork) Connect(timeout int) bool {
return false
}
func (pn *P2PNetwork) autoReconnectApp() {
gLog.Println(LevelINFO, "autoReconnectApp start")
retryApps := make([]AppConfig, 0)
func (pn *P2PNetwork) runAll() {
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
for _, config := range gConf.Apps {
if config.Enabled == 0 {
continue
}
if config.AppName == "" {
config.AppName = fmt.Sprintf("%s%d", config.Protocol, config.SrcPort)
}
appExist := false
appActive := false
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
appExist = true
if app.isActive() {
appActive = true
}
}
if appExist && appActive {
continue
}
if appExist && !appActive {
gLog.Printf(LevelINFO, "detect app %s disconnect, reconnecting...", config.AppName)
pn.DeleteApp(config)
if config.retryTime.Add(time.Minute * 15).Before(time.Now()) {
config.retryNum = 0
}
config.retryNum++
config.retryTime = time.Now()
if config.retryNum > MaxRetry {
gLog.Printf(LevelERROR, "app %s%d retry more than %d times, exit.", config.Protocol, config.SrcPort, MaxRetry)
continue
}
}
go pn.AddApp(config)
}
}
func (pn *P2PNetwork) autorunApp() {
gLog.Println(LevelINFO, "autorunApp start")
// TODO: use gConf to check reconnect
for pn.running {
time.Sleep(time.Second)
if !pn.online {
continue
}
if len(retryApps) > 0 {
gLog.Printf(LevelINFO, "retryApps len=%d", len(retryApps))
thisRound := make([]AppConfig, 0)
for i := 0; i < len(retryApps); i++ {
// reset retryNum when running 15min continuously
delay := math.Exp(float64(retryApps[i].retryNum+1)/2) * 5
if delay > 1800 { // max delay 30min
delay = 1800
}
if retryApps[i].retryTime.Add(time.Minute * 15).Before(time.Now()) {
retryApps[i].retryNum = 0
}
retryApps[i].retryNum++
retryApps[i].retryTime = time.Now()
if retryApps[i].retryNum > MaxRetry {
gLog.Printf(LevelERROR, "app %s%d retry more than %d times, exit.", retryApps[i].Protocol, retryApps[i].SrcPort, MaxRetry)
continue
}
pn.DeleteApp(retryApps[i])
if err := pn.AddApp(retryApps[i]); err != nil {
gLog.Printf(LevelERROR, "AddApp %s%d error:%s", retryApps[i].Protocol, retryApps[i].SrcPort, err)
thisRound = append(thisRound, retryApps[i])
time.Sleep(RetryInterval)
}
}
retryApps = thisRound
}
pn.apps.Range(func(_, i interface{}) bool {
app := i.(*p2pApp)
if app.isActive() {
return true
}
gLog.Printf(LevelINFO, "detect app %s%d disconnect,last hb %s reconnecting...", app.config.Protocol, app.config.SrcPort, app.hbTime)
config := app.config
// clear peerinfo
config.peerConeNatPort = 0
config.peerIP = ""
config.peerNatType = 0
config.peerToken = 0
pn.DeleteApp(config)
retryApps = append(retryApps, config)
return true
})
pn.runAll()
time.Sleep(time.Second * 10)
}
gLog.Println(LevelINFO, "autoReconnectApp end")
gLog.Println(LevelINFO, "autorunApp end")
}
func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint64) (*P2PTunnel, uint64, error) {
gLog.Printf(LevelINFO, "addRelayTunnel to %s start", config.PeerNode)
defer gLog.Printf(LevelINFO, "addRelayTunnel to %s end", config.PeerNode)
pn.write(MsgRelay, MsgRelayNodeReq, nil)
pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode})
head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, time.Second*10)
if head == nil {
return nil, 0, errors.New("read MsgRelayNodeRsp error")
@@ -201,21 +199,17 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint
}
func (pn *P2PNetwork) AddApp(config AppConfig) error {
gLog.Printf(LevelINFO, "addApp %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelINFO, "addApp %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d start", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d end", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
if !pn.online {
return errors.New("P2PNetwork offline")
}
// check if app already exist?
appExist := false
pn.apps.Range(func(_, i interface{}) bool {
app := i.(*p2pApp)
if app.config.Protocol == config.Protocol && app.config.SrcPort == config.SrcPort {
appExist = true
return false
}
return true
})
_, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
appExist = true
}
if appExist {
return errors.New("P2PApp already exist")
}
@@ -224,7 +218,7 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
t, err := pn.addDirectTunnel(config, 0)
var rtid uint64
relayNode := ""
peerNatType := 100
peerNatType := NATUnknown
peerIP := ""
errMsg := ""
if err != nil && err == ErrorHandshake {
@@ -250,10 +244,9 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
PeerNode: config.PeerNode,
DstPort: config.DstPort,
DstHost: config.DstHost,
PeerUser: config.PeerUser,
PeerNatType: peerNatType,
PeerIP: peerIP,
ShareBandwidth: pn.config.shareBandwidth,
ShareBandwidth: pn.config.ShareBandwidth,
RelayNode: relayNode,
Version: OpenP2PVersion,
}
@@ -261,16 +254,18 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
if err != nil {
return err
}
app := p2pApp{
id: appID,
key: appKey,
tunnel: t,
config: config,
rtid: rtid,
hbTime: time.Now()}
pn.apps.Store(appID, &app)
go app.listen()
id: appID,
key: appKey,
tunnel: t,
config: config,
rtid: rtid,
relayNode: relayNode,
hbTime: time.Now()}
pn.apps.Store(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort), &app)
if err == nil {
go app.listen()
}
return err
}
@@ -278,22 +273,18 @@ func (pn *P2PNetwork) DeleteApp(config AppConfig) {
gLog.Printf(LevelINFO, "DeleteApp %s%d start", config.Protocol, config.SrcPort)
defer gLog.Printf(LevelINFO, "DeleteApp %s%d end", config.Protocol, config.SrcPort)
// close the apps of this config
pn.apps.Range(func(_, i interface{}) bool {
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
if app.config.Protocol == config.Protocol && app.config.SrcPort == config.SrcPort {
gLog.Printf(LevelINFO, "app %s exist, delete it", fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
app := i.(*p2pApp)
app.close()
pn.apps.Delete(app.id)
return false
}
return true
})
gLog.Printf(LevelINFO, "app %s exist, delete it", fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
app.close()
pn.apps.Delete(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
}
}
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel, error) {
gLog.Printf(LevelINFO, "addDirectTunnel %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelINFO, "addDirectTunnel %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
isClient := false
// client side tid=0, assign random uint64
if tid == 0 {
@@ -369,22 +360,17 @@ func (pn *P2PNetwork) init() error {
gLog.Println(LevelINFO, "init start")
var err error
for {
pn.config.hostName, err = os.Hostname()
if err != nil {
break
}
// detect nat type
pn.config.publicIP, pn.config.natType, err = getNATType(pn.config.ServerHost, pn.config.UDPPort1, pn.config.UDPPort2)
// TODO rm test s2s
if pn.config.Node == "hhd1207-222S2S" {
if strings.Contains(pn.config.Node, "openp2pS2STest") {
pn.config.natType = NATSymmetric
}
if err != nil {
gLog.Println(LevelINFO, "detect NAT type error:", err)
gLog.Println(LevelDEBUG, "detect NAT type error:", err)
break
}
gLog.Println(LevelINFO, "detect NAT type:", pn.config.natType, " publicIP:", pn.config.publicIP)
gLog.Println(LevelDEBUG, "detect NAT type:", pn.config.natType, " publicIP:", pn.config.publicIP)
gatewayURL := fmt.Sprintf("%s:%d", pn.config.ServerHost, pn.config.ServerPort)
forwardPath := "/openp2p/v1/login"
config := tls.Config{InsecureSkipVerify: true} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
@@ -392,17 +378,10 @@ func (pn *P2PNetwork) init() error {
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: forwardPath}
q := u.Query()
q.Add("node", pn.config.Node)
q.Add("user", pn.config.User)
q.Add("password", pn.config.Password)
q.Add("token", fmt.Sprintf("%d", pn.config.Token))
q.Add("version", OpenP2PVersion)
q.Add("nattype", fmt.Sprintf("%d", pn.config.natType))
q.Add("timestamp", fmt.Sprintf("%d", time.Now().Unix()))
noShareStr := "false"
if pn.config.NoShare {
noShareStr = "true"
}
q.Add("noshare", noShareStr)
q.Add("sharebandwidth", fmt.Sprintf("%d", pn.config.ShareBandwidth))
u.RawQuery = q.Encode()
var ws *websocket.Conn
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
@@ -422,15 +401,24 @@ func (pn *P2PNetwork) init() error {
pn.config.mac = getmac(pn.config.localIP)
pn.config.os = getOsName()
req := ReportBasic{
Mac: pn.config.mac,
LanIP: pn.config.localIP,
OS: pn.config.os,
IPv6: pn.config.ipv6,
Version: OpenP2PVersion,
}
rsp := netInfo()
gLog.Println(LevelDEBUG, "netinfo:", rsp)
if rsp != nil && rsp.Country != "" {
if len(rsp.IP) == net.IPv6len {
pn.config.ipv6 = rsp.IP.String()
req.IPv6 = rsp.IP.String()
}
req.NetInfo = *rsp
}
pn.write(MsgReport, MsgReportBasic, &req)
gLog.Println(LevelINFO, "P2PNetwork init ok")
gLog.Println(LevelDEBUG, "P2PNetwork init ok")
break
}
if err != nil {
@@ -461,13 +449,21 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
gLog.Printf(LevelERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
pn.running = false
} else {
gLog.Printf(LevelINFO, "login ok. Server ts=%d, local ts=%d", rsp.Ts, time.Now().Unix())
pn.serverTs = rsp.Ts
pn.config.Token = rsp.Token
pn.config.User = rsp.User
gConf.mtx.Lock()
gConf.Network.Token = rsp.Token
gConf.Network.User = rsp.User
gConf.mtx.Unlock()
gConf.save()
pn.localTs = time.Now().Unix()
gLog.Printf(LevelINFO, "login ok. user=%s,Server ts=%d, local ts=%d", rsp.User, rsp.Ts, pn.localTs)
}
case MsgHeartbeat:
gLog.Printf(LevelDEBUG, "P2PNetwork heartbeat ok")
case MsgPush:
pn.handlePush(head.SubType, msg)
handlePush(pn, head.SubType, msg)
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[0]
@@ -478,7 +474,7 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
}
func (pn *P2PNetwork) readLoop() {
gLog.Printf(LevelINFO, "P2PNetwork readLoop start")
gLog.Printf(LevelDEBUG, "P2PNetwork readLoop start")
pn.wg.Add(1)
defer pn.wg.Done()
for pn.running {
@@ -492,7 +488,7 @@ func (pn *P2PNetwork) readLoop() {
}
pn.handleMessage(t, msg)
}
gLog.Printf(LevelINFO, "P2PNetwork readLoop end")
gLog.Printf(LevelDEBUG, "P2PNetwork readLoop end")
}
func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error {
@@ -562,12 +558,15 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
} else {
nodeID = nodeNameToID(node)
}
pn.msgMapMtx.Lock()
ch := pn.msgMap[nodeID]
pn.msgMapMtx.Unlock()
for {
select {
case <-time.After(timeout):
gLog.Printf(LevelERROR, "wait msg%d:%d timeout", mainType, subType)
return
case msg := <-pn.msgMap[nodeID]:
case msg := <-ch:
head = &openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
@@ -587,104 +586,12 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
}
}
func (pn *P2PNetwork) handlePush(subType uint16, msg []byte) error {
pushHead := PushHeader{}
err := binary.Read(bytes.NewReader(msg[openP2PHeaderSize:openP2PHeaderSize+PushHeaderSize]), binary.LittleEndian, &pushHead)
if err != nil {
return err
}
gLog.Printf(LevelDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead)
switch subType {
case MsgPushConnectReq:
req := PushConnectReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushConnectReq:%s", err)
return err
}
gLog.Printf(LevelINFO, "%s is connecting...", req.From)
gLog.Println(LevelDEBUG, "push connect response to ", req.From)
// verify token or name&password
if VerifyTOTP(req.Token, pn.config.User, pn.config.Password, time.Now().Unix()) || (req.User == pn.config.User && req.Password == pn.config.Password) {
gLog.Printf(LevelINFO, "Access Granted\n")
config := AppConfig{}
config.peerNatType = req.NatType
config.peerConeNatPort = req.ConeNatPort
config.peerIP = req.FromIP
config.PeerNode = req.From
// share relay node will limit bandwidth
if req.User != pn.config.User || req.Password != pn.config.Password {
gLog.Printf(LevelINFO, "set share bandwidth %d mbps", pn.config.shareBandwidth)
config.shareBandwidth = pn.config.shareBandwidth
}
// go pn.AddTunnel(config, req.ID)
go pn.addDirectTunnel(config, req.ID)
break
}
gLog.Println(LevelERROR, "Access Denied:", req.From)
rsp := PushConnectRsp{
Error: 1,
Detail: fmt.Sprintf("connect to %s error: Access Denied", pn.config.Node),
To: req.From,
From: pn.config.Node,
}
pn.push(req.From, MsgPushConnectRsp, rsp)
case MsgPushRsp:
rsp := PushRsp{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong pushRsp:%s", err)
return err
}
if rsp.Error == 0 {
gLog.Printf(LevelDEBUG, "push ok, detail:%s", rsp.Detail)
} else {
gLog.Printf(LevelERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail)
}
case MsgPushAddRelayTunnelReq:
req := AddRelayTunnelReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
return err
}
config := AppConfig{}
config.PeerNode = req.RelayName
config.peerToken = req.RelayToken
// set user password, maybe the relay node is your private node
config.PeerUser = pn.config.User
config.PeerPassword = pn.config.Password
go func(r AddRelayTunnelReq) {
t, errDt := pn.addDirectTunnel(config, 0)
if errDt == nil {
// notify peer relay ready
msg := TunnelMsg{ID: t.id}
pn.push(r.From, MsgPushAddRelayTunnelRsp, msg)
SaveKey(req.AppID, req.AppKey)
}
}(req)
case MsgPushUpdate:
update()
if gConf.daemonMode {
os.Exit(0)
}
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[pushHead.From]
pn.msgMapMtx.Unlock()
ch <- msg
}
return nil
}
func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
pn.apps.Range(func(id, i interface{}) bool {
key := id.(uint64)
if key != appID {
app := i.(*p2pApp)
if app.id != appID {
return true
}
app := i.(*p2pApp)
app.updateHeartbeat()
return false
})

View File

@@ -39,7 +39,7 @@ func (t *P2PTunnel) init() {
localPort := int(rand.Uint32()%10000 + 50000)
if t.pn.config.natType == NATCone {
// prepare one random cone hole
_, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort)
_, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0)
t.coneLocalPort = localPort
t.coneNatPort = port1
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
@@ -52,13 +52,12 @@ func (t *P2PTunnel) init() {
}
func (t *P2PTunnel) connect() error {
gLog.Printf(LevelINFO, "start p2pTunnel to %s ", t.config.PeerNode)
gLog.Printf(LevelDEBUG, "start p2pTunnel to %s ", t.config.PeerNode)
t.isServer = false
req := PushConnectReq{
User: t.config.PeerUser,
Password: t.config.PeerPassword,
Token: t.config.peerToken,
From: t.pn.config.Node,
FromToken: t.pn.config.Token,
FromIP: t.pn.config.publicIP,
ConeNatPort: t.coneNatPort,
NatType: t.pn.config.natType,
@@ -144,7 +143,7 @@ func (t *P2PTunnel) handshake() error {
return err
}
}
gLog.Println(LevelINFO, "handshake to ", t.config.PeerNode)
gLog.Println(LevelDEBUG, "handshake to ", t.config.PeerNode)
var err error
// TODO: handle NATNone, nodes with public ip has no punching
if (t.pn.config.natType == NATCone && t.config.peerNatType == NATCone) || (t.pn.config.natType == NATNone || t.config.peerNatType == NATNone) {
@@ -163,7 +162,7 @@ func (t *P2PTunnel) handshake() error {
gLog.Println(LevelERROR, "punch handshake error:", err)
return err
}
gLog.Printf(LevelINFO, "handshake to %s ok", t.config.PeerNode)
gLog.Printf(LevelDEBUG, "handshake to %s ok", t.config.PeerNode)
err = t.run()
if err != nil {
gLog.Println(LevelERROR, err)
@@ -198,7 +197,7 @@ func (t *P2PTunnel) run() error {
gLog.Println(LevelDEBUG, string(buff))
}
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Println(LevelINFO, "quic connection ok")
gLog.Println(LevelDEBUG, "quic connection ok")
t.conn = qConn
t.setRun(true)
go t.readLoop()
@@ -216,7 +215,7 @@ func (t *P2PTunnel) run() error {
}
}
t.pn.read(t.config.PeerNode, MsgPush, MsgPushQuicConnect, time.Second*5)
gLog.Println(LevelINFO, "quic dial to ", t.ra.String())
gLog.Println(LevelDEBUG, "quic dial to ", t.ra.String())
qConn, e := dialQuic(conn, t.ra, TunnelIdleTimeout)
if e != nil {
return fmt.Errorf("quic dial to %s error:%s", t.ra.String(), e)
@@ -233,7 +232,7 @@ func (t *P2PTunnel) run() error {
}
gLog.Println(LevelINFO, "rtt=", time.Since(handshakeBegin))
gLog.Println(LevelINFO, "quic connection ok")
gLog.Println(LevelDEBUG, "quic connection ok")
t.conn = qConn
t.setRun(true)
go t.readLoop()
@@ -243,7 +242,7 @@ func (t *P2PTunnel) run() error {
func (t *P2PTunnel) readLoop() {
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
gLog.Printf(LevelINFO, "%d tunnel readloop start", t.id)
gLog.Printf(LevelDEBUG, "%d tunnel readloop start", t.id)
for t.isRuning() {
t.conn.SetReadDeadline(time.Now().Add(TunnelIdleTimeout))
head, body, err := t.conn.ReadMessage()
@@ -326,14 +325,14 @@ func (t *P2PTunnel) readLoop() {
gLog.Printf(LevelERROR, "wrong MsgOverlayConnectReq:%s", err)
continue
}
// app connect only accept user/password, avoid someone using the share relay node's token
if req.User != t.pn.config.User || req.Password != t.pn.config.Password {
gLog.Println(LevelERROR, "Access Denied:", req.User)
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
if req.Token != t.pn.config.Token {
gLog.Println(LevelERROR, "Access Denied:", req.Token)
continue
}
overlayID := req.ID
gLog.Printf(LevelINFO, "App:%d overlayID:%d connect %+v", req.AppID, overlayID, req)
gLog.Printf(LevelDEBUG, "App:%d overlayID:%d connect %+v", req.AppID, overlayID, req)
if req.Protocol == "tcp" {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), time.Second*5)
if err != nil {
@@ -368,7 +367,7 @@ func (t *P2PTunnel) readLoop() {
continue
}
overlayID := req.ID
gLog.Printf(LevelINFO, "%d disconnect overlay connection %d", t.id, overlayID)
gLog.Printf(LevelDEBUG, "%d disconnect overlay connection %d", t.id, overlayID)
i, ok := t.overlayConns.Load(overlayID)
if ok {
otcp := i.(*overlayTCP)
@@ -379,13 +378,13 @@ func (t *P2PTunnel) readLoop() {
}
t.setRun(false)
t.conn.Close()
gLog.Printf(LevelINFO, "%d tunnel readloop end", t.id)
gLog.Printf(LevelDEBUG, "%d tunnel readloop end", t.id)
}
func (t *P2PTunnel) writeLoop() {
tc := time.NewTicker(TunnelHeartbeatTime)
defer tc.Stop()
defer gLog.Printf(LevelINFO, "%d tunnel writeloop end", t.id)
defer gLog.Printf(LevelDEBUG, "%d tunnel writeloop end", t.id)
for t.isRuning() {
select {
case <-tc.C:
@@ -402,7 +401,7 @@ func (t *P2PTunnel) writeLoop() {
}
func (t *P2PTunnel) listen() error {
gLog.Printf(LevelINFO, "p2ptunnel wait for connecting")
gLog.Printf(LevelDEBUG, "p2ptunnel wait for connecting")
t.isServer = true
return t.handshake()
}

View File

@@ -4,12 +4,13 @@ import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"hash/crc64"
"math/big"
"net"
"time"
)
const OpenP2PVersion = "0.95.5"
const OpenP2PVersion = "1.1.0"
const ProducnName string = "openp2p"
type openP2PHeader struct {
@@ -78,6 +79,10 @@ const (
MsgPushUpdate = 6
MsgPushReportApps = 7
MsgPushQuicConnect = 8
MsgPushEditApp = 9
MsgPushSwitchApp = 10
MsgPushRestart = 11
MsgPushEditNode = 12
)
// MsgP2P sub type message
@@ -108,6 +113,7 @@ const (
MsgReportBasic = iota
MsgReportQuery
MsgReportConnect
MsgReportApps
)
const (
@@ -125,16 +131,9 @@ const (
AESKeySize = 16
MaxRetry = 10
RetryInterval = time.Second * 30
PublicIPEchoTimeout = time.Second * 5
PublicIPEchoTimeout = time.Second * 3
NatTestTimeout = time.Second * 10
)
// error message
var (
// ErrorS2S string = "s2s is not supported"
// ErrorHandshake string = "handshake error"
ErrorS2S = errors.New("s2s is not supported")
ErrorHandshake = errors.New("handshake error")
ClientAPITimeout = time.Second * 10
)
// NATNone has public ip
@@ -142,6 +141,7 @@ const (
NATNone = 0
NATCone = 1
NATSymmetric = 2
NATUnknown = 314
)
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
@@ -170,9 +170,8 @@ func nodeNameToID(name string) uint64 {
type PushConnectReq struct {
From string `json:"from,omitempty"`
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Token uint64 `json:"token,omitempty"`
FromToken uint64 `json:"fromToken,omitempty"` //my token
Token uint64 `json:"token,omitempty"` // totp token
ConeNatPort int `json:"coneNatPort,omitempty"`
NatType int `json:"natType,omitempty"`
FromIP string `json:"fromIP,omitempty"`
@@ -196,7 +195,9 @@ type PushRsp struct {
type LoginRsp struct {
Error int `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
Ts uint64 `json:"ts,omitempty"`
User string `json:"user,omitempty"`
Token uint64 `json:"token,omitempty"`
Ts int64 `json:"ts,omitempty"`
}
type NatDetectReq struct {
@@ -216,8 +217,7 @@ type P2PHandshakeReq struct {
type OverlayConnectReq struct {
ID uint64 `json:"id,omitempty"`
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Token uint64 `json:"token,omitempty"` // not totp token
DstIP string `json:"dstIP,omitempty"`
DstPort int `json:"dstPort,omitempty"`
Protocol string `json:"protocol,omitempty"`
@@ -231,6 +231,10 @@ type TunnelMsg struct {
ID uint64 `json:"id,omitempty"`
}
type RelayNodeReq struct {
PeerNode string `json:"peerNode,omitempty"`
}
type RelayNodeRsp struct {
RelayName string `json:"relayName,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
@@ -250,11 +254,12 @@ type RelayHeartbeat struct {
}
type ReportBasic struct {
OS string `json:"os,omitempty"`
Mac string `json:"mac,omitempty"`
LanIP string `json:"lanIP,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
Version string `json:"version,omitempty"`
OS string `json:"os,omitempty"`
Mac string `json:"mac,omitempty"`
LanIP string `json:"lanIP,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"`
}
type ReportConnect struct {
@@ -273,8 +278,67 @@ type ReportConnect struct {
Version string `json:"version,omitempty"`
}
type AppInfo struct {
AppName string `json:"appName,omitempty"`
Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"`
SrcPort int `json:"srcPort,omitempty"`
Protocol0 string `json:"protocol0,omitempty"`
SrcPort0 int `json:"srcPort0,omitempty"`
NatType int `json:"natType,omitempty"`
PeerNode string `json:"peerNode,omitempty"`
DstPort int `json:"dstPort,omitempty"`
DstHost string `json:"dstHost,omitempty"`
PeerUser string `json:"peerUser,omitempty"`
PeerNatType int `json:"peerNatType,omitempty"`
PeerIP string `json:"peerIP,omitempty"`
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,omitempty"`
Version string `json:"version,omitempty"`
RetryTime string `json:"retryTime,omitempty"`
IsActive int `json:"isActive,omitempty"`
Enabled int `json:"enabled,omitempty"`
}
type ReportApps struct {
Apps []AppInfo
}
type UpdateInfo struct {
Error int `json:"error,omitempty"`
ErrorDetail string `json:"errorDetail,omitempty"`
Url string `json:"url,omitempty"`
}
type NetInfo struct {
IP net.IP `json:"ip"`
IPDecimal *big.Int `json:"ip_decimal"`
Country string `json:"country,omitempty"`
CountryISO string `json:"country_iso,omitempty"`
CountryEU *bool `json:"country_eu,omitempty"`
RegionName string `json:"region_name,omitempty"`
RegionCode string `json:"region_code,omitempty"`
MetroCode uint `json:"metro_code,omitempty"`
PostalCode string `json:"zip_code,omitempty"`
City string `json:"city,omitempty"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
Timezone string `json:"time_zone,omitempty"`
ASN string `json:"asn,omitempty"`
ASNOrg string `json:"asn_org,omitempty"`
Hostname string `json:"hostname,omitempty"`
}
type ProfileInfo struct {
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
Token string `json:"token,omitempty"`
Addtime string `json:"addtime,omitempty"`
}
type EditNode struct {
NewName string `json:"newName,omitempty"`
Bandwidth int `json:"bandwidth,omitempty"`
}

View File

@@ -99,7 +99,7 @@ func (conn *quicConn) Accept() error {
}
func listenQuic(addr string, idleTimeout time.Duration) (*quicConn, error) {
gLog.Println(LevelINFO, "quic listen on ", addr)
gLog.Println(LevelDEBUG, "quic listen on ", addr)
listener, err := quic.ListenAddr(addr, generateTLSConfig(),
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil {

View File

@@ -8,6 +8,11 @@ import (
"syscall"
)
const (
defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p"
)
func getOsName() (osName string) {
output := execOutput("sw_vers", "-productVersion")
osName = "Mac OS X " + strings.TrimSpace(output)

View File

@@ -12,6 +12,11 @@ import (
"syscall"
)
const (
defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p"
)
func getOsName() (osName string) {
var sysnamePath string
sysnamePath = "/etc/redhat-release"

View File

@@ -13,6 +13,11 @@ import (
"golang.org/x/sys/windows/registry"
)
const (
defaultInstallPath = "C:\\Program Files\\OpenP2P"
defaultBinName = "openp2p.exe"
)
func getOsName() (osName string) {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE|registry.WOW64_64KEY)
if err != nil {

10
totp.go
View File

@@ -8,9 +8,11 @@ import (
)
const TOTPStep = 30 // 30s
func GenTOTP(user string, password string, ts int64) uint64 {
func GenTOTP(token uint64, ts int64) uint64 {
step := ts / TOTPStep
mac := hmac.New(sha256.New, []byte(user+password))
tbuff := make([]byte, 8)
binary.LittleEndian.PutUint64(tbuff, token)
mac := hmac.New(sha256.New, tbuff)
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(step))
mac.Write(b)
@@ -19,11 +21,11 @@ func GenTOTP(user string, password string, ts int64) uint64 {
return num
}
func VerifyTOTP(code uint64, user string, password string, ts int64) bool {
func VerifyTOTP(code uint64, token uint64, ts int64) bool {
if code == 0 {
return false
}
if code == GenTOTP(user, password, ts) || code == GenTOTP(user, password, ts-TOTPStep) || code == GenTOTP(user, password, ts+TOTPStep) {
if code == GenTOTP(token, ts) || code == GenTOTP(token, ts-TOTPStep) || code == GenTOTP(token, ts+TOTPStep) {
return true
}
return false

View File

@@ -9,24 +9,24 @@ import (
func TestTOTP(t *testing.T) {
for i := 0; i < 20; i++ {
ts := time.Now().Unix()
code := GenTOTP("testuser1", "testpassword1", ts)
code := GenTOTP(13666999958022769123, ts)
t.Log(code)
if !VerifyTOTP(code, "testuser1", "testpassword1", ts) {
if !VerifyTOTP(code, 13666999958022769123, ts) {
t.Error("TOTP error")
}
if !VerifyTOTP(code, "testuser1", "testpassword1", ts-10) {
if !VerifyTOTP(code, 13666999958022769123, ts-10) {
t.Error("TOTP error")
}
if !VerifyTOTP(code, "testuser1", "testpassword1", ts+10) {
if !VerifyTOTP(code, 13666999958022769123, ts+10) {
t.Error("TOTP error")
}
if VerifyTOTP(code, "testuser1", "testpassword1", ts+60) {
if VerifyTOTP(code, 13666999958022769123, ts+60) {
t.Error("TOTP error")
}
if VerifyTOTP(code, "testuser2", "testpassword1", ts+1) {
if VerifyTOTP(code, 13666999958022769124, ts+1) {
t.Error("TOTP error")
}
if VerifyTOTP(code, "testuser1", "testpassword2", ts+1) {
if VerifyTOTP(code, 13666999958022769125, ts+1) {
t.Error("TOTP error")
}
time.Sleep(time.Second)

View File

@@ -16,18 +16,9 @@ import (
"time"
)
// type updateFileInfo struct {
// Name string `json:"name,omitempty"`
// RelativePath string `json:"relativePath,omitempty"`
// Length int64 `json:"length,omitempty"`
// URL string `json:"url,omitempty"`
// Hash string `json:"hash,omitempty"`
// }
func update() {
gLog.Println(LevelINFO, "update start")
defer gLog.Println(LevelINFO, "update end")
// TODO: download from gitee. save flow
c := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
@@ -36,7 +27,7 @@ func update() {
}
goos := runtime.GOOS
goarch := runtime.GOARCH
rsp, err := c.Get(fmt.Sprintf("https://openp2p.cn:27182/api/v1/update?fromver=%s&os=%s&arch=%s", OpenP2PVersion, goos, goarch))
rsp, err := c.Get(fmt.Sprintf("https://openp2p.cn:27183/api/v1/update?fromver=%s&os=%s&arch=%s", OpenP2PVersion, goos, goarch))
if err != nil {
gLog.Println(LevelERROR, "update:query update list failed:", err)
return
@@ -61,7 +52,6 @@ func update() {
gLog.Println(LevelERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail)
return
}
os.MkdirAll("download", 0666)
err = updateFile(updateInfo.Url, "", "openp2p")
if err != nil {
gLog.Println(LevelERROR, "update: download failed:", err)
@@ -112,6 +102,7 @@ func updateFile(url string, checksum string, dst string) error {
os.Rename(os.Args[0]+"0", os.Args[0])
return err
}
os.Remove(tmpFile)
return nil
}
@@ -133,11 +124,6 @@ func unzip(dst, src string) (err error) {
for _, f := range archive.File {
filePath := filepath.Join(dst, f.Name)
fmt.Println("unzipping file ", filePath)
// if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) {
// fmt.Println("invalid file path")
// return
// }
if f.FileInfo().IsDir() {
fmt.Println("creating directory...")
os.MkdirAll(filePath, os.ModePerm)