ServerBrowser: Use random search ID rather than public IP

This commit is contained in:
mkwcat 2023-12-07 00:03:13 -05:00
parent b9f2a7c9aa
commit e510b2ccd6
No known key found for this signature in database
GPG Key ID: 7A505679CE9E7AA9
6 changed files with 432 additions and 150 deletions

View File

@ -354,6 +354,92 @@ func DecodeMatchCommand(command byte, buffer []byte) (MatchCommandData, bool) {
return MatchCommandData{}, false
}
func EncodeMatchCommand(command byte, data MatchCommandData) ([]byte, bool) {
switch command {
case MatchReservation:
message := binary.LittleEndian.AppendUint32([]byte{}, uint32(data.Reservation.MatchType))
message = binary.BigEndian.AppendUint32(message, data.Reservation.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.Reservation.PublicPort))
message = binary.BigEndian.AppendUint32(message, data.Reservation.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.Reservation.LocalPort))
message = binary.LittleEndian.AppendUint32(message, data.Reservation.Unknown)
isFriendInt := uint32(0)
if data.Reservation.IsFriend {
isFriendInt = 1
}
message = binary.LittleEndian.AppendUint32(message, isFriendInt)
message = binary.LittleEndian.AppendUint32(message, data.Reservation.LocalPlayerCount)
message = binary.LittleEndian.AppendUint32(message, data.Reservation.ResvCheckValue)
return message, true
case MatchResvOK:
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvOK.MaxPlayers)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.SenderAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ProfileID)
message = binary.BigEndian.AppendUint32(message, data.ResvOK.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.PublicPort))
message = binary.BigEndian.AppendUint32(message, data.ResvOK.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.LocalPort))
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.Unknown)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.LocalPlayerCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.GroupID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ReceiverNewAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ClientCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ResvCheckValue)
return message, true
case MatchResvDeny:
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvDeny.Reason)
return message, true
case MatchResvWait:
return []byte{}, true
case MatchResvCancel:
return []byte{}, true
case MatchTellAddr:
message := binary.BigEndian.AppendUint32([]byte{}, data.TellAddr.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.TellAddr.LocalPort))
return message, true
case MatchServerCloseClient:
message := []byte{}
for i := 0; i < len(data.ServerCloseClient.ProfileIDs); i++ {
message = binary.LittleEndian.AppendUint32(message, data.ServerCloseClient.ProfileIDs[i])
}
return message, true
case MatchSuspendMatch:
message := binary.LittleEndian.AppendUint32([]byte{}, data.SuspendMatch.HostProfileID)
isHostInt := uint32(0)
if data.SuspendMatch.IsHost {
isHostInt = 1
}
message = binary.LittleEndian.AppendUint32(message, isHostInt)
if data.SuspendMatch.SuspendValue != nil {
suspendValueInt := uint32(0)
if *data.SuspendMatch.SuspendValue {
suspendValueInt = 1
}
message = binary.LittleEndian.AppendUint32(message, suspendValueInt)
if data.SuspendMatch.ClientAID != nil {
message = binary.LittleEndian.AppendUint32(message, *data.SuspendMatch.ClientAID)
} else if data.SuspendMatch.ClientAIDUsageMask != nil {
message = binary.LittleEndian.AppendUint32(message, *data.SuspendMatch.ClientAIDUsageMask)
}
}
return message, true
}
return []byte{}, false
}
func LogMatchCommand(moduleName string, dest string, command byte, data MatchCommandData) {
logging.Notice(moduleName, "Match", aurora.Yellow(GetMatchCommandString(command)), "to", aurora.BrightCyan(dest))

View File

@ -19,14 +19,28 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
payload := map[string]string{}
for i := 0; i < len(values); i += 2 {
if values[i] == "" {
break
if len(values[i]) == 0 || values[i][0] == '+' {
continue
}
payload[values[i]] = values[i+1]
logging.Info(moduleName, aurora.Cyan(values[i]).String()+":", aurora.Cyan(values[i+1]))
}
realIP, realPort := common.IPFormatToString(addr.String())
if ip, ok := payload["publicip"]; !ok || ip == "0" {
// Set the public IP key to the real IP
payload["publicip"] = realIP
payload["publicport"] = realPort
}
// Client is mistaken about its public IP
if payload["publicip"] != realIP || payload["publicport"] != realPort {
logging.Error(moduleName, "Public IP mismatch")
return
}
if statechanged, ok := payload["statechanged"]; ok {
if statechanged == "1" {
// TODO: This would be a good place to run the server->client message exploit
@ -43,21 +57,12 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
}
}
realIP, realPort := common.IPFormatToString(addr.String())
publicIPKey, hasPublicIPKey := payload["publicip"]
if !hasPublicIPKey || publicIPKey != realIP {
// Set the public IP key to the real IP, and then send the challenge (done later)
payload["publicip"] = realIP
payload["publicport"] = realPort
}
session, ok := setSessionData(sessionId, payload)
session, ok := setSessionData(sessionId, payload, addr)
if !ok {
return
}
if !session.Authenticated || !hasPublicIPKey || publicIPKey != realIP {
if !session.Authenticated {
logging.Notice(moduleName, "Sending challenge")
sendChallenge(conn, addr, session)
return

View File

@ -5,19 +5,11 @@ import (
"github.com/logrusorgru/aurora/v3"
"net"
"strconv"
"sync"
"time"
"wwfc/common"
"wwfc/logging"
)
var (
// I would use a sync.Map instead of the map mutex combo, but this performs better.
sessions = map[uint32]*Session{}
mutex = sync.RWMutex{}
masterConn net.PacketConn
)
const (
QueryRequest = 0x00
ChallengeRequest = 0x01
@ -32,6 +24,8 @@ const (
ClientRegisteredReply = 0x0A
)
var masterConn net.PacketConn
func StartServer() {
// Get config
config := common.GetConfig()

View File

@ -4,8 +4,10 @@ import (
"bytes"
"encoding/binary"
"github.com/logrusorgru/aurora/v3"
"math/rand"
"net"
"strconv"
"sync"
"time"
"wwfc/common"
"wwfc/gpcm"
@ -20,6 +22,7 @@ const (
type Session struct {
SessionID uint32
SearchID uint64
Addr net.Addr
Challenge string
Authenticated bool
@ -29,13 +32,19 @@ type Session struct {
PacketCount uint32
}
var (
// I would use a sync.Map instead of the map mutex combo, but this performs better.
sessions = map[uint32]*Session{}
mutex = sync.RWMutex{}
)
// Remove a session.
func removeSession(sessionId uint32) {
delete(sessions, sessionId)
}
// Update session data, creating the session if it doesn't exist. Returns a copy of the session data.
func setSessionData(sessionId uint32, payload map[string]string) (Session, bool) {
func setSessionData(sessionId uint32, payload map[string]string, addr net.Addr) (Session, bool) {
moduleName := "QR2:" + strconv.FormatInt(int64(sessionId), 10)
// Perform sanity checks on the session data. This is a mess but
@ -55,6 +64,11 @@ func setSessionData(sessionId uint32, payload map[string]string) (Session, bool)
defer mutex.Unlock()
session, sessionExists := sessions[sessionId]
if sessionExists && session.Data["publicip"] != publicIP {
logging.Error(moduleName, "Public IP mismatch")
return Session{}, false
}
if newPIDValid {
var oldPID string
oldPIDValid := false
@ -117,6 +131,8 @@ func setSessionData(sessionId uint32, payload map[string]string) (Session, bool)
logging.Notice(moduleName, "Creating session", aurora.Cyan(sessionId).String())
data := Session{
SessionID: sessionId,
SearchID: uint64(rand.Int63n(0x400 << 32)),
Addr: addr,
Challenge: "",
Authenticated: false,
LastKeepAlive: time.Now().Unix(),
@ -124,11 +140,13 @@ func setSessionData(sessionId uint32, payload map[string]string) (Session, bool)
Data: payload,
PacketCount: 0,
}
sessions[sessionId] = &data
data.Data["+searchid"] = strconv.FormatUint(data.SearchID, 10)
sessions[sessionId] = &data
return data, true
}
payload["+searchid"] = session.Data["+searchid"]
session.Data = payload
session.LastKeepAlive = time.Now().Unix()
return *session, true
@ -168,11 +186,79 @@ func GetSessionServers() []map[string]string {
return servers
}
func SendClientMessage(destIP string, message []byte) {
func getSessionByPublicIP(publicIP uint32, publicPort uint16) *Session {
ipStr := strconv.FormatInt(int64(int32(publicIP)), 10)
portStr := strconv.FormatUint(uint64(publicPort), 10)
currentTime := time.Now().Unix()
mutex.Lock()
defer mutex.Unlock()
// Find the session with the IP
for _, session := range sessions {
if !session.Authenticated {
continue
}
// If the last keep alive was over a minute ago then consider the server unreachable
if session.LastKeepAlive < currentTime-60 {
continue
}
if session.Data["publicip"] == ipStr && session.Data["publicport"] == portStr {
return session
}
}
return nil
}
func getSessionBySearchID(searchID uint64) *Session {
currentTime := time.Now().Unix()
mutex.Lock()
defer mutex.Unlock()
// Find the session with the ID
for _, session := range sessions {
if !session.Authenticated {
continue
}
// If the last keep alive was over a minute ago then consider the server unreachable
if session.LastKeepAlive < currentTime-60 {
continue
}
if session.SearchID == searchID {
return session
}
}
return nil
}
func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
moduleName := "QR2/MSG"
var matchData common.MatchCommandData
senderProfileID := uint32(0)
var sender *Session
var receiver *Session
senderIPInt, _ := common.IPFormatToInt(senderIP)
useSearchID := destSearchID < (0x400 << 32)
if useSearchID {
receiver = getSessionBySearchID(destSearchID)
} else {
// It's an IP address, used in some circumstances
receiverIPInt := uint32(destSearchID & 0xffffffff)
receiverPort := uint16(destSearchID >> 32)
receiver = getSessionByPublicIP(receiverIPInt, receiverPort)
}
if receiver == nil {
logging.Error(moduleName, "Destination", aurora.Cyan(destSearchID), "does not exist")
return
}
// Decode and validate the message
isNatnegPacket := false
@ -186,7 +272,6 @@ func SendClientMessage(destIP string, message []byte) {
senderSessionID := binary.LittleEndian.Uint32(message[0x6:0xA])
moduleName = "QR2/MSG:s" + strconv.FormatUint(uint64(senderSessionID), 10)
} else if bytes.Equal(message[:4], []byte{0xbb, 0x49, 0xcc, 0x4d}) {
// DWC match command
if len(message) < 0x14 {
@ -194,7 +279,7 @@ func SendClientMessage(destIP string, message []byte) {
return
}
senderProfileID = binary.LittleEndian.Uint32(message[0x10:0x14])
senderProfileID := binary.LittleEndian.Uint32(message[0x10:0x14])
moduleName = "QR2/MSG:p" + strconv.FormatUint(uint64(senderProfileID), 10)
if (int(message[9]) + 0x14) != len(message) {
@ -202,6 +287,19 @@ func SendClientMessage(destIP string, message []byte) {
return
}
qr2IP := binary.BigEndian.Uint32(message[0x0C:0x10])
qr2Port := binary.LittleEndian.Uint16(message[0x0A:0x0C])
if senderIPInt != int32(qr2IP) {
logging.Error(moduleName, "Wrong QR2 IP in match command packet header")
}
sender = getSessionByPublicIP(qr2IP, qr2Port)
if sender == nil {
logging.Error(moduleName, "Session does not exist with QR2 IP and port")
return
}
var ok bool
matchData, ok = common.DecodeMatchCommand(message[8], message[0x14:])
if !ok {
@ -216,10 +314,9 @@ func SendClientMessage(destIP string, message []byte) {
return
}
if common.IsReservedIP(int32(matchData.Reservation.PublicIP)) {
logging.Warn(moduleName, "RESERVATION: Public IP is reserved")
// Temporarily disabled for localhost testing
// TODO: Add a config option or something
if qr2IP != matchData.Reservation.PublicIP {
logging.Error(moduleName, "RESERVATION: Public IP mismatch in header and command")
return
}
if matchData.Reservation.PublicPort < 1024 {
@ -231,12 +328,19 @@ func SendClientMessage(destIP string, message []byte) {
logging.Error(moduleName, "RESERVATION: Local port is reserved")
return
}
if useSearchID {
matchData.Reservation.PublicIP = uint32(sender.SearchID & 0xffffffff)
matchData.Reservation.PublicPort = uint16((sender.SearchID >> 32) & 0xffff)
matchData.Reservation.LocalIP = 0
matchData.Reservation.LocalPort = 0
}
}
if message[8] == common.MatchResvOK {
if common.IsReservedIP(int32(matchData.ResvOK.PublicIP)) {
logging.Warn(moduleName, "RESV_OK: Public IP is reserved")
// TODO: See above
if qr2IP != matchData.ResvOK.PublicIP {
logging.Error(moduleName, "RESERVATION: Public IP mismatch in header and command")
return
}
if matchData.ResvOK.PublicPort < 1024 {
@ -253,69 +357,81 @@ func SendClientMessage(destIP string, message []byte) {
logging.Error(moduleName, "RESV_OK: Profile ID mismatch in header")
return
}
if useSearchID {
matchData.ResvOK.PublicIP = uint32(sender.SearchID & 0xffffffff)
matchData.ResvOK.PublicPort = uint16((sender.SearchID >> 32) & 0xffff)
matchData.ResvOK.LocalIP = 0
matchData.ResvOK.LocalPort = 0
}
}
if message[8] == common.MatchTellAddr {
// TODO: Check if the public IPs are actually the same
mutex.Lock()
if sender.Data["publicip"] != receiver.Data["publicip"] {
mutex.Unlock()
logging.Error(moduleName, "TELL_ADDR: Public IP does not match receiver")
return
}
mutex.Unlock()
if matchData.TellAddr.LocalPort < 1024 {
logging.Error(moduleName, "TELL_ADDR: Local port is reserved")
return
}
}
}
destIPIntStr, destPortStr := common.IPFormatToString(destIP)
currentTime := time.Now().Unix()
mutex.Lock()
// Find the session with the IP
for _, session := range sessions {
if !session.Authenticated {
continue
if useSearchID {
// Convert public IP to search ID
qr2SearchID := binary.LittleEndian.AppendUint16([]byte{}, uint16((sender.SearchID>>32)&0xffff))
qr2SearchID = binary.BigEndian.AppendUint32(qr2SearchID, uint32(sender.SearchID&0xffffffff))
message = append(message[:0x0A], append(qr2SearchID, message[0x10:0x14]...)...)
} else {
message = message[:0x14]
}
// If the last keep alive was over a minute ago then consider the server unreachable
if session.LastKeepAlive < currentTime-60 {
continue
}
if session.Data["publicip"] == destIPIntStr && session.Data["publicport"] == destPortStr {
destPid, ok := session.Data["dwc_pid"]
if !ok || destPid == "" {
destPid = "<UNKNOWN>"
}
destSessionID := session.SessionID
packetCount := session.PacketCount + 1
session.PacketCount = packetCount
mutex.Unlock()
if isNatnegPacket {
cookie := binary.BigEndian.Uint32(message[0x2:0x6])
logging.Notice(moduleName, "Send NN cookie", aurora.Cyan(cookie), "to", aurora.BrightCyan(destPid))
} else {
common.LogMatchCommand(moduleName, destPid, message[8], matchData)
}
// Found the client, now send the message
payload := createResponseHeader(ClientMessageRequest, destSessionID)
payload = append(payload, []byte{0, 0, 0, 0}...)
binary.BigEndian.PutUint32(payload[len(payload)-4:], packetCount)
payload = append(payload, message...)
destIPAddr, err := net.ResolveUDPAddr("udp", destIP)
if err != nil {
panic(err)
}
// TODO: Send again if no CLIENT_MESSAGE_ACK is received after
masterConn.WriteTo(payload, destIPAddr)
var matchMessage []byte
matchMessage, ok = common.EncodeMatchCommand(message[8], matchData)
if !ok {
logging.Error(moduleName, "Failed to reencode match command:", aurora.Cyan(message[8]))
return
}
if len(matchMessage) != 0 {
message = append(message, matchMessage...)
}
}
mutex.Lock()
destPid, ok := receiver.Data["dwc_pid"]
if !ok || destPid == "" {
destPid = "<UNKNOWN>"
}
destSessionID := receiver.SessionID
packetCount := receiver.PacketCount + 1
receiver.PacketCount = packetCount
destAddr := receiver.Addr
mutex.Unlock()
logging.Error(moduleName, "Could not find destination server")
if isNatnegPacket {
cookie := binary.BigEndian.Uint32(message[0x2:0x6])
logging.Notice(moduleName, "Send NN cookie", aurora.Cyan(cookie), "to", aurora.BrightCyan(destPid))
} else {
common.LogMatchCommand(moduleName, destPid, message[8], matchData)
}
payload := createResponseHeader(ClientMessageRequest, destSessionID)
payload = append(payload, []byte{0, 0, 0, 0}...)
binary.BigEndian.PutUint32(payload[len(payload)-4:], packetCount)
payload = append(payload, message...)
// TODO: Send again if no CLIENT_MESSAGE_ACK is received after
_, err := masterConn.WriteTo(payload, destAddr)
if err != nil {
logging.Error(moduleName, "Error sending message:", err.Error())
}
}

View File

@ -2,7 +2,6 @@ package serverbrowser
import (
"github.com/logrusorgru/aurora/v3"
"regexp"
"wwfc/logging"
"wwfc/serverbrowser/filter"
)
@ -15,8 +14,6 @@ import (
// Example: dwc_mver = 90 and dwc_pid != 43 and maxplayers = 11 and numplayers < 11 and dwc_mtype = 0 and dwc_hoststate = 2 and dwc_suspend = 0 and (rk = 'vs' and ev >= 4250 and ev <= 5750 and p = 0)
var regexSelfLookup = regexp.MustCompile(`^dwc_pid ?= ?(\d{1,10})$`)
func filterServers(servers []map[string]string, queryGame string, expression string, publicIP string) []map[string]string {
if match := regexSelfLookup.FindStringSubmatch(expression); match != nil {
dwc_pid := match[1]
@ -36,7 +33,8 @@ func filterServers(servers []map[string]string, queryGame string, expression str
}
logging.Info(ModuleName, "Self lookup from", aurora.Cyan(dwc_pid), "ok")
return []map[string]string{server}
filtered = []map[string]string{server}
break
}
// Alternatively, if the server hasn't set its dwc_pid field yet, we return servers matching the request's public IP.
@ -96,3 +94,47 @@ func filterServers(servers []map[string]string, queryGame string, expression str
logging.Info(ModuleName, "Matched", aurora.BrightCyan(len(filtered)), "servers")
return filtered
}
func filterSelfLookup(servers []map[string]string, queryGame string, dwc_pid string, publicIP string) []map[string]string {
filtered := []map[string]string{}
// Search for where the profile ID matches
for _, server := range servers {
if server["gamename"] != queryGame {
continue
}
if server["dwc_pid"] == dwc_pid {
if server["publicip"] != publicIP {
logging.Error(ModuleName, "Self lookup", aurora.Cyan(dwc_pid), "from wrong IP")
return []map[string]string{}
}
logging.Info(ModuleName, "Self lookup from", aurora.Cyan(dwc_pid), "ok")
return []map[string]string{server}
}
// Alternatively, if the server hasn't set its dwc_pid field yet, we return servers matching the request's public IP.
// If multiple servers exist with the same public IP then the client will use the one with the matching port.
// This is a bit of a hack to speed up server creation.
if _, ok := server["dwc_pid"]; !ok && server["publicip"] == publicIP {
// Create a copy of the map with some values changed
newServer := map[string]string{}
for k, v := range server {
newServer[k] = v
}
newServer["dwc_pid"] = dwc_pid
newServer["dwc_mtype"] = "0"
newServer["dwc_mver"] = "0"
filtered = append(filtered, newServer)
}
}
if len(filtered) == 0 {
logging.Error(ModuleName, "Could not find server with dwc_pid", aurora.Cyan(dwc_pid))
return []map[string]string{}
}
logging.Info(ModuleName, "Self lookup for", aurora.Cyan(dwc_pid), "matched", aurora.BrightCyan(len(filtered)), "servers via public IP")
return filtered
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/logrusorgru/aurora/v3"
"net"
"regexp"
"strconv"
"strings"
"wwfc/common"
@ -51,6 +52,8 @@ func popUint32(buffer []byte, index int) (uint32, int) {
return binary.BigEndian.Uint32(buffer[index:]), index + 4
}
var regexSelfLookup = regexp.MustCompile(`^dwc_pid ?= ?(\d{1,10})$`)
func handleServerListRequest(conn net.Conn, buffer []byte) {
index := 9
queryGame, index := popString(buffer, index)
@ -84,6 +87,11 @@ func handleServerListRequest(conn net.Conn, buffer []byte) {
continue
}
// Skip private fields
if field == "publicip" || field == "publicport" || strings.HasPrefix(field, "localip") || field == "localport" {
continue
}
fieldList = append(fieldList, field)
}
@ -116,8 +124,15 @@ func handleServerListRequest(conn net.Conn, buffer []byte) {
}
output = append(output, 0x00) // Zero length string to end the list
publicIP, _ := common.IPFormatToString(conn.RemoteAddr().String())
servers := filterServers(qr2.GetSessionServers(), queryGame, filter, publicIP)
callerPublicIP, _ := common.IPFormatToString(conn.RemoteAddr().String())
var servers []map[string]string
if match := regexSelfLookup.FindStringSubmatch(filter); match != nil {
// Self lookup is handled differently
servers = filterSelfLookup(qr2.GetSessionServers(), queryGame, match[1], callerPublicIP)
} else {
servers = filterServers(qr2.GetSessionServers(), queryGame, filter, callerPublicIP)
}
for _, server := range servers {
var flags byte
@ -138,82 +153,107 @@ func handleServerListRequest(conn net.Conn, buffer []byte) {
continue
}
ip, err := strconv.ParseInt(publicip, 10, 32)
if err != nil {
logging.Error(ModuleName, "Server has invalid public IP value:", aurora.Cyan(publicip))
}
flagsBuffer = binary.BigEndian.AppendUint32(flagsBuffer, uint32(ip))
var port string
port, exists = server["publicport"]
if !exists {
// Fall back to local port if public port doesn't exist
if port, exists = server["localport"]; !exists {
logging.Error(ModuleName, "Server exists without port (publicip =", aurora.Cyan(publicip).String()+")")
continue
if publicip == callerPublicIP {
// Use the real public IP if it matches the caller's
ip, err := strconv.ParseInt(publicip, 10, 32)
if err != nil {
logging.Error(ModuleName, "Server has invalid public IP value:", aurora.Cyan(publicip))
}
}
portValue, err := strconv.ParseUint(port, 10, 16)
if err != nil {
logging.Error(ModuleName, "Server has invalid port value:", aurora.Cyan(port))
continue
}
flagsBuffer = binary.BigEndian.AppendUint32(flagsBuffer, uint32(ip))
flags |= NonstandardPortFlag
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
var port string
port, exists = server["publicport"]
if !exists {
// Fall back to local port if public port doesn't exist
if port, exists = server["localport"]; !exists {
logging.Error(ModuleName, "Server exists without port (publicip =", aurora.Cyan(publicip).String()+")")
continue
}
}
// Use the first local IP if it exists, this is used to skip natneg if multiple players are on the same network
if localip0, exists := server["localip0"]; exists {
flags |= PrivateIPFlag
// localip is written like "192.168.255.255" for example, so it needs to be parsed
ipSplit := strings.Split(localip0, ".")
if len(ipSplit) != 4 {
logging.Error(ModuleName, "Server has invalid local IP:", aurora.Cyan(localip0))
portValue, err := strconv.ParseUint(port, 10, 16)
if err != nil {
logging.Error(ModuleName, "Server has invalid port value:", aurora.Cyan(port))
continue
}
err = nil
for _, s := range ipSplit {
val, err := strconv.ParseUint(s, 10, 8)
if err != nil {
break
if portValue < 1024 {
logging.Error(ModuleName, "Server uses reserved port:", aurora.Cyan(portValue))
continue
}
flags |= NonstandardPortFlag
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
// Use the first local IP if it exists
if localip0, exists := server["localip0"]; exists {
flags |= PrivateIPFlag
// localip is written like "192.168.255.255" for example, so it needs to be parsed
ipSplit := strings.Split(localip0, ".")
if len(ipSplit) != 4 {
logging.Error(ModuleName, "Server has invalid local IP:", aurora.Cyan(localip0))
continue
}
flagsBuffer = append(flagsBuffer, byte(val))
err = nil
for _, s := range ipSplit {
val, err := strconv.ParseUint(s, 10, 8)
if err != nil {
break
}
flagsBuffer = append(flagsBuffer, byte(val))
}
if err != nil {
logging.Error(ModuleName, "Server has invalid local IP value:", aurora.Cyan(localip0))
continue
}
}
if err != nil {
logging.Error(ModuleName, "Server has invalid local IP value:", aurora.Cyan(localip0))
continue
}
}
if localport, exists := server["localport"]; exists {
portValue, err = strconv.ParseUint(localport, 10, 16)
if err != nil {
logging.Error(ModuleName, "Server has invalid local port value:", aurora.Cyan(localport))
continue
}
if localport, exists := server["localport"]; exists {
portValue, err = strconv.ParseUint(localport, 10, 16)
if err != nil {
logging.Error(ModuleName, "Server has invalid local port value:", aurora.Cyan(localport))
flags |= NonstandardPrivatePortFlag
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
}
// Just a dummy IP? This is taken from dwc_network_server_emulator
// TODO: Check if this is actually needed
flags |= ICMPIPFlag
flagsBuffer = append(flagsBuffer, []byte{0, 0, 0, 0}...)
} else {
// Regular server, hide the public IP until match reservation is made
var searchIDStr string
if searchIDStr, exists = server["+searchid"]; !exists {
logging.Error(ModuleName, "Server exists without search ID")
continue
}
flags |= NonstandardPrivatePortFlag
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
searchID, err := strconv.ParseInt(searchIDStr, 10, 64)
if err != nil {
logging.Error(ModuleName, "Server has invalid search ID value:", aurora.Cyan(searchIDStr))
}
// Append low value as public IP
flagsBuffer = binary.BigEndian.AppendUint32(flagsBuffer, uint32(searchID&0xffffffff))
// Append high value as public port
flags |= NonstandardPortFlag
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16((searchID>>32)&0xffff))
}
// Just a dummy IP? This is taken from dwc_network_server_emulator
// TODO: Check if this is actually needed
flags |= ICMPIPFlag
flagsBuffer = append(flagsBuffer, []byte{0, 0, 0, 0}...)
// Finally, write the server buffer to the output
// Append the server buffer to the output
output = append(output, flags)
output = append(output, flagsBuffer...)
if (flags & HasKeysFlag) == 0 {
// Server does not have keys, so skip them
logging.Info(ModuleName, "Wrote server without keys")
continue
}
@ -225,11 +265,9 @@ func handleServerListRequest(conn net.Conn, buffer []byte) {
output = append(output, []byte(str)...)
}
// Add null terminator so the string will be empty if the field doesn't exist
// Add null terminator
output = append(output, 0x00)
}
logging.Info(ModuleName, "Wrote server with keys")
}
// Server with 0 flags and IP of 0xffffffff terminates the list
@ -240,10 +278,11 @@ func handleServerListRequest(conn net.Conn, buffer []byte) {
}
func handleSendMessageRequest(conn net.Conn, buffer []byte) {
// Read destination IP from buffer
destIP := fmt.Sprintf("%d.%d.%d.%d:%d", buffer[3], buffer[4], buffer[5], buffer[6], binary.BigEndian.Uint16(buffer[7:9]))
// Read search ID from buffer
searchID := uint64(binary.BigEndian.Uint32(buffer[3:7]))
searchID |= uint64(binary.BigEndian.Uint16(buffer[7:9])) << 32
logging.Notice(ModuleName, "Send message from", aurora.BrightCyan(conn.RemoteAddr()), "to", aurora.BrightCyan(destIP).String())
logging.Notice(ModuleName, "Send message from", aurora.BrightCyan(conn.RemoteAddr()), "to", aurora.Cyan(fmt.Sprintf("%012x", searchID)))
qr2.SendClientMessage(destIP, buffer[9:])
qr2.SendClientMessage(conn.RemoteAddr().String(), searchID, buffer[9:])
}