Merge lp:~widelands-dev/widelands-metaserver/ipv6 into lp:widelands-metaserver

Proposed by Notabilis
Status: Rejected
Rejected by: Notabilis
Proposed branch: lp:~widelands-dev/widelands-metaserver/ipv6
Merge into: lp:widelands-metaserver
Diff against target: 448 lines (+217/-49)
4 files modified
wlms/client.go (+117/-15)
wlms/game.go (+90/-28)
wlms/main.go (+5/-1)
wlms/server.go (+5/-5)
To merge this branch: bzr merge lp:~widelands-dev/widelands-metaserver/ipv6
Reviewer Review Type Date Requested Status
Widelands Developers Pending
Review via email: mp+326028@code.launchpad.net

Description of the change

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

To post a comment you must log in.
Revision history for this message
SirVer (sirver) wrote :

Will try to review on the weekend!

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks! No hurry, though.
If you have experience with go (golang?), please also check stuff like code style or so. It is my first try working with it.

50. By Notabilis

Fixed bug with game hosts only supporting one IP version.

Revision history for this message
Notabilis (notabilis27) wrote :

It is also online on GitHub now:
https://github.com/widelands/widelands_metaserver/pull/2

Your choice where you want to review it.

Revision history for this message
Notabilis (notabilis27) wrote :

Review is done on GitHub, this branch is no longer valid.

Unmerged revisions

50. By Notabilis

Fixed bug with game hosts only supporting one IP version.

49. By Notabilis

Updated metaserver to support IPv6.
For doing so, increased maximum supported version from 0 to 1.

48. By Notabilis

