init
This commit is contained in:
+418
@@ -0,0 +1,418 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type P2PTunnel struct {
|
||||
pn *P2PNetwork
|
||||
conn p2pConn
|
||||
hbTime time.Time
|
||||
hbMtx sync.Mutex
|
||||
hbTimeRelay time.Time
|
||||
config AppConfig
|
||||
la *net.UDPAddr // local hole address
|
||||
ra *net.UDPAddr // remote hole address
|
||||
overlayConns sync.Map // both TCP and UDP
|
||||
id uint64
|
||||
running bool
|
||||
runMtx sync.Mutex
|
||||
isServer bool // 0:server 1:client
|
||||
coneLocalPort int
|
||||
coneNatPort int
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) init() {
|
||||
t.running = true
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
t.hbMtx.Unlock()
|
||||
t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time
|
||||
localPort := int(rand.Uint32()%10000 + 50000)
|
||||
if t.pn.config.natType == NATCone {
|
||||
// prepare one random cone hole
|
||||
_, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort)
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = port1
|
||||
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
|
||||
} else {
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = localPort // NATNONE or symmetric doesn't need coneNatPort
|
||||
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
|
||||
}
|
||||
gLog.Printf(LevelDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connect() error {
|
||||
gLog.Printf(LevelINFO, "start p2pTunnel to %s ", t.config.PeerNode)
|
||||
t.isServer = false
|
||||
req := PushConnectReq{
|
||||
User: t.config.PeerUser,
|
||||
Password: t.config.PeerPassword,
|
||||
Token: t.config.peerToken,
|
||||
From: t.pn.config.Node,
|
||||
FromIP: t.pn.config.publicIP,
|
||||
ConeNatPort: t.coneNatPort,
|
||||
NatType: t.pn.config.natType,
|
||||
ID: t.id}
|
||||
t.pn.push(t.config.PeerNode, MsgPushConnectReq, req)
|
||||
head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, time.Second*10)
|
||||
if head == nil {
|
||||
return errors.New("connect error")
|
||||
}
|
||||
rsp := PushConnectRsp{}
|
||||
err := json.Unmarshal(body, &rsp)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "wrong MsgPushConnectRsp:%s", err)
|
||||
return err
|
||||
}
|
||||
// gLog.Println(LevelINFO, rsp)
|
||||
if rsp.Error != 0 {
|
||||
return errors.New(rsp.Detail)
|
||||
}
|
||||
t.config.peerNatType = int(rsp.NatType)
|
||||
t.config.peerConeNatPort = rsp.ConeNatPort
|
||||
t.config.peerIP = rsp.FromIP
|
||||
err = t.handshake()
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "handshake error:", err)
|
||||
err = ErrorHandshake
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) isRuning() bool {
|
||||
t.runMtx.Lock()
|
||||
defer t.runMtx.Unlock()
|
||||
return t.running
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) setRun(running bool) {
|
||||
t.runMtx.Lock()
|
||||
defer t.runMtx.Unlock()
|
||||
t.running = running
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) isActive() bool {
|
||||
t.hbMtx.Lock()
|
||||
defer t.hbMtx.Unlock()
|
||||
return time.Now().Before(t.hbTime.Add(TunnelIdleTimeout))
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) checkActive() bool {
|
||||
hbt := time.Now()
|
||||
t.hbMtx.Lock()
|
||||
if t.hbTime.Before(time.Now().Add(-TunnelHeartbeatTime)) {
|
||||
t.hbMtx.Unlock()
|
||||
return false
|
||||
}
|
||||
t.hbMtx.Unlock()
|
||||
// hbtime within TunnelHeartbeatTime, check it now
|
||||
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
|
||||
isActive := false
|
||||
// wait at most 5s
|
||||
for i := 0; i < 50 && !isActive; i++ {
|
||||
t.hbMtx.Lock()
|
||||
if t.hbTime.After(hbt) {
|
||||
isActive = true
|
||||
}
|
||||
t.hbMtx.Unlock()
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
return isActive
|
||||
}
|
||||
|
||||
// call when user delete tunnel
|
||||
func (t *P2PTunnel) close() {
|
||||
t.setRun(false)
|
||||
t.pn.allTunnels.Delete(t.id)
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) handshake() error {
|
||||
if t.config.peerConeNatPort > 0 {
|
||||
var err error
|
||||
t.ra, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
gLog.Println(LevelINFO, "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) {
|
||||
err = handshakeC2C(t)
|
||||
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATSymmetric {
|
||||
err = ErrorS2S
|
||||
t.close()
|
||||
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATCone {
|
||||
err = handshakeC2S(t)
|
||||
} else if t.config.peerNatType == NATCone && t.pn.config.natType == NATSymmetric {
|
||||
err = handshakeS2C(t)
|
||||
} else {
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "punch handshake error:", err)
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LevelINFO, "handshake to %s ok", t.config.PeerNode)
|
||||
err = t.run()
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) run() error {
|
||||
if t.isServer {
|
||||
qConn, e := listenQuic(t.la.String(), TunnelIdleTimeout)
|
||||
if e != nil {
|
||||
gLog.Println(LevelINFO, "listen quic error:", e, ", retry...")
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
qConn, e = listenQuic(t.la.String(), TunnelIdleTimeout)
|
||||
if e != nil {
|
||||
return fmt.Errorf("listen quic error:%s", e)
|
||||
}
|
||||
}
|
||||
t.pn.push(t.config.PeerNode, MsgPushQuicConnect, nil)
|
||||
e = qConn.Accept()
|
||||
if e != nil {
|
||||
qConn.CloseListener()
|
||||
return fmt.Errorf("accept quic error:%s", e)
|
||||
}
|
||||
_, buff, err := qConn.ReadMessage()
|
||||
if e != nil {
|
||||
qConn.listener.Close()
|
||||
return fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LevelDEBUG, string(buff))
|
||||
}
|
||||
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Println(LevelINFO, "quic connection ok")
|
||||
t.conn = qConn
|
||||
t.setRun(true)
|
||||
go t.readLoop()
|
||||
go t.writeLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
//else
|
||||
conn, e := net.ListenUDP("udp", t.la)
|
||||
if e != nil {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
conn, e = net.ListenUDP("udp", t.la)
|
||||
if e != nil {
|
||||
return fmt.Errorf("quic listen error:%s", e)
|
||||
}
|
||||
}
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushQuicConnect, time.Second*5)
|
||||
gLog.Println(LevelINFO, "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)
|
||||
}
|
||||
handshakeBegin := time.Now()
|
||||
qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
|
||||
_, buff, err := qConn.ReadMessage()
|
||||
if e != nil {
|
||||
qConn.listener.Close()
|
||||
return fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LevelDEBUG, string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LevelINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LevelINFO, "quic connection ok")
|
||||
t.conn = qConn
|
||||
t.setRun(true)
|
||||
go t.readLoop()
|
||||
go t.writeLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) readLoop() {
|
||||
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
|
||||
gLog.Printf(LevelINFO, "%d tunnel readloop start", t.id)
|
||||
for t.isRuning() {
|
||||
t.conn.SetReadDeadline(time.Now().Add(TunnelIdleTimeout))
|
||||
head, body, err := t.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if t.isRuning() {
|
||||
gLog.Printf(LevelERROR, "%d tunnel read error:%s", t.id, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if head.MainType != MsgP2P {
|
||||
continue
|
||||
}
|
||||
switch head.SubType {
|
||||
case MsgTunnelHeartbeat:
|
||||
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil)
|
||||
gLog.Printf(LevelDEBUG, "%d read tunnel heartbeat", t.id)
|
||||
case MsgTunnelHeartbeatAck:
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
t.hbMtx.Unlock()
|
||||
gLog.Printf(LevelDEBUG, "%d read tunnel heartbeat ack", t.id)
|
||||
case MsgOverlayData:
|
||||
if len(body) < overlayHeaderSize {
|
||||
continue
|
||||
}
|
||||
overlayID := binary.LittleEndian.Uint64(body[:8])
|
||||
gLog.Printf(LevelDEBUG, "%d tunnel read overlay data %d", t.id, overlayID)
|
||||
s, ok := t.overlayConns.Load(overlayID)
|
||||
if !ok {
|
||||
// debug level, when overlay connection closed, always has some packet not found tunnel
|
||||
gLog.Printf(LevelDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID)
|
||||
continue
|
||||
}
|
||||
overlayConn, ok := s.(*overlayTCP)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
payload := body[overlayHeaderSize:]
|
||||
var err error
|
||||
if overlayConn.appKey != 0 {
|
||||
payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize)))
|
||||
}
|
||||
_, err = overlayConn.Write(payload)
|
||||
if err != nil {
|
||||
gLog.Println(LevelERROR, "overlay write error:", err)
|
||||
}
|
||||
case MsgRelayData:
|
||||
gLog.Printf(LevelDEBUG, "got relay data datalen=%d", head.DataLen)
|
||||
if len(body) < 8 {
|
||||
continue
|
||||
}
|
||||
tunnelID := binary.LittleEndian.Uint64(body[:8])
|
||||
t.pn.relay(tunnelID, body[8:])
|
||||
case MsgRelayHeartbeat:
|
||||
req := RelayHeartbeat{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "wrong RelayHeartbeat:%s", err)
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LevelDEBUG, "got MsgRelayHeartbeat from %d:%d", req.RelayTunnelID, req.AppID)
|
||||
relayHead := new(bytes.Buffer)
|
||||
binary.Write(relayHead, binary.LittleEndian, req.RelayTunnelID)
|
||||
msg, _ := newMessage(MsgP2P, MsgRelayHeartbeatAck, &req)
|
||||
msgWithHead := append(relayHead.Bytes(), msg...)
|
||||
t.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
|
||||
case MsgRelayHeartbeatAck:
|
||||
req := RelayHeartbeat{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "wrong RelayHeartbeat:%s", err)
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LevelDEBUG, "got MsgRelayHeartbeatAck to %d", req.AppID)
|
||||
t.pn.updateAppHeartbeat(req.AppID)
|
||||
case MsgOverlayConnectReq:
|
||||
req := OverlayConnectReq{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "wrong MsgOverlayConnectReq:%s", err)
|
||||
continue
|
||||
}
|
||||
// app connect only accept user/password, avoid someone using the share relay node's token
|
||||
if req.User != t.pn.config.User || req.Password != t.pn.config.Password {
|
||||
gLog.Println(LevelERROR, "Access Denied:", req.User)
|
||||
continue
|
||||
}
|
||||
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LevelINFO, "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 {
|
||||
gLog.Println(LevelERROR, err)
|
||||
continue
|
||||
}
|
||||
otcp := overlayTCP{
|
||||
tunnel: t,
|
||||
conn: conn,
|
||||
id: overlayID,
|
||||
isClient: false,
|
||||
rtid: req.RelayTunnelID,
|
||||
appID: req.AppID,
|
||||
appKey: GetKey(req.AppID),
|
||||
}
|
||||
// calc key bytes for encrypt
|
||||
if otcp.appKey != 0 {
|
||||
encryptKey := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(encryptKey, otcp.appKey)
|
||||
binary.LittleEndian.PutUint64(encryptKey[8:], otcp.appKey)
|
||||
otcp.appKeyBytes = encryptKey
|
||||
}
|
||||
|
||||
t.overlayConns.Store(otcp.id, &otcp)
|
||||
go otcp.run()
|
||||
}
|
||||
case MsgOverlayDisconnectReq:
|
||||
req := OverlayDisconnectReq{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "wrong OverlayDisconnectRequest:%s", err)
|
||||
continue
|
||||
}
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LevelINFO, "%d disconnect overlay connection %d", t.id, overlayID)
|
||||
i, ok := t.overlayConns.Load(overlayID)
|
||||
if ok {
|
||||
otcp := i.(*overlayTCP)
|
||||
otcp.running = false
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
t.setRun(false)
|
||||
t.conn.Close()
|
||||
gLog.Printf(LevelINFO, "%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)
|
||||
for t.isRuning() {
|
||||
select {
|
||||
case <-tc.C:
|
||||
// tunnel send
|
||||
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
|
||||
if err != nil {
|
||||
gLog.Printf(LevelERROR, "%d write tunnel heartbeat error %s", t.id, err)
|
||||
t.setRun(false)
|
||||
return
|
||||
}
|
||||
gLog.Printf(LevelDEBUG, "%d write tunnel heartbeat ok", t.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) listen() error {
|
||||
gLog.Printf(LevelINFO, "p2ptunnel wait for connecting")
|
||||
t.isServer = true
|
||||
return t.handshake()
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) closeOverlayConns(appID uint64) {
|
||||
t.overlayConns.Range(func(_, i interface{}) bool {
|
||||
otcp := i.(*overlayTCP)
|
||||
if otcp.appID == appID {
|
||||
otcp.conn.Close()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user