diff --git a/.gitignore b/.gitignore
index 255c7d8..eebff51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ openp2p
lib/openp2p.dll
cmd/config.json0
test/docker/Dockerfile
+test/docker/get-client.sh
diff --git a/Changelog.md b/Changelog.md
index 159269f..410f6ba 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,25 @@
ChangeLog
+v3.25.8更新 (2026.3.13)
+Feature
+1. web控制台可以修改公网监听端口
+1. 可以修改虚拟网络网段
+1. 回滚至go1.20因为需要支持老版本的macos和windows
+
+Issue
+1. 修复客户端重装后强制v6连接失效bug
+
+
+v3.25.4更新 (2026.2.9)
+Feature
+1. 优化websocket读数据卡死问题
+1. 优化睡眠唤醒客户端恢复慢问题
+
+Issue
+1. 修复获取ifconfig异常
+1. 修复数据同步异常导致设备间连接失败
+1. 修复底层连接潜在发送数据不完整问题
+
v3.24.33更新 (2025.12.10)
Feature
1. 安装和升级下载文件到临时目录
diff --git a/core/common.go b/core/common.go
index 6f7de12..518150d 100644
--- a/core/common.go
+++ b/core/common.go
@@ -138,8 +138,8 @@ func netInfo() *NetInfo {
defer r.Body.Close()
buf := make([]byte, 1024*64)
n, err := r.Body.Read(buf)
- if err != nil {
- gLog.d("netInfo error:%s", err)
+ if err != nil && err != io.EOF {
+ gLog.d("error reading response body: %s", err)
continue
}
rsp := NetInfo{}
@@ -391,3 +391,15 @@ func lookupWithCustomDNS(ctx context.Context, domain string) ([]string, error) {
return resolver.LookupHost(ctx, domain)
}
+
+func writeFull(w io.Writer, data []byte) error {
+ totalWritten := 0
+ for totalWritten < len(data) {
+ n, err := w.Write(data[totalWritten:])
+ if err != nil {
+ return fmt.Errorf("write failed after %d bytes: %w", totalWritten, err)
+ }
+ totalWritten += n
+ }
+ return nil
+}
diff --git a/core/config.go b/core/config.go
index 5ff3815..ae2ef58 100644
--- a/core/config.go
+++ b/core/config.go
@@ -199,6 +199,11 @@ func (c *Config) retryAllMemApp() {
if app.config.SrcPort != 0 {
return true
}
+ if app.tunnelNum != int(gConf.sdwan.TunnelNum) {
+ gLog.d("memapp %s tunnelNum changed from %d to %d, delete it and not retry", app.config.LogPeerNode(), app.tunnelNum, gConf.sdwan.TunnelNum)
+ GNetwork.DeleteApp(app.config)
+ return true
+ }
app.Retry(true)
return true
})
@@ -333,6 +338,11 @@ func (c *Config) setNode(node string) {
c.Network.Node = node
c.Network.nodeID = NodeNameToID(c.Network.Node)
}
+func (c *Config) setForcev6(force bool) {
+ c.mtx.Lock()
+ defer c.mtx.Unlock()
+ c.Forcev6 = force
+}
func (c *Config) nodeID() uint64 {
c.mtx.Lock()
defer c.mtx.Unlock()
diff --git a/core/handlepush.go b/core/handlepush.go
index 680efd5..7e5bf6f 100644
--- a/core/handlepush.go
+++ b/core/handlepush.go
@@ -51,7 +51,7 @@ func handlePush(subType uint16, msg []byte) error {
config.PunchPriority = req.PunchPriority
config.UnderlayProtocol = req.UnderlayProtocol
go func(r AddRelayTunnelReq) {
- t, errDt := GNetwork.addDirectTunnel(config, 0)
+ t, errDt := GNetwork.addDirectTunnel(config, 0, nil)
if errDt == nil && t != nil {
// notify peer relay ready
msg := TunnelMsg{ID: t.id}
@@ -90,18 +90,24 @@ func handlePush(subType uint16, msg []byte) error {
appIdx = req.AppID
}
existApp, appok := GNetwork.apps.Load(appIdx)
+ var app *p2pApp
if appok {
- app := existApp.(*p2pApp)
+ app = existApp.(*p2pApp)
+ if app.tunnelNum != int(req.TunnelNum) {
+ gLog.d("memapp tunnelNum changed from %d to %d", app.tunnelNum, req.TunnelNum)
+ GNetwork.DeleteApp(app.config)
+ app = nil
+ }
+ }
+ if app != nil {
app.config.AppName = fmt.Sprintf("%d", peerID)
app.id = req.AppID
app.key = req.AppKey
app.PreCalcKeyBytes()
app.relayMode[req.RelayIndex] = req.RelayMode
app.hbTime[req.RelayIndex] = time.Now()
- if req.RelayTunnelID == 0 {
- app.SetTunnel(existTunnel, 0)
- } else {
- app.SetTunnel(existTunnel, int(req.RelayIndex)) // TODO: merge two func
+ app.SetTunnel(existTunnel, int(req.RelayIndex))
+ if req.RelayTunnelID != 0 {
app.SetRelayTunnelID(req.RelayTunnelID, int(req.RelayIndex)) // direct tunnel rtid=0, no need set rtid
}
gLog.d("found existing memapp, update it")
@@ -111,7 +117,7 @@ func handlePush(subType uint16, msg []byte) error {
appConfig.Protocol = ""
appConfig.AppName = fmt.Sprintf("%d", peerID)
appConfig.PeerNode = req.From
- app := p2pApp{
+ app = &p2pApp{
id: req.AppID,
config: appConfig,
running: true,
@@ -126,17 +132,13 @@ func handlePush(subType uint16, msg []byte) error {
app.Init(tunnelNum)
app.relayMode[req.RelayIndex] = req.RelayMode
app.hbTime[req.RelayIndex] = time.Now()
- if req.RelayTunnelID == 0 {
- app.SetTunnel(existTunnel, 0)
- } else {
- app.SetTunnel(existTunnel, int(req.RelayIndex))
- app.SetRelayTunnelID(req.RelayTunnelID, int(req.RelayIndex))
- }
+ app.SetTunnel(existTunnel, int(req.RelayIndex))
if req.RelayTunnelID != 0 {
+ app.SetRelayTunnelID(req.RelayTunnelID, int(req.RelayIndex))
app.relayNode[req.RelayIndex] = req.Node
}
app.Start(false)
- GNetwork.apps.Store(appIdx, &app)
+ GNetwork.apps.Store(appIdx, app)
gLog.d("store memapp %d %d", appIdx, req.SrcPort)
}
@@ -175,6 +177,9 @@ func handlePush(subType uint16, msg []byte) error {
}
gConf.setNode(req.NewName)
gConf.setShareBandwidth(req.Bandwidth)
+ if req.PublicIPPort != 0 {
+ gConf.Network.PublicIPPort = req.PublicIPPort
+ }
gConf.Forcev6 = (req.Forcev6 != 0)
gLog.i("set forcev6 to %v", gConf.Forcev6)
gConf.save()
@@ -341,7 +346,7 @@ func handleConnectReq(msg []byte) (err error) {
}
// go GNetwork.AddTunnel(config, req.ID)
go func() {
- GNetwork.addDirectTunnel(config, req.ID)
+ GNetwork.addDirectTunnel(config, req.ID, nil)
}()
return nil
}
diff --git a/core/nat.go b/core/nat.go
index 0ba05c0..3047cfa 100644
--- a/core/nat.go
+++ b/core/nat.go
@@ -9,6 +9,8 @@ import (
"strings"
"time"
+ upnp "openp2p/pkg/upnp"
+
reuse "github.com/openp2p-cn/go-reuseport"
)
@@ -190,23 +192,23 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
}
func setUPNP(echoPort int) {
- nat, err := Discover()
- if err != nil || nat == nil {
- gLog.d("could not perform UPNP discover:%s", err)
+ nat := upnp.Any() // Initialize the NAT interface
+ if nat == nil {
+ gLog.d("NAT interface is not available")
return
}
- ext, err := nat.GetExternalAddress()
+ ext, err := nat.ExternalIP()
if err != nil {
gLog.d("could not perform UPNP external address:%s", err)
return
}
gLog.i("PublicIP:%v", ext)
- externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 604800)
+ externalPort, err := nat.AddMapping("udp", echoPort, echoPort, "openp2p", 604800)
if err != nil {
gLog.d("could not add udp UPNP port mapping %d", externalPort)
return
} else {
- nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800)
+ nat.AddMapping("tcp", echoPort, echoPort, "openp2p", 604800)
}
}
diff --git a/core/optun_linux.go b/core/optun_linux.go
index 4f7ded2..de39b26 100644
--- a/core/optun_linux.go
+++ b/core/optun_linux.go
@@ -8,8 +8,6 @@ import (
"fmt"
"net"
"os"
- "os/exec"
- "strings"
"github.com/openp2p-cn/wireguard-go/tun"
"github.com/vishvananda/netlink"
@@ -116,26 +114,24 @@ func delRoute(dst, gw string) error {
}
func delRoutesByGateway(gateway string) error {
- cmd := exec.Command("route", "-n")
- output, err := cmd.Output()
- if err != nil {
- return err
+ ipGW := net.ParseIP(gateway)
+ if ipGW == nil {
+ return fmt.Errorf("invalid gateway IP: %s", gateway)
}
- lines := strings.Split(string(output), "\n")
- for _, line := range lines {
- if !strings.Contains(line, gateway) {
- continue
- }
- fields := strings.Fields(line)
- if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway {
- delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway)
- err := delCmd.Run()
+ routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
+ if err != nil {
+ return fmt.Errorf("failed to list routes: %v", err)
+ }
+
+ for _, route := range routes {
+ if route.Gw != nil && route.Gw.Equal(ipGW) || (route.Dst != nil && route.Dst.IP.Equal(ipGW)) {
+ err := netlink.RouteDel(&route)
if err != nil {
- gLog.e("Delete route %s error:%s", fields[0], err)
+ gLog.e("Failed to delete route: %v, error: %v", route, err)
continue
}
- gLog.i("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
+ gLog.i("Deleted route: %v", route)
}
}
return nil
diff --git a/core/overlay.go b/core/overlay.go
index db718e7..b707dc6 100644
--- a/core/overlay.go
+++ b/core/overlay.go
@@ -141,7 +141,8 @@ func (oConn *overlayConn) Write(buff []byte) (n int, err error) {
return
}
if oConn.connTCP != nil {
- n, err = oConn.connTCP.Write(buff)
+ err = writeFull(oConn.connTCP, buff)
+ n = len(buff)
}
if err != nil {
diff --git a/core/p2papp.go b/core/p2papp.go
index 03e0023..f52808d 100644
--- a/core/p2papp.go
+++ b/core/p2papp.go
@@ -42,6 +42,7 @@ type p2pApp struct {
msgChan chan appMsgCtx
once sync.Once
tunnelNum int
+ relayIdxStart int
allTunnels []*P2PTunnel
retryNum []int
retryTime []time.Time
@@ -106,7 +107,7 @@ func (app *p2pApp) RetryTime() time.Time {
if app.allTunnels[0] != nil {
return app.config.retryTime
}
- return app.retryTime[1]
+ return app.retryTime[app.relayIdxStart]
}
func (app *p2pApp) Init(tunnelNum int) {
@@ -133,6 +134,10 @@ func (app *p2pApp) Init(tunnelNum int) {
for i := 0; i < tunnelNum; i++ {
app.hbTime[i] = time.Now()
}
+ app.relayIdxStart = app.tunnelNum - 2
+ if app.relayIdxStart == 0 {
+ app.relayIdxStart = 1 // at least one direct tunnel
+ }
// app.unAckSeqStart.Store(0)
// app.mergeAckTs.Store(0)
// for i := 0; i < relayNum; i++ {
@@ -152,64 +157,72 @@ func (app *p2pApp) Start(isClient bool) {
func (app *p2pApp) daemonP2PTunnel() error {
for app.running {
- app.daemonDirectTunnel()
- if app.config.peerIP == gConf.Network.publicIP {
- time.Sleep(time.Second * 10) // if peerIP is local IP, delay relay tunnel
- }
- for i := 1; i < app.tunnelNum; i++ {
- app.daemonRelayTunnel(i)
- }
+ for i := 0; i < app.relayIdxStart; i++ {
+ app.daemonDirectTunnel(i)
+ time.Sleep(time.Second)
+ }
+ for i := app.relayIdxStart; i < app.tunnelNum; i++ {
+ if i > app.relayIdxStart {
+ app.nextRetryTime[i] = time.Now().Add(time.Second * 180) // the second relay tunnel wait 3 mins
+ }
+ app.daemonRelayTunnel(i)
+ time.Sleep(time.Second)
+ }
time.Sleep(time.Second * 3)
}
return nil
}
-func (app *p2pApp) daemonDirectTunnel() error {
+func (app *p2pApp) daemonDirectTunnel(idx int) error {
if !GNetwork.online {
return nil
}
if app.config.ForceRelay == 1 && app.config.RelayNode != app.config.PeerNode {
return nil
}
- if app.Tunnel(0) != nil && app.Tunnel(0).isActive() {
+ // TODO: multi direct tunnel support symmetric NAT traversal later
+ if idx > 0 && gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 && app.config.hasIPv4 == 0 && app.config.hasUPNPorNATPMP == 0 && (gConf.Network.natType == NATSymmetric || app.config.peerNatType == NATSymmetric) {
+ return nil
+ }
+ if app.Tunnel(idx) != nil && app.Tunnel(idx).isActive() {
return nil
}
if app.config.nextRetryTime.After(time.Now()) || app.config.Enabled == 0 {
return nil
}
if time.Now().Add(-time.Minute * 15).After(app.config.retryTime) { // run normally 15min, reset retrynum
- app.config.retryNum = 1
+ app.retryNum[idx] = 1
}
- if app.config.retryNum > 0 { // first time not show reconnect log
- gLog.i("appid:%d checkDirectTunnel detect peer %s disconnect, reconnecting the %d times...", app.id, app.config.LogPeerNode(), app.config.retryNum)
+ if app.retryNum[idx] > 0 { // first time not show reconnect log
+ gLog.i("appid:%d checkDirectTunnel detect peer %s disconnect, reconnecting the %d times...", app.id, app.config.LogPeerNode(), app.retryNum[idx])
}
- app.config.retryNum++
+ app.retryNum[idx]++
app.config.retryTime = time.Now()
app.config.connectTime = time.Now()
- err := app.buildDirectTunnel()
+ err := app.buildDirectTunnel(idx)
if err != nil {
app.config.errMsg = err.Error()
- if err == ErrPeerOffline && app.config.retryNum > 2 { // stop retry, waiting for online
- app.config.retryNum = retryLimit
+ if err == ErrPeerOffline && app.retryNum[idx] > 2 { // stop retry, waiting for online
+ app.retryNum[idx] = retryLimit
gLog.i("appid:%d checkDirectTunnel %s offline, it will auto reconnect when peer node online", app.id, app.config.LogPeerNode())
}
if err == ErrBuildTunnelBusy {
- app.config.retryNum--
+ app.retryNum[idx]--
}
}
- interval := calcRetryTimeRelay(float64(app.config.retryNum))
+ interval := calcRetryTimeRelay(float64(app.retryNum[idx]))
if app.preDirectSuccessIP == app.config.peerIP {
interval = math.Min(interval, 1800) // if peerIP has been direct link succeed, retry 30min max
}
app.config.nextRetryTime = time.Now().Add(time.Duration(interval) * time.Second)
- if app.Tunnel(0) != nil {
+ if app.Tunnel(idx) != nil {
app.preDirectSuccessIP = app.config.peerIP
app.once.Do(func() {
go app.listen()
// memapp also need
- for i := 1; i < app.tunnelNum; i++ {
+ for i := app.relayIdxStart; i < app.tunnelNum; i++ {
go app.relayHeartbeatLoop(i)
}
@@ -217,7 +230,7 @@ func (app *p2pApp) daemonDirectTunnel() error {
}
return nil
}
-func (app *p2pApp) buildDirectTunnel() error {
+func (app *p2pApp) buildDirectTunnel(idx int) error {
relayNode := ""
peerNatType := NATUnknown
peerIP := ""
@@ -225,12 +238,13 @@ func (app *p2pApp) buildDirectTunnel() error {
var t *P2PTunnel
var err error
pn := GNetwork
+ // TODO: optimize requestPeerInfo call frequency
initErr := pn.requestPeerInfo(&app.config)
if initErr != nil {
gLog.w("appid:%d buildDirectTunnel %s requestPeerInfo error:%s", app.id, app.config.LogPeerNode(), initErr)
return initErr
}
- t, err = pn.addDirectTunnel(app.config, 0)
+ t, err = pn.addDirectTunnel(app.config, 0, app.Tunnel(idx^1))
if t != nil {
peerNatType = t.config.peerNatType
peerIP = t.config.peerIP
@@ -267,11 +281,11 @@ func (app *p2pApp) buildDirectTunnel() error {
}
gLog.d("appid:%d buildDirectTunnel sync appkey to %s", app.id, app.config.LogPeerNode())
pn.push(app.config.PeerNode, MsgPushAPPKey, &syncKeyReq)
- app.SetTunnel(t, 0)
+ app.SetTunnel(t, idx)
// if memapp notify peer addmemapp
// if app.config.SrcPort == 0 {
- req2 := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, TunnelNum: uint32(app.tunnelNum), AppID: app.id, AppKey: app.key, SrcPort: uint32(app.config.SrcPort)}
+ req2 := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, RelayIndex: uint32(idx), TunnelNum: uint32(app.tunnelNum), AppID: app.id, AppKey: app.key, SrcPort: uint32(app.config.SrcPort)}
pn.push(app.config.PeerNode, MsgPushServerSideSaveMemApp, &req2)
gLog.d("appid:%d buildDirectTunnel push %s ServerSideSaveMemApp: %s", app.id, app.config.LogPeerNode(), prettyJson(req2))
@@ -284,14 +298,15 @@ func (app *p2pApp) daemonRelayTunnel(idx int) error {
if !GNetwork.online {
return nil
}
- if app.Tunnel(0) != nil && app.Tunnel(0).linkModeWeb == LinkModeIntranet { // in the same Lan, no relay
+
+ if app.Tunnel(0) != nil && app.relayIdxStart >= 2 { // multi direct tunnel no relay
return nil
}
// if app.config.ForceRelay == 1 && (gConf.sdwan.CentralNode == app.config.PeerNode && compareVersion(app.config.peerVersion, SupportDualTunnelVersion) < 0) {
if app.config.SrcPort == 0 && (gConf.sdwan.CentralNode == app.config.PeerNode || gConf.sdwan.CentralNode == gConf.Network.Node) { // memapp central node not build relay tunnel
return nil
}
- if gConf.sdwan.CentralNode != "" && idx > 1 { // if central node exist only need one relayTunnel
+ if gConf.sdwan.CentralNode != "" && idx != app.relayIdxStart { // if central node exist only need one relayTunnel
return nil
}
app.hbMtx.Lock()
@@ -352,9 +367,12 @@ func (app *p2pApp) buildRelayTunnel(idx int) error {
return initErr
}
ExcludeNodes := ""
- kk := 1 + ((idx - 1) ^ 1)
- if app.tunnelNum > 2 && app.allTunnels[kk] != nil {
- ExcludeNodes = app.allTunnels[1+((idx-1)^1)].config.PeerNode
+ theOtherTunnelIdx := app.relayIdxStart
+ if idx == app.relayIdxStart {
+ theOtherTunnelIdx = app.relayIdxStart + 1
+ }
+ if app.tunnelNum > 2 && app.allTunnels[theOtherTunnelIdx] != nil {
+ ExcludeNodes = app.allTunnels[theOtherTunnelIdx].config.PeerNode
}
t, rtid, relayMode, err = pn.addRelayTunnel(config, ExcludeNodes)
if t != nil {
@@ -364,24 +382,27 @@ func (app *p2pApp) buildRelayTunnel(idx int) error {
if err != nil {
errMsg = err.Error()
}
- req := ReportConnect{
- Error: errMsg,
- Protocol: config.Protocol,
- SrcPort: config.SrcPort,
- NatType: gConf.Network.natType,
- PeerNode: config.PeerNode,
- DstPort: config.DstPort,
- DstHost: config.DstHost,
- PeerNatType: peerNatType,
- PeerIP: peerIP,
- ShareBandwidth: gConf.Network.ShareBandwidth,
- RelayNode: relayNode,
- Version: OpenP2PVersion,
+ if app.Tunnel(0) == nil {
+ req := ReportConnect{
+ Error: errMsg,
+ Protocol: config.Protocol,
+ SrcPort: config.SrcPort,
+ NatType: gConf.Network.natType,
+ PeerNode: config.PeerNode,
+ DstPort: config.DstPort,
+ DstHost: config.DstHost,
+ PeerNatType: peerNatType,
+ PeerIP: peerIP,
+ ShareBandwidth: gConf.Network.ShareBandwidth,
+ RelayNode: relayNode,
+ Version: OpenP2PVersion,
+ }
+ pn.write(MsgReport, MsgReportConnect, &req)
}
- pn.write(MsgReport, MsgReportConnect, &req)
- if err != nil {
+ if err != nil || t == nil {
return err
}
+
// if rtid != 0 || t.conn.Protocol() == "tcp" {
// sync appkey
syncKeyReq := APPKeySync{
@@ -446,31 +467,40 @@ func (app *p2pApp) IsActive() bool {
return res
}
+// only for relay tunnel heartbeat update
func (app *p2pApp) UpdateHeartbeat(rtid uint64) {
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
- tidx := 1
- if app.tunnelNum > 2 && rtid == app.rtid[2] || (app.Tunnel(2) != nil && app.Tunnel(2).id == rtid) { // ack return rtid!=
- tidx = 2
+ for i := app.relayIdxStart; i < app.tunnelNum; i++ {
+ if rtid == app.rtid[i] || (app.Tunnel(i) != nil && app.Tunnel(i).id == rtid) {
+ app.hbTime[i] = time.Now()
+ rtt := int32(time.Since(app.whbTime[i]) / time.Millisecond)
+ preRtt := app.rtt[i].Load()
+ if preRtt != DefaultRtt {
+ rtt = int32(float64(preRtt)*(1-ma20) + float64(rtt)*ma20)
+ }
+ app.rtt[i].Store(rtt)
+ gLog.dev("appid:%d relay heartbeat %d store rtt %d", app.id, i, rtt)
+ return
+ }
}
- app.hbTime[tidx] = time.Now()
- rtt := int32(time.Since(app.whbTime[tidx]) / time.Millisecond)
- preRtt := app.rtt[tidx].Load()
- if preRtt != DefaultRtt {
- rtt = int32(float64(preRtt)*(1-ma20) + float64(rtt)*ma20)
- }
- app.rtt[tidx].Store(rtt)
- gLog.dev("appid:%d relay heartbeat %d store rtt %d", app.id, tidx, rtt)
+
}
func (app *p2pApp) UpdateRelayHeartbeatTs(rtid uint64) {
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
- relayIdx := 1
- if app.tunnelNum > 2 && rtid == app.rtid[2] || (app.Tunnel(2) != nil && app.Tunnel(2).id == rtid) { // ack return rtid!=
- relayIdx = 2
+ for i := app.relayIdxStart; i < app.tunnelNum; i++ {
+ if rtid == app.rtid[i] || (app.Tunnel(i) != nil && app.Tunnel(i).id == rtid) {
+ app.whbTime[i] = time.Now()
+ return
+ }
}
- app.whbTime[relayIdx] = time.Now() // one side did not write relay hb, so write whbtime in this.
+ // relayIdx := 1
+ // if app.tunnelNum > 2 && rtid == app.rtid[2] || (app.Tunnel(2) != nil && app.Tunnel(2).id == rtid) { // ack return rtid!=
+ // relayIdx = 2
+ // }
+ // app.whbTime[relayIdx] = time.Now() // one side did not write relay hb, so write whbtime in this.
}
func (app *p2pApp) listenTCP() error {
@@ -714,7 +744,7 @@ func (app *p2pApp) WriteBytes(data []byte) error {
if t == nil {
return ErrAppWithoutTunnel
}
- if tidx == 0 {
+ if tidx < app.relayIdxStart { // direct mode
return t.conn.WriteBytes(MsgP2P, MsgOverlayData, data)
}
all := append(app.relayHead[tidx].Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(data)))...)
@@ -745,7 +775,7 @@ func (app *p2pApp) WriteNodeDataMP(IPPacket []byte) (err error) {
dataWithSeq.Write(IPPacket)
// gLog.d("DEBUG writeTs=%d, unAckSeqStart=%d", wu.writeTs.UnixMilli(), app.unAckSeqStart[tidx].Load())
- if tidx == 0 {
+ if tidx < app.relayIdxStart { // direct mode
t.asyncWriteNodeData(gConf.nodeID(), app.seqW, IPPacket, nil)
gLog.dev("appid:%d asyncWriteDirect IPPacket len=%d", app.id, len(IPPacket))
} else {
@@ -782,12 +812,13 @@ func (app *p2pApp) fastestTunnel() (t *P2PTunnel, idx int) {
return app.Tunnel(gConf.Network.specTunnel), gConf.Network.specTunnel
}
}
- t = app.Tunnel(0)
- idx = 0
- if app.Tunnel(1) != nil {
- t = app.Tunnel(1)
- idx = 1
+ for i := 0; i < app.tunnelNum; i++ {
+ if app.Tunnel(i) != nil {
+ t = app.Tunnel(i)
+ idx = i
+ break
+ }
}
return
}
@@ -812,7 +843,7 @@ func (app *p2pApp) Retry(all bool) {
app.hbMtx.Lock()
app.hbTime[i] = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
- app.config.retryNum = 0
+ // app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.ResetWindow()
}
diff --git a/core/p2pnetwork.go b/core/p2pnetwork.go
index 1132e3d..df39f8c 100644
--- a/core/p2pnetwork.go
+++ b/core/p2pnetwork.go
@@ -126,6 +126,7 @@ func P2PNetworkInstance() {
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
Version: OpenP2PVersion,
IPv6: newIPv6,
+ PublicIPPort: gConf.Network.PublicIPPort,
}
GNetwork.write(MsgReport, MsgReportBasic, &req)
}
@@ -142,19 +143,9 @@ func P2PNetworkInstance() {
func (pn *P2PNetwork) keepAlive() {
gLog.i("P2PNetwork keepAlive start")
- // !hbTime && !initTime = hang, exit worker
- var lastCheckTime time.Time
-
for {
time.Sleep(time.Second * 10)
- // Skip check if we're waking from sleep/hibernation
- now := time.Now()
- if !lastCheckTime.IsZero() && now.Sub(lastCheckTime) > NetworkHeartbeatTime*3 {
- gLog.i("Detected possible sleep/wake cycle, skipping this check")
- lastCheckTime = now
- continue
- }
- lastCheckTime = now
+
if pn.hbTime.Before(time.Now().Add(-NetworkHeartbeatTime * 3)) {
if pn.initTime.After(time.Now().Add(-NetworkHeartbeatTime * 3)) {
gLog.d("Init less than 3 mins, skipping this check")
@@ -285,8 +276,8 @@ func (pn *P2PNetwork) autorunApp() {
}
func (pn *P2PNetwork) addRelayTunnel(config AppConfig, excludeNodes string) (*P2PTunnel, uint64, string, error) {
- gLog.i("addRelayTunnel to %s start", config.LogPeerNode())
- defer gLog.i("addRelayTunnel to %s end", config.LogPeerNode())
+ gLog.d("addRelayTunnel to %s start", config.LogPeerNode())
+ defer gLog.d("addRelayTunnel to %s end", config.LogPeerNode())
var relayTunnel *P2PTunnel
relayConfig := AppConfig{
peerToken: config.peerToken,
@@ -342,7 +333,7 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig, excludeNodes string) (*P2
///
if relayTunnel == nil {
var err error
- relayTunnel, err = pn.addDirectTunnel(relayConfig, 0)
+ relayTunnel, err = pn.addDirectTunnel(relayConfig, 0, nil)
if err != nil || relayTunnel == nil {
gLog.w("direct connect error:%s", err)
if err != nil && config.RelayNode != "" {
@@ -392,8 +383,15 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, MsgQueueSize))
}
// check if app already exist?
- if pn.findApp(&config) != nil {
- return errors.New("P2PApp already exist")
+ existApp := pn.findApp(&config)
+ if existApp != nil {
+ if existApp.tunnelNum == int(gConf.sdwan.TunnelNum) {
+ return errors.New("P2PApp already exist")
+ } else {
+ gLog.d("app %s exist but tunnelNum changed from %d to %d, delete it and recreate", existApp.config.AppName, existApp.tunnelNum, gConf.sdwan.TunnelNum)
+ pn.DeleteApp(config)
+ }
+
}
app := p2pApp{
@@ -459,13 +457,13 @@ func (pn *P2PNetwork) DeleteApp(config AppConfig) {
}
-func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) {
+func (pn *P2PNetwork) findTunnel(peerNode string, ignoredTunnel *P2PTunnel) (t *P2PTunnel) {
t = nil
// find existing tunnel to peer
pn.allTunnels.Range(func(id, i interface{}) bool {
tmpt := i.(*P2PTunnel)
- if tmpt.config.PeerNode == peerNode {
- gLog.i("tunnel already exist %s", tmpt.config.LogPeerNode())
+ if tmpt.config.PeerNode == peerNode && tmpt != ignoredTunnel {
+ gLog.d("tunnel already exist %s", tmpt.config.LogPeerNode())
isActive := tmpt.checkActive()
// inactive, close it
if !isActive {
@@ -481,7 +479,7 @@ func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) {
return t
}
-func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunnel, err error) {
+func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
gLog.d("addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
defer gLog.d("addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
@@ -502,14 +500,14 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
}
if isClient { // only client side find existing tunnel, server side should force build tunnel
- if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil {
+ if existTunnel := pn.findTunnel(config.PeerNode, ignoredTunnel); existTunnel != nil {
return existTunnel, nil
}
}
// server side
if !isClient {
- t, err = pn.newTunnel(config, tid, isClient)
+ t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel)
return t, err // always return
}
@@ -521,15 +519,15 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
return nil, initErr
}
- gLog.d("config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,",
- config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType)
+ gLog.d("config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,config.PunchPriority=%d,IPv6=%s",
+ config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType, config.PunchPriority, gConf.IPv6())
// try Intranet
if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP
gLog.i("try Intranet")
config.linkMode = LinkModeIntranet
config.isUnderlayServer = 0
- if t, err = pn.newTunnel(config, tid, isClient); err == nil {
+ if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil
}
}
@@ -542,7 +540,7 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
if gConf.Forcev6 {
thisTunnelForcev6 = true
}
- if t, err = pn.newTunnel(config, tid, isClient); err == nil {
+ if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil
}
}
@@ -564,7 +562,7 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
} else {
config.isUnderlayServer = 0
}
- if t, err = pn.newTunnel(config, tid, isClient); err == nil {
+ if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil
}
}
@@ -581,7 +579,7 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
gLog.i("try UDP4 Punch")
config.linkMode = LinkModeUDPPunch
config.isUnderlayServer = 0
- if t, err = pn.newTunnel(config, tid, isClient); err == nil {
+ if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil
}
}
@@ -601,7 +599,7 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
gLog.i("try TCP4 Punch")
config.linkMode = LinkModeTCPPunch
config.isUnderlayServer = 0
- if t, err = pn.newTunnel(config, tid, isClient); err == nil {
+ if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
gLog.i("TCP4 Punch ok")
return t, nil
}
@@ -613,8 +611,8 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
primaryPunchFunc = funcTCP
secondaryPunchFunc = funcUDP
} else {
- primaryPunchFunc = funcTCP
- secondaryPunchFunc = funcUDP
+ primaryPunchFunc = funcUDP
+ secondaryPunchFunc = funcTCP
}
if t, err = primaryPunchFunc(); t != nil && err == nil {
return t, err
@@ -627,9 +625,9 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
return nil, err
}
-func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) {
+func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
if isClient { // only client side find existing tunnel, server side should force build tunnel
- if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil {
+ if existTunnel := pn.findTunnel(config.PeerNode, ignoredTunnel); existTunnel != nil {
return existTunnel, nil
}
}
@@ -665,8 +663,9 @@ func (pn *P2PNetwork) init() error {
pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done()
var err error
+ initOK := false
defer func() {
- if err != nil {
+ if !initOK {
// init failed, retry
pn.close(true)
gLog.e("P2PNetwork init error:%s", err)
@@ -760,6 +759,14 @@ func (pn *P2PNetwork) init() error {
ws, _, err := d.Dial(u.String(), nil)
if err != nil {
gLog.e("Dial error:%s", err)
+ switch gConf.Network.ServerPort {
+ case WsPort:
+ gConf.Network.ServerPort = WsPort2
+ gLog.i("try alternative port %d", WsPort2)
+ case WsPort2:
+ gConf.Network.ServerPort = WsPort
+ gLog.i("try alternative port %d", WsPort)
+ }
break
}
pn.running = true
@@ -769,7 +776,7 @@ func (pn *P2PNetwork) init() error {
if len(localAddr) == 2 {
gConf.Network.localIP = localAddr[0]
} else {
- err = errors.New("get local ip failed")
+ gLog.e("get local ip failed:%s", ws.LocalAddr().String())
break
}
go pn.readLoop()
@@ -781,6 +788,7 @@ func (pn *P2PNetwork) init() error {
LanIP: gConf.Network.localIP,
OS: gConf.Network.os,
HasIPv4: gConf.Network.hasIPv4,
+ PublicIPPort: gConf.Network.PublicIPPort,
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
Version: OpenP2PVersion,
}
@@ -795,10 +803,22 @@ func (pn *P2PNetwork) init() error {
pn.refreshIPv6()
}
req.IPv6 = gConf.IPv6()
- pn.write(MsgReport, MsgReportBasic, &req)
+ pn.write(MsgReport, MsgReportBasic, &req) // TODO: if report failed, many logic problems, loss lanip os version...
+ head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout)
+ if head == nil {
+ gLog.e("read MsgReportBasic rsp error, retry")
+ pn.write(MsgReport, MsgReportBasic, &req) // TODO: if report failed, many logic problems, loss lanip os version...
+ head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout)
+ if head == nil {
+ gLog.e("read MsgReportBasic rsp error again, exit")
+ os.Exit(9)
+ }
+ return
+ }
}()
go pn.autorunApp()
pn.write(MsgSDWAN, MsgSDWANInfoReq, nil)
+ initOK = true
gLog.d("P2PNetwork init ok")
break
}
@@ -828,6 +848,10 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
} else {
gConf.setToken(rsp.Token)
gConf.setUser(rsp.User)
+ gConf.setForcev6(rsp.Forcev6 != 0)
+ if rsp.PublicIPPort != 0 {
+ gConf.Network.PublicIPPort = rsp.PublicIPPort
+ }
if len(rsp.Node) >= MinNodeNameLen {
gConf.setNode(rsp.Node)
}
@@ -1039,7 +1063,7 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
if head.MainType != mainType || head.SubType != subType {
// gLog.d("read msg error type %d:%d expect %d:%d, requeue it", head.MainType, head.SubType, mainType, subType)
ch <- msg
- time.Sleep(time.Second)
+ time.Sleep(time.Millisecond * 50)
continue
}
if mainType == MsgPush {
@@ -1069,9 +1093,14 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64, rtid uint64, updateRelayT
// ipv6 will expired need to refresh.
func (pn *P2PNetwork) refreshIPv6() {
+
for i := 0; i < 2; i++ {
+ url := "http://ipv6.ddnspod.com/"
+ if i == 1 {
+ url = "ipv6.icanhazip.com"
+ }
client := &http.Client{Timeout: time.Second * 10}
- r, err := client.Get("http://ipv6.ddnspod.com/")
+ r, err := client.Get(url)
if err != nil {
gLog.d("refreshIPv6 error:%s", err)
continue
@@ -1221,4 +1250,3 @@ func (pn *P2PNetwork) ReadNode(tm time.Duration) []byte {
}
return nil
}
-
diff --git a/core/p2ptunnel.go b/core/p2ptunnel.go
index a426dbc..4e5df6b 100644
--- a/core/p2ptunnel.go
+++ b/core/p2ptunnel.go
@@ -293,7 +293,7 @@ func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
gLog.d("UDP4 connection ok")
} else {
if t.config.UnderlayProtocol == "kcp" {
- ul, err = listenKCP(t.localHoleAddr.String(), TunnelIdleTimeout)
+ // ul, err = listenKCP(t.localHoleAddr.String(), TunnelIdleTimeout)
} else {
ul, err = listenQuic(t.localHoleAddr.String(), TunnelIdleTimeout)
}
@@ -337,7 +337,7 @@ func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
gLog.d("%s dial to %s", underlayProtocol, t.remoteHoleAddr.String())
if t.config.UnderlayProtocol == "kcp" {
- ul, errL = dialKCP(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
+ // ul, errL = dialKCP(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
} else {
ul, errL = dialQuic(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
}
@@ -602,7 +602,7 @@ func (t *P2PTunnel) readLoop() {
head, body, err := t.conn.ReadBuffer()
if err != nil || head == nil {
if t.isRuning() {
- gLog.w("%d tunnel read error:%s", t.id, err)
+ gLog.d("%d tunnel read error:%s", t.id, err)
}
break
}
@@ -630,7 +630,12 @@ func (t *P2PTunnel) readLoop() {
existApp, appok := GNetwork.apps.Load(memAppPeerID)
if appok {
app := existApp.(*p2pApp)
- app.rtt[0].Store(int32(time.Since(t.whbTime) / time.Millisecond))
+ for i := 0; i < app.relayIdxStart; i++ {
+ if app.Tunnel(i) == t {
+ app.rtt[i].Store(int32(time.Since(t.whbTime) / time.Millisecond))
+ break
+ }
+ }
}
}
@@ -806,7 +811,7 @@ func (t *P2PTunnel) writeLoop() {
t.whbTime = time.Now()
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
if err != nil {
- gLog.w("%d write tunnel heartbeat error %s", t.id, err)
+ gLog.d("%d write tunnel heartbeat error %s", t.id, err)
t.close()
return
}
diff --git a/core/protocol.go b/core/protocol.go
index 534bf24..34d0ba5 100644
--- a/core/protocol.go
+++ b/core/protocol.go
@@ -10,7 +10,7 @@ import (
"time"
)
-const OpenP2PVersion = "3.24.33"
+const OpenP2PVersion = "3.25.8"
const ProductName string = "openp2p"
const LeastSupportVersion = "3.0.0"
const SyncServerTimeVersion = "3.9.0"
@@ -20,10 +20,12 @@ const SupportIntranetVersion = "3.14.5"
const SupportDualTunnelVersion = "3.15.5"
const IPv6PunchVersion = "3.24.9"
const SupportUDP4DirectVersion = "3.24.16"
+const SupportMultiDirectVersion = "3.25.1"
const (
NATDetectPort1 = 27180
NATDetectPort2 = 27181
WsPort = 27183
+ WsPort2 = 465
UDPPort1 = 27182
UDPPort2 = 27183
)
@@ -124,41 +126,42 @@ const (
// MsgP2P sub type message
const (
- MsgPunchHandshake = iota
- MsgPunchHandshakeAck
- MsgTunnelHandshake
- MsgTunnelHandshakeAck
- MsgTunnelHeartbeat
- MsgTunnelHeartbeatAck
- MsgOverlayConnectReq
- MsgOverlayConnectRsp
- MsgOverlayDisconnectReq
- MsgOverlayData
- MsgRelayData
- MsgRelayHeartbeat
- MsgRelayHeartbeatAck
- MsgNodeData
- MsgRelayNodeData
- MsgNodeDataMP
- MsgNodeDataMPAck
- MsgRelayHeartbeatAck2
+ MsgPunchHandshake = 0
+ MsgPunchHandshakeAck = 1
+ MsgTunnelHandshake = 2
+ MsgTunnelHandshakeAck = 3
+ MsgTunnelHeartbeat = 4
+ MsgTunnelHeartbeatAck = 5
+ MsgOverlayConnectReq = 6
+ MsgOverlayConnectRsp = 7
+ MsgOverlayDisconnectReq = 8
+ MsgOverlayData = 9
+ MsgRelayData = 10
+ MsgRelayHeartbeat = 11
+ MsgRelayHeartbeatAck = 12
+ MsgNodeData = 13
+ MsgRelayNodeData = 14
+ MsgNodeDataMP = 15
+ MsgNodeDataMPAck = 16
+ MsgRelayHeartbeatAck2 = 17
)
// MsgRelay sub type message
const (
- MsgRelayNodeReq = iota
- MsgRelayNodeRsp
+ MsgRelayNodeReq = 0
+ MsgRelayNodeRsp = 1
)
// MsgReport sub type message
const (
- MsgReportBasic = iota
- MsgReportQuery
- MsgReportConnect
- MsgReportApps
- MsgReportLog
- MsgReportMemApps
- MsgReportResponse
+ MsgReportBasic = 0
+ MsgReportQuery = 1
+ MsgReportConnect = 2
+ MsgReportApps = 3
+ MsgReportLog = 4
+ MsgReportMemApps = 5
+ MsgReportResponse = 6
+ MsgReportBasicRsp = 7
)
const (
@@ -218,19 +221,19 @@ const (
)
const (
- MsgQueryPeerInfoReq = iota
- MsgQueryPeerInfoRsp
+ MsgQueryPeerInfoReq = 0
+ MsgQueryPeerInfoRsp = 1
)
const (
- MsgSDWANInfoReq = iota
- MsgSDWANInfoRsp
+ MsgSDWANInfoReq = 0
+ MsgSDWANInfoRsp = 1
)
// MsgNATDetect
const (
- MsgNAT = iota
- MsgPublicIP
+ MsgNAT = 0
+ MsgPublicIP = 1
)
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
@@ -320,6 +323,8 @@ type LoginRsp struct {
Token uint64 `json:"token,omitempty"`
Ts int64 `json:"ts,omitempty"`
LoginMaxDelay int `json:"loginMaxDelay,omitempty"` // seconds
+ Forcev6 int `json:"forcev6,omitempty"`
+ PublicIPPort int `json:"publicIPPort,omitempty"`
}
type NatDetectReq struct {
@@ -394,6 +399,7 @@ type ReportBasic struct {
LanIP string `json:"lanIP,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
+ PublicIPPort int `json:"publicIPPort,omitempty"`
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"`
@@ -498,9 +504,10 @@ type ProfileInfo struct {
}
type EditNode struct {
- NewName string `json:"newName,omitempty"`
- Bandwidth int `json:"bandwidth,omitempty"`
- Forcev6 int `json:"forcev6,omitempty"`
+ NewName string `json:"newName,omitempty"`
+ Bandwidth int `json:"bandwidth,omitempty"`
+ Forcev6 int `json:"forcev6,omitempty"`
+ PublicIPPort int `json:"publicIPPort,omitempty"`
}
type QueryPeerInfoReq struct {
diff --git a/core/sdwan.go b/core/sdwan.go
index 29ca4bd..c89e120 100644
--- a/core/sdwan.go
+++ b/core/sdwan.go
@@ -57,6 +57,10 @@ func (s *p2pSDWAN) reset() {
gLog.i("reset sdwan when network disconnected")
// clear sysroute
delRoutesByGateway(s.gateway.String())
+ s.sysRoute.Range(func(key, value interface{}) bool {
+ s.sysRoute.Delete(key)
+ return true
+ })
// clear internel route
s.internalRoute = NewIPTree("")
// clear p2papp
@@ -67,6 +71,7 @@ func (s *p2pSDWAN) reset() {
gConf.resetSDWAN()
}
+
func (s *p2pSDWAN) init() error {
gConf.Network.previousIP = gConf.Network.publicIP
if gConf.getSDWAN().Gateway == "" {
@@ -88,6 +93,15 @@ func (s *p2pSDWAN) init() error {
s.internalRoute.Del(node.IP, node.IP)
ipNum, _ := inetAtoN(node.IP)
s.sysRoute.Delete(ipNum)
+ // if node.Name == gConf.Network.Node {
+ // // this is local node, need rm all client-side apps
+ // GNetwork.apps.Range(func(id, i interface{}) bool {
+ // app := i.(*p2pApp)
+ // if app.config.is
+ // return true
+ // })
+ // continue
+ // }
gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name})
GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name})
arr := strings.Split(node.Resource, ",")
@@ -309,7 +323,7 @@ func handleSDWAN(subType uint16, msg []byte) error {
}
gLog.i("sdwan init:%s", prettyJson(rsp))
// GNetwork.sdwan.detail = &rsp
- if gConf.Network.previousIP != gConf.Network.publicIP || gConf.getSDWAN().CentralNode != rsp.CentralNode {
+ if gConf.Network.previousIP != gConf.Network.publicIP || gConf.getSDWAN().CentralNode != rsp.CentralNode || gConf.getSDWAN().Gateway != rsp.Gateway {
GNetwork.sdwan.reset()
preAndroidSDWANConfig = "" // let androind app reset vpnservice
}
diff --git a/core/underlay.go b/core/underlay.go
index f889d17..746aeca 100644
--- a/core/underlay.go
+++ b/core/underlay.go
@@ -42,7 +42,7 @@ func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
- _, err := ul.Write(writeBytes)
+ err := writeFull(ul, writeBytes)
ul.WUnlock()
return err
}
@@ -50,7 +50,7 @@ func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error
func DefaultWriteBuffer(ul underlay, data []byte) error {
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
- _, err := ul.Write(data)
+ err := writeFull(ul, data)
ul.WUnlock()
return err
}
@@ -62,7 +62,7 @@ func DefaultWriteMessage(ul underlay, mainType uint16, subType uint16, packet in
}
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
- _, err = ul.Write(writeBytes)
+ err = writeFull(ul, writeBytes)
ul.WUnlock()
return err
}
diff --git a/core/underlay_kcp.go b/core/underlay_kcp.go-
similarity index 100%
rename from core/underlay_kcp.go
rename to core/underlay_kcp.go-
diff --git a/core/underlay_quic.go b/core/underlay_quic.go
index 6e44f33..a9bb585 100644
--- a/core/underlay_quic.go
+++ b/core/underlay_quic.go
@@ -16,14 +16,14 @@ import (
"github.com/quic-go/quic-go"
)
-// quic.Dial do not support version 44, disable it
-var quicVersion []quic.Version
+// quic.DialContext do not support version 44,disable it
+var quicVersion []quic.VersionNumber
type underlayQUIC struct {
- listener *quic.Listener
+ listener quic.Listener
writeMtx *sync.Mutex
- *quic.Stream
- *quic.Conn
+ quic.Stream
+ quic.Connection
}
func (conn *underlayQUIC) Protocol() string {
@@ -47,17 +47,8 @@ func (conn *underlayQUIC) WriteMessage(mainType uint16, subType uint16, packet i
}
func (conn *underlayQUIC) Close() error {
- // CancelRead expects a StreamErrorCode; using 1 as before (application-defined)
- if conn.Stream != nil {
- conn.Stream.CancelRead(1)
- // close send-side of stream
- _ = conn.Stream.Close()
- }
- if conn.Conn != nil {
- // CloseWithError expects an ApplicationErrorCode and a description.
- // 0 is zero-value; keep behavior similar to old CloseWithError(0,"")
- _ = conn.Conn.CloseWithError(0, "")
- }
+ conn.Stream.CancelRead(1)
+ conn.Connection.CloseWithError(0, "")
conn.CloseListener()
return nil
}
@@ -69,7 +60,7 @@ func (conn *underlayQUIC) WUnlock() {
}
func (conn *underlayQUIC) CloseListener() {
if conn.listener != nil {
- _ = conn.listener.Close()
+ conn.listener.Close()
}
}
@@ -85,7 +76,7 @@ func (conn *underlayQUIC) Accept() error {
return err
}
conn.Stream = stream
- conn.Conn = sess
+ conn.Connection = sess
return nil
}
@@ -105,25 +96,23 @@ func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
return ul, nil
}
-func dialQuic(pconn *net.UDPConn, remoteAddr *net.UDPAddr, timeout time.Duration) (*underlayQUIC, error) {
+func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, timeout time.Duration) (*underlayQUIC, error) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"openp2pv1"},
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
-
- // New API: quic.Dial(ctx, packetConn, remoteAddr, tlsConf, quicConfig)
- connection, err := quic.Dial(ctx, pconn, remoteAddr, tlsConf,
+ Connection, err := quic.DialContext(ctx, conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
&quic.Config{Versions: quicVersion, MaxIdleTimeout: TunnelIdleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
- return nil, fmt.Errorf("quic.Dial error:%s", err)
+ return nil, fmt.Errorf("quic.DialContext error:%s", err)
}
- stream, err := connection.OpenStreamSync(context.Background())
+ stream, err := Connection.OpenStreamSync(context.Background())
if err != nil {
return nil, fmt.Errorf("OpenStreamSync error:%s", err)
}
- qConn := &underlayQUIC{nil, &sync.Mutex{}, stream, connection}
+ qConn := &underlayQUIC{nil, &sync.Mutex{}, stream, Connection}
return qConn, nil
}
diff --git a/core/update.go b/core/update.go
index 24d6797..274e703 100644
--- a/core/update.go
+++ b/core/update.go
@@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"net/http"
"net/url"
"os"
@@ -49,7 +48,7 @@ func update(host string, port int) error {
gLog.e("get update info error:%s", rsp.Status)
return err
}
- rspBuf, err := ioutil.ReadAll(rsp.Body)
+ rspBuf, err := io.ReadAll(rsp.Body)
if err != nil {
gLog.e("update:read update list failed:%s", err)
return err
@@ -94,7 +93,8 @@ func downloadFile(url string, checksum string, dstFile string) error {
RootCAs: caCertPool,
InsecureSkipVerify: gConf.TLSInsecureSkipVerify},
}
- client := &http.Client{Transport: tr}
+ client := &http.Client{Transport: tr,
+ Timeout: 60 * time.Second}
response, err := client.Get(url)
if err != nil {
gLog.e("download url %s error:%s", url, err)
diff --git a/core/v4listener.go b/core/v4listener.go
index 7a3f673..bad163a 100644
--- a/core/v4listener.go
+++ b/core/v4listener.go
@@ -17,13 +17,13 @@ type v4Listener struct {
acceptCh chan bool
running bool
tcpListener *net.TCPListener
- udpListener *quic.Listener
+ udpListener quic.Listener
wg sync.WaitGroup
}
func (vl *v4Listener) start() {
vl.running = true
- vl.acceptCh = make(chan bool, 500)
+ v4l.acceptCh = make(chan bool, 500)
vl.wg.Add(1)
go func() {
defer vl.wg.Done()
@@ -56,11 +56,11 @@ func (vl *v4Listener) stop() {
func (vl *v4Listener) listenTCP() error {
gLog.d("v4Listener listenTCP %d start", vl.port)
defer gLog.d("v4Listener listenTCP %d end", vl.port)
- addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", vl.port))
+ addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", vl.port)) // system will auto listen both v4 and v6
var err error
vl.tcpListener, err = net.ListenTCP("tcp", addr)
if err != nil {
- gLog.e("v4Listener listen %d error:%s", vl.port, err)
+ gLog.e("v4Listener listen %d error:", vl.port, err)
return err
}
defer vl.tcpListener.Close()
@@ -69,11 +69,7 @@ func (vl *v4Listener) listenTCP() error {
if err != nil {
break
}
- utcp := &underlayTCP{
- writeMtx: &sync.Mutex{},
- Conn: c,
- connectTime: time.Now(),
- }
+ utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()}
go vl.handleConnection(utcp)
}
vl.tcpListener = nil
@@ -84,15 +80,8 @@ func (vl *v4Listener) listenUDP() error {
gLog.d("v4Listener listenUDP %d start", vl.port)
defer gLog.d("v4Listener listenUDP %d end", vl.port)
var err error
- vl.udpListener, err = quic.ListenAddr(
- fmt.Sprintf("0.0.0.0:%d", vl.port),
- generateTLSConfig(),
- &quic.Config{
- Versions: quicVersion,
- MaxIdleTimeout: TunnelIdleTimeout,
- DisablePathMTUDiscovery: true,
- },
- )
+ vl.udpListener, err = quic.ListenAddr(fmt.Sprintf("0.0.0.0:%d", vl.port), generateTLSConfig(),
+ &quic.Config{Versions: quicVersion, MaxIdleTimeout: TunnelIdleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
return err
}
@@ -108,14 +97,7 @@ func (vl *v4Listener) listenUDP() error {
if err != nil {
break
}
-
- ul := &underlayQUIC{
- listener: nil,
- writeMtx: &sync.Mutex{},
- Stream: stream,
- Conn: sess,
- }
-
+ ul := &underlayQUIC{writeMtx: &sync.Mutex{}, Stream: stream, Connection: sess}
go vl.handleConnection(ul)
}
vl.udpListener = nil
@@ -128,14 +110,14 @@ func (vl *v4Listener) handleConnection(ul underlay) {
_, buff, err := ul.ReadBuffer()
if err != nil || buff == nil {
gLog.e("v4Listener read MsgTunnelHandshake error:%s", err)
- return
}
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
var tid uint64
if string(buff) == "OpenP2P,hello" { // old client
+ // save remoteIP as key
remoteAddr := ul.RemoteAddr().(*net.TCPAddr).IP
ipBytes := remoteAddr.To4()
- tid = uint64(binary.BigEndian.Uint32(ipBytes))
+ tid = uint64(binary.BigEndian.Uint32(ipBytes)) // bytes not enough for uint64
gLog.d("hello %s", string(buff))
} else {
if len(buff) < 8 {
@@ -164,7 +146,7 @@ func (vl *v4Listener) handleConnection(ul underlay) {
func (vl *v4Listener) getUnderlay(tid uint64) underlay {
for i := 0; i < 100; i++ {
select {
- case <-time.After(50 * time.Millisecond):
+ case <-time.After(time.Millisecond * 50):
case <-vl.acceptCh:
}
if u, ok := vl.conns.LoadAndDelete(tid); ok {
diff --git a/go.mod b/go.mod
index eda274d..7e58f99 100644
--- a/go.mod
+++ b/go.mod
@@ -1,32 +1,37 @@
module openp2p
-go 1.25
+go 1.20
require (
github.com/emirpasic/gods v1.18.1
github.com/gorilla/websocket v1.5.3
+ github.com/huin/goupnp v1.3.0
+ github.com/jackpal/go-nat-pmp v1.0.2
github.com/openp2p-cn/go-reuseport v0.3.2
github.com/openp2p-cn/service v1.0.0
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e
github.com/openp2p-cn/wireguard-go v0.0.20241020
- github.com/quic-go/quic-go v0.57.1
+ github.com/quic-go/quic-go v0.34.0
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
- github.com/xtaci/kcp-go/v5 v5.5.17
- golang.org/x/net v0.43.0
- golang.org/x/sys v0.35.0
+ golang.org/x/net v0.30.0
+ golang.org/x/sys v0.26.0
golang.zx2c4.com/wireguard/windows v0.5.3
)
require (
+ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
+ github.com/golang/mock v1.7.0-rc.1 // indirect
+ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/kardianos/service v1.2.2 // indirect
- github.com/klauspost/cpuid/v2 v2.2.5 // indirect
- github.com/klauspost/reedsolomon v1.11.8 // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/templexxx/cpu v0.1.0 // indirect
- github.com/templexxx/xorsimd v0.4.2 // indirect
- github.com/tjfoc/gmsm v1.4.1 // indirect
+ github.com/onsi/ginkgo/v2 v2.2.0 // indirect
+ github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
+ github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
- golang.org/x/crypto v0.41.0 // indirect
+ golang.org/x/crypto v0.28.0 // indirect
+ golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
+ golang.org/x/mod v0.21.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/tools v0.26.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 // indirect
diff --git a/go.sum b/go.sum
index 552db00..527dbb0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,44 +1,32 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
+github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
-github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
+github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
-github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
-github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
-github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
-github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
-github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY=
-github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
-github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
+github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
+github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
+github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/openp2p-cn/go-reuseport v0.3.2 h1:TO78WsyJ1F6g7rLp3hpTKOBxtZTU5Lz+Y4Mj+fVUfZc=
github.com/openp2p-cn/go-reuseport v0.3.2/go.mod h1:+EwCusXz50jaYkPNZcCrK4cLoA9tr2jEiJC+bjzpWc8=
github.com/openp2p-cn/service v1.0.0 h1:1++FroLvW4Mc/PStFIAF0mzudVW6E8EAeqWyIESTGZA=
@@ -47,98 +35,65 @@ github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e h1:QqP3Va/nPj45wq0
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e/go.mod h1:RYVP3CTIvHD9IwQe2M3zy5iLKNjusRVDz/4gQuKcc/o=
github.com/openp2p-cn/wireguard-go v0.0.20241020 h1:cNgG8o2ctYT9YanqalfMQo+jVju7MrdJFI6WLZZRr7M=
github.com/openp2p-cn/wireguard-go v0.0.20241020/go.mod h1:ka26SCScyLEd+uFrnq6w4n65Sxq1W/xIJfXEXLLvJEc=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
-github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
-github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
-github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
-github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
-github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
-github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
-github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
-github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
-github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
-github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
+github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
+github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
+github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
+github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU=
+github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
-github.com/xtaci/kcp-go/v5 v5.5.17 h1:bkdaqtER0PMlP05BBHfu6W+71kt/NwbAk93KH7F78Ck=
-github.com/xtaci/kcp-go/v5 v5.5.17/go.mod h1:pVx3jb4LT5edTmPayc77tIU9nRsjGck8wep5ZV/RBO0=
-github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
-github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
-go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
-golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
-golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
+golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
-golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
-golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
-golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
@@ -146,24 +101,9 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 h1:QyA/pFgC67EZ5+0oRfiNFhfEGd3NqZM1A2HQEuPKC3c=
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/pkg/upnp/nat.go b/pkg/upnp/nat.go
new file mode 100644
index 0000000..c5be869
--- /dev/null
+++ b/pkg/upnp/nat.go
@@ -0,0 +1,239 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package nat provides access to common network port mapping protocols.
+package openp2p
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+ "time"
+
+ natpmp "github.com/jackpal/go-nat-pmp"
+)
+
+// Interface An implementation of nat.Interface can map local ports to ports
+// accessible from the Internet.
+type Interface interface {
+ // These methods manage a mapping between a port on the local
+ // machine to a port that can be connected to from the internet.
+ //
+ // protocol is "UDP" or "TCP". Some implementations allow setting
+ // a display name for the mapping. The mapping may be removed by
+ // the gateway when its lifetime ends.
+ AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error)
+ DeleteMapping(protocol string, extport, intport int) error
+
+ // ExternalIP should return the external (Internet-facing)
+ // address of the gateway device.
+ ExternalIP() (net.IP, error)
+
+ // String should return name of the method. This is used for logging.
+ String() string
+}
+
+// Parse parses a NAT interface description.
+// The following formats are currently accepted.
+// Note that mechanism names are not case-sensitive.
+//
+// "" or "none" return nil
+// "extip:77.12.33.4" will assume the local machine is reachable on the given IP
+// "any" uses the first auto-detected mechanism
+// "upnp" uses the Universal Plug and Play protocol
+// "pmp" uses NAT-PMP with an auto-detected gateway address
+// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
+func Parse(spec string) (Interface, error) {
+ var (
+ before, after, found = strings.Cut(spec, ":")
+ mech = strings.ToLower(before)
+ ip net.IP
+ )
+ if found {
+ ip = net.ParseIP(after)
+ if ip == nil {
+ return nil, errors.New("invalid IP address")
+ }
+ }
+ switch mech {
+ case "", "none", "off":
+ return nil, nil
+ case "any", "auto", "on":
+ return Any(), nil
+ case "extip", "ip":
+ if ip == nil {
+ return nil, errors.New("missing IP address")
+ }
+ return ExtIP(ip), nil
+ case "upnp":
+ return UPnP(), nil
+ case "pmp", "natpmp", "nat-pmp":
+ return PMP(ip), nil
+ default:
+ return nil, fmt.Errorf("unknown mechanism %q", before)
+ }
+}
+
+const (
+ DefaultMapTimeout = 10 * time.Minute
+)
+
+// Map adds a port mapping on m and keeps it alive until c is closed.
+// This function is typically invoked in its own goroutine.
+//
+// Note that Map does not handle the situation where the NAT interface assigns a different
+// external port than the requested one.
+func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
+ // log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
+ refresh := time.NewTimer(DefaultMapTimeout)
+ defer func() {
+ refresh.Stop()
+ // log.Debug("Deleting port mapping")
+ m.DeleteMapping(protocol, extport, intport)
+ }()
+ if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
+ // log.Debug("Couldn't add port mapping", "err", err)
+ } else {
+ // log.Info("Mapped network port")
+ }
+ for {
+ select {
+ case _, ok := <-c:
+ if !ok {
+ return
+ }
+ case <-refresh.C:
+ // log.Trace("Refreshing port mapping")
+ if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
+ // log.Debug("Couldn't add port mapping", "err", err)
+ }
+ refresh.Reset(DefaultMapTimeout)
+ }
+ }
+}
+
+// ExtIP assumes that the local machine is reachable on the given
+// external IP address, and that any required ports were mapped manually.
+// Mapping operations will not return an error but won't actually do anything.
+type ExtIP net.IP
+
+func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
+func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
+
+// These do nothing.
+
+func (ExtIP) AddMapping(string, int, int, string, time.Duration) (uint16, error) { return 0, nil }
+func (ExtIP) DeleteMapping(string, int, int) error { return nil }
+
+// Any returns a port mapper that tries to discover any supported
+// mechanism on the local network.
+func Any() Interface {
+ // TODO: attempt to discover whether the local machine has an
+ // Internet-class address. Return ExtIP in this case.
+ return startautodisc("UPnP or NAT-PMP", func() Interface {
+ found := make(chan Interface, 2)
+ go func() { found <- discoverUPnP() }()
+ go func() { found <- discoverPMP() }()
+ for i := 0; i < cap(found); i++ {
+ if c := <-found; c != nil {
+ return c
+ }
+ }
+ return nil
+ })
+}
+
+// UPnP returns a port mapper that uses UPnP. It will attempt to
+// discover the address of your router using UDP broadcasts.
+func UPnP() Interface {
+ return startautodisc("UPnP", discoverUPnP)
+}
+
+// PMP returns a port mapper that uses NAT-PMP. The provided gateway
+// address should be the IP of your router. If the given gateway
+// address is nil, PMP will attempt to auto-discover the router.
+func PMP(gateway net.IP) Interface {
+ if gateway != nil {
+ return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
+ }
+ return startautodisc("NAT-PMP", discoverPMP)
+}
+
+// autodisc represents a port mapping mechanism that is still being
+// auto-discovered. Calls to the Interface methods on this type will
+// wait until the discovery is done and then call the method on the
+// discovered mechanism.
+//
+// This type is useful because discovery can take a while but we
+// want return an Interface value from UPnP, PMP and Auto immediately.
+type autodisc struct {
+ what string // type of interface being autodiscovered
+ once sync.Once
+ doit func() Interface
+
+ mu sync.Mutex
+ found Interface
+}
+
+func startautodisc(what string, doit func() Interface) Interface {
+ // TODO: monitor network configuration and rerun doit when it changes.
+ return &autodisc{what: what, doit: doit}
+}
+
+func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
+ if err := n.wait(); err != nil {
+ return 0, err
+ }
+ return n.found.AddMapping(protocol, extport, intport, name, lifetime)
+}
+
+func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
+ if err := n.wait(); err != nil {
+ return err
+ }
+ return n.found.DeleteMapping(protocol, extport, intport)
+}
+
+func (n *autodisc) ExternalIP() (net.IP, error) {
+ if err := n.wait(); err != nil {
+ return nil, err
+ }
+ return n.found.ExternalIP()
+}
+
+func (n *autodisc) String() string {
+ n.mu.Lock()
+ defer n.mu.Unlock()
+ if n.found == nil {
+ return n.what
+ }
+ return n.found.String()
+}
+
+// wait blocks until auto-discovery has been performed.
+func (n *autodisc) wait() error {
+ n.once.Do(func() {
+ n.mu.Lock()
+ n.found = n.doit()
+ n.mu.Unlock()
+ })
+ if n.found == nil {
+ return fmt.Errorf("no %s router discovered", n.what)
+ }
+ return nil
+}
diff --git a/pkg/upnp/natpmp.go b/pkg/upnp/natpmp.go
new file mode 100644
index 0000000..12489d3
--- /dev/null
+++ b/pkg/upnp/natpmp.go
@@ -0,0 +1,130 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package openp2p
+
+import (
+ "fmt"
+ "net"
+ "strings"
+ "time"
+
+ natpmp "github.com/jackpal/go-nat-pmp"
+)
+
+// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
+// the common interface.
+type pmp struct {
+ gw net.IP
+ c *natpmp.Client
+}
+
+func (n *pmp) String() string {
+ return fmt.Sprintf("NAT-PMP(%v)", n.gw)
+}
+
+func (n *pmp) ExternalIP() (net.IP, error) {
+ response, err := n.c.GetExternalAddress()
+ if err != nil {
+ return nil, err
+ }
+ return response.ExternalIPAddress[:], nil
+}
+
+func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
+ if lifetime <= 0 {
+ return 0, fmt.Errorf("lifetime must not be <= 0")
+ }
+ // Note order of port arguments is switched between our
+ // AddMapping and the client's AddPortMapping.
+ res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
+ if err != nil {
+ return 0, err
+ }
+
+ // NAT-PMP maps an alternative available port number if the requested port
+ // is already mapped to another address and returns success. Handling of
+ // alternate port numbers is done by the caller.
+ return res.MappedExternalPort, nil
+}
+
+func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
+ // To destroy a mapping, send an add-port with an internalPort of
+ // the internal port to destroy, an external port of zero and a
+ // time of zero.
+ _, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
+ return err
+}
+
+func discoverPMP() Interface {
+ // run external address lookups on all potential gateways
+ gws := potentialGateways()
+ found := make(chan *pmp, len(gws))
+ for i := range gws {
+ gw := gws[i]
+ go func() {
+ c := natpmp.NewClient(gw)
+ if _, err := c.GetExternalAddress(); err != nil {
+ found <- nil
+ } else {
+ found <- &pmp{gw, c}
+ }
+ }()
+ }
+ // return the one that responds first.
+ // discovery needs to be quick, so we stop caring about
+ // any responses after a very short timeout.
+ timeout := time.NewTimer(1 * time.Second)
+ defer timeout.Stop()
+ for range gws {
+ select {
+ case c := <-found:
+ if c != nil {
+ return c
+ }
+ case <-timeout.C:
+ return nil
+ }
+ }
+ return nil
+}
+
+// TODO: improve this. We currently assume that (on most networks)
+// the router is X.X.X.1 in a local LAN range.
+func potentialGateways() (gws []net.IP) {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return nil
+ }
+ for _, iface := range ifaces {
+ ifaddrs, err := iface.Addrs()
+ if err != nil {
+ return gws
+ }
+ for _, addr := range ifaddrs {
+ if x, ok := addr.(*net.IPNet); ok {
+ if x.IP.IsPrivate() {
+ ip := x.IP.Mask(x.Mask).To4()
+ if ip != nil {
+ ip[3] = ip[3] | 0x01
+ gws = append(gws, ip)
+ }
+ }
+ }
+ }
+ }
+ return gws
+}
diff --git a/pkg/upnp/natupnp.go b/pkg/upnp/natupnp.go
new file mode 100644
index 0000000..1c24da2
--- /dev/null
+++ b/pkg/upnp/natupnp.go
@@ -0,0 +1,247 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package openp2p
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "math/rand"
+ "net"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/huin/goupnp"
+ "github.com/huin/goupnp/dcps/internetgateway1"
+ "github.com/huin/goupnp/dcps/internetgateway2"
+)
+
+const (
+ soapRequestTimeout = 3 * time.Second
+ rateLimit = 200 * time.Millisecond
+)
+
+type upnp struct {
+ dev *goupnp.RootDevice
+ service string
+ client upnpClient
+ mu sync.Mutex
+ lastReqTime time.Time
+ rand *rand.Rand
+}
+
+type upnpClient interface {
+ GetExternalIPAddress() (string, error)
+ AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
+ DeletePortMapping(string, uint16, string) error
+ GetNATRSIPStatus() (sip bool, nat bool, err error)
+}
+
+func (n *upnp) natEnabled() bool {
+ var ok bool
+ var err error
+ n.withRateLimit(func() error {
+ _, ok, err = n.client.GetNATRSIPStatus()
+ return err
+ })
+ return err == nil && ok
+}
+
+func (n *upnp) ExternalIP() (addr net.IP, err error) {
+ var ipString string
+ n.withRateLimit(func() error {
+ ipString, err = n.client.GetExternalIPAddress()
+ return err
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ ip := net.ParseIP(ipString)
+ if ip == nil {
+ return nil, errors.New("bad IP in response")
+ }
+ return ip, nil
+}
+
+func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
+ ip, err := n.internalAddress()
+ if err != nil {
+ return 0, nil // TODO: Shouldn't we return the error?
+ }
+ protocol = strings.ToUpper(protocol)
+ lifetimeS := uint32(lifetime / time.Second)
+ n.DeleteMapping(protocol, extport, intport)
+
+ err = n.withRateLimit(func() error {
+ return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+ })
+ if err == nil {
+ return uint16(extport), nil
+ }
+
+ return uint16(extport), n.withRateLimit(func() error {
+ p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
+ if err == nil {
+ extport = int(p)
+ }
+ return err
+ })
+}
+
+func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
+ if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
+ return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+ }
+ // It will retry with a random port number if the client does
+ // not support AddAnyPortMapping.
+ extport = n.randomPort()
+ err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+ if err != nil {
+ return 0, err
+ }
+ return uint16(extport), nil
+}
+
+func (n *upnp) randomPort() int {
+ if n.rand == nil {
+ n.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
+ }
+ return n.rand.Intn(math.MaxUint16-10000) + 10000
+}
+
+func (n *upnp) internalAddress() (net.IP, error) {
+ devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
+ if err != nil {
+ return nil, err
+ }
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return nil, err
+ }
+ for _, iface := range ifaces {
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return nil, err
+ }
+ for _, addr := range addrs {
+ if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
+ return x.IP, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
+}
+
+func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
+ return n.withRateLimit(func() error {
+ return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
+ })
+}
+
+func (n *upnp) String() string {
+ return "UPNP " + n.service
+}
+
+func (n *upnp) withRateLimit(fn func() error) error {
+ n.mu.Lock()
+ defer n.mu.Unlock()
+
+ lastreq := time.Since(n.lastReqTime)
+ if lastreq < rateLimit {
+ time.Sleep(rateLimit - lastreq)
+ }
+ err := fn()
+ n.lastReqTime = time.Now()
+ return err
+}
+
+// discoverUPnP searches for Internet Gateway Devices
+// and returns the first one it can find on the local network.
+func discoverUPnP() Interface {
+ found := make(chan *upnp, 2)
+ // IGDv1
+ go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
+ switch sc.Service.ServiceType {
+ case internetgateway1.URN_WANIPConnection_1:
+ return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
+ case internetgateway1.URN_WANPPPConnection_1:
+ return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
+ }
+ return nil
+ })
+ // IGDv2
+ go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
+ switch sc.Service.ServiceType {
+ case internetgateway2.URN_WANIPConnection_1:
+ return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
+ case internetgateway2.URN_WANIPConnection_2:
+ return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
+ case internetgateway2.URN_WANPPPConnection_1:
+ return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
+ }
+ return nil
+ })
+ for i := 0; i < cap(found); i++ {
+ if c := <-found; c != nil {
+ return c
+ }
+ }
+ return nil
+}
+
+// finds devices matching the given target and calls matcher for all
+// advertised services of each device. The first non-nil service found
+// is sent into out. If no service matched, nil is sent.
+func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
+ devs, err := goupnp.DiscoverDevices(target)
+ if err != nil {
+ out <- nil
+ return
+ }
+ found := false
+ for i := 0; i < len(devs) && !found; i++ {
+ if devs[i].Root == nil {
+ continue
+ }
+ devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
+ if found {
+ return
+ }
+ // check for a matching IGD service
+ sc := goupnp.ServiceClient{
+ SOAPClient: service.NewSOAPClient(),
+ RootDevice: devs[i].Root,
+ Location: devs[i].Location,
+ Service: service,
+ }
+ sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
+ upnp := matcher(sc)
+ if upnp == nil {
+ return
+ }
+ upnp.dev = devs[i].Root
+
+ out <- upnp
+ found = true
+ })
+ }
+ if !found {
+ out <- nil
+ }
+}