Fixed crash when no configuration is found.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'wlms/client.go'
2--- wlms/client.go 2014-02-04 09:45:04 +0000
3+++ wlms/client.go 2017-06-21 19:57:39 +0000
4@@ -67,6 +67,17 @@
5 // the buildId of Widelands executable that this client is using.
6 buildId string
7
8+ // the nonce to link multiple connections by the same client.
9+ nonce string
10+
11+ // the IP of the secondary connection.
12+ // usually this is an IPv4 address.
13+ secondaryIp string
14+
15+ // Whether this client has a known IPv4/6 address.
16+ hasV4 bool
17+ hasV6 bool
18+
19 // The game we are currently in. nil if not in game.
20 game *Game
21
22@@ -155,6 +166,13 @@
23 client.timeoutTimer.Reset(server.ClientSendingTimeout())
24 client.waitingForPong = false
25
26+ ip := net.ParseIP(client.remoteIp())
27+ if ip.To4() != nil {
28+ client.hasV4 = true
29+ } else {
30+ client.hasV6 = true
31+ }
32+
33 for {
34 select {
35 case pkg, ok := <-client.dataStream:
36@@ -258,6 +276,10 @@
37 return host
38 }
39
40+func (client Client) otherIp() string {
41+ return client.secondaryIp
42+}
43+
44 func (newClient *Client) successfulRelogin(server *Server, oldClient *Client) {
45 server.RemoveClient(oldClient)
46
47@@ -338,10 +360,18 @@
48 return CriticalCmdPacketError{err.Error()}
49 }
50
51- if c.protocolVersion != 0 {
52+ if c.protocolVersion != 0 && c.protocolVersion != 1 {
53 return CriticalCmdPacketError{"UNSUPPORTED_PROTOCOL"}
54 }
55
56+ if isRegisteredOnServer || c.protocolVersion == 1 {
57+ nonce, err := pkg.ReadString()
58+ if err != nil {
59+ return CriticalCmdPacketError{err.Error()}
60+ }
61+ c.nonce = nonce
62+ }
63+
64 if isRegisteredOnServer {
65 if server.HasClient(c.userName) != nil {
66 return CriticalCmdPacketError{"ALREADY_LOGGED_IN"}
67@@ -349,11 +379,7 @@
68 if !server.UserDb().ContainsName(c.userName) {
69 return CriticalCmdPacketError{"WRONG_PASSWORD"}
70 }
71- password, err := pkg.ReadString()
72- if err != nil {
73- return CriticalCmdPacketError{err.Error()}
74- }
75- if !server.UserDb().PasswordCorrect(c.userName, password) {
76+ if !server.UserDb().PasswordCorrect(c.userName, c.nonce) {
77 return CriticalCmdPacketError{"WRONG_PASSWORD"}
78 }
79 c.permissions = server.UserDb().Permissions(c.userName)
80@@ -381,11 +407,19 @@
81 func (client *Client) Handle_RELOGIN(server *Server, pkg *packet.Packet) CmdError {
82 var isRegisteredOnServer bool
83 var protocolVersion int
84- var userName, buildId string
85+ var userName, buildId, nonce string
86 if err := pkg.Unpack(&protocolVersion, &userName, &buildId, &isRegisteredOnServer); err != nil {
87 return CriticalCmdPacketError{err.Error()}
88 }
89
90+ if isRegisteredOnServer || protocolVersion == 1 {
91+ n, err := pkg.ReadString()
92+ if err != nil {
93+ return CriticalCmdPacketError{err.Error()}
94+ }
95+ nonce = n
96+ }
97+
98 oldClient := server.HasClient(userName)
99 if oldClient == nil {
100 return CriticalCmdPacketError{"NOT_LOGGED_IN"}
101@@ -396,11 +430,7 @@
102 buildId == oldClient.buildId
103
104 if isRegisteredOnServer {
105- password, err := pkg.ReadString()
106- if err != nil {
107- return CriticalCmdPacketError{err.Error()}
108- }
109- if oldClient.permissions == UNREGISTERED || !server.UserDb().PasswordCorrect(userName, password) {
110+ if oldClient.permissions == UNREGISTERED || !server.UserDb().PasswordCorrect(userName, nonce) {
111 informationMatches = false
112 }
113 } else if oldClient.permissions != UNREGISTERED {
114@@ -417,6 +447,7 @@
115 client.userName = oldClient.userName
116 client.buildId = oldClient.buildId
117 client.game = oldClient.game
118+ client.nonce = nonce
119
120 log.Printf("%s wants to reconnect.\n", client.Name())
121 if oldClient.state == RECENTLY_DISCONNECTED {
122@@ -432,6 +463,42 @@
123 return nil
124 }
125
126+func (client *Client) Handle_TELL_IP(server *Server, pkg *packet.Packet) CmdError {
127+ var protocolVersion int
128+ var name, nonce string
129+ if err := pkg.Unpack(&protocolVersion, &name, &nonce); err != nil {
130+ return CmdPacketError{err.Error()}
131+ }
132+
133+ if protocolVersion != 1 {
134+ return CriticalCmdPacketError{"UNSUPPORTED_PROTOCOL"}
135+ }
136+
137+ old_client := server.HasClient(name)
138+ if old_client == nil || old_client.userName != name || old_client.nonce != nonce {
139+ log.Printf("Someone failed to register an IP for %s.", old_client.Name())
140+ return CriticalCmdPacketError{"NOT_LOGGED_IN"}
141+ }
142+
143+ // We found the existing connection of this client.
144+ // Update his IP and close this connection.
145+ old_client.secondaryIp = client.remoteIp()
146+ ip := net.ParseIP(old_client.otherIp())
147+ if ip.To4() != nil {
148+ old_client.hasV4 = true
149+ } else {
150+ old_client.hasV6 = true
151+ }
152+ log.Printf("%s is now known to use %s and %s.", old_client.Name(), old_client.remoteIp(), old_client.otherIp())
153+ client.Disconnect(*server)
154+ // Tell the client to get a new list of games. The availability of games might have changed now that
155+ // he supports more IP versions
156+ old_client.SendPacket("GAMES_UPDATE")
157+
158+ return nil
159+}
160+
161+
162 func (client *Client) Handle_GAME_OPEN(server *Server, pkg *packet.Packet) CmdError {
163 var gameName string
164 var maxPlayer int
165@@ -463,9 +530,36 @@
166 }
167
168 host := server.HasClient(game.Host())
169- log.Printf("%s joined %s at IP %s.", client.userName, game.Name(), host.remoteIp())
170+ log.Printf("%s joined %s.", client.userName, game.Name())
171
172- client.SendPacket("GAME_CONNECT", host.remoteIp())
173+ var ipv4, ipv6 string
174+ ip := net.ParseIP(host.remoteIp())
175+ if ip.To4() != nil {
176+ ipv4 = host.remoteIp()
177+ ipv6 = host.otherIp()
178+ } else {
179+ ipv4 = host.otherIp()
180+ ipv6 = host.remoteIp()
181+ }
182+ if client.protocolVersion == 0 {
183+ // Legacy client: Send the IPv4 address
184+ client.SendPacket("GAME_CONNECT", ipv4)
185+ // One of the two has to be IPv4, otherwise the client wouldn't come this
186+ // far anyway (game would appear closed)
187+ } else {
188+ // Newer client which supports two IPs
189+ // Only send him the IPs he can deal with
190+ if client.hasV4 && client.hasV6 && host.otherIp() != "" {
191+ // Both client and server have both IPs
192+ client.SendPacket("GAME_CONNECT", ipv6, "true", ipv4)
193+ } else if client.hasV4 && len(ipv4) != 0 {
194+ // Client and server have an IPv4 address
195+ client.SendPacket("GAME_CONNECT", ipv4, "false")
196+ } else if client.hasV6 && len(ipv6) != 0 {
197+ // Client and server have an IPv6 address
198+ client.SendPacket("GAME_CONNECT", ipv6, "false")
199+ }
200+ }
201 client.setGame(game, server)
202
203 return nil
204@@ -526,7 +620,15 @@
205 host := server.HasClient(game.Host())
206 data[n+0] = game.Name()
207 data[n+1] = host.buildId
208- data[n+2] = game.State() == CONNECTABLE
209+ // A game is connectable when the client supports the IP version of the game
210+ // (and the game is connectable itself, of course)
211+ connectable := game.State() == CONNECTABLE_BOTH
212+ if client.hasV4 && game.State() == CONNECTABLE_V4 {
213+ connectable = true
214+ } else if client.hasV6 && game.State() == CONNECTABLE_V6 {
215+ connectable = true
216+ }
217+ data[n+2] = connectable
218 n += 3
219 })
220 client.SendPacket(data...)
221
222=== modified file 'wlms/game.go'
223--- wlms/game.go 2014-02-08 20:48:04 +0000
224+++ wlms/game.go 2017-06-21 19:57:39 +0000
225@@ -3,6 +3,7 @@
226 import (
227 "log"
228 "time"
229+ "net"
230 )
231
232 type GameState int
233@@ -10,7 +11,9 @@
234 const (
235 INITIAL_SETUP GameState = iota
236 NOT_CONNECTABLE
237- CONNECTABLE
238+ CONNECTABLE_V4
239+ CONNECTABLE_V6
240+ CONNECTABLE_BOTH
241 RUNNING
242 )
243
244@@ -27,11 +30,70 @@
245 C chan bool
246 }
247
248+func (game *Game) handlePingResult(server *Server, result bool, is_v4 bool) {
249+ if result {
250+ log.Printf("Successfull ping reply from game %s.", game.Name())
251+ switch game.state {
252+ case INITIAL_SETUP:
253+ if is_v4 {
254+ game.SetState(*server, CONNECTABLE_V4)
255+ } else {
256+ game.SetState(*server, CONNECTABLE_V6)
257+ }
258+ case NOT_CONNECTABLE:
259+ if is_v4 {
260+ game.SetState(*server, CONNECTABLE_V4)
261+ } else {
262+ game.SetState(*server, CONNECTABLE_V6)
263+ }
264+ case CONNECTABLE_V4:
265+ if !is_v4 {
266+ game.SetState(*server, CONNECTABLE_BOTH)
267+ }
268+ case CONNECTABLE_V6:
269+ if is_v4 {
270+ game.SetState(*server, CONNECTABLE_BOTH)
271+ }
272+ case CONNECTABLE_BOTH, RUNNING:
273+ // Do nothing
274+ default:
275+ log.Fatalf("Unhandled game.state: %v", game.state)
276+ }
277+ } else {
278+ log.Printf("Failed ping reply from game %s.", game.Name())
279+ switch game.state {
280+ case INITIAL_SETUP:
281+ game.SetState(*server, NOT_CONNECTABLE)
282+ case NOT_CONNECTABLE:
283+ // Do nothing.
284+ case CONNECTABLE_V4:
285+ if is_v4 {
286+ game.SetState(*server, NOT_CONNECTABLE)
287+ }
288+ case CONNECTABLE_V6:
289+ if !is_v4 {
290+ game.SetState(*server, NOT_CONNECTABLE)
291+ }
292+ case CONNECTABLE_BOTH:
293+ if is_v4 {
294+ game.SetState(*server, CONNECTABLE_V6)
295+ } else {
296+ game.SetState(*server, CONNECTABLE_V4)
297+ }
298+ case RUNNING:
299+ // Do nothing
300+ default:
301+ log.Fatalf("Unhandled game.state: %v", game.state)
302+ }
303+ }
304+}
305+
306 func (game *Game) pingCycle(server *Server) {
307 // Remember to remove the game when we no longer receive pings.
308 defer server.RemoveGame(game)
309
310 first_ping := true
311+ ping_primary_ip := true
312 for {
313 // This game is not even in our list anymore. Give up. If the game has no
314 // host anymore or it has disconnected, remove the game.
315@@ -46,37 +108,37 @@
316 if first_ping {
317 pingTimeout = server.GameInitialPingTimeout()
318 }
319- first_ping = false
320-
321- pinger := server.NewGamePinger(host, pingTimeout)
322- success, ok := <-pinger.C
323- if success && ok {
324- log.Printf("Successfull ping reply from game %s.", game.Name())
325- switch game.state {
326- case INITIAL_SETUP:
327+
328+ connected := false
329+
330+ // The idea is to alternate between pinging the two IP addresses of the client, except for the
331+ // first round or if there is only one IP address
332+ if ping_primary_ip || host.otherIp() == "" {
333+ // Primary IP
334+ pinger := server.NewGamePinger(host.remoteIp(), pingTimeout)
335+ success, ok := <-pinger.C
336+ connected = success && ok
337+ game.handlePingResult(server, connected, net.ParseIP(host.remoteIp()).To4() != nil)
338+ }
339+ if (!ping_primary_ip || first_ping) && host.otherIp() != "" {
340+ // Secondary IP
341+ pinger := server.NewGamePinger(host.otherIp(), pingTimeout)
342+ success, ok := <-pinger.C
343+ connected = success && ok
344+ game.handlePingResult(server, connected, net.ParseIP(host.otherIp()).To4() != nil)
345+
346+ }
347+ if first_ping {
348+ // On first ping, inform the client about the result
349+ if connected {
350 host.SendPacket("GAME_OPEN")
351- game.SetState(*server, CONNECTABLE)
352- case NOT_CONNECTABLE:
353- game.SetState(*server, CONNECTABLE)
354- case CONNECTABLE, RUNNING:
355- // Do nothing
356- default:
357- log.Fatalf("Unhandled game.state: %v", game.state)
358- }
359- } else {
360- log.Printf("Failed ping reply from game %s.", game.Name())
361- switch game.state {
362- case INITIAL_SETUP:
363+ } else {
364 host.SendPacket("ERROR", "GAME_OPEN", "GAME_TIMEOUT")
365- game.SetState(*server, NOT_CONNECTABLE)
366- case NOT_CONNECTABLE:
367- // Do nothing.
368- case CONNECTABLE, RUNNING:
369- return
370- default:
371- log.Fatalf("Unhandled game.state: %v", game.state)
372 }
373+ first_ping = false
374 }
375+
376+ ping_primary_ip = !ping_primary_ip
377 time.Sleep(server.GamePingTimeout())
378 }
379 }
380
381=== modified file 'wlms/main.go'
382--- wlms/main.go 2014-01-29 20:01:23 +0000
383+++ wlms/main.go 2017-06-21 19:57:39 +0000
384@@ -28,6 +28,7 @@
385 var db UserDb
386 var ircbridge IRCBridger
387 if config != "" {
388+ log.Println("Loading configuration")
389 var cfg Config
390 if err := cfg.ConfigFrom(config); err != nil {
391 log.Fatalf("Could not parse config file: %v", err)
392@@ -39,13 +40,16 @@
393 }
394 ircbridge = NewIRCBridge(cfg.IRCServer, cfg.Realname, cfg.Nickname, cfg.Channel, cfg.UseTLS)
395 } else {
396+ log.Println("No configuration found, using in-memory database")
397 db = NewInMemoryDb()
398 }
399 defer db.Close()
400
401 messagesToIrc := make(chan Message, 50)
402 messagesToLobby := make(chan Message, 50)
403- ircbridge.Connect(messagesToIrc, messagesToLobby)
404+ if ircbridge != nil {
405+ ircbridge.Connect(messagesToIrc, messagesToLobby)
406+ }
407 RunServer(db, messagesToLobby, messagesToIrc)
408
409 }
410
411=== modified file 'wlms/server.go'
412--- wlms/server.go 2014-03-17 18:12:40 +0000
413+++ wlms/server.go 2017-06-21 19:57:39 +0000
414@@ -38,7 +38,7 @@
415 }
416
417 type GamePingerFactory interface {
418- New(client *Client, timeout time.Duration) *GamePinger
419+ New(ip string, timeout time.Duration) *GamePinger
420 }
421
422 func (s Server) ClientSendingTimeout() time.Duration {
423@@ -96,8 +96,8 @@
424 <-s.serverHasShutdown
425 }
426
427-func (s *Server) NewGamePinger(client *Client, ping_timeout time.Duration) *GamePinger {
428- return s.gamePingerFactory.New(client, ping_timeout)
429+func (s *Server) NewGamePinger(ip string, ping_timeout time.Duration) *GamePinger {
430+ return s.gamePingerFactory.New(ip, ping_timeout)
431 }
432
433 func (s *Server) AddClient(client *Client) {
434@@ -241,12 +241,12 @@
435 server *Server
436 }
437
438-func (gpf RealGamePingerFactory) New(client *Client, timeout time.Duration) *GamePinger {
439+func (gpf RealGamePingerFactory) New(ip string, timeout time.Duration) *GamePinger {
440 pinger := &GamePinger{make(chan bool)}
441
442 data := make([]byte, len(NETCMD_METASERVER_PING))
443 go func() {
444- conn, err := net.DialTimeout("tcp", net.JoinHostPort(client.remoteIp(), "7396"), timeout)
445+ conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, "7396"), timeout)
446 if err != nil {
447 pinger.C <- false
448 return

Subscribers

People subscribed via source and target branches

to all changes: