mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-25 10:04:14 -05:00
NATNEG: Fully implement the init command
This commit is contained in:
@@ -2,8 +2,6 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Base32Encode(value int64) string {
|
||||
@@ -30,39 +28,3 @@ func reverse(s string) string {
|
||||
|
||||
return string(rns)
|
||||
}
|
||||
|
||||
func IPFormatToInt(ip string) (int32, uint16) {
|
||||
port := 0
|
||||
|
||||
if strings.Contains(ip, ":") {
|
||||
ipSplit := strings.Split(ip, ":")
|
||||
|
||||
var err error
|
||||
port, err = strconv.Atoi(ipSplit[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ip = ipSplit[0]
|
||||
}
|
||||
|
||||
var intIP int
|
||||
for i, s := range strings.Split(ip, ".") {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
intIP |= val << (24 - i*8)
|
||||
}
|
||||
|
||||
// TODO: Check if this handles negative numbers properly
|
||||
return int32(intIP), uint16(port)
|
||||
}
|
||||
|
||||
func IPFormatToString(ip string) (string, string) {
|
||||
intIP, intPort := IPFormatToInt(ip)
|
||||
|
||||
// TODO: Check if this handles negative numbers properly
|
||||
return strconv.FormatInt(int64(intIP), 10), strconv.FormatUint(uint64(intPort), 10)
|
||||
}
|
||||
|
||||
105
common/ip_address.go
Normal file
105
common/ip_address.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IPFormatToInt(ip string) (int32, uint16) {
|
||||
port := 0
|
||||
|
||||
if strings.Contains(ip, ":") {
|
||||
ipSplit := strings.Split(ip, ":")
|
||||
|
||||
var err error
|
||||
port, err = strconv.Atoi(ipSplit[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ip = ipSplit[0]
|
||||
}
|
||||
|
||||
var intIP int
|
||||
for i, s := range strings.Split(ip, ".") {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
intIP |= val << (24 - i*8)
|
||||
}
|
||||
|
||||
// TODO: Check if this handles negative numbers properly
|
||||
return int32(intIP), uint16(port)
|
||||
}
|
||||
|
||||
func IPFormatNoPortToInt(ip string) int32 {
|
||||
intIP, _ := IPFormatToInt(ip)
|
||||
return intIP
|
||||
}
|
||||
|
||||
func IPFormatToString(ip string) (string, string) {
|
||||
intIP, intPort := IPFormatToInt(ip)
|
||||
|
||||
// TODO: Check if this handles negative numbers properly
|
||||
return strconv.FormatInt(int64(intIP), 10), strconv.FormatUint(uint64(intPort), 10)
|
||||
}
|
||||
|
||||
func IPFormatBytes(ip string) []byte {
|
||||
if strings.Contains(ip, ":") {
|
||||
ip = strings.Split(ip, ":")[0]
|
||||
}
|
||||
|
||||
bytes := []byte{}
|
||||
for _, s := range strings.Split(ip, ".") {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bytes = append(bytes, byte(val))
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
var (
|
||||
reservedIPList = []struct {
|
||||
ip int32
|
||||
mask int32
|
||||
}{
|
||||
{IPFormatNoPortToInt("0.0.0.0"), 8}, // RFC1122 "This host on this network"
|
||||
{IPFormatNoPortToInt("10.0.0.0"), 8}, // RFC1918 Private-Use
|
||||
{IPFormatNoPortToInt("100.64.0.0"), 10}, // RFC6598 Shared Address Space
|
||||
{IPFormatNoPortToInt("127.0.0.0"), 8}, // RFC1122 Loopback
|
||||
{IPFormatNoPortToInt("169.254.0.0"), 16}, // RFC3927 Link-Local
|
||||
{IPFormatNoPortToInt("172.16.0.0"), 12}, // RFC1918 Private-Use
|
||||
{IPFormatNoPortToInt("192.0.0.0"), 24}, // RFC6890 IETF Protocol Assignments
|
||||
{IPFormatNoPortToInt("192.0.2.0"), 24}, // RFC5737 Documentation (TEST-NET-1)
|
||||
{IPFormatNoPortToInt("192.31.196.0"), 24}, // RFC7535 AS112-v4
|
||||
{IPFormatNoPortToInt("192.52.193.0"), 24}, // RFC7450 AMT
|
||||
{IPFormatNoPortToInt("192.88.99.0"), 24}, // RFC7526 6to4 Relay Anycast
|
||||
{IPFormatNoPortToInt("192.168.0.0"), 16}, // RFC1918 Private-Use
|
||||
{IPFormatNoPortToInt("192.175.48.0"), 24}, // RFC7534 Direct Delegation AS112 Service
|
||||
{IPFormatNoPortToInt("198.18.0.0"), 15}, // RFC2544 Benchmarking
|
||||
{IPFormatNoPortToInt("198.51.100.0"), 24}, // RFC5737 Documentation (TEST-NET-2)
|
||||
{IPFormatNoPortToInt("203.0.113.0"), 24}, // RFC5737 Documentation (TEST-NET-3)
|
||||
{IPFormatNoPortToInt("224.0.0.0"), 4}, // RFC1112 Multicast
|
||||
{IPFormatNoPortToInt("240.0.0.0"), 4}, // RFC1112 Reserved for Future Use + RFC919 Limited Broadcast
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: Test this
|
||||
func IsReservedIP(ip string) bool {
|
||||
intIP, _ := IPFormatToInt(ip)
|
||||
|
||||
for _, reserved := range reservedIPList {
|
||||
rMask := 32 - reserved.mask
|
||||
if intIP>>rMask == reserved.ip>>rMask {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
211
natneg/main.go
211
natneg/main.go
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"sync"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
@@ -28,6 +29,48 @@ const (
|
||||
NNReportReply = 0x0E
|
||||
NNPreInitRequest = 0x0F
|
||||
NNPreInitReply = 0x10
|
||||
|
||||
// Port type
|
||||
PortTypeGamePort = 0x00
|
||||
PortTypeNATNEG1 = 0x01
|
||||
PortTypeNATNEG2 = 0x02
|
||||
PortTypeNATNEG3 = 0x03
|
||||
|
||||
// NAT type
|
||||
NATTypeNoNat = 0x00
|
||||
NATTypeFirewallOnly = 0x01
|
||||
NATTypeFullCone = 0x02
|
||||
NATTypeRestrictedCone = 0x03
|
||||
NATTypePortRestrictedCone = 0x04
|
||||
NATTypeSymmetric = 0x05
|
||||
NATTypeUnknown = 0x06
|
||||
|
||||
// NAT mapping scheme
|
||||
NATMappingUnknown = 0x00
|
||||
NATMappingSamePrivatePublic = 0x01
|
||||
NATMappingConsistent = 0x02
|
||||
NATMappingIncremental = 0x03
|
||||
NATMappingMixed = 0x04
|
||||
)
|
||||
|
||||
type NATNEGSession struct {
|
||||
Cookie uint32
|
||||
Mutex sync.RWMutex
|
||||
Clients map[byte]*NATNEGClient
|
||||
}
|
||||
|
||||
type NATNEGClient struct {
|
||||
Cookie uint32
|
||||
Connected bool
|
||||
NegotiateIP string
|
||||
LocalIP string
|
||||
ServerIP string
|
||||
GameName string
|
||||
}
|
||||
|
||||
var (
|
||||
sessions = map[uint32]*NATNEGSession{}
|
||||
mutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
@@ -45,13 +88,13 @@ func StartServer() {
|
||||
logging.Notice("NATNEG", "Listening on", address)
|
||||
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
_, addr, err := conn.ReadFrom(buf)
|
||||
buffer := make([]byte, 1024)
|
||||
size, addr, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
go handleConnection(conn, addr, buf)
|
||||
go handleConnection(conn, addr, buffer[:size])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,18 +111,37 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
// xx - Packet Type / Command
|
||||
// xx xx xx xx - Cookie
|
||||
|
||||
// version := buffer[6]
|
||||
version := buffer[6]
|
||||
command := buffer[7]
|
||||
cookie := binary.BigEndian.Uint32(buffer[8:12])
|
||||
|
||||
moduleName := "NATNEG:" + fmt.Sprintf("%08x", cookie) + addr.String() + ":"
|
||||
moduleName := "NATNEG:" + fmt.Sprintf("%08x/", cookie) + addr.String()
|
||||
|
||||
mutex.Lock()
|
||||
session, exists := sessions[cookie]
|
||||
if !exists {
|
||||
// TODO: Figure out removing the session if this request is nonsense or something
|
||||
logging.Notice(moduleName, "Creating session")
|
||||
session = &NATNEGSession{
|
||||
Cookie: cookie,
|
||||
Mutex: sync.RWMutex{},
|
||||
Clients: map[byte]*NATNEGClient{},
|
||||
}
|
||||
sessions[cookie] = session
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
session.Mutex.Lock()
|
||||
defer session.Mutex.Unlock()
|
||||
|
||||
switch command {
|
||||
default:
|
||||
logging.Error(moduleName, "Received unknown command type:", aurora.Cyan(command))
|
||||
break
|
||||
|
||||
case NNInitRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("NNInitRequest"))
|
||||
session.handleInit(conn, addr, buffer[12:], moduleName, version)
|
||||
break
|
||||
|
||||
case NNInitReply:
|
||||
@@ -104,6 +166,7 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
|
||||
case NNConnectReply:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("NNConnectReply"))
|
||||
// TODO: Set the client Connected value to true here
|
||||
break
|
||||
|
||||
case NNConnectPing:
|
||||
@@ -132,6 +195,7 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
|
||||
case NNReportRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("NNReportRequest"))
|
||||
session.handleReport(conn, addr, buffer[12:], moduleName, version)
|
||||
break
|
||||
|
||||
case NNReportReply:
|
||||
@@ -147,3 +211,140 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func getPortTypeName(portType byte) string {
|
||||
switch portType {
|
||||
default:
|
||||
return fmt.Sprintf("Unknown (0x%02x)", portType)
|
||||
|
||||
case PortTypeGamePort:
|
||||
return "GamePort"
|
||||
|
||||
case PortTypeNATNEG1:
|
||||
return "NATNEG1"
|
||||
|
||||
case PortTypeNATNEG2:
|
||||
return "NATNEG2"
|
||||
|
||||
case PortTypeNATNEG3:
|
||||
return "NATNEG3"
|
||||
}
|
||||
}
|
||||
|
||||
func (session *NATNEGSession) handleInit(conn net.PacketConn, addr net.Addr, buffer []byte, moduleName string, version byte) {
|
||||
portType := buffer[0]
|
||||
clientIndex := buffer[1]
|
||||
useGamePort := buffer[2]
|
||||
localIPBytes := buffer[3:7]
|
||||
localPort := binary.BigEndian.Uint16(buffer[7:9])
|
||||
gameName := common.GetString(buffer[9:])
|
||||
|
||||
expectedSize := 9 + len(gameName) + 1
|
||||
if len(buffer) != expectedSize {
|
||||
logging.Warn(moduleName, "Stray", aurora.BrightCyan(len(buffer)-expectedSize), "bytes after packet")
|
||||
}
|
||||
|
||||
localIPStr := fmt.Sprintf("%d.%d.%d.%d:%d", localIPBytes[0], localIPBytes[1], localIPBytes[2], localIPBytes[3], localPort)
|
||||
|
||||
logging.Info(moduleName, "Game Name:", aurora.Cyan(gameName), "Version:", aurora.Cyan(version), "Port Type:", aurora.Yellow(getPortTypeName(portType)), "Client Index:", aurora.Cyan(clientIndex), "Use Game Port:", aurora.Cyan(useGamePort))
|
||||
logging.Info(moduleName, "Local IP:", aurora.Cyan(localIPStr))
|
||||
|
||||
if portType > 0x03 {
|
||||
logging.Error(moduleName, "Invalid port type")
|
||||
return
|
||||
}
|
||||
if useGamePort > 1 {
|
||||
logging.Error(moduleName, "Invalid", aurora.BrightGreen("Use Game Port"), "value")
|
||||
return
|
||||
}
|
||||
if useGamePort == 0 && portType == PortTypeGamePort {
|
||||
logging.Error(moduleName, "Request uses game port but use game port is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
// Write the init acknowledgement to the requester address
|
||||
ackHeader := createPacketHeader(version, NNInitReply, session.Cookie)
|
||||
ackHeader = append(ackHeader, portType, clientIndex)
|
||||
ackHeader = append(ackHeader, 0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea)
|
||||
conn.WriteTo(ackHeader, addr)
|
||||
|
||||
sender, exists := session.Clients[clientIndex]
|
||||
if !exists {
|
||||
logging.Notice(moduleName, "Creating client index", aurora.Cyan(clientIndex))
|
||||
sender = &NATNEGClient{
|
||||
Cookie: session.Cookie,
|
||||
Connected: false,
|
||||
NegotiateIP: "",
|
||||
LocalIP: "",
|
||||
ServerIP: "",
|
||||
GameName: "",
|
||||
}
|
||||
session.Clients[clientIndex] = sender
|
||||
}
|
||||
|
||||
sender.Connected = false
|
||||
sender.GameName = gameName
|
||||
|
||||
if portType != PortTypeGamePort {
|
||||
sender.NegotiateIP = addr.String()
|
||||
}
|
||||
if localPort != 0 {
|
||||
sender.LocalIP = localIPStr
|
||||
}
|
||||
if useGamePort == 0 || portType == PortTypeGamePort {
|
||||
sender.ServerIP = addr.String()
|
||||
}
|
||||
|
||||
if !sender.isMapped() {
|
||||
return
|
||||
}
|
||||
logging.Notice(moduleName, "Mapped", aurora.BrightCyan(sender.NegotiateIP), aurora.BrightCyan(sender.LocalIP), aurora.BrightCyan(sender.ServerIP))
|
||||
|
||||
for id, destination := range session.Clients {
|
||||
if id == clientIndex || destination.Connected || !destination.isMapped() {
|
||||
continue
|
||||
}
|
||||
|
||||
logging.Notice(moduleName, "Exchange connect requests")
|
||||
|
||||
// Send the requests back and forth
|
||||
// TODO: Send again if no reply received from client
|
||||
sender.sendConnectRequest(conn, destination, version)
|
||||
destination.sendConnectRequest(conn, sender, version)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *NATNEGClient) isMapped() bool {
|
||||
if client.NegotiateIP == "" || client.LocalIP == "" || client.ServerIP == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func createPacketHeader(version byte, command byte, cookie uint32) []byte {
|
||||
header := []byte{0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2, version, command}
|
||||
return binary.BigEndian.AppendUint32(header, cookie)
|
||||
}
|
||||
|
||||
func (client *NATNEGClient) sendConnectRequest(conn net.PacketConn, destination *NATNEGClient, version byte) {
|
||||
connectHeader := createPacketHeader(version, NNConnectRequest, destination.Cookie)
|
||||
connectHeader = append(connectHeader, common.IPFormatBytes(client.ServerIP)...)
|
||||
_, port := common.IPFormatToInt(client.ServerIP)
|
||||
connectHeader = binary.BigEndian.AppendUint16(connectHeader, port)
|
||||
// Two bytes: "gotyourdata" and "finished"
|
||||
connectHeader = append(connectHeader, 0x42, 0x00)
|
||||
|
||||
destIPAddr, err := net.ResolveUDPAddr("udp", destination.NegotiateIP)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conn.WriteTo(connectHeader, destIPAddr)
|
||||
}
|
||||
|
||||
func (session *NATNEGSession) handleReport(conn net.PacketConn, addr net.Addr, buffer []byte, moduleName string, version byte) {
|
||||
response := createPacketHeader(version, NNReportReply, session.Cookie)
|
||||
response = append(response, buffer[:9]...)
|
||||
response[14] = 0
|
||||
conn.WriteTo(response, addr)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/gpcm"
|
||||
@@ -167,18 +166,7 @@ func GetSessionServers() []map[string]string {
|
||||
}
|
||||
|
||||
func SendClientMessage(destIP string, message []byte) {
|
||||
var rawIP int
|
||||
for i, s := range strings.Split(strings.Split(destIP, ":")[0], ".") {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rawIP |= val << (24 - i*8)
|
||||
}
|
||||
|
||||
// TODO: Check if this handles negative numbers correctly
|
||||
destIPIntStr := strconv.FormatInt(int64(int32(rawIP)), 10)
|
||||
destIPIntStr, destPortStr := common.IPFormatToString(destIP)
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
@@ -194,7 +182,7 @@ func SendClientMessage(destIP string, message []byte) {
|
||||
continue
|
||||
}
|
||||
|
||||
if session.Data["publicip"] == destIPIntStr {
|
||||
if session.Data["publicip"] == destIPIntStr && session.Data["publicport"] == destPortStr {
|
||||
// Found the client, now send the message
|
||||
payload := createResponseHeader(ClientMessageRequest, session.SessionID)
|
||||
mutex.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user