mirror of
https://github.com/openp2p-cn/openp2p.git
synced 2026-05-07 13:52:14 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7686af39e0 | ||
|
|
16b937ebd7 | ||
|
|
ac454ec694 | ||
|
|
029d69869f |
@@ -45,7 +45,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
|
||||

|
||||
3. 在家里下载最新的OpenP2P,解压出来,在命令行执行
|
||||
```
|
||||
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 --peernode OFFICEPC1 --dstip 127.0.0.1 --dstport 3389 --srcport 23389 --protocol tcp
|
||||
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -appname WindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
```
|
||||
> :warning: **切记将标记大写的参数改成自己的**
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Under the outbreak of covid-19 pandemic, surely remote work becomes a fundamenta
|
||||
|
||||
3. Download OpenP2P on your home device,unzip 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
|
||||
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -appname OfficeSSH -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 22 -srcport 22022 -protocol tcp
|
||||
```
|
||||
|
||||
> :warning: **Must change the parameters marked in UPPERCASE to your own**
|
||||
|
||||
49
USAGE-ZH.md
49
USAGE-ZH.md
@@ -14,24 +14,65 @@
|
||||
>* -node: 独一无二的节点名字,唯一标识
|
||||
>* -user: 独一无二的用户名字,该节点属于这个user
|
||||
>* -password: 密码
|
||||
>* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好
|
||||
>* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好. -1表示不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点
|
||||
>* -loglevel: 需要查看更多调试日志,设置0;默认是1
|
||||
>* -noshare: 不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点
|
||||
|
||||
## 连接
|
||||
```
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
使用配置文件,建立多个P2PApp
|
||||
./openp2p -d -f
|
||||
./openp2p -f
|
||||
```
|
||||
>* -appname: 这个P2P应用名字
|
||||
>* -peernode: 目标节点名字
|
||||
>* -dstip: 目标服务地址,默认本机127.0.0.1
|
||||
>* -dstport: 目标服务端口,常见的如windows远程桌面3389,Linux ssh 22
|
||||
>* -protocol: 目标服务协议 tcp、udp
|
||||
>* -peeruser: 目标用户,如果是同一个用户下的节点,则无需设置
|
||||
>* -peerpassword: 目标密码,如果是同一个用户下的节点,则无需设置
|
||||
>* -f: 配置文件,如果希望配置多个P2PApp参考[config.json](/config.json)
|
||||
|
||||
## 配置文件
|
||||
一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json` 或 `/usr/local/openp2p/config.json`
|
||||
希望修改参数,或者配置多个P2PApp可手动修改配置文件
|
||||
|
||||
配置实例
|
||||
```
|
||||
{
|
||||
"network": {
|
||||
"Node": "hhd1207-222",
|
||||
"User": "USERNAME1",
|
||||
"Password": "PASSWORD1",
|
||||
"ShareBandwidth": -1,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
"ServerPort": 27182,
|
||||
"UDPPort1": 27182,
|
||||
"UDPPort2": 27183
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"AppName": "OfficeWindowsPC",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 23389,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 3389,
|
||||
"DstHost": "localhost",
|
||||
"PeerUser": "",
|
||||
"PeerPassword": ""
|
||||
},
|
||||
{
|
||||
"AppName": "OfficeServerSSH",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 22,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 22,
|
||||
"DstHost": "192.168.1.5",
|
||||
"PeerUser": "",
|
||||
"PeerPassword": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 升级客户端
|
||||
```
|
||||
|
||||
48
USAGE.md
48
USAGE.md
@@ -15,25 +15,65 @@ Or
|
||||
>* -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
|
||||
>* -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. -1 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 people’s shared nodes
|
||||
>* -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 people’s shared nodes
|
||||
|
||||
## Connect
|
||||
```
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
Create multiple P2PApp by config file
|
||||
./openp2p -d -f
|
||||
./openp2p -f
|
||||
```
|
||||
>* -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
|
||||
>* -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)
|
||||
|
||||
## 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",
|
||||
"User": "USERNAME1",
|
||||
"Password": "PASSWORD1",
|
||||
"ShareBandwidth": -1,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
"ServerPort": 27182,
|
||||
"UDPPort1": 27182,
|
||||
"UDPPort2": 27183
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"AppName": "OfficeWindowsPC",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 23389,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 3389,
|
||||
"DstHost": "localhost",
|
||||
"PeerUser": "",
|
||||
"PeerPassword": ""
|
||||
},
|
||||
{
|
||||
"AppName": "OfficeServerSSH",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 22,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 22,
|
||||
"DstHost": "192.168.1.5",
|
||||
"PeerUser": "",
|
||||
"PeerPassword": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
## Client update
|
||||
```
|
||||
# update local client
|
||||
|
||||
37
config.go
37
config.go
@@ -3,13 +3,17 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var gConf Config
|
||||
|
||||
const IntValueNotSet int = -99999999
|
||||
|
||||
type AppConfig struct {
|
||||
// required
|
||||
AppName string
|
||||
Protocol string
|
||||
SrcPort int
|
||||
PeerNode string
|
||||
@@ -29,12 +33,16 @@ type AppConfig struct {
|
||||
|
||||
// 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) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
if app.SrcPort == 0 || app.DstPort == 0 {
|
||||
return
|
||||
}
|
||||
@@ -46,8 +54,24 @@ func (c *Config) add(app AppConfig) {
|
||||
c.Apps = append(c.Apps, app)
|
||||
}
|
||||
|
||||
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() {
|
||||
data, _ := json.MarshalIndent(c, "", "")
|
||||
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)
|
||||
@@ -55,6 +79,10 @@ func (c *Config) save() {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -72,7 +100,6 @@ type NetworkConfig struct {
|
||||
Node string
|
||||
User string
|
||||
Password string
|
||||
NoShare bool
|
||||
localIP string
|
||||
ipv6 string
|
||||
hostName string
|
||||
|
||||
18
daemon.go
18
daemon.go
@@ -38,7 +38,6 @@ func (d *daemon) Stop(s service.Service) error {
|
||||
func (d *daemon) run() {
|
||||
gLog.Println(LevelINFO, "daemon run start")
|
||||
defer gLog.Println(LevelINFO, "daemon run end")
|
||||
os.Chdir(filepath.Dir(os.Args[0])) // for system service
|
||||
d.running = true
|
||||
binPath, _ := os.Executable()
|
||||
mydir, err := os.Getwd()
|
||||
@@ -63,7 +62,6 @@ func (d *daemon) run() {
|
||||
break
|
||||
}
|
||||
}
|
||||
args = append(args, "-bydaemon")
|
||||
for {
|
||||
// start worker
|
||||
gLog.Println(LevelINFO, "start worker process")
|
||||
@@ -106,9 +104,9 @@ func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) erro
|
||||
|
||||
// examples:
|
||||
// listen:
|
||||
// ./openp2p install -node hhd1207-222 -user tenderiron -password 13760636579 -noshare
|
||||
// ./openp2p install -node hhd1207-222 -user tenderiron -password 13760636579 -sharebandwidth 0
|
||||
// listen and build p2papp:
|
||||
// ./openp2p install -node hhd1207-222 -user tenderiron -password 13760636579 -noshare -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
|
||||
// ./openp2p install -node hhd1207-222 -user tenderiron -password 13760636579 -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
|
||||
func install() {
|
||||
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p-install", LevelDEBUG, 1024*1024, LogConsole)
|
||||
// save config file
|
||||
@@ -125,11 +123,13 @@ func install() {
|
||||
dstPort := installFlag.Int("dstport", 0, "destination port ")
|
||||
srcPort := installFlag.Int("srcport", 0, "source port ")
|
||||
protocol := installFlag.String("protocol", "tcp", "tcp or udp")
|
||||
noShare := installFlag.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")
|
||||
appName := flag.String("appname", "", "app name")
|
||||
installFlag.Bool("noshare", false, "deprecated. uses -sharebandwidth -1")
|
||||
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")
|
||||
logLevel := installFlag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
|
||||
installFlag.Parse(os.Args[2:])
|
||||
checkParams(*node, *user, *password)
|
||||
gConf.LogLevel = *logLevel
|
||||
gConf.Network.ServerHost = *serverHost
|
||||
gConf.Network.User = *user
|
||||
gConf.Network.Node = *node
|
||||
@@ -137,7 +137,6 @@ func install() {
|
||||
gConf.Network.ServerPort = 27182
|
||||
gConf.Network.UDPPort1 = 27182
|
||||
gConf.Network.UDPPort2 = 27183
|
||||
gConf.Network.NoShare = *noShare
|
||||
gConf.Network.ShareBandwidth = *shareBandwidth
|
||||
config := AppConfig{}
|
||||
config.PeerNode = *peerNode
|
||||
@@ -147,6 +146,7 @@ func install() {
|
||||
config.DstPort = *dstPort
|
||||
config.SrcPort = *srcPort
|
||||
config.Protocol = *protocol
|
||||
config.AppName = *appName
|
||||
gConf.add(config)
|
||||
os.MkdirAll(defaultInstallPath, 0775)
|
||||
err := os.Chdir(defaultInstallPath)
|
||||
@@ -184,14 +184,14 @@ func install() {
|
||||
|
||||
// args := []string{""}
|
||||
gLog.Println(LevelINFO, "targetPath:", targetPath)
|
||||
err = d.Control("install", targetPath, []string{"-d", "-f"})
|
||||
err = d.Control("install", targetPath, []string{"-d"})
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "install system service error:", err)
|
||||
} else {
|
||||
gLog.Println(LevelINFO, "install system service ok.")
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
err = d.Control("start", targetPath, []string{"-d", "-f"})
|
||||
err = d.Control("start", targetPath, []string{"-d"})
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "start openp2p service error:", err)
|
||||
} else {
|
||||
|
||||
191
handlepush.go
Normal file
191
handlepush.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"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 token or name&password
|
||||
if VerifyTOTP(req.Token, pn.config.User, pn.config.Password, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
|
||||
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:
|
||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
args := []string{"update"}
|
||||
env := os.Environ()
|
||||
// Windows does not support exec syscall.
|
||||
if runtime.GOOS == "windows" {
|
||||
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
|
||||
}
|
||||
return syscall.Exec(targetPath, args, env)
|
||||
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 {
|
||||
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: 1,
|
||||
}
|
||||
req.Apps = append(req.Apps, appInfo)
|
||||
}
|
||||
// pn.apps.Range(func(_, i interface{}) bool {
|
||||
// app := i.(*p2pApp)
|
||||
// appInfo := AppInfo{
|
||||
// AppName: app.config.AppName,
|
||||
// Protocol: app.config.Protocol,
|
||||
// SrcPort: app.config.SrcPort,
|
||||
// RelayNode: app.relayNode,
|
||||
// PeerNode: app.config.PeerNode,
|
||||
// DstHost: app.config.DstHost,
|
||||
// DstPort: app.config.DstPort,
|
||||
// PeerUser: app.config.PeerUser,
|
||||
// PeerIP: app.config.peerIP,
|
||||
// PeerNatType: app.config.peerNatType,
|
||||
// RetryTime: app.config.retryTime.String(),
|
||||
// IsActive: 1,
|
||||
// }
|
||||
// req.Apps = append(req.Apps, appInfo)
|
||||
// return true
|
||||
// })
|
||||
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
|
||||
}
|
||||
var oldConf AppConfig
|
||||
// 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)
|
||||
gConf.save()
|
||||
pn.DeleteApp(oldConf) // save quickly for the next request reportApplist
|
||||
// autoReconnect will auto AddApp
|
||||
// pn.AddApp(config)
|
||||
// TODO: report result
|
||||
default:
|
||||
pn.msgMapMtx.Lock()
|
||||
ch := pn.msgMap[pushHead.From]
|
||||
pn.msgMapMtx.Unlock()
|
||||
ch <- msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
21
log.go
21
log.go
@@ -47,7 +47,7 @@ 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
|
||||
@@ -92,17 +92,10 @@ func InitLogger(path string, filePrefix string, level LogLevel, maxLogSize int64
|
||||
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() {
|
||||
@@ -150,7 +143,7 @@ func (vl *V8log) Printf(level LogLevel, format string, params ...interface{}) {
|
||||
if vl.stoped {
|
||||
return
|
||||
}
|
||||
if level < vl.llevel {
|
||||
if level < vl.level {
|
||||
return
|
||||
}
|
||||
pidAndLevel := []interface{}{vl.pid, loglevel[level]}
|
||||
@@ -170,7 +163,7 @@ func (vl *V8log) Println(level LogLevel, params ...interface{}) {
|
||||
if vl.stoped {
|
||||
return
|
||||
}
|
||||
if level < vl.llevel {
|
||||
if level < vl.level {
|
||||
return
|
||||
}
|
||||
pidAndLevel := []interface{}{vl.pid, " ", loglevel[level], " "}
|
||||
|
||||
6
nat.go
6
nat.go
@@ -43,7 +43,7 @@ func natTest(serverHost string, serverPort int, localPort int, echoPort int) (pu
|
||||
// testing for public ip
|
||||
if echoPort != 0 {
|
||||
for {
|
||||
gLog.Printf(LevelINFO, "public ip test start %s:%d", natRsp.IP, echoPort)
|
||||
gLog.Printf(LevelDEBUG, "public ip test start %s:%d", natRsp.IP, echoPort)
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -60,10 +60,10 @@ func natTest(serverHost string, serverPort int, localPort int, echoPort int) (pu
|
||||
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
||||
_, _, err = conn.ReadFromUDP(buf)
|
||||
if err == nil {
|
||||
gLog.Println(LevelINFO, "public ip:YES")
|
||||
gLog.Println(LevelDEBUG, "public ip:YES")
|
||||
natRsp.IsPublicIP = 1
|
||||
} else {
|
||||
gLog.Println(LevelINFO, "public ip:NO")
|
||||
gLog.Println(LevelDEBUG, "public ip:NO")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
153
openp2p.go
153
openp2p.go
@@ -11,6 +11,9 @@ 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)
|
||||
|
||||
// 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")
|
||||
@@ -24,7 +27,7 @@ func main() {
|
||||
update()
|
||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
d := daemon{}
|
||||
err := d.Control("restart", targetPath, []string{"-d", "-f"})
|
||||
err := d.Control("restart", targetPath, nil)
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "restart service error:", err)
|
||||
} else {
|
||||
@@ -51,36 +54,15 @@ func main() {
|
||||
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")
|
||||
appName := flag.String("appname", "", "app name")
|
||||
flag.Bool("noshare", false, "deprecated. uses -sharebandwidth -1") // Deprecated, rm later
|
||||
shareBandwidth := flag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private node no limit")
|
||||
configFile := flag.Bool("f", false, "config file")
|
||||
flag.Bool("f", false, "deprecated. config file") // Deprecated, rm later
|
||||
daemonMode := flag.Bool("d", false, "daemonMode")
|
||||
byDaemon := flag.Bool("bydaemon", false, "start by daemon")
|
||||
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()
|
||||
|
||||
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p", LogLevel(*logLevel), 1024*1024, LogFileAndConsole)
|
||||
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
if *daemonMode {
|
||||
d := daemon{}
|
||||
d.run()
|
||||
return
|
||||
}
|
||||
if !*configFile {
|
||||
// validate cmd params
|
||||
checkParams(*node, *user, *password)
|
||||
if *peerNode != "" {
|
||||
if *dstPort == 0 {
|
||||
gLog.Println(LevelERROR, "dstPort not set")
|
||||
return
|
||||
}
|
||||
if *srcPort == 0 {
|
||||
gLog.Println(LevelERROR, "srcPort not set")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config := AppConfig{}
|
||||
config.PeerNode = *peerNode
|
||||
config.PeerUser = *peerUser
|
||||
@@ -89,73 +71,72 @@ func main() {
|
||||
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
|
||||
}
|
||||
} 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,
|
||||
}
|
||||
}
|
||||
// gConf.save() // not change config file
|
||||
gConf.daemonMode = *byDaemon
|
||||
config.AppName = *appName
|
||||
// add command config first
|
||||
gConf.add(config)
|
||||
gConf.load()
|
||||
gConf.mtx.Lock()
|
||||
|
||||
gLog.Println(LevelINFO, gConf)
|
||||
// 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 == "user" {
|
||||
gConf.Network.User = *user
|
||||
}
|
||||
if f.Name == "password" {
|
||||
gConf.Network.Password = *password
|
||||
}
|
||||
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 == "" {
|
||||
gConf.Network.Node = *node
|
||||
}
|
||||
if gConf.Network.User == "" {
|
||||
gConf.Network.User = *user
|
||||
}
|
||||
if gConf.Network.Password == "" {
|
||||
gConf.Network.Password = *password
|
||||
}
|
||||
if gConf.LogLevel == IntValueNotSet {
|
||||
gConf.LogLevel = *logLevel
|
||||
}
|
||||
if gConf.Network.ShareBandwidth == IntValueNotSet {
|
||||
gConf.Network.ShareBandwidth = *shareBandwidth
|
||||
}
|
||||
|
||||
gConf.Network.ServerPort = 27182
|
||||
gConf.Network.UDPPort1 = 27182
|
||||
gConf.Network.UDPPort2 = 27183
|
||||
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.setLevel(LogLevel(gConf.LogLevel))
|
||||
gConf.mtx.Unlock()
|
||||
gConf.save()
|
||||
if *daemonMode {
|
||||
d := daemon{}
|
||||
d.run()
|
||||
return
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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]
|
||||
|
||||
23
p2papp.go
23
p2papp.go
@@ -11,16 +11,17 @@ 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 {
|
||||
@@ -72,7 +73,7 @@ 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,
|
||||
|
||||
268
p2pnetwork.go
268
p2pnetwork.go
@@ -37,7 +37,7 @@ type P2PNetwork struct {
|
||||
msgMapMtx sync.Mutex
|
||||
config NetworkConfig
|
||||
allTunnels sync.Map
|
||||
apps sync.Map
|
||||
apps sync.Map //key: protocol+srcport; value: p2pApp
|
||||
limiter *BandwidthLimiter
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func P2PNetworkInstance(config *NetworkConfig) *P2PNetwork {
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) run() {
|
||||
go pn.autoReconnectApp()
|
||||
go pn.autorunApp()
|
||||
heartbeatTimer := time.NewTicker(NetworkHeartbeatTime)
|
||||
for pn.running {
|
||||
select {
|
||||
@@ -93,55 +93,61 @@ 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 {
|
||||
// set default peer user password
|
||||
if config.PeerPassword == "" {
|
||||
config.PeerPassword = gConf.Network.Password
|
||||
}
|
||||
if config.PeerUser == "" {
|
||||
config.PeerUser = gConf.Network.User
|
||||
}
|
||||
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
|
||||
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) {
|
||||
@@ -198,21 +204,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")
|
||||
}
|
||||
@@ -221,7 +223,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 {
|
||||
@@ -257,13 +259,14 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
|
||||
pn.write(MsgReport, MsgReportConnect, &req)
|
||||
|
||||
app := p2pApp{
|
||||
id: appID,
|
||||
key: appKey,
|
||||
tunnel: t,
|
||||
config: config,
|
||||
rtid: rtid,
|
||||
hbTime: time.Now()}
|
||||
pn.apps.Store(appID, &app)
|
||||
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()
|
||||
}
|
||||
@@ -274,22 +277,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 {
|
||||
@@ -377,10 +376,10 @@ func (pn *P2PNetwork) init() error {
|
||||
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,12 +391,7 @@ func (pn *P2PNetwork) init() error {
|
||||
q.Add("password", pn.config.Password)
|
||||
q.Add("version", OpenP2PVersion)
|
||||
q.Add("nattype", fmt.Sprintf("%d", pn.config.natType))
|
||||
|
||||
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)
|
||||
@@ -425,7 +419,7 @@ func (pn *P2PNetwork) init() error {
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
rsp := netInfo()
|
||||
gLog.Println(LevelINFO, rsp)
|
||||
gLog.Println(LevelDEBUG, "netinfo:", rsp)
|
||||
if rsp != nil && rsp.Country != "" {
|
||||
if len(rsp.IP) == net.IPv6len {
|
||||
pn.config.ipv6 = rsp.IP.String()
|
||||
@@ -434,7 +428,7 @@ func (pn *P2PNetwork) init() error {
|
||||
req.NetInfo = *rsp
|
||||
}
|
||||
pn.write(MsgReport, MsgReportBasic, &req)
|
||||
gLog.Println(LevelINFO, "P2PNetwork init ok")
|
||||
gLog.Println(LevelDEBUG, "P2PNetwork init ok")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
@@ -472,7 +466,7 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
|
||||
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]
|
||||
@@ -483,7 +477,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 {
|
||||
@@ -497,7 +491,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 {
|
||||
@@ -592,106 +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()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
|
||||
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
|
||||
})
|
||||
|
||||
24
p2ptunnel.go
24
p2ptunnel.go
@@ -52,7 +52,7 @@ 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,
|
||||
@@ -144,7 +144,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 +163,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 +198,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 +216,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 +233,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 +243,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()
|
||||
@@ -333,7 +333,7 @@ func (t *P2PTunnel) readLoop() {
|
||||
}
|
||||
|
||||
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 +368,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 +379,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 +402,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()
|
||||
}
|
||||
|
||||
31
protocol.go
31
protocol.go
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const OpenP2PVersion = "0.97.1"
|
||||
const OpenP2PVersion = "0.99.0"
|
||||
const ProducnName string = "openp2p"
|
||||
|
||||
type openP2PHeader struct {
|
||||
@@ -79,6 +79,7 @@ const (
|
||||
MsgPushUpdate = 6
|
||||
MsgPushReportApps = 7
|
||||
MsgPushQuicConnect = 8
|
||||
MsgPushEditApp = 9
|
||||
)
|
||||
|
||||
// MsgP2P sub type message
|
||||
@@ -109,6 +110,7 @@ const (
|
||||
MsgReportBasic = iota
|
||||
MsgReportQuery
|
||||
MsgReportConnect
|
||||
MsgReportApps
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -128,6 +130,7 @@ const (
|
||||
RetryInterval = time.Second * 30
|
||||
PublicIPEchoTimeout = time.Second * 3
|
||||
NatTestTimeout = time.Second * 10
|
||||
ClientAPITimeout = time.Second * 10
|
||||
)
|
||||
|
||||
// NATNone has public ip
|
||||
@@ -135,6 +138,7 @@ const (
|
||||
NATNone = 0
|
||||
NATCone = 1
|
||||
NATSymmetric = 2
|
||||
NATUnknown = 314
|
||||
)
|
||||
|
||||
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
|
||||
@@ -271,6 +275,31 @@ 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"`
|
||||
}
|
||||
|
||||
type ReportApps struct {
|
||||
Apps []AppInfo
|
||||
}
|
||||
|
||||
type UpdateInfo struct {
|
||||
Error int `json:"error,omitempty"`
|
||||
ErrorDetail string `json:"errorDetail,omitempty"`
|
||||
|
||||
16
update.go
16
update.go
@@ -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},
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user