widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #10476
[Merge] lp:~widelands-dev/widelands-metaserver/ipv6 into lp:widelands-metaserver
Notabilis has proposed merging lp:~widelands-dev/widelands-metaserver/ipv6 into lp:widelands-metaserver.
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1689087 in widelands: "Implementing a relay server"
https://bugs.launchpad.net/widelands/+bug/1689087
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands-metaserver/ipv6/+merge/326028
Updated metaserver to support IPv6.
While doing so, increased maximum supported protocol version from 0 to 1. Connecting with older versions should be fine even after this merge, but they will only be able to connect to IPv4 games.
The related proposed changes in the client are at:
https://code.launchpad.net/~widelands-dev/widelands/net-internetgaming-ipv6
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands-metaserver/ipv6 into lp:widelands-metaserver.
=== modified file 'wlms/client.go'
--- wlms/client.go 2014-02-04 09:45:04 +0000
+++ wlms/client.go 2017-06-20 20:47:51 +0000
@@ -67,6 +67,17 @@
// the buildId of Widelands executable that this client is using.
buildId string
+ // the nonce to link multiple connections by the same client.
+ nonce string
+
+ // the IP of the secondary connection.
+ // usually this is an IPv4 address.
+ secondaryIp string
+
+ // Whether this client has a known IPv4/6 address.
+ hasV4 bool
+ hasV6 bool
+
// The game we are currently in. nil if not in game.
game *Game
@@ -155,6 +166,13 @@
client.timeoutTimer.Reset(server.ClientSendingTimeout())
client.waitingForPong = false
+ ip := net.ParseIP(client.remoteIp())
+ if ip.To4() != nil {
+ client.hasV4 = true
+ } else {
+ client.hasV6 = true
+ }
+
for {
select {
case pkg, ok := <-client.dataStream:
@@ -258,6 +276,10 @@
return host
}
+func (client Client) otherIp() string {
+ return client.secondaryIp
+}
+
func (newClient *Client) successfulRelogin(server *Server, oldClient *Client) {
server.RemoveClient(oldClient)
@@ -338,10 +360,18 @@
return CriticalCmdPacketError{err.Error()}
}
- if c.protocolVersion != 0 {
+ if c.protocolVersion != 0 && c.protocolVersion != 1 {
return CriticalCmdPacketError{"UNSUPPORTED_PROTOCOL"}
}
+ if isRegisteredOnServer || c.protocolVersion == 1 {
+ nonce, err := pkg.ReadString()
+ if err != nil {
+ return CriticalCmdPacketError{err.Error()}
+ }
+ c.nonce = nonce
+ }
+
if isRegisteredOnServer {
if server.HasClient(c.userName) != nil {
return CriticalCmdPacketError{"ALREADY_LOGGED_IN"}
@@ -349,11 +379,7 @@
if !server.UserDb().ContainsName(c.userName) {
return CriticalCmdPacketError{"WRONG_PASSWORD"}
}
- password, err := pkg.ReadString()
- if err != nil {
- return CriticalCmdPacketError{err.Error()}
- }
- if !server.UserDb().PasswordCorrect(c.userName, password) {
+ if !server.UserDb().PasswordCorrect(c.userName, c.nonce) {
return CriticalCmdPacketError{"WRONG_PASSWORD"}
}
c.permissions = server.UserDb().Permissions(c.userName)
@@ -381,11 +407,19 @@
func (client *Client) Handle_RELOGIN(server *Server, pkg *packet.Packet) CmdError {
var isRegisteredOnServer bool
var protocolVersion int
- var userName, buildId string
+ var userName, buildId, nonce string
if err := pkg.Unpack(&protocolVersion, &userName, &buildId, &isRegisteredOnServer); err != nil {
return CriticalCmdPacketError{err.Error()}
}
+ if isRegisteredOnServer || protocolVersion == 1 {
+ n, err := pkg.ReadString()
+ if err != nil {
+ return CriticalCmdPacketError{err.Error()}
+ }
+ nonce = n
+ }
+
oldClient := server.HasClient(userName)
if oldClient == nil {
return CriticalCmdPacketError{"NOT_LOGGED_IN"}
@@ -396,11 +430,7 @@
buildId == oldClient.buildId
if isRegisteredOnServer {
- password, err := pkg.ReadString()
- if err != nil {
- return CriticalCmdPacketError{err.Error()}
- }
- if oldClient.permissions == UNREGISTERED || !server.UserDb().PasswordCorrect(userName, password) {
+ if oldClient.permissions == UNREGISTERED || !server.UserDb().PasswordCorrect(userName, nonce) {
informationMatches = false
}
} else if oldClient.permissions != UNREGISTERED {
@@ -417,6 +447,7 @@
client.userName = oldClient.userName
client.buildId = oldClient.buildId
client.game = oldClient.game
+ client.nonce = nonce
log.Printf("%s wants to reconnect.\n", client.Name())
if oldClient.state == RECENTLY_DISCONNECTED {
@@ -432,6 +463,42 @@
return nil
}
+func (client *Client) Handle_TELL_IP(server *Server, pkg *packet.Packet) CmdError {
+ var protocolVersion int
+ var name, nonce string
+ if err := pkg.Unpack(&protocolVersion, &name, &nonce); err != nil {
+ return CmdPacketError{err.Error()}
+ }
+
+ if protocolVersion != 1 {
+ return CriticalCmdPacketError{"UNSUPPORTED_PROTOCOL"}
+ }
+
+ old_client := server.HasClient(name)
+ if old_client == nil || old_client.userName != name || old_client.nonce != nonce {
+ log.Printf("Someone failed to register an IP for %s.", old_client.Name())
+ return CriticalCmdPacketError{"NOT_LOGGED_IN"}
+ }
+
+ // We found the existing connection of this client.
+ // Update his IP and close this connection.
+ old_client.secondaryIp = client.remoteIp()
+ ip := net.ParseIP(old_client.otherIp())
+ if ip.To4() != nil {
+ old_client.hasV4 = true
+ } else {
+ old_client.hasV6 = true
+ }
+ log.Printf("%s is now known to use %s and %s.", old_client.Name(), old_client.remoteIp(), old_client.otherIp())
+ client.Disconnect(*server)
+ // Tell the client to get a new list of games. The availability of games might have changed now that
+ // he supports more IP versions
+ old_client.SendPacket("GAMES_UPDATE")
+
+ return nil
+}
+
+
func (client *Client) Handle_GAME_OPEN(server *Server, pkg *packet.Packet) CmdError {
var gameName string
var maxPlayer int
@@ -463,9 +530,33 @@
}
host := server.HasClient(game.Host())
- log.Printf("%s joined %s at IP %s.", client.userName, game.Name(), host.remoteIp())
+ log.Printf("%s joined %s.", client.userName, game.Name())
- client.SendPacket("GAME_CONNECT", host.remoteIp())
+ var ipv4, ipv6 string
+ ip := net.ParseIP(host.remoteIp())
+ if ip.To4() != nil {
+ ipv4 = host.remoteIp()
+ ipv6 = host.otherIp()
+ } else {
+ ipv4 = host.otherIp()
+ ipv6 = host.remoteIp()
+ }
+ if client.protocolVersion == 0 {
+ // Legacy client: Send the IPv4 address
+ client.SendPacket("GAME_CONNECT", ipv4)
+ // One of the two has to be IPv4, otherwise the client wouldn't come this
+ // far anyway (game would appear closed)
+ } else {
+ // Newer client which supports two IPs
+ // Only send him the IPs he can deal with
+ if client.hasV4 && client.hasV6 && host.otherIp() != "" {
+ client.SendPacket("GAME_CONNECT", ipv6, "1", ipv4)
+ } else if client.hasV4 {
+ client.SendPacket("GAME_CONNECT", ipv4, "0")
+ } else if client.hasV6 {
+ client.SendPacket("GAME_CONNECT", ipv6, "0")
+ }
+ }
client.setGame(game, server)
return nil
@@ -526,7 +617,15 @@
host := server.HasClient(game.Host())
data[n+0] = game.Name()
data[n+1] = host.buildId
- data[n+2] = game.State() == CONNECTABLE
+ // A game is connectable when the client supports the IP version of the game
+ // (and the game is connectable itself, of course)
+ connectable := game.State() == CONNECTABLE_BOTH
+ if client.hasV4 && game.State() == CONNECTABLE_V4 {
+ connectable = true
+ } else if client.hasV6 && game.State() == CONNECTABLE_V6 {
+ connectable = true
+ }
+ data[n+2] = connectable
n += 3
})
client.SendPacket(data...)
=== modified file 'wlms/game.go'
--- wlms/game.go 2014-02-08 20:48:04 +0000
+++ wlms/game.go 2017-06-20 20:47:51 +0000
@@ -3,6 +3,7 @@
import (
"log"
"time"
+ "net"
)
type GameState int
@@ -10,7 +11,9 @@
const (
INITIAL_SETUP GameState = iota
NOT_CONNECTABLE
- CONNECTABLE
+ CONNECTABLE_V4
+ CONNECTABLE_V6
+ CONNECTABLE_BOTH
RUNNING
)
@@ -27,11 +30,70 @@
C chan bool
}
+func (game *Game) handlePingResult(server *Server, result bool, is_v4 bool) {
+ if result {
+ log.Printf("Successfull ping reply from game %s.", game.Name())
+ switch game.state {
+ case INITIAL_SETUP:
+ if is_v4 {
+ game.SetState(*server, CONNECTABLE_V4)
+ } else {
+ game.SetState(*server, CONNECTABLE_V6)
+ }
+ case NOT_CONNECTABLE:
+ if is_v4 {
+ game.SetState(*server, CONNECTABLE_V4)
+ } else {
+ game.SetState(*server, CONNECTABLE_V6)
+ }
+ case CONNECTABLE_V4:
+ if !is_v4 {
+ game.SetState(*server, CONNECTABLE_BOTH)
+ }
+ case CONNECTABLE_V6:
+ if is_v4 {
+ game.SetState(*server, CONNECTABLE_BOTH)
+ }
+ case CONNECTABLE_BOTH, RUNNING:
+ // Do nothing
+ default:
+ log.Fatalf("Unhandled game.state: %v", game.state)
+ }
+ } else {
+ log.Printf("Failed ping reply from game %s.", game.Name())
+ switch game.state {
+ case INITIAL_SETUP:
+ game.SetState(*server, NOT_CONNECTABLE)
+ case NOT_CONNECTABLE:
+ // Do nothing.
+ case CONNECTABLE_V4:
+ if is_v4 {
+ game.SetState(*server, NOT_CONNECTABLE)
+ }
+ case CONNECTABLE_V6:
+ if !is_v4 {
+ game.SetState(*server, NOT_CONNECTABLE)
+ }
+ case CONNECTABLE_BOTH:
+ if is_v4 {
+ game.SetState(*server, CONNECTABLE_V6)
+ } else {
+ game.SetState(*server, CONNECTABLE_V4)
+ }
+ case RUNNING:
+ // Do nothing
+ default:
+ log.Fatalf("Unhandled game.state: %v", game.state)
+ }
+ }
+}
+
func (game *Game) pingCycle(server *Server) {
// Remember to remove the game when we no longer receive pings.
defer server.RemoveGame(game)
first_ping := true
+ ping_primary_ip := true
for {
// This game is not even in our list anymore. Give up. If the game has no
// host anymore or it has disconnected, remove the game.
@@ -46,37 +108,37 @@
if first_ping {
pingTimeout = server.GameInitialPingTimeout()
}
- first_ping = false
-
- pinger := server.NewGamePinger(host, pingTimeout)
- success, ok := <-pinger.C
- if success && ok {
- log.Printf("Successfull ping reply from game %s.", game.Name())
- switch game.state {
- case INITIAL_SETUP:
+
+ connected := false
+
+ // The idea is to alternate between pinging the two IP addresses of the client, except for the
+ // first round or if there is only one IP address
+ if ping_primary_ip || host.otherIp() == "" {
+ // Primary IP
+ pinger := server.NewGamePinger(host.remoteIp(), pingTimeout)
+ success, ok := <-pinger.C
+ connected = success && ok
+ game.handlePingResult(server, connected, net.ParseIP(host.remoteIp()).To4() != nil)
+ }
+ if (!ping_primary_ip || first_ping) && host.otherIp() != "" {
+ // Secondary IP
+ pinger := server.NewGamePinger(host.otherIp(), pingTimeout)
+ success, ok := <-pinger.C
+ connected = success && ok
+ game.handlePingResult(server, connected, net.ParseIP(host.otherIp()).To4() != nil)
+
+ }
+ if first_ping {
+ // On first ping, inform the client about the result
+ if connected {
host.SendPacket("GAME_OPEN")
- game.SetState(*server, CONNECTABLE)
- case NOT_CONNECTABLE:
- game.SetState(*server, CONNECTABLE)
- case CONNECTABLE, RUNNING:
- // Do nothing
- default:
- log.Fatalf("Unhandled game.state: %v", game.state)
- }
- } else {
- log.Printf("Failed ping reply from game %s.", game.Name())
- switch game.state {
- case INITIAL_SETUP:
+ } else {
host.SendPacket("ERROR", "GAME_OPEN", "GAME_TIMEOUT")
- game.SetState(*server, NOT_CONNECTABLE)
- case NOT_CONNECTABLE:
- // Do nothing.
- case CONNECTABLE, RUNNING:
- return
- default:
- log.Fatalf("Unhandled game.state: %v", game.state)
}
+ first_ping = false
}
+
+ ping_primary_ip = !ping_primary_ip
time.Sleep(server.GamePingTimeout())
}
}
=== modified file 'wlms/main.go'
--- wlms/main.go 2014-01-29 20:01:23 +0000
+++ wlms/main.go 2017-06-20 20:47:51 +0000
@@ -28,6 +28,7 @@
var db UserDb
var ircbridge IRCBridger
if config != "" {
+ log.Println("Loading configuration")
var cfg Config
if err := cfg.ConfigFrom(config); err != nil {
log.Fatalf("Could not parse config file: %v", err)
@@ -39,13 +40,16 @@
}
ircbridge = NewIRCBridge(cfg.IRCServer, cfg.Realname, cfg.Nickname, cfg.Channel, cfg.UseTLS)
} else {
+ log.Println("No configuration found, using in-memory database")
db = NewInMemoryDb()
}
defer db.Close()
messagesToIrc := make(chan Message, 50)
messagesToLobby := make(chan Message, 50)
- ircbridge.Connect(messagesToIrc, messagesToLobby)
+ if ircbridge != nil {
+ ircbridge.Connect(messagesToIrc, messagesToLobby)
+ }
RunServer(db, messagesToLobby, messagesToIrc)
}
=== modified file 'wlms/server.go'
--- wlms/server.go 2014-03-17 18:12:40 +0000
+++ wlms/server.go 2017-06-20 20:47:51 +0000
@@ -38,7 +38,7 @@
}
type GamePingerFactory interface {
- New(client *Client, timeout time.Duration) *GamePinger
+ New(ip string, timeout time.Duration) *GamePinger
}
func (s Server) ClientSendingTimeout() time.Duration {
@@ -96,8 +96,8 @@
<-s.serverHasShutdown
}
-func (s *Server) NewGamePinger(client *Client, ping_timeout time.Duration) *GamePinger {
- return s.gamePingerFactory.New(client, ping_timeout)
+func (s *Server) NewGamePinger(ip string, ping_timeout time.Duration) *GamePinger {
+ return s.gamePingerFactory.New(ip, ping_timeout)
}
func (s *Server) AddClient(client *Client) {
@@ -241,12 +241,12 @@
server *Server
}
-func (gpf RealGamePingerFactory) New(client *Client, timeout time.Duration) *GamePinger {
+func (gpf RealGamePingerFactory) New(ip string, timeout time.Duration) *GamePinger {
pinger := &GamePinger{make(chan bool)}
data := make([]byte, len(NETCMD_METASERVER_PING))
go func() {
- conn, err := net.DialTimeout("tcp", net.JoinHostPort(client.remoteIp(), "7396"), timeout)
+ conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, "7396"), timeout)
if err != nil {
pinger.C <- false
return
Follow ups