mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-23 00:54:11 -05:00
MASTER: Server list and session logic
This commit is contained in:
@@ -8,34 +8,35 @@ import (
|
||||
"wwfc/common"
|
||||
)
|
||||
|
||||
func sendChallenge(conn net.PacketConn, addr net.Addr, sessionId uint32) {
|
||||
addrString := strings.Split(addr.String(), ":")
|
||||
func sendChallenge(conn net.PacketConn, addr net.Addr, session Session) {
|
||||
challenge := session.Challenge
|
||||
if challenge == "" {
|
||||
// Generate challenge
|
||||
addrString := strings.Split(addr.String(), ":")
|
||||
var hexIP string
|
||||
for _, i := range strings.Split(addrString[0], ".") {
|
||||
val, err := strconv.ParseUint(i, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Generate challenge and send to server
|
||||
var hexIP string
|
||||
for _, i := range strings.Split(addrString[0], ".") {
|
||||
val, err := strconv.ParseUint(i, 10, 64)
|
||||
hexIP += fmt.Sprintf("%02X", val)
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(addrString[1], 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hexIP += fmt.Sprintf("%02X", val)
|
||||
hexPort := fmt.Sprintf("%04X", port)
|
||||
|
||||
challenge = common.RandomString(6) + "00" + hexIP + hexPort
|
||||
mutex.Lock()
|
||||
sessions[session.SessionID].Challenge = challenge
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(addrString[1], 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hexPort := fmt.Sprintf("%04X", port)
|
||||
|
||||
challenge := common.RandomString(6) + "00" + hexIP + hexPort
|
||||
mutex.Lock()
|
||||
session := sessions[sessionId]
|
||||
session.Challenge = challenge
|
||||
mutex.Unlock()
|
||||
|
||||
response := createResponseHeader(CommandChallenge, sessionId)
|
||||
response := createResponseHeader(ChallengeRequest, session.SessionID)
|
||||
response = append(response, []byte(challenge)...)
|
||||
response = append(response, 0)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
moduleName := "AVAILABLE:" + strconv.FormatInt(int64(sessionId), 10)
|
||||
moduleName := "MASTER:" + strconv.FormatInt(int64(sessionId), 10)
|
||||
|
||||
logging.Notice(moduleName, "Received heartbeat from", aurora.Cyan(addr).String())
|
||||
values := strings.Split(string(buffer[5:]), "\u0000")
|
||||
@@ -26,28 +26,49 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
logging.Notice(moduleName, aurora.Cyan(values[i]).String()+":", aurora.Cyan(values[i+1]).String())
|
||||
}
|
||||
|
||||
publicip, ok := payload["publicip"]
|
||||
if !ok || publicip == "0" {
|
||||
sendChallenge(conn, addr, sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Check if the client is registered
|
||||
|
||||
statechanged, ok := payload["statechanged"]
|
||||
if ok {
|
||||
if statechanged, ok := payload["statechanged"]; ok {
|
||||
if statechanged == "1" {
|
||||
// statechanged is 1 and publicip is not 0
|
||||
// TODO: This would be a good place to run the server->client message exploit
|
||||
// for DNS patcher games that require code patches. The status code should be
|
||||
// set to 5 at this point, which is required.
|
||||
logging.Notice(moduleName, "Client server update")
|
||||
// set to 5 at this point (if publicip is not 0), which is required.
|
||||
logging.Notice(moduleName, "Client session update")
|
||||
// Fall through
|
||||
}
|
||||
|
||||
if statechanged == "2" {
|
||||
logging.Notice(moduleName, "Client server shutdown")
|
||||
logging.Notice(moduleName, "Client session shutdown")
|
||||
removeSession(sessionId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addrString := strings.Split(addr.String(), ":")
|
||||
|
||||
var rawIP int
|
||||
for i, s := range strings.Split(addrString[0], ".") {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rawIP |= val << (24 - i*8)
|
||||
}
|
||||
|
||||
// TODO: Check if this handles negative numbers correctly
|
||||
realIP := strconv.FormatInt(int64(int32(rawIP)), 10)
|
||||
realPort := addrString[1]
|
||||
|
||||
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 := setSessionData(sessionId, payload)
|
||||
if !session.Authenticated || !hasPublicIPKey || publicIPKey != realIP {
|
||||
logging.Notice(moduleName, "Sending challenge")
|
||||
sendChallenge(conn, addr, session)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"encoding/binary"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
@@ -16,17 +18,17 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
CommandQuery = 0x00
|
||||
CommandChallenge = 0x01
|
||||
CommandEcho = 0x02
|
||||
CommandHeartbeat = 0x03
|
||||
CommandAddError = 0x04
|
||||
CommandEchoResponse = 0x05
|
||||
CommandClientMessage = 0x06
|
||||
CommandClientMessageAck = 0x07
|
||||
CommandKeepAlive = 0x08
|
||||
CommandAvailable = 0x09
|
||||
CommandClientRegistered = 0x0A
|
||||
QueryRequest = 0x00
|
||||
ChallengeRequest = 0x01
|
||||
EchoRequest = 0x02
|
||||
HeartbeatRequest = 0x03
|
||||
AddErrorRequest = 0x04
|
||||
EchoResponseRequest = 0x05
|
||||
ClientMessageRequest = 0x06
|
||||
ClientMessageAckRequest = 0x07
|
||||
KeepAliveRequest = 0x08
|
||||
AvailableRequest = 0x09
|
||||
ClientRegisteredReply = 0x0A
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
@@ -55,61 +57,71 @@ func StartServer() {
|
||||
}
|
||||
|
||||
func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
if buffer[0] != 9 {
|
||||
addSession(addr, buffer)
|
||||
if buffer[0] == AvailableRequest {
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("AVAILABLE").String())
|
||||
conn.WriteTo(createResponseHeader(AvailableRequest, 0), addr)
|
||||
return
|
||||
}
|
||||
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
moduleName := "MASTER:" + strconv.FormatInt(int64(sessionId), 10)
|
||||
|
||||
switch buffer[0] {
|
||||
case CommandQuery:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("QUERY").String())
|
||||
case QueryRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("QUERY").String())
|
||||
break
|
||||
|
||||
case CommandChallenge:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("CHALLENGE").String())
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
conn.WriteTo(createResponseHeader(CommandClientRegistered, sessionId), addr)
|
||||
case ChallengeRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CHALLENGE").String())
|
||||
|
||||
mutex.Lock()
|
||||
sessions[sessionId].Authenticated = true
|
||||
mutex.Unlock()
|
||||
conn.WriteTo(createResponseHeader(ClientRegisteredReply, sessionId), addr)
|
||||
break
|
||||
|
||||
case CommandEcho:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("ECHO").String())
|
||||
case EchoRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("ECHO").String())
|
||||
break
|
||||
|
||||
case CommandHeartbeat:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("HEARTBEAT").String())
|
||||
case HeartbeatRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("HEARTBEAT").String())
|
||||
heartbeat(conn, addr, buffer)
|
||||
break
|
||||
|
||||
case CommandAddError:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("ADDERROR").String())
|
||||
case AddErrorRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("ADDERROR").String())
|
||||
break
|
||||
|
||||
case CommandEchoResponse:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("ECHO_RESPONSE").String())
|
||||
case EchoResponseRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("ECHO_RESPONSE").String())
|
||||
break
|
||||
|
||||
case CommandClientMessage:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("CLIENT_MESSAGE").String())
|
||||
case ClientMessageRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_MESSAGE").String())
|
||||
return
|
||||
|
||||
case CommandClientMessageAck:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("CLIENT_MESSAGE_ACK").String())
|
||||
case ClientMessageAckRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_MESSAGE_ACK").String())
|
||||
return
|
||||
|
||||
case CommandKeepAlive:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("KEEPALIVE").String())
|
||||
case KeepAliveRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("KEEPALIVE").String())
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
mutex.Lock()
|
||||
sessions[sessionId].LastKeepAlive = time.Now().Unix()
|
||||
mutex.Unlock()
|
||||
return
|
||||
|
||||
case CommandAvailable:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("AVAILABLE").String())
|
||||
conn.WriteTo(createResponseHeader(CommandAvailable, 0), addr)
|
||||
break
|
||||
case AvailableRequest:
|
||||
return
|
||||
|
||||
case CommandClientRegistered:
|
||||
logging.Notice("MASTER", "Command:", aurora.Yellow("QUERY").String())
|
||||
case ClientRegisteredReply:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_REGISTERED").String())
|
||||
break
|
||||
|
||||
default:
|
||||
logging.Notice("MASTER", "Unknown command:", aurora.Yellow(buffer[0]).String())
|
||||
logging.Notice(moduleName, "Unknown command:", aurora.Yellow(buffer[0]).String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,76 @@
|
||||
package master
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"time"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientNoEndian = iota
|
||||
ClientBigEndian
|
||||
ClientLittleEndian
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
SessionID uint32
|
||||
Challenge string
|
||||
SecretKey string
|
||||
IsConnected bool
|
||||
SessionID uint32
|
||||
Challenge string
|
||||
Authenticated bool
|
||||
LastKeepAlive int64
|
||||
Endianness byte // Some fields depend on the client's endianness
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
func addSession(addr net.Addr, buffer []byte) {
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
// 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 {
|
||||
mutex.Lock()
|
||||
if _, ok := sessions[sessionId]; !ok {
|
||||
sessions[sessionId] = &Session{
|
||||
SessionID: sessionId,
|
||||
Challenge: "",
|
||||
// TODO: This is hardcoded for Mario Kart Wii
|
||||
SecretKey: "9r3Rmy",
|
||||
IsConnected: true,
|
||||
defer mutex.Unlock()
|
||||
|
||||
session, exists := sessions[sessionId]
|
||||
if !exists {
|
||||
logging.Notice("MASTER", "Creating session", aurora.Cyan(sessionId).String())
|
||||
data := Session{
|
||||
SessionID: sessionId,
|
||||
Challenge: "",
|
||||
Authenticated: false,
|
||||
LastKeepAlive: time.Now().Unix(),
|
||||
Endianness: ClientNoEndian,
|
||||
Data: payload,
|
||||
}
|
||||
sessions[sessionId] = &data
|
||||
return data
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
session.Data = payload
|
||||
session.LastKeepAlive = time.Now().Unix()
|
||||
return *session
|
||||
}
|
||||
|
||||
// Get a copy of the list of servers
|
||||
func GetSessionServers() []map[string]string {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
var servers []map[string]string
|
||||
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
|
||||
}
|
||||
|
||||
servers = append(servers, session.Data)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
"wwfc/master"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,36 +28,18 @@ const (
|
||||
KeyTypeShort = 0x02
|
||||
|
||||
// Options for ServerListRequest
|
||||
SendFieldsForAllOption = 1 << 0 // 0x01 / 1
|
||||
NoServerListOption = 1 << 1 // 0x02 / 2
|
||||
PushUpdatesOption = 1 << 2 // 0x04 / 4
|
||||
AlternateSourceIPOption = 1 << 3 // 0x08 / 8
|
||||
SendGroupsOption = 1 << 5 // 0x20 / 32
|
||||
NoListCacheOption = 1 << 6 // 0x40 / 64
|
||||
LimitResultCountOption = 1 << 7 // 0x80 / 128
|
||||
)
|
||||
|
||||
func FindServers(gueryGame string, filter string) ([]map[string]string, error) {
|
||||
// TODO
|
||||
|
||||
// This is a temporary hardcoded server
|
||||
server := map[string]string{
|
||||
"localip0": "192.168.1.100",
|
||||
"localport": "64174",
|
||||
"natneg": "1",
|
||||
"gamename": "mariokartwii",
|
||||
"publicip": "2130706433", // 127.0.0.1
|
||||
"publicport": "64174",
|
||||
"numplayers": "0",
|
||||
"maxplayers": "11",
|
||||
"dwc_pid": "27",
|
||||
"dwc_mtype": "2",
|
||||
"dwc_mver": "90",
|
||||
"dwc_eval": "1",
|
||||
"dwc_groupid": "100000027",
|
||||
"dwc_hoststate": "2",
|
||||
"dwc_suspend": "1",
|
||||
}
|
||||
|
||||
return []map[string]string{server}, nil
|
||||
// TODO: Handle gueryGame, filter
|
||||
return master.GetSessionServers(), nil
|
||||
}
|
||||
|
||||
func popString(buffer []byte, index int) (string, int) {
|
||||
|
||||
Reference in New Issue
Block a user