mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-25 18:14:39 -05:00
QR2: Verify profile ID and public IP
This commit is contained in:
@@ -90,3 +90,23 @@ func (g *GameSpySession) Login(pool *pgxpool.Pool, ctx context.Context, command
|
||||
},
|
||||
}), true
|
||||
}
|
||||
|
||||
func IsLoggedIn(profileID uint32) bool {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
session, exists := sessions[profileID]
|
||||
return exists && session.LoggedIn
|
||||
}
|
||||
|
||||
func GetSessionIP(profileID uint32) string {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
session, exists := sessions[profileID]
|
||||
if exists && session.LoggedIn {
|
||||
return session.Conn.RemoteAddr().String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
@@ -42,21 +43,7 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
realIP, realPort := common.IPFormatToString(addr.String())
|
||||
|
||||
publicIPKey, hasPublicIPKey := payload["publicip"]
|
||||
if !hasPublicIPKey || publicIPKey != realIP {
|
||||
@@ -65,7 +52,11 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
payload["publicport"] = realPort
|
||||
}
|
||||
|
||||
session := setSessionData(sessionId, payload)
|
||||
session, ok := setSessionData(sessionId, payload)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !session.Authenticated || !hasPublicIPKey || publicIPKey != realIP {
|
||||
logging.Notice(moduleName, "Sending challenge")
|
||||
sendChallenge(conn, addr, session)
|
||||
|
||||
12
qr2/main.go
12
qr2/main.go
@@ -78,9 +78,15 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CHALLENGE"))
|
||||
|
||||
mutex.Lock()
|
||||
sessions[sessionId].Authenticated = true
|
||||
mutex.Unlock()
|
||||
conn.WriteTo(createResponseHeader(ClientRegisteredReply, sessionId), addr)
|
||||
if sessions[sessionId].Challenge != "" {
|
||||
// TODO: Verify the challenge
|
||||
sessions[sessionId].Authenticated = true
|
||||
mutex.Unlock()
|
||||
|
||||
conn.WriteTo(createResponseHeader(ClientRegisteredReply, sessionId), addr)
|
||||
} else {
|
||||
mutex.Unlock()
|
||||
}
|
||||
break
|
||||
|
||||
case EchoRequest:
|
||||
|
||||
111
qr2/session.go
111
qr2/session.go
@@ -7,6 +7,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/gpcm"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
@@ -32,13 +34,86 @@ func removeSession(sessionId uint32) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func setSessionData(sessionId uint32, payload map[string]string) (Session, bool) {
|
||||
moduleName := "QR2:" + strconv.FormatInt(int64(sessionId), 10)
|
||||
|
||||
// Perform sanity checks on the session data. This is a mess but
|
||||
|
||||
// This is an internal error that should not happen
|
||||
publicIP := ""
|
||||
var ok bool
|
||||
if publicIP, ok = payload["publicip"]; !ok || publicIP == "0" {
|
||||
logging.Error(moduleName, "Missing publicip in session data")
|
||||
return Session{}, false
|
||||
}
|
||||
|
||||
newPID, newPIDValid := payload["dwc_pid"]
|
||||
|
||||
// Moving into performing operations on the session data, so lock the mutex
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
session, sessionExists := sessions[sessionId]
|
||||
|
||||
session, exists := sessions[sessionId]
|
||||
if !exists {
|
||||
logging.Notice("QR2", "Creating session", aurora.Cyan(sessionId).String())
|
||||
if newPIDValid {
|
||||
var oldPID string
|
||||
oldPIDValid := false
|
||||
|
||||
if sessionExists {
|
||||
if oldPID, oldPIDValid = session.Data["dwc_pid"]; oldPIDValid && newPID != oldPID {
|
||||
logging.Error(moduleName, "New dwc_pid mismatch: new:", aurora.Cyan(newPID), "old:", aurora.Cyan(oldPID))
|
||||
return Session{}, false
|
||||
}
|
||||
}
|
||||
|
||||
if !oldPIDValid {
|
||||
// Setting a new PID so validate it
|
||||
profileID, err := strconv.ParseUint(newPID, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid dwc_pid value:", aurora.Cyan(newPID))
|
||||
return Session{}, false
|
||||
}
|
||||
|
||||
// Reformat dwc_pid string
|
||||
newPID = strconv.FormatUint(uint64(profileID), 10)
|
||||
payload["dwc_pid"] = newPID
|
||||
|
||||
// Lookup the profile ID in GPCM and verify it's logged in.
|
||||
// Maybe we don't need this? It relies on GPCM being hosted in the same application, and
|
||||
// makes GPCM a dependency of QR2. Perhaps we could use the database.
|
||||
gpcmIP := gpcm.GetSessionIP(uint32(profileID))
|
||||
if gpcmIP == "" {
|
||||
logging.Error(moduleName, "Provided dwc_pid is not logged in:", aurora.Cyan(newPID))
|
||||
return Session{}, false
|
||||
}
|
||||
|
||||
gpcmIPStr, _ := common.IPFormatToString(gpcmIP)
|
||||
if gpcmIPStr != publicIP {
|
||||
logging.Error(moduleName, "Caller public IP does not match GPCM session")
|
||||
return Session{}, false
|
||||
}
|
||||
|
||||
// Constraint: only one session can exist with a profile ID
|
||||
outdated := []uint32{}
|
||||
for sessionID, otherSession := range sessions {
|
||||
if otherPID, ok := otherSession.Data["dwc_pid"]; !ok || otherPID != newPID {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove old sessions with the PID
|
||||
outdated = append(outdated, sessionID)
|
||||
}
|
||||
|
||||
for _, sessionID := range outdated {
|
||||
logging.Notice(moduleName, "Removing outdated session", aurora.BrightCyan(sessionID), "with PID", aurora.Cyan(newPID))
|
||||
delete(sessions, sessionID)
|
||||
}
|
||||
|
||||
logging.Notice(moduleName, "Opened session with PID", aurora.Cyan(newPID))
|
||||
}
|
||||
}
|
||||
|
||||
if !sessionExists {
|
||||
logging.Notice(moduleName, "Creating session", aurora.Cyan(sessionId).String())
|
||||
data := Session{
|
||||
SessionID: sessionId,
|
||||
Challenge: "",
|
||||
@@ -49,35 +124,45 @@ func setSessionData(sessionId uint32, payload map[string]string) Session {
|
||||
}
|
||||
sessions[sessionId] = &data
|
||||
|
||||
return data
|
||||
return data, true
|
||||
}
|
||||
|
||||
session.Data = payload
|
||||
session.LastKeepAlive = time.Now().Unix()
|
||||
|
||||
return *session
|
||||
return *session, true
|
||||
}
|
||||
|
||||
// Get a copy of the list of servers
|
||||
func GetSessionServers() []map[string]string {
|
||||
var servers []map[string]string
|
||||
servers := []map[string]string{}
|
||||
unreachable := []uint32{}
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
for _, session := range sessions {
|
||||
// If the last keep alive was over a minute ago then consider the server unreachable
|
||||
if session.LastKeepAlive < currentTime-60 {
|
||||
// If the last keep alive was over an hour ago then remove the server
|
||||
if session.LastKeepAlive < currentTime-((60*60)*1) {
|
||||
unreachable = append(unreachable, session.SessionID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Remove unreachable sessions
|
||||
for _, sessionID := range unreachable {
|
||||
logging.Notice("QR2", "Removing unreachable session", aurora.BrightCyan(sessionID))
|
||||
delete(sessions, sessionID)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,15 @@ func filterServers(servers []map[string]string, queryGame string, filter string,
|
||||
// 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 {
|
||||
server["dwc_pid"] = dwc_pid
|
||||
server["dwc_mtype"] = "0"
|
||||
server["dwc_mver"] = "0"
|
||||
filtered = append(filtered, server)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user