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
6 changed files with 432 additions and 150 deletions

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:])
}