playit.gg go implementation #1

Open
Sirherobrine23 wants to merge 6 commits from breaked into main
35 changed files with 1857 additions and 1630 deletions
Showing only changes of commit c756fc28b0 - Show all commits

203
enc/enc.go Normal file
View File

@ -0,0 +1,203 @@
package enc
import (
"encoding/binary"
"fmt"
"io"
"net/netip"
)
func ReadU8(r io.Reader) uint8 {
var d uint8
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func ReadU16(r io.Reader) uint16 {
var d uint16
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func ReadU32(r io.Reader) uint32 {
var d uint32
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func ReadU64(r io.Reader) uint64 {
var d uint64
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func WriteU8(w io.Writer, d uint8) error {
return binary.Write(w, binary.BigEndian, d)
}
func WriteU16(w io.Writer, d uint16) error {
return binary.Write(w, binary.BigEndian, d)
}
func WriteU32(w io.Writer, d uint32) error {
return binary.Write(w, binary.BigEndian, d)
}
func WriteU64(w io.Writer, d uint64) error {
return binary.Write(w, binary.BigEndian, d)
}
func Read8(r io.Reader) int8 {
var d int8
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func Read16(r io.Reader) int16 {
var d int16
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func Read32(r io.Reader) int32 {
var d int32
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func Read64(r io.Reader) int64 {
var d int64
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func Write8(w io.Writer, d int8) error {
return binary.Write(w, binary.BigEndian, d)
}
func Write16(w io.Writer, d int16) error {
return binary.Write(w, binary.BigEndian, d)
}
func Write32(w io.Writer, d int32) error {
return binary.Write(w, binary.BigEndian, d)
}
func Write64(w io.Writer, d int64) error {
return binary.Write(w, binary.BigEndian, d)
}
func ReadByteN(r io.Reader, size int) (buff []byte, err error) {
buff = make([]byte, size)
for index := range buff {
if err = binary.Read(r, binary.BigEndian, &buff[index]); err != nil {
buff = buff[:index]
return
}
}
return
}
func WriteBytes(w io.Writer, buff []byte) error {
return binary.Write(w, binary.BigEndian, buff)
}
func AddrWrite(w io.Writer, addr netip.Addr) error {
if addr.Is6() {
if err := WriteU8(w, 6); err != nil {
return err
} else if err = WriteBytes(w, addr.AsSlice()); err != nil {
return err
}
return nil
}
if err := WriteU8(w, 4); err != nil {
return err
} else if err = WriteBytes(w, addr.AsSlice()); err != nil {
return err
}
return nil
}
func AddrRead(r io.Reader) (addr netip.Addr, err error) {
var buff []byte
switch ReadU8(r) {
case 4:
if buff, err = ReadByteN(r, 4); err != nil {
return
}
addr = netip.AddrFrom4([4]byte(buff))
return
case 6:
if buff, err = ReadByteN(r, 16); err != nil {
return
}
netip.AddrFrom16([16]byte(buff))
return
}
err = fmt.Errorf("connet get ip type")
return
}
func AddrPortRead(r io.Reader) (netip.AddrPort, error) {
switch ReadU8(r) {
case 4:
buff, err := ReadByteN(r, 4)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(netip.AddrFrom4([4]byte(buff)), ReadU16(r)), nil
case 6:
buff, err := ReadByteN(r, 16)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(netip.AddrFrom16([16]byte(buff)), ReadU16(r)), nil
}
return netip.AddrPort{}, fmt.Errorf("connet get ip type")
}
func AddrPortWrite(w io.Writer, addr netip.AddrPort) error {
if !addr.IsValid() {
return fmt.Errorf("invalid ip address")
} else if addr.Addr().Is6() {
if err := WriteU8(w, 6); err != nil {
return err
} else if err = binary.Write(w, binary.BigEndian, addr.Addr().AsSlice()); err != nil {
return err
}
return nil
}
if err := WriteU8(w, 4); err != nil {
return err
} else if err = binary.Write(w, binary.BigEndian, addr.Addr().AsSlice()); err != nil {
return err
}
return nil
}
func WriteOption(w io.Writer, d any, callback func(w io.Writer) (err error)) error {
if d == nil {
return WriteU8(w, 0)
} else if err := WriteU8(w, 1); err != nil {
return err
}
return callback(w)
}
func ReadOption(r io.Reader, callback func(r io.Reader) (err error)) error {
switch ReadU8(r) {
case 0:
return nil
case 1:
return callback(r)
}
return fmt.Errorf("invalid Option value")
}

16
network/address_lookup.go Normal file
View File

@ -0,0 +1,16 @@
package network
import (
"net/netip"
"sirherobrine23.org/playit-cloud/go-playit/api"
)
type AddressValue[V any] struct {
Value V
FromPort, ToPort uint16
}
type AddressLookup[Value any] interface {
Lookup(IP netip.Addr, Port uint16, Proto api.PortProto) *AddressValue[Value]
}

View File

@ -1,4 +1,4 @@
package tunnel
package network
import (
"encoding/binary"
@ -47,15 +47,15 @@ func TcpSocket(SpecialLan bool, Peer, Host netip.AddrPort) (*net.TCPConn, error)
local_ip := mapToLocalIP4(Peer.Addr().AsSlice());
stream, err := net.DialTCP("tcp4", net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte(local_ip.To4())), 0)), net.TCPAddrFromAddrPort(Host))
if err != nil {
logDebug.Printf("Failed to establish connection using special lan %s for flow %s -> %s\n", local_ip, Peer.String(), Host.String())
// logDebug.Printf("Failed to establish connection using special lan %s for flow %s -> %s\n", local_ip, Peer.String(), Host.String())
return nil, err
}
return stream, nil
}
logDebug.Printf("Failed to bind connection to special local address to support IP based banning")
// logDebug.Printf("Failed to bind connection to special local address to support IP based banning")
stream, err := net.DialTCP("tcp", nil, net.TCPAddrFromAddrPort(Host))
if err != nil {
logDebug.Printf("Failed to establish connection for flow %s -> %s. Is your server running? %q", Peer.String(), Host.String(), err.Error())
// logDebug.Printf("Failed to establish connection for flow %s -> %s. Is your server running? %q", Peer.String(), Host.String(), err.Error())
return nil, err
}
return stream, nil
@ -68,15 +68,15 @@ func UdpSocket(SpecialLan bool, Peer, Host netip.AddrPort) (*net.UDPConn, error)
local_port := 40000 + (Peer.Port() % 24000);
stream, err := net.DialUDP("udp4", net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom16([16]byte(local_ip)), local_port)), net.UDPAddrFromAddrPort(Host))
if err != nil {
logDebug.Printf("Failed to bind UDP port to %d to have connections survive agent restart: %s", local_port, err.Error())
// logDebug.Printf("Failed to bind UDP port to %d to have connections survive agent restart: %s", local_port, err.Error())
stream, err = net.DialUDP("udp4", net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom16([16]byte(local_ip)), 0)), net.UDPAddrFromAddrPort(Host))
if err != nil {
err2 := err
// err2 := err
stream, err = net.DialUDP("udp4", nil, nil)
if err != nil {
return nil, err
}
logDebug.Printf("Failed to bind UDP to special local address, in-game ip banning will not work: %s", err2.Error())
// logDebug.Printf("Failed to bind UDP to special local address, in-game ip banning will not work: %s", err2.Error())
}
}
return stream, nil

98
network/tcp_clients.go Normal file
View File

@ -0,0 +1,98 @@
package network
import (
"net"
"net/netip"
"sync"
"sirherobrine23.org/playit-cloud/go-playit/tunnel"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
type ActiveClients struct {
locked sync.Mutex
// active: Arc<RwLock<HashMap<(SocketAddr, SocketAddr), NewClient>>>,
active map[[2]netip.AddrPort]proto.NewClient
}
func NewActiveClients() ActiveClients {
return ActiveClients{
locked: sync.Mutex{},
active: make(map[[2]netip.AddrPort]proto.NewClient),
}
}
func (clients *ActiveClients) Len() int {
return len(clients.active)
}
func (clients *ActiveClients) GetClients() []proto.NewClient {
clientsArr := []proto.NewClient{}
for _, cl := range clients.active {
clientsArr = append(clientsArr, cl)
}
return clientsArr
}
func (clients *ActiveClients) AddNew(client proto.NewClient) *Dropper {
clients.locked.Lock()
defer clients.locked.Unlock()
for actClient := range clients.active {
if client.PeerAddr.Compare(actClient[0]) == 0 && client.ConnectAddr.Compare(actClient[1]) == 0 {
return nil
}
}
key := [2]netip.AddrPort{client.PeerAddr, client.ConnectAddr}
clients.active[key] = client
return &Dropper{key, *clients}
}
type Dropper struct {
key [2]netip.AddrPort
inner ActiveClients
}
func (dr *Dropper) Drop() {
PeerAddr, ConnectAddr := dr.key[0], dr.key[1]
dr.inner.locked.Lock()
defer dr.inner.locked.Unlock()
for client := range dr.inner.active {
if client[0].Compare(PeerAddr) == 0 && client[1].Compare(ConnectAddr) == 0 {
delete(dr.inner.active, client)
break
}
}
}
func NewTcpClients() TcpClients {
return TcpClients{NewActiveClients(), true}
}
type TcpClients struct {
active ActiveClients
UseSpecialLAN bool
}
func (tcp *TcpClients) ActiveClients() ActiveClients {
return tcp.active
}
func (tcp *TcpClients) Connect(newClient proto.NewClient) (*TcpClient, error) {
claimInstruction := newClient.ClaimInstructions
droppe := tcp.active.AddNew(newClient)
if droppe == nil {
return nil, nil
}
stream, err := (&tunnel.TcpTunnel{claimInstruction}).Connect()
if err != nil {
return nil, err
}
return &TcpClient{*stream, *droppe}, nil
}
type TcpClient struct {
Stream net.TCPConn
Dropper Dropper
}

175
network/udp_clients.go Normal file
View File

@ -0,0 +1,175 @@
package network
import (
"fmt"
"log"
"net"
"net/netip"
"reflect"
"sync"
"sync/atomic"
"time"
"sirherobrine23.org/playit-cloud/go-playit/api"
"sirherobrine23.org/playit-cloud/go-playit/tunnel"
)
type UdpClient struct {
clientKey ClientKey
sendFlow tunnel.UdpFlow
udpTunnel tunnel.UdpTunnel
localUdp net.UDPConn
localStartAddr netip.AddrPort
tunnelFromPort uint16
tunnelToPort uint16
udpClientsLock sync.Mutex
udpClients map[ClientKey]UdpClient
lastActivity atomic.Uint32
}
func (self *UdpClient) SendLocal(dstPort uint16, data []byte) error {
portOffset := dstPort - self.tunnelFromPort
self.lastActivity.Store(uint32(time.Now().UnixMilli() / 1_000))
if portOffset == 0 {
_, err := self.localUdp.WriteToUDP(data, net.UDPAddrFromAddrPort(self.localStartAddr))
return err
}
_, err := self.localUdp.WriteToUDP(data, net.UDPAddrFromAddrPort(netip.AddrPortFrom(self.localStartAddr.Addr(), self.localStartAddr.Port()+portOffset)))
return err
}
type HostToTunnelForwarder struct{ UdpClient }
func (self *HostToTunnelForwarder) Run() {
buffer := make([]byte, 2048)
for {
buffer = make([]byte, 2048)
self.localUdp.SetReadDeadline(time.Now().Add(time.Second * 30))
size, source, err := self.localUdp.ReadFromUDPAddrPort(buffer)
if err != nil {
log.Println(err)
break
} else if source.Addr().Compare(self.localStartAddr.Addr()) != 0 {
// "dropping packet from different unexpected source"
continue
}
portCount := self.tunnelToPort - self.tunnelFromPort
localFrom := self.localStartAddr.Port()
localTo := localFrom + portCount
if source.Port() < localFrom || localTo <= source.Port() {
// "dropping packet outside of expected port range"
continue
}
buffer = buffer[:size]
portOffset := source.Port() - localFrom
flow := self.sendFlow.WithSrcPort(self.tunnelFromPort + portOffset)
if _, err = self.udpTunnel.Send(buffer, flow); err != nil {
// "failed to send packet to through tunnel"
}
}
self.UdpClient.udpClientsLock.Lock()
if _, is := self.UdpClient.udpClients[self.clientKey]; is {
// if !reflect.DeepEqual(v, self) {} else {}
delete(self.UdpClient.udpClients, self.clientKey)
}
self.UdpClient.udpClientsLock.Unlock()
}
type ClientKey struct {
ClientAddr, TunnelAddr netip.AddrPort
}
type UdpClients struct {
udpTunnel tunnel.UdpTunnel
lookup AddressLookup[netip.AddrPort]
udpClientsLocker sync.Mutex
udpClients map[ClientKey]UdpClient
UseSpecialLan bool
}
func NewUdpClients(Tunnel tunnel.UdpTunnel, Lookup AddressLookup[netip.AddrPort]) UdpClients {
return UdpClients{
udpTunnel: Tunnel,
lookup: Lookup,
udpClientsLocker: sync.Mutex{},
udpClients: make(map[ClientKey]UdpClient),
UseSpecialLan: true,
}
}
func (self *UdpClients) ClientCount() int {
return len(self.udpClients)
}
func (self *UdpClients) ForwardPacket(Flow tunnel.UdpFlow, data []byte) error {
flowDst := Flow.Dst()
found := self.lookup.Lookup(flowDst.Addr(), Flow.Dst().Port(), api.PortProto("udp"))
if found == nil {
return fmt.Errorf("could not find tunnel")
}
key := ClientKey{ClientAddr: Flow.Src(), TunnelAddr: netip.AddrPortFrom(flowDst.Addr(), found.FromPort)}
for kkey, client := range self.udpClients {
if reflect.DeepEqual(kkey, key) {
return client.SendLocal(flowDst.Port(), data)
}
}
self.udpClientsLocker.Lock()
defer self.udpClientsLocker.Unlock()
client, err := func() (*UdpClient, error) {
for kkey, client := range self.udpClients {
if reflect.DeepEqual(kkey, key) {
return &client, nil
}
}
localAddr := found.Value
var sendFlow tunnel.UdpFlow
var clientAddr netip.AddrPort
if Flow.V4 != nil {
clientAddr = netip.AddrPortFrom(Flow.V4.Src.Addr(), Flow.V4.Src.Port())
sendFlow.V4 = &tunnel.UdpFlowBase{
Src: netip.AddrPortFrom(Flow.V4.Dst.Addr(), found.FromPort),
Dst: Flow.Src(),
}
} else {
clientAddr = netip.AddrPortFrom(Flow.V6.Src.Addr(), Flow.V6.Src.Port())
sendFlow.V6 = &struct {
tunnel.UdpFlowBase
Flow uint32
}{
Flow: sendFlow.V6.Flow,
UdpFlowBase: tunnel.UdpFlowBase{
Src: netip.AddrPortFrom(Flow.V6.Dst.Addr(), found.FromPort),
Dst: Flow.Src(),
},
}
}
usock, err := UdpSocket(self.UseSpecialLan, clientAddr, localAddr)
if err != nil {
return nil, err
}
client := UdpClient{
clientKey: key,
sendFlow: sendFlow,
localUdp: *usock,
udpTunnel: self.udpTunnel,
localStartAddr: localAddr,
tunnelFromPort: found.FromPort,
tunnelToPort: found.ToPort,
udpClients: self.udpClients,
lastActivity: atomic.Uint32{},
}
self.udpClients[key] = client
go (&HostToTunnelForwarder{client}).Run()
return &client, nil
}()
if err != nil {
return err
}
return client.SendLocal(flowDst.Port(), data)
}

103
proto/control_feed.go Normal file
View File

@ -0,0 +1,103 @@
package proto
import (
"fmt"
"io"
"net/netip"
"sirherobrine23.org/playit-cloud/go-playit/enc"
)
var (
ErrFeedRead error = fmt.Errorf("invalid controlFeed id")
)
type ControlFeed struct {
Response *ControlRpcMessage[*ControlResponse]
NewClient *NewClient
}
func (Feed *ControlFeed) ReadFrom(r io.Reader) error {
id := enc.ReadU32(r)
if id == 1 {
Feed.Response = new(ControlRpcMessage[*ControlResponse])
Feed.Response.Content = new(ControlResponse)
return Feed.Response.ReadFrom(r)
} else if id == 2 {
Feed.NewClient = &NewClient{}
return Feed.NewClient.ReadFrom(r)
}
return ErrFeedRead
}
func (Feed *ControlFeed) WriteTo(w io.Writer) error {
if Feed.Response != nil {
if err := enc.WriteU32(w, 1); err != nil {
return err
}
return Feed.Response.WriteTo(w)
} else if Feed.NewClient != nil {
if err := enc.WriteU32(w, 2); err != nil {
return err
}
return Feed.NewClient.WriteTo(w)
}
return fmt.Errorf("set Response or NewClient")
}
type NewClient struct {
ConnectAddr netip.AddrPort
PeerAddr netip.AddrPort
ClaimInstructions ClaimInstructions
TunnelServerId uint64
DataCenterId uint32
}
func (client *NewClient) ReadFrom(r io.Reader) error {
var err error
if client.ConnectAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if client.PeerAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if err = client.ClaimInstructions.ReadFrom(r); err != nil {
return err
}
client.TunnelServerId, client.DataCenterId = enc.ReadU64(r), enc.ReadU32(r)
return nil
}
func (client *NewClient) WriteTo(w io.Writer) error {
if err := enc.AddrPortWrite(w, client.ConnectAddr); err != nil {
return err
} else if err := enc.AddrPortWrite(w, client.PeerAddr); err != nil {
return err
} else if err := client.ClaimInstructions.WriteTo(w); err != nil {
return err
} else if err := enc.WriteU64(w, client.TunnelServerId); err != nil {
return err
} else if err := enc.WriteU32(w, client.DataCenterId); err != nil {
return err
}
return nil
}
type ClaimInstructions struct {
Address netip.AddrPort
Token []byte
}
func (claim *ClaimInstructions) ReadFrom(r io.Reader) (err error) {
if claim.Address, err = enc.AddrPortRead(r); err != nil {
return err
}
claim.Token, err = enc.ReadByteN(r, int(enc.ReadU64(r)))
return
}
func (claim *ClaimInstructions) WriteTo(w io.Writer) error {
if err := enc.AddrPortWrite(w, claim.Address); err != nil {
return err
} else if err := enc.WriteU64(w, uint64(len(claim.Token))); err != nil {
return err
} else if err = enc.WriteBytes(w, claim.Token); err != nil {
return err
}
return nil
}

394
proto/control_messages.go Normal file
View File

@ -0,0 +1,394 @@
package proto
import (
"bytes"
"fmt"
"io"
"net/netip"
"time"
"sirherobrine23.org/playit-cloud/go-playit/enc"
)
type ControlRequest struct {
Ping *Ping
AgentRegister *AgentRegister
AgentKeepAlive *AgentSessionId
SetupUdpChannel *AgentSessionId
AgentCheckPortMapping *AgentCheckPortMapping
}
func (Control *ControlRequest) WriteTo(w io.Writer) error {
if Control.Ping != nil {
if err := enc.WriteU32(w, 6); err != nil {
return err
}
return Control.Ping.WriteTo(w)
} else if Control.AgentRegister != nil {
if err := enc.WriteU32(w, 2); err != nil {
return err
}
return Control.AgentRegister.WriteTo(w)
} else if Control.AgentKeepAlive != nil {
if err := enc.WriteU32(w, 3); err != nil {
return err
}
return Control.AgentKeepAlive.WriteTo(w)
} else if Control.SetupUdpChannel != nil {
if err := enc.WriteU32(w, 4); err != nil {
return err
}
return Control.SetupUdpChannel.WriteTo(w)
} else if Control.AgentCheckPortMapping != nil {
if err := enc.WriteU32(w, 5); err != nil {
return err
}
return Control.AgentCheckPortMapping.WriteTo(w)
}
return fmt.Errorf("set ControlRequest")
}
func (Control *ControlRequest) ReadFrom(r io.Reader) error {
switch enc.ReadU32(r) {
case 1:
Control.Ping = new(Ping)
return Control.Ping.ReadFrom(r)
case 2:
Control.AgentRegister = new(AgentRegister)
return Control.AgentRegister.ReadFrom(r)
case 3:
Control.AgentKeepAlive = new(AgentSessionId)
return Control.AgentKeepAlive.ReadFrom(r)
case 4:
Control.SetupUdpChannel = new(AgentSessionId)
return Control.SetupUdpChannel.ReadFrom(r)
case 5:
Control.AgentCheckPortMapping = new(AgentCheckPortMapping)
return Control.AgentCheckPortMapping.ReadFrom(r)
}
return fmt.Errorf("invalid ControlRequest id")
}
type AgentCheckPortMapping struct {
AgentSessionId AgentSessionId
PortRange PortRange
}
func (Agent *AgentCheckPortMapping) WriteTo(w io.Writer) error {
if err := Agent.AgentSessionId.WriteTo(w); err != nil {
return err
}
return Agent.PortRange.WriteTo(w)
}
func (Agent *AgentCheckPortMapping) ReadFrom(r io.Reader) error {
if err := Agent.AgentSessionId.ReadFrom(r); err != nil {
return err
}
return Agent.AgentSessionId.ReadFrom(r)
}
type Ping struct {
Now time.Time
CurrentPing *uint64
SessionId *AgentSessionId
}
func (ping *Ping) WriteTo(w io.Writer) error {
if err := enc.WriteU64(w, uint64(ping.Now.UnixMilli())); err != nil {
return err
}
if err := enc.WriteOption(w, ping.CurrentPing, func(w io.Writer) error {
return enc.WriteU64(w, *ping.CurrentPing)
}); err != nil {
return err
}
if err := enc.WriteOption(w, ping.SessionId, ping.SessionId.WriteTo); err != nil {
return err
}
return nil
}
func (ping *Ping) ReadFrom(r io.Reader) error {
ping.Now = time.UnixMilli(int64(enc.ReadU64(r)))
if err := enc.ReadOption(r, func(r io.Reader) error {
*ping.CurrentPing = enc.ReadU64(r)
return nil
}); err != nil {
return err
}
if err := enc.ReadOption(r, func(r io.Reader) error {
return ping.SessionId.ReadFrom(r)
}); err != nil {
return err
}
return nil
}
type AgentRegister struct {
AccountID, AgentId, AgentVersion, Timestamp uint64
ClientAddr, TunnelAddr netip.AddrPort
Signature [32]byte
}
func (agent *AgentRegister) writePlain() *bytes.Buffer {
buff := new(bytes.Buffer)
enc.WriteU64(buff, agent.AccountID)
enc.WriteU64(buff, agent.AgentId)
enc.WriteU64(buff, agent.AgentVersion)
enc.WriteU64(buff, agent.Timestamp)
enc.AddrPortWrite(buff, agent.ClientAddr)
enc.AddrPortWrite(buff, agent.TunnelAddr)
return buff
}
func (agent *AgentRegister) UpdateSignature(hmac HmacSha256) {
agent.Signature = hmac.Sign(agent.writePlain().Bytes())
}
func (agent *AgentRegister) VerifySignature(hmac HmacSha256) bool {
return hmac.Verify(agent.writePlain().Bytes(), agent.Signature[:])
}
func (AgentReg *AgentRegister) WriteTo(w io.Writer) error {
if err := enc.WriteU64(w, AgentReg.AccountID); err != nil {
return err
} else if err := enc.WriteU64(w, AgentReg.AgentId); err != nil {
return err
} else if err := enc.WriteU64(w, AgentReg.AgentVersion); err != nil {
return err
} else if err := enc.WriteU64(w, AgentReg.Timestamp); err != nil {
return err
}
if err := enc.AddrPortWrite(w, AgentReg.ClientAddr); err != nil {
return err
} else if err := enc.AddrPortWrite(w, AgentReg.TunnelAddr); err != nil {
return err
}
if _, err := w.Write(AgentReg.Signature[:]); err != nil {
return err
}
return nil
}
func (AgentReg *AgentRegister) ReadFrom(r io.Reader) error {
AgentReg.AccountID, AgentReg.AccountID, AgentReg.AgentVersion, AgentReg.Timestamp = enc.ReadU64(r), enc.ReadU64(r), enc.ReadU64(r), enc.ReadU64(r)
var err error
if AgentReg.ClientAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if AgentReg.TunnelAddr, err = enc.AddrPortRead(r); err != nil {
return err
}
AgentReg.Signature = [32]byte(make([]byte, 32))
if n, _ := r.Read(AgentReg.Signature[:]); n != 32 {
return fmt.Errorf("missing signature")
}
return nil
}
type ControlResponse struct {
InvalidSignature, Unauthorized, RequestQueued, TryAgainLater bool
Pong *Pong
AgentRegistered *AgentRegistered
AgentPortMapping *AgentPortMapping
UdpChannelDetails *UdpChannelDetails
}
func (Control *ControlResponse) WriteTo(w io.Writer) error {
if Control.Pong != nil {
if err := enc.WriteU32(w, 1); err != nil {
return err
}
return Control.Pong.WriteTo(w)
} else if Control.InvalidSignature {
return enc.WriteU32(w, 2)
} else if Control.Unauthorized {
return enc.WriteU32(w, 3)
} else if Control.RequestQueued {
return enc.WriteU32(w, 4)
} else if Control.TryAgainLater {
return enc.WriteU32(w, 5)
} else if Control.AgentRegistered != nil {
if err := enc.WriteU32(w, 6); err != nil {
return err
}
return Control.AgentRegistered.WriteTo(w)
} else if Control.AgentPortMapping != nil {
if err := enc.WriteU32(w, 7); err != nil {
return err
}
return Control.AgentPortMapping.WriteTo(w)
} else if Control.UdpChannelDetails != nil {
if err := enc.WriteU32(w, 8); err != nil {
return err
}
return Control.UdpChannelDetails.WriteTo(w)
}
return fmt.Errorf("insert any options to write")
}
func (Control *ControlResponse) ReadFrom(r io.Reader) error {
switch enc.ReadU32(r) {
case 1:
Control.Pong = new(Pong)
return Control.Pong.ReadFrom(r)
case 2:
Control.InvalidSignature = true
return nil
case 3:
Control.Unauthorized = true
return nil
case 4:
Control.RequestQueued = true
return nil
case 5:
Control.TryAgainLater = true
return nil
case 6:
Control.AgentRegistered = new(AgentRegistered)
return Control.AgentRegistered.ReadFrom(r)
case 7:
Control.AgentPortMapping = new(AgentPortMapping)
return Control.AgentPortMapping.ReadFrom(r)
case 8:
Control.UdpChannelDetails = new(UdpChannelDetails)
return Control.UdpChannelDetails.ReadFrom(r)
}
return fmt.Errorf("invalid ControlResponse id")
}
type AgentPortMapping struct {
Range PortRange
Found *AgentPortMappingFound
}
func (Agent *AgentPortMapping) WriteTo(w io.Writer) error {
if err := Agent.Range.WriteTo(w); err != nil {
return err
} else if err := Agent.Found.WriteTo(w); err != nil {
return err
}
return nil
}
func (Agent *AgentPortMapping) ReadFrom(r io.Reader) error {
if err := Agent.Range.ReadFrom(r); err != nil {
return err
} else if err := Agent.Found.ReadFrom(r); err != nil {
return err
}
return nil
}
type AgentPortMappingFound struct {
ToAgent *AgentSessionId
}
func (Agent *AgentPortMappingFound) WriteTo(w io.Writer) error {
if Agent.ToAgent != nil {
if err := enc.WriteU32(w, 1); err != nil {
return err
} else if err := Agent.ToAgent.WriteTo(w); err != nil {
return err
}
return nil
}
return nil
}
func (Agent *AgentPortMappingFound) ReadFrom(r io.Reader) error {
if enc.ReadU32(r) == 1 {
Agent.ToAgent = new(AgentSessionId)
return Agent.ToAgent.ReadFrom(r)
}
return fmt.Errorf("unknown AgentPortMappingFound id")
}
type UdpChannelDetails struct {
TunnelAddr netip.AddrPort
Token []byte
}
func (UdpChannel *UdpChannelDetails) WriteTo(w io.Writer) error {
if err := enc.AddrPortWrite(w, UdpChannel.TunnelAddr); err != nil {
return err
} else if err := enc.WriteU64(w, uint64(len(UdpChannel.Token))); err != nil {
return err
} else if err := enc.WriteBytes(w, UdpChannel.Token); err != nil {
return err
}
return nil
}
func (UdpChannel *UdpChannelDetails) ReadFrom(r io.Reader) error {
var err error
if UdpChannel.TunnelAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if UdpChannel.Token, err = enc.ReadByteN(r, int(enc.ReadU64(r))); err != nil {
return err
}
return nil
}
type Pong struct {
RequestNow, ServerNow time.Time
ServerId uint64
DataCenterId uint32
ClientAddr, TunnelAddr netip.AddrPort
SessionExpireAt *time.Time
}
func (pong *Pong) WriteTo(w io.Writer) error {
if err := enc.Write64(w, pong.RequestNow.UnixMilli()); err != nil {
return err
} else if err := enc.Write64(w, pong.ServerNow.UnixMilli()); err != nil {
return err
} else if err := enc.WriteU64(w, pong.ServerId); err != nil {
return err
} else if err := enc.WriteU32(w, pong.DataCenterId); err != nil {
return err
} else if err := enc.AddrPortWrite(w, pong.ClientAddr); err != nil {
return err
} else if err := enc.AddrPortWrite(w, pong.TunnelAddr); err != nil {
return err
} else if err := enc.WriteOption(w, pong.SessionExpireAt, func(w io.Writer) (err error) {
return enc.Write64(w, pong.SessionExpireAt.UnixMilli())
}); err != nil {
return err
}
return nil
}
func (pong *Pong) ReadFrom(r io.Reader) error {
pong.RequestNow = time.UnixMilli(enc.Read64(r))
pong.ServerNow = time.UnixMilli(enc.Read64(r))
pong.ServerId = enc.ReadU64(r)
pong.DataCenterId = enc.ReadU32(r)
var err error
if pong.ClientAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if pong.TunnelAddr, err = enc.AddrPortRead(r); err != nil {
return err
} else if err := enc.ReadOption(r, func(r io.Reader) (err error) {
pong.SessionExpireAt = new(time.Time)
d, err := time.UnixMilli(enc.Read64(r)).MarshalBinary()
if err != nil {
return err
}
return pong.SessionExpireAt.UnmarshalBinary(d)
}); err != nil {
return err
}
return nil
}
type AgentRegistered struct {
Id AgentSessionId
ExpiresAt time.Time
}
func (agent *AgentRegistered) WriteTo(w io.Writer) error {
if err := agent.Id.WriteTo(w); err != nil {
return err
} else if err := enc.Write64(w, agent.ExpiresAt.UnixMilli()); err != nil {
return err
}
return nil
}
func (agent *AgentRegistered) ReadFrom(r io.Reader) error {
if err := agent.Id.ReadFrom(r); err != nil {
return err
}
agent.ExpiresAt = time.UnixMilli(enc.Read64(r))
return nil
}

10
proto/encoding.go Normal file
View File

@ -0,0 +1,10 @@
package proto
import (
"io"
)
type MessageEncoding interface {
ReadFrom(r io.Reader) error
WriteTo(w io.Writer) error
}

View File

@ -1,4 +1,4 @@
package tunnel
package proto
import (
"crypto/hmac"

87
proto/lib.go Normal file
View File

@ -0,0 +1,87 @@
package proto
import (
"fmt"
"io"
"net/netip"
"sirherobrine23.org/playit-cloud/go-playit/enc"
)
type AgentSessionId struct {
SessionID, AccountID, AgentID uint64
}
type PortRange struct {
IP netip.Addr
PortStart, PortEnd uint16
PortProto PortProto
}
type PortProto string
func (AgentSession *AgentSessionId) WriteTo(w io.Writer) error {
if err := enc.WriteU64(w, AgentSession.SessionID); err != nil {
return err
} else if err := enc.WriteU64(w, AgentSession.AccountID); err != nil {
return err
} else if err := enc.WriteU64(w, AgentSession.AgentID); err != nil {
return err
}
return nil
}
func (AgentSession *AgentSessionId) ReadFrom(r io.Reader) error {
AgentSession.SessionID, AgentSession.AccountID, AgentSession.AgentID = enc.ReadU64(r), enc.ReadU64(r), enc.ReadU64(r)
return nil
}
func (portRange *PortRange) WriteTo(w io.Writer) error {
if err := enc.AddrWrite(w, portRange.IP); err != nil {
return err
} else if err := enc.WriteU16(w, portRange.PortStart); err != nil {
return err
} else if err := enc.WriteU16(w, portRange.PortEnd); err != nil {
return err
} else if err := portRange.PortProto.WriteTo(w); err != nil {
return err
}
return nil
}
func (portRange *PortRange) ReadFrom(r io.Reader) error {
var err error
portRange.IP, err = enc.AddrRead(r)
if err != nil {
return err
}
portRange.PortStart, portRange.PortEnd = enc.ReadU16(r), enc.ReadU16(r)
portRange.PortProto = PortProto("")
if err := portRange.PortProto.ReadFrom(r); err != nil {
return err
}
return nil
}
func (proto PortProto) WriteTo(w io.Writer) error {
switch proto {
case "tcp":
return enc.WriteU8(w, 1)
case "udp":
return enc.WriteU8(w, 2)
case "both":
return enc.WriteU8(w, 3)
}
return fmt.Errorf("invalid port proto")
}
func (proto PortProto) ReadFrom(r io.Reader) error {
switch enc.ReadU8(r) {
case 1:
proto = PortProto("tcp")
case 2:
proto = PortProto("udp")
case 3:
proto = PortProto("both")
default:
return fmt.Errorf("invalid port proto")
}
return nil
}

21
proto/raw_slice.go Normal file
View File

@ -0,0 +1,21 @@
package proto
import (
"fmt"
"io"
)
type RawSlice []byte
func (buff RawSlice) ReadFrom(r io.Reader) error {
return fmt.Errorf("cannot read for RawSlice")
}
func (buff RawSlice) WriteTo(w io.Writer) error {
size, err := w.Write(buff)
if err != nil {
return err
} else if size != len(buff) {
return fmt.Errorf("not enough space to write raw slice")
}
return nil
}

28
proto/rpc.go Normal file
View File

@ -0,0 +1,28 @@
package proto
import (
"encoding/binary"
"io"
)
type ControlRpcMessage[T MessageEncoding] struct {
RequestID uint64
Content T // Convert with .(*type)
}
func (rpc *ControlRpcMessage[T]) WriteTo(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, rpc.RequestID); err != nil {
return err
} else if err = rpc.Content.WriteTo(w); err != nil {
return err
}
return nil
}
func (rpc *ControlRpcMessage[T]) ReadFrom(r io.Reader) error {
if err := binary.Read(r, binary.BigEndian, &rpc.RequestID); err != nil {
return err
} else if err = rpc.Content.ReadFrom(r); err != nil {
return err
}
return nil
}

View File

@ -1,4 +1,4 @@
package tunnel
package proto
import (
"log"
@ -6,4 +6,4 @@ import (
)
// Write log and show in terminal to debug
var logDebug *log.Logger = log.New(os.Stderr, "plait.gg", log.Ltime|log.Ldate)
var logDebug *log.Logger = log.New(os.Stderr, "plait.gg", log.Ltime|log.Ldate)

113
runner/tunnel_runner.go Normal file
View File

@ -0,0 +1,113 @@
package runner
import (
"io"
"net/netip"
"sync/atomic"
"time"
"sirherobrine23.org/playit-cloud/go-playit/api"
"sirherobrine23.org/playit-cloud/go-playit/network"
"sirherobrine23.org/playit-cloud/go-playit/tunnel"
)
type TunnelRunner struct {
Lookup network.AddressLookup[netip.AddrPort]
Tunnel tunnel.SimpleTunnel
UdpClients network.UdpClients
TcpClients network.TcpClients
KeepRunning atomic.Bool
}
func NewTunnelRunner(Api api.Api, Lookup network.AddressLookup[netip.AddrPort]) (TunnelRunner, error) {
tunnel := tunnel.NewSimpleTunnel(Api)
if err := tunnel.Setup(); err != nil {
return TunnelRunner{}, err
}
udp_clients := network.NewUdpClients(tunnel.UdpTunnel(), Lookup)
var keep atomic.Bool
keep.Store(true)
return TunnelRunner{
Lookup: Lookup,
Tunnel: tunnel,
UdpClients: udp_clients,
TcpClients: network.NewTcpClients(),
KeepRunning: keep,
}, nil
}
func (self *TunnelRunner) SetSpecialLan(setUse bool) {
self.TcpClients.UseSpecialLAN = setUse
self.UdpClients.UseSpecialLan = setUse
}
func (self *TunnelRunner) Run() {
end := make(chan error)
tunnel := self.Tunnel
udp := tunnel.UdpTunnel()
go func() {
lastControlUpdate := time.Now().UnixMilli()
for self.KeepRunning.Load() {
now := time.Now().UnixMilli()
if 30_000 < time.Now().UnixMilli()-lastControlUpdate {
lastControlUpdate = now
if _, err := tunnel.ReloadControlAddr(); err != nil {
}
}
if new_client := tunnel.Update(); new_client != nil {
clients := self.TcpClients
found := self.Lookup.Lookup(new_client.ConnectAddr.Addr(), new_client.ConnectAddr.Port(), api.PortProto("tcp"))
if found == nil {
continue
}
local_addr := netip.AddrPortFrom(found.Value.Addr(), (new_client.ConnectAddr.Port()-found.FromPort)+found.Value.Port())
go func() {
peerAddr := new_client.PeerAddr
tunnel_conn, err := clients.Connect(*new_client)
if err != nil {
return
}
defer tunnel_conn.Stream.Close()
local_conn, err := network.TcpSocket(self.TcpClients.UseSpecialLAN, peerAddr, local_addr)
if err != nil {
}
defer local_conn.Close()
done := make(chan struct{})
go func() {
io.Copy(&tunnel_conn.Stream, local_conn)
done <- struct{}{}
}()
go func() {
io.Copy(local_conn, &tunnel_conn.Stream)
done <- struct{}{}
}()
<-done
<-done
}()
}
}
}()
udp_clients := self.UdpClients
go func(){
buffer := make([]byte, 2048)
// had_success := false
for self.KeepRunning.Load() {
rx, err := udp.ReceiveFrom(buffer)
if err != nil {
time.Sleep(time.Second)
continue
}
if rx.ConfirmerdConnection {
continue
}
bytes, flow := rx.ReceivedPacket.Bytes, rx.ReceivedPacket.Flow
if err := udp_clients.ForwardPacket(flow, buffer[:bytes]); err != nil {
panic(err)
}
}
}()
<-end
}

View File

@ -1,178 +0,0 @@
package tunnel
import (
"encoding/binary"
"fmt"
"io"
"net/netip"
)
func readU8(r io.Reader) uint8 {
var d uint8
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func readU16(r io.Reader) uint16 {
var d uint16
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func readU32(r io.Reader) uint32 {
var d uint32
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func readU64(r io.Reader) uint64 {
var d uint64
err := binary.Read(r, binary.BigEndian, d)
if err != nil {
panic(err)
}
return d
}
func writeU8(w io.Writer, d uint8) (int64, error) {
return 1, binary.Write(w, binary.BigEndian, d)
}
func writeU16(w io.Writer, d uint16) (int64, error) {
return 2, binary.Write(w, binary.BigEndian, d)
}
func writeU32(w io.Writer, d uint32) (int64, error) {
return 4, binary.Write(w, binary.BigEndian, d)
}
func writeU64(w io.Writer, d uint64) (int64, error) {
return 8, binary.Write(w, binary.BigEndian, d)
}
func readByteN(r io.Reader, size int) (buff []byte, err error) {
buff = make([]byte, size)
for index := range buff {
if err = binary.Read(r, binary.BigEndian, &buff[index]); err != nil {
buff = buff[:index]
return
}
}
return
}
func writeBytes(w io.Writer, buff []byte) (n int64, err error) {
n = int64(len(buff))
err = binary.Write(w, binary.BigEndian, buff)
return
}
func addrWrite(w io.Writer, addr netip.Addr) (n int64, err error) {
if addr.Is6() {
if _, err = writeU8(w, 6); err != nil {
n = 0
return
} else if _, err = writeBytes(w, addr.AsSlice()); err != nil {
n = 1
return
}
n = 17
return
}
if _, err = writeU8(w, 4); err != nil {
n = 0
return
} else if _, err = writeBytes(w, addr.AsSlice()); err != nil {
n = 1
return
}
n = 5
return
}
func addrRead(r io.Reader) (addr netip.Addr, n int64, err error) {
var buff []byte
n = 1
switch readU8(r) {
case 4:
buff, err = readByteN(r, 4)
n += int64(len(buff))
if err != nil {
return
}
addr = netip.AddrFrom4([4]byte(buff))
return
case 6:
buff, err = readByteN(r, 16)
n += int64(len(buff))
if err != nil {
return
}
netip.AddrFrom16([16]byte(buff))
return
}
err = fmt.Errorf("connet get ip type")
return
}
func addrPortRead(r io.Reader) (netip.AddrPort, int64, error) {
switch readU8(r) {
case 4:
buff, err := readByteN(r, 4)
if err != nil {
return netip.AddrPort{}, int64(len(buff)), err
}
return netip.AddrPortFrom(netip.AddrFrom4([4]byte(buff)), readU16(r)), 6, nil
case 6:
buff, err := readByteN(r, 16)
if err != nil {
return netip.AddrPort{}, int64(len(buff)), err
}
return netip.AddrPortFrom(netip.AddrFrom16([16]byte(buff)), readU16(r)), 19, nil
}
return netip.AddrPort{}, 1, fmt.Errorf("connet get ip type")
}
func addrPortWrite(w io.Writer, addr netip.AddrPort) (n int64, err error) {
if !addr.IsValid() {
return 0, fmt.Errorf("invalid ip address")
} else if addr.Addr().Is6() {
if _, err = writeU8(w, 6); err != nil {
return 0, err
} else if err = binary.Write(w, binary.BigEndian, addr.Addr().AsSlice()); err != nil {
return 1, err
}
n = 18
return
}
if _, err = writeU8(w, 4); err != nil {
return 0, err
} else if err = binary.Write(w, binary.BigEndian, addr.Addr().AsSlice()); err != nil {
return 1, err
}
n = 5
return
}
func writeOption(w io.Writer, d any, callback func(w io.Writer) (n int64, err error)) (n int64, err error) {
if d == nil {
return writeU8(w, 0)
}
n, err = writeU8(w, 1)
if err != nil {
return
}
n2, err2 := callback(w)
return n + n2, err2
}
func readOption(r io.Reader, callback func(r io.Reader) (n int64, err error)) (n int64, err error) {
n = 1
switch readU8(r) {
case 0:
return 1, nil
case 1:
n2, err := callback(r)
return n + n2, err
}
return 1, fmt.Errorf("invalid Option value")
}

118
tunnel/control.go Normal file
View File

@ -0,0 +1,118 @@
package tunnel
import (
"bytes"
"fmt"
"net"
"time"
"sirherobrine23.org/playit-cloud/go-playit/api"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
type AuthenticatedControl struct {
Api api.Api
Conn ConnectedControl
LastPong proto.Pong
Registered proto.AgentRegistered
buffer *bytes.Buffer
ForceExpire bool
CurrentPing *uint64
}
func (self *AuthenticatedControl) SendKeepAlive(requestId uint64) error {
return self.Send(proto.ControlRpcMessage[*proto.ControlRequest]{
requestId,
&proto.ControlRequest{
AgentKeepAlive: &self.Registered.Id,
},
})
}
func (self *AuthenticatedControl) SendSetupUdpChannel(requestId uint64) error {
return self.Send(proto.ControlRpcMessage[*proto.ControlRequest]{
requestId,
&proto.ControlRequest{
SetupUdpChannel: &self.Registered.Id,
},
})
}
func (self *AuthenticatedControl) SendPing(requestId uint64, Now time.Time) error {
return self.Send(proto.ControlRpcMessage[*proto.ControlRequest]{
requestId,
&proto.ControlRequest{
Ping: &proto.Ping{Now, self.CurrentPing, &self.Registered.Id},
},
})
}
func (self *AuthenticatedControl) GetExpireAt() time.Time {
return self.Registered.ExpiresAt
}
func (self *AuthenticatedControl) IsExpired() bool {
return self.ForceExpire || self.LastPong.SessionExpireAt == nil || self.FlowChanged()
}
func (self *AuthenticatedControl) SetExpired() {
self.ForceExpire = true
}
func (self *AuthenticatedControl) FlowChanged() bool {
return self.Conn.Pong.ClientAddr.Compare(self.LastPong.ClientAddr) != 0
}
func (self *AuthenticatedControl) Send(req proto.ControlRpcMessage[*proto.ControlRequest]) error {
self.buffer.Reset()
if err := req.WriteTo(self.buffer); err != nil {
return err
} else if _, err := self.Conn.Udp.WriteTo(self.buffer.Bytes(), net.UDPAddrFromAddrPort(self.Conn.ControlAddr)); err != nil {
return err
}
return nil
}
func (self *AuthenticatedControl) IntoRequireAuth() ConnectedControl {
return ConnectedControl{
ControlAddr: self.Conn.ControlAddr,
Udp: self.Conn.Udp,
Pong: self.LastPong,
}
}
func (self *AuthenticatedControl) Authenticate() error {
conn := self.IntoRequireAuth()
var err error
if *self, err = conn.Authenticate(self.Api); err != nil {
return err
}
return nil
}
func (self *AuthenticatedControl) RecvFeedMsg() (proto.ControlFeed, error) {
buff := make([]byte, 1024)
size, remote, err := self.Conn.Udp.ReadFromUDPAddrPort(buff)
self.buffer.Reset()
self.buffer.Write(buff[:size])
if err != nil {
return proto.ControlFeed{}, err
} else if remote.Compare(self.Conn.ControlAddr) != 0 {
return proto.ControlFeed{}, fmt.Errorf("invalid remote, expected %q got %q", remote.String(), self.Conn.ControlAddr.String())
}
feed := proto.ControlFeed{}
if err := feed.ReadFrom(self.buffer); err != nil {
return proto.ControlFeed{}, err
}
if feed.Response != nil {
res := feed.Response
if registered := res.Content.AgentRegistered; registered != nil {
self.Registered = *registered
}
if pong := res.Content.Pong; pong != nil {
*self.CurrentPing = uint64(time.Now().UnixMilli() - pong.RequestNow.UnixMilli())
self.LastPong = *pong
}
}
return feed, nil
}

View File

@ -1,140 +0,0 @@
package tunnel
import (
"fmt"
"io"
"net/netip"
)
var (
ErrFeedRead error = fmt.Errorf("invalid controlFeed id")
)
type ControlFeed struct {
Response *ControlRpcMessage[*ControlResponse]
NewClient *NewClient
}
func (Feed *ControlFeed) ReadFrom(r io.Reader) (n int64, err error) {
id := readU32(r)
if id == 1 {
Feed.Response = new(ControlRpcMessage[*ControlResponse])
Feed.Response.Content = new(ControlResponse)
n, err = Feed.Response.ReadFrom(r)
n += 4
return
} else if id == 2 {
Feed.NewClient = &NewClient{}
n, err = Feed.NewClient.ReadFrom(r)
n += 4
return
}
return 4, ErrFeedRead
}
func (Feed *ControlFeed) WriteTo(w io.Writer) (n int64, err error) {
if Feed.Response != nil {
if err := writeU32(w, 1); err != nil {
return 0, err
}
n, err = Feed.Response.WriteTo(w)
n += 4
return
} else if Feed.NewClient != nil {
if err := writeU32(w, 2); err != nil {
return 0, err
}
n, err = Feed.NewClient.WriteTo(w)
n += 4
return
}
return 0, fmt.Errorf("")
}
type NewClient struct {
ConnectAddr netip.AddrPort
PeerAddr netip.AddrPort
ClaimInstructions ClaimInstructions
TunnelServerId uint64
DataCenterId uint32
}
func (client *NewClient) ReadFrom(r io.Reader) (n int64, err error) {
client.ConnectAddr, n, err = addrPortRead(r)
if err != nil {
return n, err
}
n2 := n
client.PeerAddr, n, err = addrPortRead(r)
if err != nil {
return n2 + n, err
}
n3 := n2 + n
n, err = client.ClaimInstructions.ReadFrom(r);
if err != nil {
return n3 + n, err
}
n+=n3 + 8 + 4
client.TunnelServerId, client.DataCenterId = readU64(r), readU32(r)
return
}
func (client *NewClient) WriteTo(w io.Writer) (n int64, err error) {
n, err = addrPortWrite(w, client.ConnectAddr)
if err != nil {
return n, err
}
n2 := n
n, err = addrPortWrite(w, client.PeerAddr)
if err != nil {
return n+n2, err
}
n3:= n+n2
if n, err = client.ClaimInstructions.WriteTo(w); err != nil {
return n + n3, err
}
n4 := n + n3
if err = writeU64(w, client.TunnelServerId); err != nil {
return n4, err
}
n4 += 8
if err = writeU32(w, client.DataCenterId); err != nil {
return n4, err
}
n = n4+8
return
}
type ClaimInstructions struct {
Address netip.AddrPort
Token []byte
}
func (claim *ClaimInstructions) ReadFrom(r io.Reader) (n int64, err error) {
claim.Address, n, err = addrPortRead(r)
if err != nil {
return n, err
}
claim.Token, err = readByteN(r, int(readU64(r)))
n += int64(len(claim.Token)) + 8
return
}
func (claim *ClaimInstructions) WriteTo(w io.Writer) (n int64, err error) {
n, err = addrPortWrite(w, claim.Address)
if err != nil {
return n, err
}
if err = writeU64(w, uint64(len(claim.Token))); err != nil {
return n, err
}
n2 := 8 + n
n, err = writeBytes(w, claim.Token)
if err != nil {
return n2, err
}
n = n2 + n
return
}

View File

@ -1,394 +0,0 @@
package tunnel
import (
"bytes"
"fmt"
"io"
"net/netip"
"time"
)
type ControlRequest struct {
Ping *Ping
AgentRegister *AgentRegister
AgentKeepAlive *AgentSessionId
SetupUdpChannel *AgentSessionId
AgentCheckPortMapping *AgentCheckPortMapping
}
func (Control *ControlRequest) WriteTo(w io.Writer) (n int64, err error) {
if Control.Ping != nil {
n, err = writeU32(w, 6)
if err != nil {
return
}
n2 := n
n, err = Control.Ping.WriteTo(w)
if err != nil {
return n2, err
}
n += n2
return
} else if Control.AgentRegister != nil {
n, err = writeU32(w, 2)
if err != nil {
return
}
n2 := n
n, err = Control.AgentRegister.WriteTo(w)
if err != nil {
return n2, err
}
n += n2
return
} else if Control.AgentKeepAlive != nil {
n, err = writeU32(w, 3)
if err != nil {
return
}
n2 := n
n, err = Control.AgentKeepAlive.WriteTo(w)
if err != nil {
return n2, err
}
n += n2
return
} else if Control.SetupUdpChannel != nil {
n, err = writeU32(w, 4)
if err != nil {
return
}
n2 := n
n, err = Control.SetupUdpChannel.WriteTo(w)
if err != nil {
return n2, err
}
n += n2
return
} else if Control.AgentCheckPortMapping != nil {
n, err = writeU32(w, 5)
if err != nil {
return
}
n2 := n
n, err = Control.AgentCheckPortMapping.WriteTo(w)
if err != nil {
return n2, err
}
n += n2
return
}
err = fmt.Errorf("set ControlRequest")
return
}
func (Control *ControlRequest) ReadFrom(r io.Reader) (n int64, err error) {
n = 1
switch readU32(r) {
case 1:
Control.Ping = new(Ping)
np, err := Control.Ping.ReadFrom(r)
return np + n, err
case 2:
Control.AgentRegister = new(AgentRegister)
np, err := Control.AgentRegister.ReadFrom(r)
return np + n, err
case 3:
Control.AgentKeepAlive = new(AgentSessionId)
np, err := Control.AgentKeepAlive.ReadFrom(r)
return np + n, err
case 4:
Control.SetupUdpChannel = new(AgentSessionId)
np, err := Control.SetupUdpChannel.ReadFrom(r)
return np + n, err
case 5:
Control.AgentCheckPortMapping = new(AgentCheckPortMapping)
np, err := Control.AgentCheckPortMapping.ReadFrom(r)
return np + n, err
}
err = fmt.Errorf("invalid ControlRequest id")
return
}
type AgentCheckPortMapping struct {
AgentSessionId AgentSessionId
PortRange PortRange
}
func (Agent *AgentCheckPortMapping) WriteTo(w io.Writer) (n int64, err error) {
n, err = Agent.AgentSessionId.WriteTo(w)
if err != nil {
return
}
n2 := n
n, err = Agent.PortRange.WriteTo(w)
return n + n2, err
}
func (Agent *AgentCheckPortMapping) ReadFrom(r io.Reader) (n int64, err error) {
n, err = Agent.AgentSessionId.ReadFrom(r)
if err != nil {
return
}
n2 := n
n, err = Agent.AgentSessionId.ReadFrom(r)
return n + n2, err
}
type Ping struct {
Now time.Time
CurrentPing *time.Time
SessionId *AgentSessionId
}
func (ping *Ping) WriteTo(w io.Writer) (n int64, err error) {
n, err = writeU64(w, uint64(ping.Now.UnixMilli()))
if err != nil {
return
}
n2 := n
if n, err = writeOption(w, ping.CurrentPing, func(w io.Writer) (int64, error) {
return writeU64(w, uint64(ping.CurrentPing.UnixMilli()))
}); err != nil {
n = n2
return
}
n += n2
if n, err = writeOption(w, ping.SessionId, ping.SessionId.WriteTo); err != nil {
n = n2
return
}
return
}
func (ping *Ping) ReadFrom(r io.Reader) (n int64, err error) {
ping.Now = time.UnixMilli(int64(readU64(r)))
n, err = readOption(r, func(r io.Reader) (n int64, err error) {
ping.CurrentPing = new(time.Time)
d, _ := time.UnixMilli(int64(readU64(r))).MarshalBinary()
ping.CurrentPing.UnmarshalBinary(d)
return 8, nil
})
n += 8
if err != nil {
return
}
n, err = readOption(r, func(r io.Reader) (n int64, err error) {
return ping.SessionId.ReadFrom(r)
})
if err != nil {
return
}
return
}
type AgentRegister struct {
AccountID, AgentId, AgentVersion, Timestamp uint64
ClientAddr, TunnelAddr netip.AddrPort
Signature [32]byte
}
func (agent *AgentRegister) writePlain() *bytes.Buffer {
buff := new(bytes.Buffer)
writeU64(buff, agent.AccountID)
writeU64(buff, agent.AgentId)
writeU64(buff, agent.AgentVersion)
writeU64(buff, agent.Timestamp)
addrPortWrite(buff, agent.ClientAddr)
addrPortWrite(buff, agent.TunnelAddr)
return buff
}
func (agent *AgentRegister) UpdateSignature(hmac HmacSha256) {
agent.Signature = hmac.Sign(agent.writePlain().Bytes())
}
func (agent *AgentRegister) VerifySignature(hmac HmacSha256) bool {
return hmac.Verify(agent.writePlain().Bytes(), agent.Signature[:])
}
func (AgentReg *AgentRegister) WriteTo(w io.Writer) (n int64, err error) {
if _, err := writeU64(w, AgentReg.AccountID); err != nil {
return 0, err
} else if _, err := writeU64(w, AgentReg.AgentId); err != nil {
return 0, err
} else if _, err := writeU64(w, AgentReg.AgentVersion); err != nil {
return 0, err
} else if _, err := writeU64(w, AgentReg.Timestamp); err != nil {
return 0, err
}
n = 8 * 4
if n2, err := addrPortWrite(w, AgentReg.ClientAddr); err != nil {
return n, err
} else if n3, err := addrPortWrite(w, AgentReg.TunnelAddr); err != nil {
return n + n2, err
} else {
n += n3
}
if n4, err := w.Write(AgentReg.Signature[:]); err != nil {
return n, err
} else {
n += int64(n4)
}
return
}
func (AgentReg *AgentRegister) ReadFrom(r io.Reader) (n int64, err error) {
AgentReg.AccountID, AgentReg.AccountID, AgentReg.AgentVersion, AgentReg.Timestamp = readU64(r), readU64(r), readU64(r), readU64(r)
if AgentReg.ClientAddr, n, err = addrPortRead(r); err != nil {
return
} else if AgentReg.TunnelAddr, n, err = addrPortRead(r); err != nil {
return
}
AgentReg.Signature = [32]byte(make([]byte, 32))
if n2, _ := r.Read(AgentReg.Signature[:]); n != 32 {
return int64(n2), fmt.Errorf("missing signature")
}
return
}
type ControlResponse struct {
InvalidSignature, Unauthorized, RequestQueued, TryAgainLater bool
Pong *Pong
AgentRegistered *AgentRegistered
AgentPortMapping *AgentPortMapping
UdpChannelDetails *UdpChannelDetails
}
func (Control *ControlResponse) WriteTo(w io.Writer) (n int64, err error) {
defer func() {
if err == nil {
n += 4
}
}()
if Control.Pong != nil {
writeU32(w, 1)
n, err = Control.Pong.WriteTo(w)
return
} else if Control.InvalidSignature {
return writeU32(w, 2)
} else if Control.Unauthorized {
return writeU32(w, 3)
} else if Control.RequestQueued {
return writeU32(w, 4)
} else if Control.TryAgainLater {
return writeU32(w, 5)
} else if Control.AgentRegistered != nil {
writeU32(w, 6)
return Control.AgentRegistered.WriteTo(w)
} else if Control.AgentPortMapping != nil {
writeU32(w, 7)
return Control.AgentPortMapping.WriteTo(w)
} else if Control.UdpChannelDetails != nil {
writeU32(w, 8)
return Control.UdpChannelDetails.WriteTo(w)
} else {
err = fmt.Errorf("insert any options to write")
}
return
}
func (Control *ControlResponse) ReadFrom(r io.Reader) (n int64, err error) {
defer func() {
if err == nil {
n += 4
}
}()
switch readU32(r) {
case 1:
Control.Pong = &Pong{}
return Control.Pong.ReadFrom(r)
case 2:
Control.InvalidSignature = true
return
case 3:
Control.Unauthorized = true
return
case 4:
Control.RequestQueued = true
return
case 5:
Control.TryAgainLater = true
return
case 6:
Control.AgentRegistered = &AgentRegistered{}
return Control.AgentRegistered.ReadFrom(r)
case 7:
Control.AgentPortMapping = &AgentPortMapping{}
return Control.AgentPortMapping.ReadFrom(r)
case 8:
Control.UdpChannelDetails = &UdpChannelDetails{}
return Control.UdpChannelDetails.ReadFrom(r)
default:
err = fmt.Errorf("invalid ControlResponse id")
}
return
}
type AgentPortMapping struct {
Range PortRange
Found *AgentPortMappingFound
}
func (Agent *AgentPortMapping) WriteTo(w io.Writer) (n int64, err error) {
Agent.Range.WriteTo(w)
Agent.Found.WriteTo(w)
return
}
func (Agent *AgentPortMapping) ReadFrom(r io.Reader) (n int64, err error) {
Agent.Range.ReadFrom(r)
Agent.Found.ReadFrom(r)
return
}
type AgentPortMappingFound struct {
ToAgent *AgentSessionId
}
func (Agent *AgentPortMappingFound) WriteTo(w io.Writer) (n int64, err error) {
if Agent.ToAgent != nil {
writeU32(w, 1)
Agent.ToAgent.WriteTo(w)
return
}
return
}
func (Agent *AgentPortMappingFound) ReadFrom(r io.Reader) (n int64, err error) {
if readU32(r) == 1 {
defer func() { n += 4 }()
Agent.ToAgent = new(AgentSessionId)
return Agent.ToAgent.ReadFrom(r)
}
return 4, fmt.Errorf("unknown AgentPortMappingFound id")
}
type UdpChannelDetails struct {
TunnelAddr netip.AddrPort
Token []byte
}
func (UdpChannel *UdpChannelDetails) WriteTo(w io.Writer) (n int64, err error) {
addrPortWrite(w, UdpChannel.TunnelAddr)
writeU64(w, uint64(len(UdpChannel.Token)))
writeBytes(w, UdpChannel.Token)
return
}
func (UdpChannel *UdpChannelDetails) ReadFrom(r io.Reader) (n int64, err error) {
UdpChannel.TunnelAddr, _, _ = addrPortRead(r)
UdpChannel.Token, _ = readByteN(r, int(readU64(r)))
return
}
type Pong struct {
RequestNow, ServerNow time.Time
ServerId uint64
DataCenterId uint32
ClientAddr, TunnelAddr netip.AddrPort
SessionExpireAt *time.Time
}
func (pong *Pong) WriteTo(w io.Writer) (n int64, err error) {
}
func (pong *Pong) ReadFrom(r io.Reader) (n int64, err error) {}
type AgentRegistered struct {
Id AgentSessionId
ExpiresAt time.Time
}
func (agent *AgentRegistered) WriteTo(w io.Writer) (n int64, err error) {}
func (agent *AgentRegistered) ReadFrom(r io.Reader) (n int64, err error) {}

View File

@ -1,97 +0,0 @@
package tunnel
import (
"fmt"
"io"
"net/netip"
)
type AgentSessionId struct {
SessionID, AccountID, AgentID uint64
}
type PortRange struct {
IP netip.Addr
PortStart, PortEnd uint16
PortProto PortProto
}
type PortProto string
func (AgentSession *AgentSessionId) WriteTo(w io.Writer) (int64, error) {
if _, err := writeU64(w, AgentSession.SessionID); err != nil {
return 0, err
} else if _, err = writeU64(w, AgentSession.AccountID); err != nil {
return 8, err
} else if _, err = writeU64(w, AgentSession.AgentID); err != nil {
return 16, err
}
return 24, nil
}
func (AgentSession *AgentSessionId) ReadFrom(r io.Reader) (int64, error) {
AgentSession.SessionID, AgentSession.AccountID, AgentSession.AgentID = readU64(r), readU64(r), readU64(r)
return 24, nil
}
func (portRange *PortRange) WriteTo(w io.Writer) (int64, error) {
var len int64 = 4
sizeIP, err := addrWrite(w, portRange.IP)
if err != nil {
return len, err
}
len += sizeIP
if _, err = writeU16(w, portRange.PortStart); err != nil {
return len, err
} else if _, err = writeU16(w, portRange.PortEnd); err != nil {
return len, err
}
protoSize, err := portRange.PortProto.WriteTo(w)
if err != nil {
return len, err
}
return len + protoSize, nil
}
func (portRange *PortRange) ReadFrom(r io.Reader) (int64, error) {
var (
ipSize int64
err error
)
portRange.IP, ipSize, err = addrRead(r)
if err != nil {
return ipSize, err
}
ipSize += 4
portRange.PortStart, portRange.PortEnd = readU16(r), readU16(r)
portRange.PortProto = PortProto("")
protoSize, err := portRange.PortProto.ReadFrom(r)
if err != nil {
return ipSize, err
}
return ipSize + protoSize, nil
}
func (proto PortProto) WriteTo(w io.Writer) (int64, error) {
switch proto {
case "tcp":
return writeU8(w, 1)
case "udp":
return writeU8(w, 2)
case "both":
return writeU8(w, 3)
}
return 0, fmt.Errorf("invalid port proto")
}
func (proto PortProto) ReadFrom(r io.Reader) (int64, error) {
switch readU8(r) {
case 1:
proto = PortProto("tcp")
case 2:
proto = PortProto("udp")
case 3:
proto = PortProto("both")
default: return 0, fmt.Errorf("invalid port proto")
}
return 1, nil
}

View File

@ -1,36 +0,0 @@
package tunnel
import (
"encoding/binary"
"io"
)
type MessageEncoding interface {
io.ReaderFrom
io.WriterTo
}
type ControlRpcMessage[T MessageEncoding] struct {
RequestID uint64
Content T // Convert with .(*type)
}
func (rpc *ControlRpcMessage[T]) WriteTo(w io.Writer) (n int64, err error) {
if err = binary.Write(w, binary.BigEndian, rpc.RequestID); err != nil {
return 0, err
} else if n, err = rpc.Content.WriteTo(w); err != nil {
return 8, err
}
n += 8
return
}
func (rpc *ControlRpcMessage[T]) ReadFrom(r io.Reader) (n int64, err error) {
if err = binary.Read(r, binary.BigEndian, &rpc.RequestID); err != nil {
n = 0
return n, err
} else if n, err = rpc.Content.ReadFrom(r); err != nil {
return 8, err
}
n += 8
return
}

159
tunnel/setup.go Normal file
View File

@ -0,0 +1,159 @@
package tunnel
import (
"bytes"
"encoding/hex"
"fmt"
"net"
"net/netip"
"time"
"sirherobrine23.org/playit-cloud/go-playit/api"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
type SetupFindSuitableChannel struct {
options []netip.AddrPort
}
func (self *SetupFindSuitableChannel) Setup() (ConnectedControl, error) {
for _, addr := range self.options {
var (
err error
socket *net.UDPConn
)
isIPv6 := addr.Addr().Is6()
if isIPv6 {
if socket, err = net.ListenUDP("udp6", nil); err != nil {
continue // Next address to listen
}
} else {
if socket, err = net.ListenUDP("udp4", nil); err != nil {
continue // Next address to listen
}
}
var attempts int
if attempts = 3; isIPv6 {
attempts = 1
}
for range attempts {
buffer := new(bytes.Buffer)
if err = (&proto.ControlRpcMessage[*proto.ControlRequest]{
RequestID: 1,
Content: &proto.ControlRequest{
Ping: &proto.Ping{
Now: time.Now(),
CurrentPing: nil,
SessionId: nil,
},
},
}).WriteTo(buffer); err != nil {
return ConnectedControl{}, err
} else if _, err = socket.WriteTo(buffer.Bytes(), net.UDPAddrFromAddrPort(addr)); err != nil {
return ConnectedControl{}, fmt.Errorf("failed to send initial ping: %s", err.Error())
}
buffer.Reset()
var waits int
if waits = 5; isIPv6 {
waits = 3
}
for range waits {
buff := make([]byte, 1024)
socket.SetReadDeadline(time.Now().Add(time.Millisecond * 5))
size, peer, err := socket.ReadFromUDPAddrPort(buff)
if err != nil {
continue
} else if peer.Compare(addr) != 0 {
continue
}
buffer = bytes.NewBuffer(buff[:size])
feed := proto.ControlFeed{}
if err := feed.ReadFrom(buffer); err != nil {
continue
} else if feed.Response != nil {
continue
} else if feed.Response.RequestID != 1 {
continue
} else if feed.Response.Content.Pong != nil {
continue
}
return ConnectedControl{addr, *socket, *feed.Response.Content.Pong}, nil
}
}
}
return ConnectedControl{}, fmt.Errorf("failed to connect")
}
type ConnectedControl struct {
ControlAddr netip.AddrPort
Udp net.UDPConn
Pong proto.Pong
}
func (self *ConnectedControl) Authenticate(Api api.Api) (AuthenticatedControl, error) {
key, err := Api.ProtoRegisterRegister(self.Pong.ClientAddr, self.Pong.TunnelAddr)
if err != nil {
return AuthenticatedControl{}, err
}
keyBytes, err := hex.DecodeString(key)
if err != nil {
return AuthenticatedControl{}, err
}
for range 5 {
buffer := new(bytes.Buffer)
if err := (&proto.ControlRpcMessage[proto.RawSlice]{
RequestID: 10,
Content: proto.RawSlice(keyBytes),
}).WriteTo(buffer); err != nil {
return AuthenticatedControl{}, err
} else if _, err := self.Udp.WriteTo(buffer.Bytes(), net.UDPAddrFromAddrPort(self.ControlAddr)); err != nil {
return AuthenticatedControl{}, err
}
for range 5 {
buff := make([]byte, 1024)
size, remote, err := self.Udp.ReadFromUDPAddrPort(buff)
if err != nil {
break
} else if self.ControlAddr.Compare(remote) != 0 {
continue
}
buffer.Reset()
buffer.Write(buff[:size]) // Write only reader data
var feed proto.ControlFeed
if err := feed.ReadFrom(buffer); err != nil {
continue
} else if response := feed.Response; response != nil {
if response.RequestID != 10 {
continue
}
if content := response.Content; content.RequestQueued {
time.Sleep(time.Second) // Sleep to wait register
break
} else if content.InvalidSignature {
return AuthenticatedControl{}, fmt.Errorf("invalid signature")
} else if content.Unauthorized {
return AuthenticatedControl{}, fmt.Errorf("unauthorized")
} else if registered := content.AgentRegistered; registered != nil {
// secret_key,
// api_client: api,
// conn: self,
// last_pong: pong,
// registered,
// buffer,
// current_ping: None,
// force_expired: false,
return AuthenticatedControl{
Api: Api,
Conn: *self,
LastPong: self.Pong,
Registered: *registered,
buffer: buffer,
CurrentPing: nil,
ForceExpire: false,
}, nil
}
}
}
}
return AuthenticatedControl{}, fmt.Errorf("failed to connect")
}

170
tunnel/simple_tunnel.go Normal file
View File

@ -0,0 +1,170 @@
package tunnel
import (
"net/netip"
"slices"
"time"
"sirherobrine23.org/playit-cloud/go-playit/api"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
func getControlAddresses(api api.Api) ([]netip.AddrPort, error) {
routing, err := api.AgentRoutings(nil)
if err != nil {
return nil, err
}
addresses := []netip.AddrPort{}
for _, ip6 := range append(routing.Targets6, routing.Targets4...) {
addresses = append(addresses, netip.AddrPortFrom(ip6, 5525))
}
return addresses, nil
}
type SimpleTunnel struct {
api api.Api
controlAddr netip.AddrPort
controlChannel AuthenticatedControl
udpTunnel UdpTunnel
lastKeepAlive, lastPing, lastPong, lastUdpAuth time.Time
lastControlTargets []netip.AddrPort
}
func NewSimpleTunnel(Api api.Api) SimpleTunnel {
return SimpleTunnel{
api: Api,
}
}
func (self *SimpleTunnel) Setup() error {
udpTunnel := UdpTunnel{}
if err := AssignUdpTunnel(&udpTunnel); err != nil {
return err
}
addresses, err := getControlAddresses(self.api)
if err != nil {
return err
}
setup, err := (&SetupFindSuitableChannel{addresses}).Setup()
if err != nil {
return err
}
controlChannel, err := setup.Authenticate(self.api)
if err != nil {
return err
}
self.lastControlTargets = addresses
self.controlAddr = setup.ControlAddr
self.controlChannel = controlChannel
self.udpTunnel = udpTunnel
self.lastKeepAlive = time.UnixMilli(0)
self.lastPing = time.UnixMilli(0)
self.lastPong = time.UnixMilli(0)
self.lastUdpAuth = time.UnixMilli(0)
return nil
}
func (self *SimpleTunnel) ReloadControlAddr() (bool, error) {
addresses, err := getControlAddresses(self.api)
if err != nil {
return false, err
} else if slices.ContainsFunc(self.lastControlTargets, func(e1 netip.AddrPort) bool {
return !slices.Contains(addresses, e1)
}) {
return false, nil
}
setup, err := (&SetupFindSuitableChannel{addresses}).Setup()
if err != nil {
return false, err
}
updated, err := self.UpdateControlAddr(setup)
if err == nil {
self.lastControlTargets = addresses
}
return updated, err
}
func (self *SimpleTunnel) UpdateControlAddr(connected ConnectedControl) (bool, error) {
newControlAddr := connected.ControlAddr
if self.controlAddr.Compare(newControlAddr) == 0 {
return false, nil
}
controlChannel, err := connected.Authenticate(self.api)
if err != nil {
return false, err
}
self.controlChannel = controlChannel
self.controlAddr = newControlAddr
self.lastPing, self.lastKeepAlive, self.lastUdpAuth = time.UnixMilli(0), time.UnixMilli(0), time.UnixMilli(0)
self.udpTunnel.InvalidateSession()
return true, nil
}
func (self *SimpleTunnel) UdpTunnel() UdpTunnel {
return self.udpTunnel
}
func (self *SimpleTunnel) Update() *proto.NewClient {
if self.controlChannel.IsExpired() {
if err := self.controlChannel.Authenticate(); err != nil {
time.Sleep(time.Second * 2)
return nil
}
}
now := time.Now()
if now.UnixMilli() - self.lastPing.UnixMilli() > 1_000 {
self.lastPing = now
if err := self.controlChannel.SendPing(200, now); err != nil {}
}
if self.udpTunnel.RequiresAuth() {
if 5_000 < now.UnixMilli() - self.lastUdpAuth.UnixMilli() {
self.lastUdpAuth = now
if err := self.controlChannel.SendSetupUdpChannel(9_000); err != nil {}
}
} else if self.udpTunnel.RequireResend() {
if 1_000 < now.UnixMilli() - self.lastUdpAuth.UnixMilli() {
self.lastUdpAuth = now
if _, err := self.udpTunnel.ResendToken(); err != nil {}
}
}
timeTillExpire := max(self.controlChannel.GetExpireAt().UnixMilli(), now.UnixMilli()) - now.UnixMilli()
if 10_000 < now.UnixMilli() - self.lastKeepAlive.UnixMilli() && timeTillExpire < 30_000 {
self.lastKeepAlive = now
if err := self.controlChannel.SendKeepAlive(100); err != nil {}
if err := self.controlChannel.SendSetupUdpChannel(1); err != nil {}
}
timeout := 0
for range 30 {
feed, err := self.controlChannel.RecvFeedMsg()
if err != nil {
continue
} else if newClient := feed.NewClient; newClient != nil {
return newClient
} else if msg := feed.Response; msg != nil {
if content := msg.Content; content != nil {
if details := content.UdpChannelDetails; details != nil {
if err := self.udpTunnel.SetUdpTunnel(*details); err != nil {
panic(err)
}
} else if content.Unauthorized {
self.controlChannel.SetExpired()
} else if pong := content.Pong; pong != nil {
self.lastPong = time.Now()
if pong.ClientAddr.Compare(self.controlChannel.Conn.Pong.ClientAddr) != 0 {}
}
}
}
if timeout++; timeout >= 10 {
break
}
}
if self.lastPong.UnixMilli() != 0 && time.Now().UnixMilli() - self.lastPong.UnixMilli() > 6_000 {
self.lastPong = time.UnixMilli(0)
self.controlChannel.SetExpired()
}
return nil
}

37
tunnel/tcp_tunnel.go Normal file
View File

@ -0,0 +1,37 @@
package tunnel
import (
"fmt"
"net"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
type TcpTunnel struct {
ClaimInstruction proto.ClaimInstructions
}
func (tcpTunnel *TcpTunnel) Connect() (*net.TCPConn, error) {
conn, err := net.DialTCP("tcp", nil, net.TCPAddrFromAddrPort(tcpTunnel.ClaimInstruction.Address))
if err != nil {
if conn != nil {
conn.Close()
}
return nil, err
}
_, err = conn.Write(tcpTunnel.ClaimInstruction.Token)
if err != nil {
conn.Close()
return nil, err
}
buff := make([]byte, 8)
size, err := conn.Read(buff)
if err != nil {
conn.Close()
return nil, err
} else if size != 8 {
conn.Close()
return nil, fmt.Errorf("invalid response reader size")
}
return conn, nil
}

156
tunnel/udp_proto.go Normal file
View File

@ -0,0 +1,156 @@
package tunnel
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net/netip"
"sirherobrine23.org/playit-cloud/go-playit/enc"
)
const (
REDIRECT_FLOW_4_FOOTER_ID_OLD uint64 = 0x5cb867cf788173b2
REDIRECT_FLOW_4_FOOTER_ID uint64 = 0x4448474f48414344
REDIRECT_FLOW_6_FOOTER_ID uint64 = 0x6668676f68616366
UDP_CHANNEL_ESTABLISH_ID uint64 = 0xd01fe6830ddce781
V4_LEN int = 20
V6_LEN int = 48
)
type UdpFlowBase struct {
Src, Dst netip.AddrPort
}
type UdpFlow struct {
V4 *UdpFlowBase
V6 *struct {
UdpFlowBase
Flow uint32
}
}
func (w *UdpFlow) Len() int {
if w.V4 == nil {
return V6_LEN
}
return V4_LEN
}
func (w *UdpFlow) Src() netip.AddrPort {
if w.V4 == nil {
return w.V6.UdpFlowBase.Src
}
return w.V4.Src
}
func (w *UdpFlow) Dst() netip.AddrPort {
if w.V4 == nil {
return w.V6.UdpFlowBase.Dst
}
return w.V4.Dst
}
func (w *UdpFlow) WithSrcPort(port uint16) UdpFlow {
if w.V4 == nil {
return UdpFlow{
V6: &struct{UdpFlowBase; Flow uint32}{
Flow: w.V6.Flow,
UdpFlowBase: UdpFlowBase{
Src: netip.AddrPortFrom(w.V6.Src.Addr(), port),
Dst: w.V6.Src,
},
},
}
}
return UdpFlow{
V4: &UdpFlowBase{
Src: netip.AddrPortFrom(w.V4.Src.Addr(), port),
Dst: w.V4.Dst,
},
}
}
func (w *UdpFlow) WriteTo(writer io.Writer) error {
var conn UdpFlowBase
if w.V4 != nil {
conn = *w.V4
} else {
conn = w.V6.UdpFlowBase
}
if err := enc.WriteBytes(writer, conn.Src.Addr().AsSlice()); err != nil {
return err
} else if err := enc.WriteBytes(writer, conn.Dst.Addr().AsSlice()); err != nil {
return err
} else if err := enc.WriteU16(writer, conn.Src.Port()); err != nil {
return err
} else if err := enc.WriteU16(writer, conn.Dst.Port()); err != nil {
return err
}
if w.V4 != nil {
if err := enc.WriteU64(writer, REDIRECT_FLOW_4_FOOTER_ID_OLD); err != nil {
return err
}
} else {
if err := enc.WriteU32(writer, w.V6.Flow); err != nil {
return err
} else if err := enc.WriteU64(writer, REDIRECT_FLOW_6_FOOTER_ID); err != nil {
return err
}
}
return nil
}
func FromTailUdpFlow(slice []byte) (*UdpFlow, uint64, error) {
if len(slice) < 8 {
return nil, 0, fmt.Errorf("not space to footer")
}
footer := binary.BigEndian.Uint64(slice[len(slice)-8:])
switch footer {
case REDIRECT_FLOW_4_FOOTER_ID | REDIRECT_FLOW_4_FOOTER_ID_OLD:
if len(slice) < V4_LEN {
return nil, 0, fmt.Errorf("v4 not have space")
}
slice = slice[len(slice)-V4_LEN:]
src_ip, _ := enc.ReadByteN(bytes.NewReader(slice), 4)
srcIP, _ := netip.AddrFromSlice(src_ip)
dst_ip, _ := enc.ReadByteN(bytes.NewReader(slice), 4)
dstIP, _ := netip.AddrFromSlice(dst_ip)
src_port, dst_port := enc.ReadU16(bytes.NewReader(slice)), enc.ReadU16(bytes.NewReader(slice))
return &UdpFlow{
V4: &UdpFlowBase{
Src: netip.AddrPortFrom(srcIP, src_port),
Dst: netip.AddrPortFrom(dstIP, dst_port),
},
}, 0, nil
case REDIRECT_FLOW_6_FOOTER_ID:
if len(slice) < V6_LEN {
return nil, footer, fmt.Errorf("v6 not have space")
}
slice = slice[len(slice)-V6_LEN:]
src_ip, _ := enc.ReadByteN(bytes.NewReader(slice), 16)
srcIP, _ := netip.AddrFromSlice(src_ip)
dst_ip, _ := enc.ReadByteN(bytes.NewReader(slice), 16)
dstIP, _ := netip.AddrFromSlice(dst_ip)
src_port, dst_port := enc.ReadU16(bytes.NewReader(slice)), enc.ReadU16(bytes.NewReader(slice))
flow := enc.ReadU32(bytes.NewReader(slice))
return &UdpFlow{
V6: &struct {
UdpFlowBase
Flow uint32
}{
UdpFlowBase{
Src: netip.AddrPortFrom(srcIP, src_port),
Dst: netip.AddrPortFrom(dstIP, dst_port),
},
flow,
},
}, 0, nil
}
return nil, footer, nil
}

223
tunnel/udp_tunnel.go Normal file
View File

@ -0,0 +1,223 @@
package tunnel
import (
"bytes"
"encoding/hex"
"fmt"
"net"
"net/netip"
"slices"
"sync"
"sync/atomic"
"time"
"sirherobrine23.org/playit-cloud/go-playit/proto"
)
type UdpTunnel struct {
Udp4 *net.UDPConn
Udp6 *net.UDPConn
locker sync.Mutex
Details ChannelDetails
LastConfirm atomic.Uint32
LastSend atomic.Uint32
}
type ChannelDetails struct {
Udp *proto.UdpChannelDetails
AddrHistory []netip.AddrPort
}
func AssignUdpTunnel(tunUdp *UdpTunnel) error {
// LogDebug.Println("Assign UDP Tunnel IPv4")
udp4, err := net.ListenUDP("udp4", nil)
if err != nil {
return err
}
tunUdp.Udp4 = udp4
// IPv6 opcional
// LogDebug.Println("Assign UDP Tunnel IPv6")
if tunUdp.Udp6, err = net.ListenUDP("udp6", nil); err != nil {
// LogDebug.Println("Cannot listen IPv6 Udp Tunnel")
tunUdp.Udp6 = nil
err = nil
}
tunUdp.Details = ChannelDetails{
AddrHistory: []netip.AddrPort{},
Udp: nil,
}
tunUdp.LastConfirm = atomic.Uint32{}
tunUdp.LastSend = atomic.Uint32{}
tunUdp.LastConfirm.Store(0)
tunUdp.LastSend.Store(0)
return nil
}
func (udp *UdpTunnel) IsSetup() bool {
return udp.Details.Udp != nil
}
func (udp *UdpTunnel) InvalidateSession() {
udp.LastConfirm.Store(0)
udp.LastSend.Store(0)
}
func now_sec() uint32 {
return uint32(time.Now().UnixMilli()) / 1_000
}
func (udp *UdpTunnel) RequireResend() bool {
last_confirm := udp.LastConfirm.Load()
/* send token every 10 seconds */
return 10 < now_sec()-last_confirm
}
func (udp *UdpTunnel) RequiresAuth() bool {
lastConf, lastSend := udp.LastConfirm.Load(), udp.LastSend.Load()
if lastSend < lastConf {
return false
}
return 5 < now_sec()-lastSend
}
func (udp *UdpTunnel) SetUdpTunnel(details proto.UdpChannelDetails) error {
// LogDebug.Println("Updating Udp Tunnel")
udp.locker.Lock()
defer udp.locker.Unlock()
if udp.Details.Udp != nil {
current := udp.Details.Udp
if bytes.Equal(current.Token, details.Token) && current.TunnelAddr.Compare(details.TunnelAddr) == 0 {
udp.locker.Unlock()
return nil
}
if current.TunnelAddr.Compare(details.TunnelAddr) != 0 {
// LogDebug.Println("changed udp tunner addr")
oldAddr := current.TunnelAddr
udp.Details.AddrHistory = append(udp.Details.AddrHistory, oldAddr)
}
udp.Details.Udp = &details
}
return udp.SendToken(&details)
}
func (udp *UdpTunnel) ResendToken() (bool, error) {
lock := udp.Details
if lock.Udp == nil {
return false, nil
} else if err := udp.SendToken(lock.Udp); err != nil {
return false, err
}
return true, nil
}
func (udp *UdpTunnel) SendToken(details *proto.UdpChannelDetails) error {
if details.TunnelAddr.Addr().Is4() {
udp.Udp4.WriteToUDPAddrPort(details.Token, details.TunnelAddr)
} else {
if udp.Udp6 == nil {
return fmt.Errorf("ipv6 not supported")
}
udp.Udp6.WriteToUDPAddrPort(details.Token, details.TunnelAddr)
}
// LogDebug.Printf("send udp session token (len=%d) to %s\n", len(details.Token), details.TunnelAddr.AddrPort.String())
udp.LastSend.Store(now_sec())
return nil
}
func (udp *UdpTunnel) GetSock() (*net.UDPConn, *netip.AddrPort, error) {
lock := udp.Details
if lock.Udp == nil {
// LogDebug.Println("udp tunnel not connected")
return nil, nil, fmt.Errorf("udp tunnel not connected")
} else if lock.Udp.TunnelAddr.Addr().Is4() {
return udp.Udp4, &lock.Udp.TunnelAddr, nil
} else if udp.Udp6 == nil {
// LogDebug.Println("ipv6 not setup")
return nil, nil, fmt.Errorf("ipv6 not setup")
}
return udp.Udp6, &lock.Udp.TunnelAddr, nil
}
func (Udp *UdpTunnel) Send(data []byte, Flow UdpFlow) (int, error) {
buff := bytes.NewBuffer([]byte{})
if err := Flow.WriteTo(buff); err != nil {
return 0, err
}
socket, addr, err := Udp.GetSock()
if err != nil {
return 0, err
}
return socket.WriteToUDPAddrPort(append(data, buff.Bytes()...), *addr)
}
func (Udp *UdpTunnel) GetToken() ([]byte, error) {
lock := Udp.Details
if lock.Udp == nil {
return nil, fmt.Errorf("udp tunnel not connected")
}
return lock.Udp.Token[:], nil
}
type UdpTunnelRx struct {
ConfirmerdConnection bool
ReceivedPacket *struct {
Bytes uint64
Flow UdpFlow
}
}
func (Udp *UdpTunnel) ReceiveFrom(buff []byte) (*UdpTunnelRx, error) {
udp, tunnelAddr, err := Udp.GetSock()
if err != nil {
return nil, err
}
byteSize, remote, err := udp.ReadFromUDPAddrPort(buff)
if err != nil {
return nil, err
}
if tunnelAddr.Compare(remote) != 0 {
lock := Udp.Details
if !slices.ContainsFunc(lock.AddrHistory, func(a netip.AddrPort) bool {
return a.Compare(remote) == 0
}) {
return nil, fmt.Errorf("got data from other source")
}
}
token, err := Udp.GetToken()
if err != nil {
return nil, err
}
// LogDebug.Println("Check token")
// LogDebug.Println(buff)
// LogDebug.Println(token)
// LogDebug.Println("end check token")
if bytes.Equal(buff[:byteSize], token) {
// LogDebug.Println("udp session confirmed")
Udp.LastConfirm.Store(now_sec())
return &UdpTunnelRx{ConfirmerdConnection: true}, nil
}
if len(buff)+V6_LEN < byteSize {
return nil, fmt.Errorf("receive buffer too small")
}
footer, footerInt, err := FromTailUdpFlow(buff[byteSize:])
if err != nil {
if footerInt == UDP_CHANNEL_ESTABLISH_ID {
actual := hex.EncodeToString(buff[byteSize:])
expected := hex.EncodeToString(token)
return nil, fmt.Errorf("unexpected UDP establish packet, actual: %s, expected: %s", actual, expected)
}
return nil, fmt.Errorf("failed to extract udp footer: %s, err: %s", hex.EncodeToString(buff[byteSize:]), err.Error())
}
return &UdpTunnelRx{ReceivedPacket: &struct {
Bytes uint64
Flow UdpFlow
}{uint64(byteSize) - uint64(footer.Len()), *footer}}, nil
}