Merge lp:~widelands-dev/widelands/net-relay into lp:widelands

Proposed by Notabilis
Status: Merged
Merged at revision: 8534
Proposed branch: lp:~widelands-dev/widelands/net-relay
Merge into: lp:widelands
Prerequisite: lp:~widelands-dev/widelands/nethost-split
Diff against target: 2317 lines (+1652/-142)
25 files modified
src/network/CMakeLists.txt (+7/-0)
src/network/gameclient.cc (+19/-7)
src/network/gameclient.h (+2/-2)
src/network/gamehost.cc (+36/-20)
src/network/internet_gaming.cc (+32/-14)
src/network/internet_gaming.h (+7/-0)
src/network/internet_gaming_protocol.h (+33/-44)
src/network/netclient.cc (+15/-11)
src/network/netclient.h (+1/-1)
src/network/netclient_interface.h (+3/-5)
src/network/netclientproxy.cc (+165/-0)
src/network/netclientproxy.h (+73/-0)
src/network/nethost.cc (+16/-13)
src/network/nethost.h (+1/-1)
src/network/nethost_interface.h (+5/-5)
src/network/nethostproxy.cc (+288/-0)
src/network/nethostproxy.h (+103/-0)
src/network/netrelayconnection.cc (+337/-0)
src/network/netrelayconnection.h (+225/-0)
src/network/network.cc (+2/-1)
src/network/network.h (+1/-0)
src/network/relay_protocol.h (+242/-0)
src/ui_fsmenu/internet_lobby.cc (+37/-17)
src/ui_fsmenu/internet_lobby.h (+1/-0)
src/ui_fsmenu/multiplayer.cc (+1/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/net-relay
Reviewer Review Type Date Requested Status
SirVer Approve
Review via email: mp+332386@code.launchpad.net

This proposal supersedes a proposal from 2017-07-16.

Commit message

When running an Internet game, do not use a local server but relay all traffic over the metaserver.
Avoids problems with firewalls and NAT.

Description of the change

Finally: Ready for review!
When running an Internet game, do not use a local host but relay all traffic over the metaserver, avoiding problems with firewalls and routers/NAT.
Probably best start reviewing the code with src/network/relay_protocol.h for an overview how it works.

This change does not affect build19 clients. Those are still required to host a game locally.

Merge this together with deploying the metaserver version at:
https://github.com/widelands/widelands_metaserver/pull/12

This branch is not quite ready for merge yet: I am waiting for the net-uuid branch to continue the increasing protocol version numbers. Technically it would also be possible to merge this one first and adapt the version numbers in the other branch. Setting the version in this branch is required anyway.

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 2443. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/254133988.
Appveyor build 2269. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2269.

Revision history for this message
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal

The protocol looks good to me.

There is no description of how the relay server get shut down yet, either in game end or when host and clients disappear without telling the relay about it.

I'd add a note to RLYCMD_TO_HOST that it's identical to RLYCMD_TO_CLIENT etc., to make the documentation easier to read.

There are also a bunch of commented out braces.

Revision history for this message
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal

Thanks for looking over it!

A command to shutdown the server is really missing, I haven't thought about that one. I think there should be a (long) timeout until automated shutdown when all players disconnect, just in case the shutdown-message does not arrive. Since the game can't work without the host, we could simply interpret the (planned) disconnect message of the host as a shutdown command.

With "identical" you mean the same enum-value? That is actually a bug, good catch. Is fixed in my local file now.
Assuming with the braces you mean the ///\{ and ///\} : This is intentional. With doxygen this leads to grouping of methods. The /** command */ in front of them is the description of the group.

Revision history for this message
SirVer (sirver) : Posted in a previous version of this proposal
Revision history for this message
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal

1 comment regarding naming conventions.

I'd also have some minor phrasing/English language nits, but I'll keep those for the final code review when the branch is done.

Revision history for this message
SirVer (sirver) wrote : Posted in a previous version of this proposal

Notabilis, could you give us an update on your work? I am quite curious. Are you blocked in any way?

Revision history for this message
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal

Sorry for the long silence, I should have written much earlier. My primary problem is lack of time, but when I can work on it its going well enough. I fear I won't be able to work on it in the next two weeks but hopefully I have more time afterwards.

Thanks for the comments regarding the protocol, it should be all fixed now. I also changed some more things while working on the implementation.
First (alpha-) versions of the relay server and the game connections have been implemented. The current state is pushed here and on GitHub. I haven't tested it yet but it compiles and the general design should be recognizable. If you want to comment on it, go ahead. My next step will probably be to try to run a game over the relay (and fix all the bugs that come up...).

As a high-level overview:

- The relay server is one process which creates a Game object for each running game. New connections are all to the same port. Based on their first message (containing the game name) they are distributed between the running games.

- I extracted a NetHostInterface/NetClientInterface from the NetHost/NetClient classes. These interfaces are also implemented by NetHostProxy/NetHostClient. For LAN games the old NetHost/NetClient classes are instantiated, for internet games the *Proxy classes are used. These pack all packets and actions (e.g. (dis-)connects) as described in relay_protocol.h and sent them over a single connection to the relay server. There they are forwarded to the other participants of the respective game. This way, the GameHost/GameClient classes do not have to care whether it is an internet or a local game.

What is currently missing is the integration with the metaserver. When a player wants to start a new game the metaserver has to order the relay to create the respective game and tell the host about ip/port/host-password of it. After the host connected, the metaserver can announce the game in the lobby for other clients to join.

Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 2557. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/265326001.
Appveyor build 2269. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2269.

Revision history for this message
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal

Good to hear that it's going well whenever you have the time :)

Revision history for this message
SirVer (sirver) wrote : Posted in a previous version of this proposal

Thanks for the update! I just was curious if you are blocked. Do you want a review now? Otherwise I'd defer reviewing until you have something mergable.

Also, if some of the refactorings you did can be done beforhand in a smaller merge request, that would be very useful.

Revision history for this message
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal

If you want to say something about the design, go ahead. Looking at the details isn't worthwhile yet.

Okay, I will try to create a smaller merge when I am done.

Revision history for this message
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal

I just pushed the first "working" version of the relay server. Working as in: Everything hardcoded (IPs, name of the game, ...), but relaying game packets works and I was able to run a game.

One question where I would like some opinion: Currently the metaserver tells the client where to connect to (i.e. the IP and port of the relay). Do we need/want this flexibility or do we simply use hardcoded values in the client? It will probably always (?) be the IP of the metaserver and some fixed port number.

Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 2667. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/275219903.
Appveyor build 2488. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2488.

Revision history for this message
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal

As a matter of principle, I am against hard coding where it can be avoided without too much trouble. There could conceivably be multiple game servers in the future.

Revision history for this message
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal

Okay, thanks. Than I will implement the dynamic version.

Revision history for this message
Notabilis (notabilis27) wrote :

So much for: "[Prequisite branches] changes will not be shown in the diff."
The changes to NetHost, NetClient and the new interface classes are also at:
https://code.launchpad.net/~widelands-dev/widelands/nethost-split/+merge/332385

Maybe they will disappear from the diff here after the other branch is merged into trunk.

Revision history for this message
SirVer (sirver) wrote :

This merge request tripped up bunnybot because the number of comments went down when it was reopened. The bug is fixed now, so other branches should now get merged as normal again.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2765. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/297537925.
Appveyor build 2577. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2577.

Revision history for this message
SirVer (sirver) wrote :

Finally reviewed this! I left a few comments in the code using NOCOM as markers.

Really nice work. The structure is understandable and the feature is an absolute killer! Thanks for working on this.

Revision history for this message
GunChleoc (gunchleoc) wrote :

@SirVer: You forgot to push

Revision history for this message
SirVer (sirver) wrote :

indeed, done now.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2832. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/306718329.
Appveyor build 2642. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2642.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the review. I have a few questions regarding your NOCOMs, though.

> You can and should use std::piecewise_construct here and get rid of the default constructor for client

I guess you mean something like (not working):
 clients_.emplace(std::piecewise_construct, id, Client::State::kConnecting);
So instead of the default constructor I will have one taking the state of the argument? That could also be achieved by
 clients_.emplace(id, Client::State::kConnecting);
I don't really see why I should get rid of the default constructor, and I guess you mean something else.

> It would be nice to get syncstream data into this somehow to make it easier to debug desyncs going forward.

What exactly do you mean with this? Writing all transfered data to the syncstream? Only the commands/headers? Or just when errors occur? Desyncs are something which does not (or at least: should not) matter on this layer, the relay is happily transmitting even useless data.

> It would be nice to always get a kPong on a kPing ...

Can do so, no problem. Actually, that is what happening already (but pings are not always send). Currently, the pings are only used for the game host and not for the clients. Do you want to have pings for all participants? Kicking out slow clients is a feature I see as a responsibility of the "higher" layers, i.e., the GameHost class, and nothing done by the relay.

Revision history for this message
SirVer (sirver) wrote :

I numbered the points for easier reference:

> 1) I changed the code to my suggestion. Please revert if you disagree.

> 2) It would be nice to get syncstream data into this somehow to make it easier to debug desyncs going forward.

Sorry, that was initial excitement while reviewing, it doesn't really belong in this code review. My thoughts on this topic are not coherent yet, so I have no clear design in mind. I pulled what I have so far out into a separate bug and removed the comment.

[1] https://bugs.launchpad.net/widelands/+bug/1734671

> 3) Ping/Pong

I agree that the relay should not auto-kick users. But it is uniquely positioned to determine the actual ping values that affect gameplay. So I think it should regularly inform the players (or at least the host) how the ping to the different players is. For this a regular ping (once every 5-10 seconds) to all players would be valuable.

I pulled out a bug to design the feature: https://bugs.launchpad.net/widelands/+bug/1734673. Of course Widelands site changes and UI are not part of this MR, but I suggest to implement the regular ping for all players directly.

Revision history for this message
Notabilis (notabilis27) wrote :

1) Thanks, looks good. Not that I really understand this construct.

2) Okay. Seems to be worth looking into that, but not in this branch.

3) I added a simple protocol for this. Please see relay_protocol.h (diff line 1918ff) for the design, it should us allow what we want to do. Metaserver support is missing for it, when you approve of the design I will add it there, too. Adding the RTT-display-/kicking-GUI should be a Widelands-only change later on.

4) A still open NOCOM is regarding the move-constructor of RecvPacket. The problem is that this class extends the (old) class StreamRead, which removes the copy-constructor explicitly. We could remove this declaration but it is probably there for a reason. So I guess it is either move operations or ... no real idea.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2891. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/309199942.
Appveyor build 2700. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2700.

Revision history for this message
SirVer (sirver) wrote :

I added a few more NOCOMs again, sorry :(.

3) That design looks sufficient for what I had in mind. It is also nicely minimal. Good job!

4) Here is my suggestion:

- Change bool try_receive(RecvPacket*) to std::unique_ptr<RecvPacket> try_receive(), with nullptr being what false was before.
- The various std::deque<RecvPacket> received_ can now become std::deque<unique_ptr<RecvPacket>> - which makes them even cheaper.
- You can delete the move operator/constructor in RecvPacket

This has also the advantage that you communicate clearly that a RecvPacket never changes. The move operator makes it look like you sometimes move partially read packets - but you never do as far as I can tell.

Revision history for this message
Notabilis (notabilis27) wrote :

With such a great commit message you can give me as much NOCOMs as you want. ;)

4) I implemented your suggestion, but I am not completely happy with it. The move constructors are gone but there are still a few of the evil std::move in the code. Decide for yourself, I guess. I could get rid of them by storing raw pointers in the received_ queue but I am not such a fan of the idea.

5) I made the NOCOM regarding the duplicated code to a TODO note. Pulling out a method should be done but I don't know where to put it right now. I guess some place will present itself when the feature is added for real.

NOCOMs are all done (for now...).

Revision history for this message
SirVer (sirver) wrote :

4) I disagree. I think it looks wonderful! You only need std::move() to signalize that you move the front() of your queue and leave an empty vector - and without it, the code would read like a copy at this site. So the move adds readability. All in all the code now looks like I would expect it to.

5) Pull the function to somewhere, it doesn't matter much. Either in the one of the units where it is used and include the header from the other. Or one that is used by both. Reducing duplicated code is more important than finding the right place for the function IMHO. Please, for me? Pretty please?

Revision history for this message
Notabilis (notabilis27) wrote :

5) Since you asked that nicely, I did it, just for you. :)

On a side note: The last commits have not been tested yet, will do so when I am done with the metaserver.

Revision history for this message
SirVer (sirver) wrote :

Had another look over the code just now. LGTM! As soon as the metaserver branch is ready, this can go in.

What a great change! Thanks for working on this Notabilis!

review: Approve
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2907. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/310195149.
Appveyor build 2716. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2716.

Revision history for this message
Notabilis (notabilis27) wrote :

It turns out that the kRoundTripTimeResponse can't work as plant. It is returning the player IDs as seen by the relay, which are something different than the player IDs in the GameHost/Client code. The relay IDs are fine for the GameHost (who has the mapping) but of no use for GameClients. Answering ping messages is fine for both. So we could:

a) Only display the ping times at the GameHost. Should probably be fine since it is the only one who
is permitted to kick participants anyway.
b) Make the GameHost deliver the ping results to the GameClients. So the relay collects the data, the GameHost retrieves them and the GameClients can ask the GameHost about it. Kind of complicated but probably a cleaner design than c). The GameHost/GameClient exchange can be done in a later branch. (a and b result in the same code in this branch.)
c) Give the relay more information about the connected participants so it can use the GameHost/Client-IDs when returning ping results.
d) Do something great I haven't come up with yet.

Which one do you prefer?

Revision history for this message
SirVer (sirver) wrote :

I am on business travel and therefore slow to respond.

> It is returning the player IDs as seen by the relay, which are something different than the player IDs in the GameHost/Client code.

I think these IDs should stay private to Widelands and the metaserver/relay respectively. Instead when they need to communicate about a particular player, they should send the player id as string - and map them internally to their ids.

Is this ready for another review round? What about the metaserver branch?

Revision history for this message
Notabilis (notabilis27) wrote :

No problem, I am in no hurry.

Seems I was a bit unclear in my previous post, sorry.
These IDs are only used between the relay and the game host. The ID is created when a new client connects to the relay and announced to the host. Later messages of the client are prepended with this ID and passed on to the host. This way, we have multiple virtual network connections multiplexed about a single connection between host and relay. In the GameHost class the host can map between this "connections" (can be real connections in LAN games) and the state of the clients. The IDs are only used to number these connections, inside the GameHost class other IDs are used to number the participants of the game. So I guess this works as you want.
For the clients this mapping does not exist since they don't have to care about other clients; the game state is always send by the game host. Due to this the IDs that can be send by the relay are useless.
Currently the ID is transmitted as a single byte but I can change this to a string if you insist. (When I think about it: using a byte means that the relay has to restrict the number of participants in a game to 250. I have to check this.)

There are (at least) two problems with the pings currently:

1) Internet gaming: The relay/host IDs are useless for the client. For possible solutions see post above.

2) LAN gaming: We have no network protocol here, so no pings. When communicating with the relay, the game data packets are prepended with information regarding the intended receiver. In LAN games this is not required so the packets are send "raw". This means that we can't simply add pings on the level of the NetHost/NetClient connection but have to add them to the GameHost/GameClient layer. So for internet gaming we get our ping results from the NetHostProxy (and that from the relay) while in LANs the (higher layer) GameHost has to send the pings. I don't like it (mixing the layer responsibilities). However, if we do it that way, I am in favor of variant b) in the post above, since then we have no problems with the IDs and (at least for GameClients) the same ping-code for internet and LAN games.

Apart from this RTT/ID problem, both branches are ready for review.

Revision history for this message
SirVer (sirver) wrote :

> For the clients this mapping does not exist since they don't have to care about other clients; the game state is always send by the game host.

Okay, now I got it. using a string as identifier for all packets is not wise, since packets can be small and would grow in size tremendously though this. So I think we stick with the current design and only the host knows the ping times.

> (When I think about it: using a byte means that the relay has to restrict the number of participants in a game to 250. I have to check this.)

This seems sufficient. We must keep in mind that we multiplex the bandwith of our net-relay too with the number of connections.

1) I understood now. Let's keep this information between host and net-relay for now then. We can always expand the protocol later to get this information to every connected entity.

2) I am of the opinion that the net-relay should be internet only and LAN play should not be touched. There is really no advantage in LAN for the relay, also LAN play is these days probably a rarely used feature - so much so that basically no commercial game supports it anymore.

I take that the code is now ready for a final pass. I will try to do this this weekend.

Revision history for this message
SirVer (sirver) wrote :

Actually, that was easy. This code lgtm. Will review the metaserver soon.

Revision history for this message
SirVer (sirver) wrote :

@bunnybot merge

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt 2017-11-25 20:29:26 +0000
+++ src/network/CMakeLists.txt 2017-12-05 20:38:52 +0000
@@ -15,9 +15,15 @@
15 netclient_interface.h15 netclient_interface.h
16 netclient.cc16 netclient.cc
17 netclient.h17 netclient.h
18 netclientproxy.cc
19 netclientproxy.h
18 nethost_interface.h20 nethost_interface.h
19 nethost.cc21 nethost.cc
20 nethost.h22 nethost.h
23 nethostproxy.cc
24 nethostproxy.h
25 netrelayconnection.cc
26 netrelayconnection.h
21 network.cc27 network.cc
22 network.h28 network.h
23 network_gaming_messages.cc29 network_gaming_messages.cc
@@ -27,6 +33,7 @@
27 network_player_settings_backend.cc33 network_player_settings_backend.cc
28 network_player_settings_backend.h34 network_player_settings_backend.h
29 network_protocol.h35 network_protocol.h
36 relay_protocol.h
30 DEPENDS37 DEPENDS
31 ai38 ai
32 base_exceptions39 base_exceptions
3340
=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc 2017-11-05 19:59:33 +0000
+++ src/network/gameclient.cc 2017-12-05 20:38:52 +0000
@@ -41,6 +41,8 @@
41#include "logic/playersmanager.h"41#include "logic/playersmanager.h"
42#include "map_io/widelands_map_loader.h"42#include "map_io/widelands_map_loader.h"
43#include "network/internet_gaming.h"43#include "network/internet_gaming.h"
44#include "network/netclient.h"
45#include "network/netclientproxy.h"
44#include "network/network_gaming_messages.h"46#include "network/network_gaming_messages.h"
45#include "network/network_protocol.h"47#include "network/network_protocol.h"
46#include "scripting/lua_interface.h"48#include "scripting/lua_interface.h"
@@ -58,7 +60,7 @@
5860
59 std::string localplayername;61 std::string localplayername;
6062
61 std::unique_ptr<NetClient> net;63 std::unique_ptr<NetClientInterface> net;
6264
63 /// Currently active modal panel. Receives an end_modal on disconnect65 /// Currently active modal panel. Receives an end_modal on disconnect
64 UI::Panel* modal;66 UI::Panel* modal;
@@ -91,13 +93,22 @@
9193
92GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host,94GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host,
93 const std::string& playername,95 const std::string& playername,
94 bool internet)96 bool internet, const std::string& gamename)
95 : d(new GameClientImpl), internet_(internet) {97 : d(new GameClientImpl), internet_(internet) {
9698
97 d->net = NetClient::connect(host.first);99 if (internet) {
100 assert(!gamename.empty());
101 d->net = NetClientProxy::connect(host.first, gamename);
102 } else {
103 d->net = NetClient::connect(host.first);
104 }
98 if ((!d->net || !d->net->is_connected()) && host.second.is_valid()) {105 if ((!d->net || !d->net->is_connected()) && host.second.is_valid()) {
99 // First IP did not work? Try the second IP106 // First IP did not work? Try the second IP
100 d->net = NetClient::connect(host.second);107 if (internet) {
108 d->net = NetClientProxy::connect(host.first, gamename);
109 } else {
110 d->net = NetClient::connect(host.second);
111 }
101 }112 }
102 if (!d->net || !d->net->is_connected()) {113 if (!d->net || !d->net->is_connected()) {
103 throw WLWarning(_("Could not establish connection to host"),114 throw WLWarning(_("Could not establish connection to host"),
@@ -877,9 +888,10 @@
877 return;888 return;
878 }889 }
879 // Process all available packets890 // Process all available packets
880 RecvPacket packet;891 std::unique_ptr<RecvPacket> packet = d->net->try_receive();
881 while (d->net->try_receive(&packet)) {892 while (packet) {
882 handle_packet(packet);893 handle_packet(*packet);
894 packet = d->net->try_receive();
883 }895 }
884 } catch (const DisconnectException& e) {896 } catch (const DisconnectException& e) {
885 disconnect(e.what());897 disconnect(e.what());
886898
=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h 2017-11-27 21:21:06 +0000
+++ src/network/gameclient.h 2017-12-05 20:38:52 +0000
@@ -24,7 +24,7 @@
24#include "logic/game_controller.h"24#include "logic/game_controller.h"
25#include "logic/game_settings.h"25#include "logic/game_settings.h"
26#include "logic/player_end_result.h"26#include "logic/player_end_result.h"
27#include "network/netclient.h"27#include "network/netclient_interface.h"
2828
29struct GameClientImpl;29struct GameClientImpl;
3030
@@ -39,7 +39,7 @@
39struct GameClient : public GameController, public GameSettingsProvider, public ChatProvider {39struct GameClient : public GameController, public GameSettingsProvider, public ChatProvider {
40 GameClient(const std::pair<NetAddress, NetAddress>& host,40 GameClient(const std::pair<NetAddress, NetAddress>& host,
41 const std::string& playername,41 const std::string& playername,
42 bool internet = false);42 bool internet = false, const std::string& gamename = "");
4343
44 ~GameClient() override;44 ~GameClient() override;
4545
4646
=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc 2017-11-30 07:11:36 +0000
+++ src/network/gamehost.cc 2017-12-05 20:38:52 +0000
@@ -52,6 +52,7 @@
52#include "network/constants.h"52#include "network/constants.h"
53#include "network/internet_gaming.h"53#include "network/internet_gaming.h"
54#include "network/nethost.h"54#include "network/nethost.h"
55#include "network/nethostproxy.h"
55#include "network/network_gaming_messages.h"56#include "network/network_gaming_messages.h"
56#include "network/network_lan_promotion.h"57#include "network/network_lan_promotion.h"
57#include "network/network_player_settings_backend.h"58#include "network/network_player_settings_backend.h"
@@ -412,7 +413,7 @@
412 HostGameSettingsProvider hp;413 HostGameSettingsProvider hp;
413 NetworkPlayerSettingsBackend npsb;414 NetworkPlayerSettingsBackend npsb;
414415
415 LanGamePromoter* promoter;416 std::unique_ptr<LanGamePromoter> promoter;
416 std::unique_ptr<NetHostInterface> net;417 std::unique_ptr<NetHostInterface> net;
417418
418 /// List of connected clients. Note that clients are not in the same419 /// List of connected clients. Note that clients are not in the same
@@ -459,7 +460,7 @@
459 chat(h),460 chat(h),
460 hp(h),461 hp(h),
461 npsb(&hp),462 npsb(&hp),
462 promoter(nullptr),463 promoter(),
463 net(),464 net(),
464 game(nullptr),465 game(nullptr),
465 pseudo_networktime(0),466 pseudo_networktime(0),
@@ -480,21 +481,30 @@
480 : d(new GameHostImpl(this)), internet_(internet), forced_pause_(false) {481 : d(new GameHostImpl(this)), internet_(internet), forced_pause_(false) {
481 log("[Host]: starting up.\n");482 log("[Host]: starting up.\n");
482483
483 if (internet) {
484 InternetGaming::ref().open_game();
485 }
486
487 d->localplayername = playername;484 d->localplayername = playername;
488485
489 // create a listening socket486 // create a listening socket
490 d->net = NetHost::listen(kWidelandsLanPort);487 if (internet) {
491 if (d->net == nullptr) {488 // No real listening socket. Instead, connect to the relay server
492 // This might happen when the widelands socket is already in use489 d->net = NetHostProxy::connect(InternetGaming::ref().ips(),
493 throw WLWarning(_("Failed to start the server!"),490 InternetGaming::ref().get_local_servername(), InternetGaming::ref().relay_password());
494 _("Widelands could not start a server.\n"491 if (d->net == nullptr) {
495 "Probably some other process is already running a server on our port."));492 // Some kind of problem with the relay server. Bad luck :(
493 throw WLWarning(_("Failed to host the server!"),
494 _("Widelands could not start hosting a server.\n"
495 "This should not happen and there is most likely "
496 "nothing you can do about it. Please report a bug."));
497 }
498 } else {
499 d->net = NetHost::listen(kWidelandsLanPort);
500 if (d->net == nullptr) {
501 // This might happen when the widelands socket is already in use
502 throw WLWarning(_("Failed to start the server!"),
503 _("Widelands could not start a server.\n"
504 "Probably some other process is already running a server on our port."));
505 }
506 d->promoter.reset(new LanGamePromoter());
496 }507 }
497 d->promoter = new LanGamePromoter();
498 d->game = nullptr;508 d->game = nullptr;
499 d->pseudo_networktime = 0;509 d->pseudo_networktime = 0;
500 d->waiting = true;510 d->waiting = true;
@@ -525,7 +535,7 @@
525535
526 // close all open sockets536 // close all open sockets
527 d->net.reset();537 d->net.reset();
528 delete d->promoter;538 d->promoter.reset();
529 delete d;539 delete d;
530 delete file_;540 delete file_;
531}541}
@@ -941,8 +951,12 @@
941951
942 // if there is one client that is currently receiving a file, we can not launch.952 // if there is one client that is currently receiving a file, we can not launch.
943 for (std::vector<Client>::iterator j = d->clients.begin(); j != d->clients.end(); ++j) {953 for (std::vector<Client>::iterator j = d->clients.begin(); j != d->clients.end(); ++j) {
944 if (!d->settings.users[j->usernum].ready)954 if (j->usernum == -1) {
945 return false;955 return false;
956 }
957 if (!d->settings.users[j->usernum].ready) {
958 return false;
959 }
946 }960 }
947961
948 // all players must be connected to a controller (human/ai) or be closed.962 // all players must be connected to a controller (human/ai) or be closed.
@@ -1896,8 +1910,9 @@
18961910
1897void GameHost::handle_network() {1911void GameHost::handle_network() {
18981912
1899 if (d->promoter != nullptr)1913 if (d->promoter) {
1900 d->promoter->run();1914 d->promoter->run();
1915 }
19011916
1902 // Check for new connections.1917 // Check for new connections.
1903 Client peer;1918 Client peer;
@@ -1929,11 +1944,12 @@
1929 }1944 }
19301945
1931 // Check if we hear anything from our clients1946 // Check if we hear anything from our clients
1932 RecvPacket packet;
1933 for (size_t i = 0; i < d->clients.size(); ++i) {1947 for (size_t i = 0; i < d->clients.size(); ++i) {
1934 try {1948 try {
1935 while (d->net->try_receive(d->clients.at(i).sock_id, &packet)) {1949 std::unique_ptr<RecvPacket> packet = d->net->try_receive(d->clients.at(i).sock_id);
1936 handle_packet(i, packet);1950 while (packet) {
1951 handle_packet(i, *packet);
1952 packet = d->net->try_receive(d->clients.at(i).sock_id);
1937 }1953 }
1938 // Thrown by handle_packet()1954 // Thrown by handle_packet()
1939 } catch (const DisconnectException& e) {1955 } catch (const DisconnectException& e) {
19401956
=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc 2017-11-26 20:55:56 +0000
+++ src/network/internet_gaming.cc 2017-12-05 20:38:52 +0000
@@ -43,7 +43,7 @@
43 : net(nullptr),43 : net(nullptr),
44 state_(OFFLINE),44 state_(OFFLINE),
45 reg_(false),45 reg_(false),
46 port_(INTERNET_GAMING_PORT),46 port_(kInternetGamingPort),
47 clientrights_(INTERNET_CLIENT_UNREGISTERED),47 clientrights_(INTERNET_CLIENT_UNREGISTERED),
48 gameips_(),48 gameips_(),
49 clientupdateonmetaserver_(true),49 clientupdateonmetaserver_(true),
@@ -68,7 +68,7 @@
68 authenticator_ = "";68 authenticator_ = "";
69 reg_ = false;69 reg_ = false;
70 meta_ = INTERNET_GAMING_METASERVER;70 meta_ = INTERNET_GAMING_METASERVER;
71 port_ = INTERNET_GAMING_PORT;71 port_ = kInternetGamingPort;
72 clientname_ = "";72 clientname_ = "";
73 clientrights_ = INTERNET_CLIENT_UNREGISTERED;73 clientrights_ = INTERNET_CLIENT_UNREGISTERED;
74 gamename_ = "";74 gamename_ = "";
@@ -152,7 +152,7 @@
152 log("InternetGaming: Sending login request.\n");152 log("InternetGaming: Sending login request.\n");
153 SendPacket s;153 SendPacket s;
154 s.string(IGPCMD_LOGIN);154 s.string(IGPCMD_LOGIN);
155 s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));155 s.string(boost::lexical_cast<std::string>(kInternetGamingProtocolVersion));
156 s.string(clientname_);156 s.string(clientname_);
157 s.string(build_id());157 s.string(build_id());
158 s.string(bool2str(reg_));158 s.string(bool2str(reg_));
@@ -162,7 +162,7 @@
162 // Now let's see, whether the metaserver is answering162 // Now let's see, whether the metaserver is answering
163 uint32_t const secs = time(nullptr);163 uint32_t const secs = time(nullptr);
164 state_ = CONNECTING;164 state_ = CONNECTING;
165 while (INTERNET_GAMING_TIMEOUT > time(nullptr) - secs) {165 while (kInternetGamingTimeout > time(nullptr) - secs) {
166 handle_metaserver_communication();166 handle_metaserver_communication();
167 // Check if we are a step further... if yes handle_packet has taken care about all the167 // Check if we are a step further... if yes handle_packet has taken care about all the
168 // paperwork, so we put our feet up and just return. ;)168 // paperwork, so we put our feet up and just return. ;)
@@ -274,9 +274,9 @@
274 return;274 return;
275 }275 }
276 // Process all available packets276 // Process all available packets
277 RecvPacket packet;277 std::unique_ptr<RecvPacket> packet = net->try_receive();
278 if (net->try_receive(&packet)) {278 if (packet) {
279 handle_packet(packet);279 handle_packet(*packet);
280 } else {280 } else {
281 // Nothing more to receive281 // Nothing more to receive
282 break;282 break;
@@ -356,7 +356,7 @@
356 // Okay, we have a connection. Send the login message and terminate the connection356 // Okay, we have a connection. Send the login message and terminate the connection
357 SendPacket s;357 SendPacket s;
358 s.string(IGPCMD_TELL_IP);358 s.string(IGPCMD_TELL_IP);
359 s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));359 s.string(boost::lexical_cast<std::string>(kInternetGamingProtocolVersion));
360 s.string(clientname_);360 s.string(clientname_);
361 s.string(authenticator_);361 s.string(authenticator_);
362 tmpNet->send(s);362 tmpNet->send(s);
@@ -581,6 +581,15 @@
581 if (waitcmd_ == IGPCMD_GAME_OPEN) {581 if (waitcmd_ == IGPCMD_GAME_OPEN) {
582 waitcmd_ = "";582 waitcmd_ = "";
583 }583 }
584 // Save the received IP(s), so the client can connect to the game
585 NetAddress::parse_ip(&gameips_.first, packet.string(), kInternetRelayPort);
586 // If the next value is true, a secondary IP follows
587 if (packet.string() == bool2str(true)) {
588 NetAddress::parse_ip(&gameips_.second, packet.string(), kInternetRelayPort);
589 }
590 log("InternetGaming: Received ips of the relay to host: %s %s.\n",
591 gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
592 state_ = IN_GAME;
584 }593 }
585594
586 else if (cmd == IGPCMD_GAME_CONNECT) {595 else if (cmd == IGPCMD_GAME_CONNECT) {
@@ -588,10 +597,10 @@
588 assert(waitcmd_ == IGPCMD_GAME_CONNECT);597 assert(waitcmd_ == IGPCMD_GAME_CONNECT);
589 waitcmd_ = "";598 waitcmd_ = "";
590 // Save the received IP(s), so the client can connect to the game599 // Save the received IP(s), so the client can connect to the game
591 NetAddress::parse_ip(&gameips_.first, packet.string(), kWidelandsLanPort);600 NetAddress::parse_ip(&gameips_.first, packet.string(), kInternetRelayPort);
592 // If the next value is true, a secondary IP follows601 // If the next value is true, a secondary IP follows
593 if (packet.string() == bool2str(true)) {602 if (packet.string() == bool2str(true)) {
594 NetAddress::parse_ip(&gameips_.second, packet.string(), kWidelandsLanPort);603 NetAddress::parse_ip(&gameips_.second, packet.string(), kInternetRelayPort);
595 }604 }
596 log("InternetGaming: Received ips of the game to join: %s %s.\n",605 log("InternetGaming: Received ips of the game to join: %s %s.\n",
597 gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());606 gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
@@ -648,11 +657,18 @@
648 return gameips_;657 return gameips_;
649}658}
650659
660const std::string InternetGaming::relay_password() {
661 return authenticator_;
662}
663
651/// called by a client to join the game \arg gamename664/// called by a client to join the game \arg gamename
652void InternetGaming::join_game(const std::string& gamename) {665void InternetGaming::join_game(const std::string& gamename) {
653 if (!logged_in())666 if (!logged_in())
654 return;667 return;
655668
669 // Reset the game ips, we should receive new ones shortly
670 gameips_ = std::make_pair(NetAddress(), NetAddress());
671
656 SendPacket s;672 SendPacket s;
657 s.string(IGPCMD_GAME_CONNECT);673 s.string(IGPCMD_GAME_CONNECT);
658 s.string(gamename);674 s.string(gamename);
@@ -663,7 +679,7 @@
663679
664 // From now on we wait for a reply from the metaserver680 // From now on we wait for a reply from the metaserver
665 waitcmd_ = IGPCMD_GAME_CONNECT;681 waitcmd_ = IGPCMD_GAME_CONNECT;
666 waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT;682 waittimeout_ = time(nullptr) + kInternetGamingTimeout;
667}683}
668684
669/// called by a client to open a new game with name gamename_685/// called by a client to open a new game with name gamename_
@@ -671,17 +687,19 @@
671 if (!logged_in())687 if (!logged_in())
672 return;688 return;
673689
690 // Reset the game ips, we should receive new ones shortly
691 gameips_ = std::make_pair(NetAddress(), NetAddress());
692
674 SendPacket s;693 SendPacket s;
675 s.string(IGPCMD_GAME_OPEN);694 s.string(IGPCMD_GAME_OPEN);
676 s.string(gamename_);695 s.string(gamename_);
677 s.string("1024"); // Used to be maxclients, no longer used.696 s.string("1024"); // Used to be maxclients, no longer used.
678 net->send(s);697 net->send(s);
679 log("InternetGaming: Client opened a game with the name %s.\n", gamename_.c_str());698 log("InternetGaming: Client opened a game with the name %s.\n", gamename_.c_str());
680 state_ = IN_GAME;
681699
682 // From now on we wait for a reply from the metaserver700 // From now on we wait for a reply from the metaserver
683 waitcmd_ = IGPCMD_GAME_OPEN;701 waitcmd_ = IGPCMD_GAME_OPEN;
684 waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT;702 waittimeout_ = time(nullptr) + kInternetGamingTimeout;
685}703}
686704
687/// called by a client that is host of a game to inform the metaserver, that the game started705/// called by a client that is host of a game to inform the metaserver, that the game started
@@ -696,7 +714,7 @@
696714
697 // From now on we wait for a reply from the metaserver715 // From now on we wait for a reply from the metaserver
698 waitcmd_ = IGPCMD_GAME_START;716 waitcmd_ = IGPCMD_GAME_START;
699 waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT;717 waittimeout_ = time(nullptr) + kInternetGamingTimeout;
700}718}
701719
702/// called by a client to inform the metaserver, that it left the game and is back in the lobby.720/// called by a client to inform the metaserver, that it left the game and is back in the lobby.
703721
=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h 2017-11-26 20:55:56 +0000
+++ src/network/internet_gaming.h 2017-12-05 20:38:52 +0000
@@ -101,10 +101,17 @@
101 * Contains two addresses when the host supports IPv4 and IPv6, one address when the host101 * Contains two addresses when the host supports IPv4 and IPv6, one address when the host
102 * only supports one of the protocols, no addresses when no join-request was sent to102 * only supports one of the protocols, no addresses when no join-request was sent to
103 * the metaserver. "No address" means a default constructed address.103 * the metaserver. "No address" means a default constructed address.
104 * Also returns the IPs of the relay server when trying to host a game.
104 * Use NetAddress::is_valid() to check whether a NetAddress has been default constructed.105 * Use NetAddress::is_valid() to check whether a NetAddress has been default constructed.
105 * @return The addresses.106 * @return The addresses.
106 */107 */
107 const std::pair<NetAddress, NetAddress>& ips();108 const std::pair<NetAddress, NetAddress>& ips();
109
110 /**
111 * Returns the password required to connect to the relay server as host.
112 */
113 const std::string relay_password();
114
108 void join_game(const std::string& gamename);115 void join_game(const std::string& gamename);
109 void open_game();116 void open_game();
110 void set_game_playing();117 void set_game_playing();
111118
=== modified file 'src/network/internet_gaming_protocol.h'
--- src/network/internet_gaming_protocol.h 2017-11-26 20:55:56 +0000
+++ src/network/internet_gaming_protocol.h 2017-12-05 20:38:52 +0000
@@ -34,46 +34,34 @@
34 * 0: Build 19 and before [stable, supported]34 * 0: Build 19 and before [stable, supported]
35 * 1: Between build 19 and build 20 - IPv6 support added35 * 1: Between build 19 and build 20 - IPv6 support added
36 * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after36 * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after
37 * crashes.37 * crashes. When logging twice with a registered account, the second connection gets a free
38 * When logging twice with a registered account, the second38 * username assigned. Dropping RELOGIN command.
39 * connection39 * 3: Between build 19 and build 20 - Added network relay for internet games [supported]
40 * gets a free username assigned. Dropping RELOGIN command.40 */
41 * [supported]41constexpr unsigned int kInternetGamingProtocolVersion = 3;
42 */42
43#define INTERNET_GAMING_PROTOCOL_VERSION 243/**
4444 * The default timeout time after which the client tries to resend a package or even finally closes
45/**45 * the
46 * The default timeout time after which the client tries to resend a package or even finally closes46 * connection to the metaserver, if no answer to a previous package (which requires an answer) was
47 * the47 * received. In case of a login or reconnect, this is the time to wait for the metaservers answer.
48 * connection to the metaserver, if no answer to a previous package (which requires an answer) was48 *
49 * received. In case of a login or reconnect, this is the time to wait for the metaservers answer.49 * value is in milliseconds
50 *50 */
51 * value is in milliseconds51// TODO(unknown): Should this be resettable by the user?
52 */52constexpr time_t kInternetGamingTimeout = 10; // 10 seconds
53// TODO(unknown): Should this be resettable by the user?
54#define INTERNET_GAMING_TIMEOUT 10 // 10 seconds
55
56/**
57 * The default timeout time after which the client tries to resend a package or even finally closes
58 * the
59 * connection to the metaserver, if no answer to a previous package (which requires an answer) was
60 * received. In case of a login or reconnect, this is the time to wait for the metaservers answer.
61 *
62 * value is in milliseconds
63 */
64// TODO(unknown): Should this be resettable by the user?
65#define INTERNET_GAMING_CLIENT_TIMEOUT 60 // 60 seconds - some time to reconnect
66
67/**
68 * The default number of retries after a timeout after which the client finally closes the
69 * connection to the metaserver.
70 */
71// TODO(unknown): Should this be resettable by the user?
72#define INTERNET_GAMING_RETRIES 3
7353
74/// Metaserver connection details54/// Metaserver connection details
75static const std::string INTERNET_GAMING_METASERVER = "widelands.org";55static const std::string INTERNET_GAMING_METASERVER = "widelands.org";
76#define INTERNET_GAMING_PORT 739556// Default port for connecting to the metaserver
57constexpr uint16_t kInternetGamingPort = 7395;
58// Default port for connecting to the relay
59constexpr uint16_t kInternetRelayPort = 7397;
60// The following ones are only used between metaserver and relay
61// Port used by the metaserver to contact the relay
62// INTERNET_RELAY_RPC_PORT 7398
63// Port used by the relay to contact the metaserver
64// INTERNET_GAMING_RPC_PORT 7399
7765
78/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *66/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
79 * CLIENT RIGHTS *67 * CLIENT RIGHTS *
@@ -183,7 +171,7 @@
183 * used.)171 * used.)
184 * \li string: clients rights (see client rights section above)172 * \li string: clients rights (see client rights section above)
185 *173 *
186 * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will again try to login174 * If no answer is received in \ref kInternetGamingTimeout s the client will again try to login
187 * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not175 * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not
188 * answer"176 * answer"
189 *177 *
@@ -337,12 +325,13 @@
337 * \li string: number of maximal clients325 * \li string: number of maximal clients
338 * \note build_id is not necessary, as this is in every way the build_id of the hosting client.326 * \note build_id is not necessary, as this is in every way the build_id of the hosting client.
339 *327 *
340 * Sent by the metaserver to acknowledge the startup of a new game without payload. The metaserver328 * Sent by the metaserver to acknowledge the startup of a new game with the following payload:
341 * will329 * \li string: primary ip of relay server for the game.
342 * list the new game, but set it as not connectable and recheck the connectability for330 * \li string: whether a secondary ip for the relay follows ("true" or "false" as string)
343 * INTERNET_GAMING_TIMEOUT ms.331 * \li string: secondary ip of the relay - only valid if previous was true
344 * If the game gets connectable in time, the metaserver lists the game as connectable, else it332 * The metaserver will list the new game, but set it as not connectable.
345 * removes the game from the list of games.333 * When the client connects to the relay within kInternetGamingTimeout milliseconds,
334 * the metaserver lists the game as connectable, else it removes the game from the list of games.
346 */335 */
347static const std::string IGPCMD_GAME_OPEN = "GAME_OPEN";336static const std::string IGPCMD_GAME_OPEN = "GAME_OPEN";
348337
349338
=== modified file 'src/network/netclient.cc'
--- src/network/netclient.cc 2017-07-22 22:09:20 +0000
+++ src/network/netclient.cc 2017-12-05 20:38:52 +0000
@@ -51,7 +51,7 @@
51 socket_.close(ec);51 socket_.close(ec);
52}52}
5353
54bool NetClient::try_receive(RecvPacket* packet) {54std::unique_ptr<RecvPacket> NetClient::try_receive() {
55 if (is_connected()) {55 if (is_connected()) {
56 // If we are connected, try to receive some data56 // If we are connected, try to receive some data
5757
@@ -72,7 +72,12 @@
72 }72 }
73 }73 }
74 // Try to get one packet from the deserializer74 // Try to get one packet from the deserializer
75 return deserializer_.write_packet(packet);75 std::unique_ptr<RecvPacket> packet(new RecvPacket);
76 if (deserializer_.write_packet(packet.get())) {
77 return packet;
78 } else {
79 return std::unique_ptr<RecvPacket>();
80 }
76}81}
7782
78void NetClient::send(const SendPacket& packet) {83void NetClient::send(const SendPacket& packet) {
@@ -81,21 +86,20 @@
81 }86 }
8287
83 boost::system::error_code ec;88 boost::system::error_code ec;
84#ifdef NDEBUG
85 boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
86#else
87 size_t written =89 size_t written =
88 boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);90 boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
89#endif
9091
91 // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold92 if (ec == boost::asio::error::would_block) {
92 // If it doesn't, set the socket to blocking before writing93 throw wexception("[NetClient] Socket connected to relay would block when writing");
93 // If it does, remove this comment after build 2094 }
94 assert(ec != boost::asio::error::would_block);
95 assert(written == packet.get_size() || ec);
96 if (ec) {95 if (ec) {
97 log("[NetClient] Error when trying to send some data: %s.\n", ec.message().c_str());96 log("[NetClient] Error when trying to send some data: %s.\n", ec.message().c_str());
98 close();97 close();
98 return;
99 }
100 if (written < packet.get_size()) {
101 throw wexception("[NetClient] Unable to send complete packet to relay (only %lu bytes of %lu)",
102 written, packet.get_size());
99 }103 }
100}104}
101105
102106
=== modified file 'src/network/netclient.h'
--- src/network/netclient.h 2017-11-27 21:21:06 +0000
+++ src/network/netclient.h 2017-12-05 20:38:52 +0000
@@ -53,7 +53,7 @@
53 // Inherited from NetClientInterface53 // Inherited from NetClientInterface
54 bool is_connected() const override;54 bool is_connected() const override;
55 void close() override;55 void close() override;
56 bool try_receive(RecvPacket* packet) override;56 std::unique_ptr<RecvPacket> try_receive() override;
57 void send(const SendPacket& packet) override;57 void send(const SendPacket& packet) override;
5858
59private:59private:
6060
=== modified file 'src/network/netclient_interface.h'
--- src/network/netclient_interface.h 2017-10-31 03:50:46 +0000
+++ src/network/netclient_interface.h 2017-12-05 20:38:52 +0000
@@ -54,12 +54,10 @@
5454
55 /**55 /**
56 * Tries to receive a packet.56 * Tries to receive a packet.
57 * \param packet A packet that should be overwritten with the received data.57 * \return A pointer to a packet if one packet is available, an invalid pointer otherwise.
58 * \return \c true if a packet is available, \c false otherwise.58 * Calling this on a closed connection will return an invalid pointer.
59 * The given packet is only modified when \c true is returned.
60 * Calling this on a closed connection will return false.
61 */59 */
62 virtual bool try_receive(RecvPacket* packet) = 0;60 virtual std::unique_ptr<RecvPacket> try_receive() = 0;
6361
64 /**62 /**
65 * Sends a packet.63 * Sends a packet.
6664
=== added file 'src/network/netclientproxy.cc'
--- src/network/netclientproxy.cc 1970-01-01 00:00:00 +0000
+++ src/network/netclientproxy.cc 2017-12-05 20:38:52 +0000
@@ -0,0 +1,165 @@
1#include "network/netclientproxy.h"
2
3#include <memory>
4
5#include "base/log.h"
6#include "network/relay_protocol.h"
7
8std::unique_ptr<NetClientProxy> NetClientProxy::connect(const NetAddress& address, const std::string& name) {
9 std::unique_ptr<NetClientProxy> ptr(new NetClientProxy(address, name));
10 if (ptr->conn_ == nullptr || !ptr->conn_->is_connected()) {
11 ptr.reset();
12 }
13 return ptr;
14}
15
16NetClientProxy::~NetClientProxy() {
17 close();
18}
19
20
21bool NetClientProxy::is_connected() const {
22 return conn_ && conn_->is_connected();
23}
24
25void NetClientProxy::close() {
26 if (conn_ && conn_->is_connected()) {
27 conn_->close();
28 }
29}
30
31std::unique_ptr<RecvPacket> NetClientProxy::try_receive() {
32 receive_commands();
33
34 // Now check whether there is data
35 if (received_.empty()) {
36 return std::unique_ptr<RecvPacket>();
37 }
38
39 std::unique_ptr<RecvPacket> packet = std::move(received_.front());
40 received_.pop();
41 return packet;
42}
43
44void NetClientProxy::send(const SendPacket& packet) {
45 conn_->send(RelayCommand::kToHost);
46 conn_->send(packet);
47}
48
49NetClientProxy::NetClientProxy(const NetAddress& address, const std::string& name)
50 : conn_(NetRelayConnection::connect(address)) {
51 if (conn_ == nullptr || !conn_->is_connected()) {
52 return;
53 }
54
55 conn_->send(RelayCommand::kHello);
56 conn_->send(kRelayProtocolVersion);
57 conn_->send(name);
58 conn_->send("client");
59
60 // Wait 10 seconds for an answer
61 uint32_t endtime = time(nullptr) + 10;
62 while (!NetRelayConnection::Peeker(conn_.get()).cmd()) {
63 if (time(nullptr) > endtime) {
64 // No message received in time
65 conn_->close();
66 conn_.reset();
67 return;
68 }
69 }
70
71 RelayCommand cmd;
72 conn_->receive(&cmd);
73
74 if (cmd != RelayCommand::kWelcome) {
75 conn_->close();
76 conn_.reset();
77 return;
78 }
79
80 // Check version
81 endtime = time(nullptr) + 10;
82 while (!NetRelayConnection::Peeker(conn_.get()).uint8_t()) {
83 if (time(nullptr) > endtime) {
84 conn_->close();
85 conn_.reset();
86 return;
87 }
88 }
89 uint8_t relay_proto_version;
90 conn_->receive(&relay_proto_version);
91 if (relay_proto_version != kRelayProtocolVersion) {
92 conn_->close();
93 conn_.reset();
94 }
95
96 // Check game name
97 endtime = time(nullptr) + 10;
98 while (!NetRelayConnection::Peeker(conn_.get()).string()) {
99 if (time(nullptr) > endtime) {
100 conn_->close();
101 conn_.reset();
102 return;
103 }
104 }
105 std::string game_name;
106 conn_->receive(&game_name);
107 if (game_name != name) {
108 conn_->close();
109 conn_.reset();
110 return;
111 }
112}
113
114void NetClientProxy::receive_commands() {
115 if (!conn_->is_connected()) {
116 return;
117 }
118
119 // Receive all available commands
120 RelayCommand cmd;
121 NetRelayConnection::Peeker peek(conn_.get());
122 if (!peek.cmd(&cmd)) {
123 // No command to receive
124 return;
125 }
126 switch (cmd) {
127 case RelayCommand::kDisconnect:
128 if (peek.string()) {
129 // Command is completely in the buffer, handle it
130 conn_->receive(&cmd);
131 std::string reason;
132 conn_->receive(&reason);
133 // TODO(Notabilis): Handle the reason for the disconnect
134 conn_->close();
135 }
136 break;
137 case RelayCommand::kFromHost:
138 if (peek.recvpacket()) {
139 conn_->receive(&cmd);
140 std::unique_ptr<RecvPacket> packet(new RecvPacket);
141 conn_->receive(packet.get());
142 received_.push(std::move(packet));
143 }
144 break;
145 case RelayCommand::kPing:
146 if (peek.uint8_t()) {
147 conn_->receive(&cmd);
148 uint8_t seq;
149 conn_->receive(&seq);
150 // Reply with a pong
151 conn_->send(RelayCommand::kPong);
152 conn_->send(seq);
153 }
154 break;
155 case RelayCommand::kRoundTripTimeResponse:
156 conn_->ignore_rtt_response();
157 break;
158 default:
159 // Other commands should not be possible.
160 // Then is either something wrong with the protocol or there is an implementation mistake
161 log("Received command code %i from relay server, do not know what to do with it\n",
162 static_cast<uint8_t>(cmd));
163 NEVER_HERE();
164 }
165}
0166
=== added file 'src/network/netclientproxy.h'
--- src/network/netclientproxy.h 1970-01-01 00:00:00 +0000
+++ src/network/netclientproxy.h 2017-12-05 20:38:52 +0000
@@ -0,0 +1,73 @@
1/*
2 * Copyright (C) 2008-2017 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_NETWORK_NETCLIENTPROXY_H
21#define WL_NETWORK_NETCLIENTPROXY_H
22
23#include <map>
24#include <memory>
25
26#include "network/netclient_interface.h"
27#include "network/netrelayconnection.h"
28
29/**
30 * Represents a client in-game, but talks through the 'wlnr' relay binary.
31 */
32class NetClientProxy : public NetClientInterface {
33public:
34
35 /**
36 * Tries to connect to the relay at the given address.
37 * \param address The address to connect to.
38 * \param name The name of the game.
39 * \return A pointer to a ready \c NetClientProxy object or a nullptr if the connection failed.
40 */
41 static std::unique_ptr<NetClientProxy> connect(const NetAddress& address, const std::string& name);
42
43 /**
44 * Closes the server.
45 */
46 ~NetClientProxy() override;
47
48
49 // Inherited from NetClientInterface
50 bool is_connected() const override;
51 void close() override;
52 std::unique_ptr<RecvPacket> try_receive() override;
53 void send(const SendPacket& packet) override;
54
55private:
56
57 /**
58 * Tries to connect to the relay at the given address.
59 * If it fails, is_connected() will return \c false.
60 * \param address The address to connect to.
61 * \param name The name of the game.
62 */
63 NetClientProxy(const NetAddress& address, const std::string& name);
64
65 void receive_commands();
66
67 std::unique_ptr<NetRelayConnection> conn_;
68
69 /// For each connected client, the packages that have been received from him.
70 std::queue<std::unique_ptr<RecvPacket>> received_;
71};
72
73#endif // end of include guard: WL_NETWORK_NETCLIENTPROXY_H
074
=== modified file 'src/network/nethost.cc'
--- src/network/nethost.cc 2017-10-17 19:08:59 +0000
+++ src/network/nethost.cc 2017-12-05 20:38:52 +0000
@@ -127,7 +127,7 @@
127 return true;127 return true;
128}128}
129129
130bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) {130std::unique_ptr<RecvPacket> NetHost::try_receive(const ConnectionId id) {
131 // Always read all available data into buffers131 // Always read all available data into buffers
132 uint8_t buffer[kNetworkBufferSize];132 uint8_t buffer[kNetworkBufferSize];
133133
@@ -161,32 +161,35 @@
161161
162 // Now check whether there is data for the requested client162 // Now check whether there is data for the requested client
163 if (!is_connected(id))163 if (!is_connected(id))
164 return false;164 return std::unique_ptr<RecvPacket>();
165165
166 // Try to get one packet from the deserializer166 // Try to get one packet from the deserializer
167 return clients_.at(id).deserializer.write_packet(packet);167 std::unique_ptr<RecvPacket> packet(new RecvPacket);
168 if (clients_.at(id).deserializer.write_packet(packet.get())) {
169 return packet;
170 } else {
171 return std::unique_ptr<RecvPacket>();
172 }
168}173}
169174
170void NetHost::send(const ConnectionId id, const SendPacket& packet) {175void NetHost::send(const ConnectionId id, const SendPacket& packet) {
171 boost::system::error_code ec;176 boost::system::error_code ec;
172 if (is_connected(id)) {177 if (is_connected(id)) {
173#ifdef NDEBUG
174 boost::asio::write(
175 clients_.at(id).socket, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
176#else
177 size_t written = boost::asio::write(178 size_t written = boost::asio::write(
178 clients_.at(id).socket, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);179 clients_.at(id).socket, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
179#endif
180180
181 // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold181 if (ec == boost::asio::error::would_block) {
182 // If it doesn't, set the socket to blocking before writing182 throw wexception("[NetHost] Socket connected to relay would block when writing");
183 // If it does, remove this comment after build 20183 }
184 assert(ec != boost::asio::error::would_block);
185 assert(written == packet.get_size() || ec);
186 if (ec) {184 if (ec) {
187 log("[NetHost] Error when sending to a client, closing connection: %s.\n",185 log("[NetHost] Error when sending to a client, closing connection: %s.\n",
188 ec.message().c_str());186 ec.message().c_str());
189 close(id);187 close(id);
188 return;
189 }
190 if (written < packet.get_size()) {
191 throw wexception("[NetHost] Unable to send complete packet to relay (only %lu bytes of %lu)",
192 written, packet.get_size());
190 }193 }
191 }194 }
192}195}
193196
=== modified file 'src/network/nethost.h'
--- src/network/nethost.h 2017-11-28 08:57:52 +0000
+++ src/network/nethost.h 2017-12-05 20:38:52 +0000
@@ -48,7 +48,7 @@
48 bool is_connected(ConnectionId id) const override;48 bool is_connected(ConnectionId id) const override;
49 void close(ConnectionId id) override;49 void close(ConnectionId id) override;
50 bool try_accept(ConnectionId* new_id) override;50 bool try_accept(ConnectionId* new_id) override;
51 bool try_receive(ConnectionId id, RecvPacket* packet) override;51 std::unique_ptr<RecvPacket> try_receive(ConnectionId id) override;
52 void send(ConnectionId id, const SendPacket& packet) override;52 void send(ConnectionId id, const SendPacket& packet) override;
53 void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;53 void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
5454
5555
=== modified file 'src/network/nethost_interface.h'
--- src/network/nethost_interface.h 2017-10-17 19:08:59 +0000
+++ src/network/nethost_interface.h 2017-12-05 20:38:52 +0000
@@ -20,6 +20,8 @@
20#ifndef WL_NETWORK_NETHOST_INTERFACE_H20#ifndef WL_NETWORK_NETHOST_INTERFACE_H
21#define WL_NETWORK_NETHOST_INTERFACE_H21#define WL_NETWORK_NETHOST_INTERFACE_H
2222
23#include <memory>
24
23#include "network/network.h"25#include "network/network.h"
2426
25/**27/**
@@ -66,12 +68,10 @@
66 /**68 /**
67 * Tries to receive a packet.69 * Tries to receive a packet.
68 * \param id The connection id of the client that should be received.70 * \param id The connection id of the client that should be received.
69 * \param packet A packet that should be overwritten with the received data.71 * \return A pointer to a packet if one is available, an invalid pointer otherwise.
70 * \return \c true if a packet is available, \c false otherwise.72 * Calling this on a closed connection will return an invalid pointer.
71 * The given packet is only modified when \c true is returned.
72 * Calling this on a closed connection will return false.
73 */73 */
74 virtual bool try_receive(ConnectionId id, RecvPacket* packet) = 0;74 virtual std::unique_ptr<RecvPacket> try_receive(ConnectionId id) = 0;
7575
76 /**76 /**
77 * Sends a packet.77 * Sends a packet.
7878
=== added file 'src/network/nethostproxy.cc'
--- src/network/nethostproxy.cc 1970-01-01 00:00:00 +0000
+++ src/network/nethostproxy.cc 2017-12-05 20:38:52 +0000
@@ -0,0 +1,288 @@
1#include "network/nethostproxy.h"
2
3#include <memory>
4
5#include "base/log.h"
6#include "network/relay_protocol.h"
7
8std::unique_ptr<NetHostProxy> NetHostProxy::connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password) {
9 std::unique_ptr<NetHostProxy> ptr(new NetHostProxy(addresses, name, password));
10 if (ptr->conn_ == nullptr || !ptr->conn_->is_connected()) {
11 ptr.reset();
12 }
13 return ptr;
14}
15
16NetHostProxy::~NetHostProxy() {
17 if (conn_ && conn_->is_connected()) {
18 while (!clients_.empty()) {
19 close(clients_.begin()->first);
20 clients_.erase(clients_.begin());
21 }
22 conn_->close();
23 }
24}
25
26bool NetHostProxy::is_connected(const ConnectionId id) const {
27 return clients_.count(id) > 0 && clients_.at(id).state_ == Client::State::kConnected;
28}
29
30void NetHostProxy::close(const ConnectionId id) {
31 auto iter_client = clients_.find(id);
32 if (iter_client == clients_.end()) {
33 // Not connected anyway
34 return;
35 }
36 conn_->send(RelayCommand::kDisconnectClient);
37 conn_->send(id);
38 if (iter_client->second.received_.empty()) {
39 // No pending messages, remove the client
40 clients_.erase(iter_client);
41 } else {
42 // Still messages pending. Keep the structure so the host can receive them
43 iter_client->second.state_ = Client::State::kDisconnected;
44 }
45}
46
47bool NetHostProxy::try_accept(ConnectionId* new_id) {
48 // Always read all available data into buffers
49 receive_commands();
50
51 for (auto& entry : clients_) {
52 if (entry.second.state_ == Client::State::kConnecting) {
53 *new_id = entry.first;
54 entry.second.state_ = Client::State::kConnected;
55 return true;
56 }
57 }
58 return false;
59}
60
61std::unique_ptr<RecvPacket> NetHostProxy::try_receive(const ConnectionId id) {
62 receive_commands();
63
64 // Check whether client is not (yet) connected
65 if (clients_.count(id) == 0 || clients_.at(id).state_ == Client::State::kConnecting)
66 return std::unique_ptr<RecvPacket>();
67
68 std::queue<std::unique_ptr<RecvPacket>>& packet_list = clients_.at(id).received_;
69
70 // Now check whether there is data for the requested client
71 if (packet_list.empty()) {
72 // If the client is already disconnected it should not be in the map anymore
73 assert(clients_.at(id).state_ == Client::State::kConnected);
74 return std::unique_ptr<RecvPacket>();
75 }
76
77 std::unique_ptr<RecvPacket> packet = std::move(packet_list.front());
78 packet_list.pop();
79 if (packet_list.empty() && clients_.at(id).state_ == Client::State::kDisconnected) {
80 // If the receive buffer is empty now, remove client
81 clients_.erase(id);
82 }
83 return packet;
84}
85
86void NetHostProxy::send(const ConnectionId id, const SendPacket& packet) {
87 std::vector<ConnectionId> vec;
88 vec.push_back(id);
89 send(vec, packet);
90}
91
92void NetHostProxy::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) {
93 if (ids.empty()) {
94 return;
95 }
96
97 receive_commands();
98
99 bool has_connected_client = false;
100 for (ConnectionId id : ids) {
101 if (is_connected(id)) {
102 // This should be but is not always the case. It can happen that we receive a client disconnect
103 // on receive_commands() above and the GameHost did not have the chance to react to it yet.
104 has_connected_client = true;
105 }
106 }
107 if (!has_connected_client) {
108 // Oops, no clients left to send to
109 return;
110 }
111
112 conn_->send(RelayCommand::kToClients);
113 for (ConnectionId id : ids) {
114 if (is_connected(id)) {
115 conn_->send(id);
116 }
117 }
118 conn_->send(0);
119 conn_->send(packet);
120}
121
122NetHostProxy::NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password)
123 : conn_(NetRelayConnection::connect(addresses.first)) {
124
125 if ((conn_ == nullptr || !conn_->is_connected()) && addresses.second.is_valid()) {
126 conn_ = NetRelayConnection::connect(addresses.second);
127 }
128
129 if (conn_ == nullptr || !conn_->is_connected()) {
130 return;
131 }
132
133 conn_->send(RelayCommand::kHello);
134 conn_->send(kRelayProtocolVersion);
135 conn_->send(name);
136 conn_->send(password);
137 conn_->send(password);
138
139 // Wait 10 seconds for an answer
140 uint32_t endtime = time(nullptr) + 10;
141 while (!NetRelayConnection::Peeker(conn_.get()).cmd()) {
142 if (time(nullptr) > endtime) {
143 // No message received in time
144 conn_->close();
145 conn_.reset();
146 return;
147 }
148 }
149
150 RelayCommand cmd;
151 conn_->receive(&cmd);
152
153 if (cmd != RelayCommand::kWelcome) {
154 conn_->close();
155 conn_.reset();
156 return;
157 }
158
159 // Check version
160 endtime = time(nullptr) + 10;
161 while (!NetRelayConnection::Peeker(conn_.get()).uint8_t()) {
162 if (time(nullptr) > endtime) {
163 // No message received in time
164 conn_->close();
165 conn_.reset();
166 return;
167 }
168 }
169 uint8_t relay_proto_version;
170 conn_->receive(&relay_proto_version);
171 if (relay_proto_version != kRelayProtocolVersion) {
172 conn_->close();
173 conn_.reset();
174 return;
175 }
176
177 // Check game name
178 endtime = time(nullptr) + 10;
179 while (!NetRelayConnection::Peeker(conn_.get()).string()) {
180 if (time(nullptr) > endtime) {
181 // No message received in time
182 conn_->close();
183 conn_.reset();
184 return;
185 }
186 }
187 std::string game_name;
188 conn_->receive(&game_name);
189 if (game_name != name) {
190 conn_->close();
191 conn_.reset();
192 return;
193 }
194}
195
196void NetHostProxy::receive_commands() {
197 if (!conn_->is_connected()) {
198 // Seems the connection broke at some time. Set all clients to disconnected
199 for (auto iter_client = clients_.begin(); iter_client != clients_.end(); ) {
200 if (iter_client->second.received_.empty()) {
201 // No pending messages, remove the client
202 clients_.erase(iter_client++);
203 } else {
204 // Still messages pending. Keep the structure so the host can receive them
205 iter_client->second.state_ = Client::State::kDisconnected;
206 ++iter_client;
207 }
208 }
209 return;
210 }
211
212 // Receive all available commands
213 RelayCommand cmd;
214 NetRelayConnection::Peeker peek(conn_.get());
215 if (!peek.cmd(&cmd)) {
216 // No command to receive
217 return;
218 }
219 switch (cmd) {
220 case RelayCommand::kDisconnect:
221 if (peek.string()) {
222 // Command is completely in the buffer, handle it
223 conn_->receive(&cmd);
224 std::string reason;
225 conn_->receive(&reason);
226 conn_->close();
227 // Set all clients to offline
228 for (auto& entry : clients_) {
229 entry.second.state_ = Client::State::kDisconnected;
230 }
231 }
232 break;
233 case RelayCommand::kConnectClient:
234 if (peek.uint8_t()) {
235 conn_->receive(&cmd);
236 uint8_t id;
237 conn_->receive(&id);
238#ifndef NDEBUG
239 auto result = clients_.emplace(
240 std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple());
241 assert(result.second);
242#else
243 clients_.emplace(
244 std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple());
245#endif
246 }
247 break;
248 case RelayCommand::kDisconnectClient:
249 if (peek.uint8_t()) {
250 conn_->receive(&cmd);
251 uint8_t id;
252 conn_->receive(&id);
253 assert(clients_.count(id));
254 clients_.at(id).state_ = Client::State::kDisconnected;
255 }
256 break;
257 case RelayCommand::kFromClient:
258 if (peek.uint8_t() && peek.recvpacket()) {
259 conn_->receive(&cmd);
260 uint8_t id;
261 conn_->receive(&id);
262 std::unique_ptr<RecvPacket> packet(new RecvPacket);
263 conn_->receive(packet.get());
264 assert(clients_.count(id));
265 clients_.at(id).received_.push(std::move(packet));
266 }
267 break;
268 case RelayCommand::kPing:
269 if (peek.uint8_t()) {
270 conn_->receive(&cmd);
271 uint8_t seq;
272 conn_->receive(&seq);
273 // Reply with a pong
274 conn_->send(RelayCommand::kPong);
275 conn_->send(seq);
276 }
277 break;
278 case RelayCommand::kRoundTripTimeResponse:
279 conn_->ignore_rtt_response();
280 break;
281 default:
282 // Other commands should not be possible.
283 // Then is either something wrong with the protocol or there is an implementation mistake
284 log("Received command code %i from relay server, do not know what to do with it\n",
285 static_cast<uint8_t>(cmd));
286 NEVER_HERE();
287 }
288}
0289
=== added file 'src/network/nethostproxy.h'
--- src/network/nethostproxy.h 1970-01-01 00:00:00 +0000
+++ src/network/nethostproxy.h 2017-12-05 20:38:52 +0000
@@ -0,0 +1,103 @@
1/*
2 * Copyright (C) 2008-2017 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_NETWORK_NETHOSTPROXY_H
21#define WL_NETWORK_NETHOSTPROXY_H
22
23#include <map>
24#include <memory>
25
26#include "network/nethost_interface.h"
27#include "network/netrelayconnection.h"
28
29/**
30 * Represents a host in-game, but talks through the 'wlnr' relay binary.
31 */
32class NetHostProxy : public NetHostInterface {
33public:
34
35 /**
36 * Tries to connect to the relay at the given address.
37 * \param address The address to connect to.
38 * \param name The name of the game.
39 * \param password The password for connecting as host.
40 * \return A pointer to a ready \c NetHostProxy object or a nullptr if the connection failed.
41 */
42 static std::unique_ptr<NetHostProxy> connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
43
44 /**
45 * Closes the server.
46 */
47 ~NetHostProxy() override;
48
49 // Inherited from NetHostInterface
50 bool is_connected(ConnectionId id) const override;
51 void close(ConnectionId id) override;
52 bool try_accept(ConnectionId* new_id) override;
53 std::unique_ptr<RecvPacket> try_receive(ConnectionId id) override;
54 void send(ConnectionId id, const SendPacket& packet) override;
55 void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
56
57private:
58
59 /**
60 * Tries to connect to the relay at the given address.
61 * If it fails, is_connected() will return \c false.
62 * \param addresses Two possible addresses to connect to.
63 * \param name The name of the game.
64 * \param password The password for connecting as host.
65 */
66 NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
67
68 void receive_commands();
69
70 std::unique_ptr<NetRelayConnection> conn_;
71
72 /// A list of clients which want to connect.
73 std::queue<ConnectionId> accept_;
74
75 /// The clients connected through the relay
76 struct Client {
77 /// The state of the client
78 enum class State {
79 /// The relay introduced the client but try_accept() hasn't been called for it yet
80 kConnecting,
81 /// A normally connected client
82 kConnected,
83 /// The relay told us that the client disconnected but there are still packages in the buffer
84 kDisconnected
85 };
86
87 Client()
88 : state_(State::kConnecting), received_() {
89 }
90
91 // deleted since RecvPacket does not offer a copy constructor
92 Client(const Client& other) = delete;
93
94 /// The current connection state
95 State state_;
96 /// The packages that have been received
97 std::queue<std::unique_ptr<RecvPacket>> received_;
98 };
99 /// The connected clients
100 std::map<ConnectionId, Client> clients_;
101};
102
103#endif // end of include guard: WL_NETWORK_NETHOSTPROXY_H
0104
=== added file 'src/network/netrelayconnection.cc'
--- src/network/netrelayconnection.cc 1970-01-01 00:00:00 +0000
+++ src/network/netrelayconnection.cc 2017-12-05 20:38:52 +0000
@@ -0,0 +1,337 @@
1#include "network/netrelayconnection.h"
2
3#include <memory>
4
5#include "base/log.h"
6
7// Not so great: Quite some duplicated code between this class and NetClient.
8
9NetRelayConnection::Peeker::Peeker(NetRelayConnection *conn)
10 : conn_(conn), peek_pointer_(0) {
11 assert(conn_);
12}
13
14bool NetRelayConnection::Peeker::string() {
15
16 // Simple validity check. Should always be true as long as the caller has not used any receive() method.
17 assert(conn_->buffer_.size() >= peek_pointer_);
18
19 conn_->try_network_receive();
20
21 if (conn_->buffer_.size() < peek_pointer_ + 1) {
22 return false;
23 }
24
25 // A string goes until the next \0 and might have a length of 0
26 for (size_t i = peek_pointer_; i < conn_->buffer_.size(); ++i) {
27 if (conn_->buffer_[i] == '\0') {
28 peek_pointer_ = i + 1;
29 return true;
30 }
31 }
32 return false;
33}
34
35bool NetRelayConnection::Peeker::cmd(RelayCommand *out) {
36
37 assert(conn_->buffer_.size() >= peek_pointer_);
38
39 conn_->try_network_receive();
40
41 if (conn_->buffer_.size() > peek_pointer_) {
42 if (out != nullptr) {
43 *out = static_cast<RelayCommand>(conn_->buffer_[peek_pointer_]);
44 }
45 peek_pointer_++;
46 return true;
47 }
48 return false;
49}
50
51bool NetRelayConnection::Peeker::uint8_t(::uint8_t *out) {
52
53 assert(conn_->buffer_.size() >= peek_pointer_);
54
55 conn_->try_network_receive();
56
57 // If there is any byte available, we can read an uint8
58 if (conn_->buffer_.size() > peek_pointer_) {
59 if (out != nullptr) {
60 *out = static_cast<::uint8_t>(conn_->buffer_[peek_pointer_]);
61 }
62 peek_pointer_++;
63 return true;
64 }
65 return false;
66}
67
68bool NetRelayConnection::Peeker::recvpacket() {
69
70 assert(conn_->buffer_.size() >= peek_pointer_);
71
72 conn_->try_network_receive();
73
74 if (conn_->buffer_.size() < peek_pointer_ + 2) {
75 // Not even enough space for the size of the recvpacket
76 return false;
77 }
78
79 // RecvPackets have their size coded in their first two bytes
80 const uint16_t size = conn_->buffer_[peek_pointer_ + 0] << 8 | conn_->buffer_[peek_pointer_ + 1];
81 assert(size >= 2);
82
83 if (conn_->buffer_.size() >= peek_pointer_ + size) {
84 peek_pointer_ += size;
85 return true;
86 }
87 return false;
88}
89
90std::unique_ptr<NetRelayConnection> NetRelayConnection::connect(const NetAddress& host) {
91 std::unique_ptr<NetRelayConnection> ptr(new NetRelayConnection(host));
92 if (!ptr->is_connected()) {
93 ptr.reset();
94 }
95 return ptr;
96}
97
98NetRelayConnection::~NetRelayConnection() {
99 if (is_connected()) {
100 close();
101 }
102}
103
104bool NetRelayConnection::is_connected() const {
105 return socket_.is_open();
106}
107
108void NetRelayConnection::close() {
109 if (!is_connected()) {
110 return;
111 }
112 boost::system::error_code ec;
113 boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec);
114 if (!ec) {
115 log("[NetRelayConnection] Closing network socket connected to %s:%i.\n",
116 remote.address().to_string().c_str(), remote.port());
117 } else {
118 log("[NetRelayConnection] Closing network socket.\n");
119 }
120 socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
121 socket_.close(ec);
122}
123
124void NetRelayConnection::receive(std::string *str) {
125 // Try to receive something from the network.
126 try_network_receive();
127
128 #ifndef NDEBUG
129 // Check if we can read a complete string
130 assert(Peeker(this).string());
131 #endif // NDEBUG
132
133 // Read the string
134 str->clear();
135 // No range check needed, peek_string() takes care of that
136 while (buffer_.front() != '\0') {
137 str->push_back(buffer_.front());
138 buffer_.pop_front();
139 }
140 // Pop the \0
141 buffer_.pop_front();
142}
143
144void NetRelayConnection::receive(RelayCommand *out) {
145 try_network_receive();
146
147 // No complete peeker required here, we only want one byte
148 assert(!buffer_.empty());
149
150 uint8_t i;
151 receive(&i);
152 *out = static_cast<RelayCommand>(i);
153}
154
155void NetRelayConnection::receive(uint8_t *out) {
156 try_network_receive();
157
158 assert(!buffer_.empty());
159
160 *out = buffer_.front();
161 buffer_.pop_front();
162}
163
164void NetRelayConnection::receive(RecvPacket *packet) {
165 try_network_receive();
166
167
168 #ifndef NDEBUG
169 assert(Peeker(this).recvpacket());
170 #endif // NDEBUG
171
172 // Read the packet (adapted copy from Deserializer)
173 const uint16_t size = buffer_[0] << 8 | buffer_[1];
174 assert(size >= 2);
175 assert(buffer_.size() >= size);
176
177 packet->buffer.clear();
178 packet->buffer.insert(packet->buffer.end(), buffer_.begin() + 2, buffer_.begin() + size);
179 packet->index_ = 0;
180
181 buffer_.erase(buffer_.begin(), buffer_.begin() + size);
182}
183
184bool NetRelayConnection::try_network_receive() {
185 if (!is_connected()) {
186 return false;
187 }
188
189 unsigned char buffer[kNetworkBufferSize];
190 boost::system::error_code ec;
191 size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
192 if (!ec) {
193 assert(length > 0);
194 assert(length <= kNetworkBufferSize);
195 // Has read something
196 for (size_t i = 0; i < length; ++i) {
197 buffer_.push_back(buffer[i]);
198 }
199 }
200
201 if (ec && ec != boost::asio::error::would_block) {
202 // Connection closed or some error, close the socket
203 log("[NetRelayConnection] Error when trying to receive some data: %s.\n", ec.message().c_str());
204 close();
205 return false;
206 }
207 return true;
208}
209
210void NetRelayConnection::send(const RelayCommand data) {
211 send(static_cast<uint8_t>(data));
212}
213
214void NetRelayConnection::send(const uint8_t data) {
215 if (!is_connected()) {
216 return;
217 }
218
219 uint8_t buf[1];
220 buf[0] = data;
221
222 boost::system::error_code ec;
223 size_t written = boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec);
224
225 if (ec == boost::asio::error::would_block) {
226 throw wexception("[NetRelayConnection] Socket connected to relay would block when writing");
227 }
228 if (written < 1) {
229 throw wexception("[NetRelayConnection] Unable to send byte to relay");
230 }
231 if (ec) {
232 log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
233 close();
234 }
235}
236
237void NetRelayConnection::send(const std::string& str) {
238 if (!is_connected()) {
239 return;
240 }
241
242 // Append \0
243 std::vector<boost::asio::const_buffer> buffers;
244 buffers.push_back(boost::asio::buffer(str));
245 buffers.push_back(boost::asio::buffer("\0", 1));
246
247 boost::system::error_code ec;
248 size_t written = boost::asio::write(socket_, buffers, ec);
249
250 if (ec == boost::asio::error::would_block) {
251 throw wexception("[NetRelayConnection] Socket connected to relay would block when writing");
252 }
253 if (written < str.length() + 1) {
254 throw wexception(
255 "[NetRelayConnection] Unable to send complete string to relay (only %lu bytes of %lu)",
256 written, str.length() + 1);
257 }
258 if (ec) {
259 log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
260 close();
261 }
262}
263
264void NetRelayConnection::send(const SendPacket& packet) {
265 if (!is_connected()) {
266 return;
267 }
268
269 boost::system::error_code ec;
270 size_t written =
271 boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
272
273 if (ec == boost::asio::error::would_block) {
274 throw wexception("[NetRelayConnection] Socket connected to relay would block when writing");
275 }
276 if (ec) {
277 log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
278 close();
279 return;
280 }
281 if (written < packet.get_size()) {
282 throw wexception(
283 "[NetRelayConnection] Unable to send complete packet to relay (only %lu bytes of %lu)",
284 written, packet.get_size());
285 }
286}
287
288NetRelayConnection::NetRelayConnection(const NetAddress& host)
289 : io_service_(), socket_(io_service_), buffer_() {
290
291 assert(host.is_valid());
292 const boost::asio::ip::tcp::endpoint destination(host.ip, host.port);
293
294 log("[NetRelayConnection]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port);
295 boost::system::error_code ec;
296 socket_.connect(destination, ec);
297 if (!ec && is_connected()) {
298 log("success.\n");
299 socket_.non_blocking(true);
300 } else {
301 log("failed.\n");
302 socket_.close();
303 assert(!is_connected());
304 }
305}
306
307void NetRelayConnection::ignore_rtt_response() {
308
309 // TODO(Notabilis): Implement GUI with display of RTTs and possibility to kick lagging players
310 // See https://bugs.launchpad.net/widelands/+bug/1734673
311 // TODO(Notabilis): Move this method somewhere where it makes sense.
312
313 uint8_t length_list = 0;
314 RelayCommand cmd;
315 uint8_t tmp;
316
317 Peeker peek(this);
318 peek.cmd(&cmd);
319 assert(cmd == RelayCommand::kRoundTripTimeResponse);
320
321 bool data_complete = peek.uint8_t(&length_list);
322 // Each list element consists of three uint8_t
323 for (uint8_t i = 0; i < length_list * 3; i++) {
324 data_complete = data_complete && peek.uint8_t();
325 }
326 if (!data_complete) {
327 // Some part of this packet is still missing. Try again later
328 return;
329 }
330
331 // Packet completely in buffer, fetch it and ignore it
332 receive(&cmd); // Cmd
333 receive(&tmp); // Length
334 for (uint8_t i = 0; i < length_list * 3; i++) {
335 receive(&tmp); // Parts of the list. See relay_protocol.h
336 }
337}
0338
=== added file 'src/network/netrelayconnection.h'
--- src/network/netrelayconnection.h 1970-01-01 00:00:00 +0000
+++ src/network/netrelayconnection.h 2017-12-05 20:38:52 +0000
@@ -0,0 +1,225 @@
1/*
2 * Copyright (C) 2008-2017 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_NETWORK_NETRELAYCONNECTION_H
21#define WL_NETWORK_NETRELAYCONNECTION_H
22
23#include <memory>
24
25#include "network/network.h"
26#include "network/relay_protocol.h"
27
28/**
29 * A wrapper around a network connection to the 'wlnr' binary in the metaserver repo.
30 * Does not contain logic but provides a buffer to read
31 * uint8_t / std::string / SendPacket from the network stream.
32 *
33 * Use the Peeker class to check whether the required data has already been received
34 * before trying to read the data.
35 */
36class NetRelayConnection {
37public:
38
39 /**
40 * Allows to check whether the required data is completely in the buffer before starting to receive it.
41 *
42 * The idea of this methods is that the caller can check whether a complete relay command
43 * can be received before starting to remove data from the buffer. Otherwise, the caller would have to
44 * maintain their own buffer.
45 *
46 * The methods will not remove any bytes, but will check whether the required value can be read.
47 * After checking, an internal peek-pointer will be incremented. The next call of a method will then
48 * start reading after the previous value. Only a successful peek will move the pointer.
49 *
50 * Note that a successful peek does not mean that the received bytes really are of the requested type,
51 * it only means they could be interpreted that way. Whether the type matches is in the responsibility
52 * of the caller.
53 *
54 * Using any method of this class will trigger an internal read from the network socket inside the
55 * given NetRelayConnection.
56 *
57 * \warning Calling any receive() method on the given connection will invalidate the Peeker.
58 */
59 class Peeker {
60 public:
61
62 /**
63 * Creates a Peeker for the given connection.
64 * Calling any receive() method on the given connection will invalidate the Peeker.
65 * \param conn The connection which should be peeked into.
66 * \note The Peeker instance does not own the given connection. It is the responsible of the
67 * caller to make sure the given instance stays valid.
68 */
69 Peeker(NetRelayConnection *conn);
70
71 /**
72 * Checks whether a relay command can be read from the buffer.
73 * This method does not modify the buffer contents but increases the peek-pointer.
74 * \param out The command that will be returned next. It will not be removed from the input queue!
75 * If \c false is returned the contents are not modified. Can be nullptr.
76 * \return \c True if the value can be read, \c false if not enough data has been received.
77 */
78 bool cmd(RelayCommand *out = nullptr);
79
80 /**
81 * Checks whether an uint8_t can be read from the buffer.
82 * This method does not modify the buffer contents but increases the peek-pointer.
83 * \param out The uint8_t that will be returned next. It will not be removed from the input queue!
84 * If \c false is returned the contents are not modified. Can be nullptr.
85 * \return \c True if the value can be read, \c false if not enough data has been received.
86 */
87 bool uint8_t(uint8_t *out = nullptr);
88
89 /**
90 * Checks whether a std::string can be read from the buffer.
91 * This method does not modify the buffer contents but increases the peek-pointer.
92 * \return \c True if the value can be read, \c false if not enough data has been received.
93 */
94 bool string();
95
96 /**
97 * Checks whether a RecvPacket can be read from the buffer.
98 * This method does not modify the buffer contents but increases the peek-pointer.
99 * \return \c True if the value can be read, \c false if not enough data has been received.
100 */
101 bool recvpacket();
102
103 private:
104
105 /// The connection to operate on.
106 NetRelayConnection *conn_;
107
108 /// The position of the next peek.
109 size_t peek_pointer_;
110 };
111
112 /**
113 * Tries to establish a connection to the given relay.
114 * \param host The host to connect to.
115 * \return A pointer to a connected \c NetRelayConnection object or a \c nullptr.
116 */
117 static std::unique_ptr<NetRelayConnection> connect(const NetAddress& host);
118
119 /**
120 * Closes the connection.
121 * If you want to send a goodbye-message to the relay, do so before freeing the object.
122 */
123 ~NetRelayConnection();
124
125 /**
126 * Returns whether the relay is connected.
127 * \return \c true if the connection is open, \c false otherwise.
128 */
129 bool is_connected() const;
130
131 /**
132 * Closes the connection.
133 * If you want to send a goodbye-message to the relay, do so before calling this.
134 */
135 void close();
136
137 /**
138 * Receive a command.
139 * \warning Calling this method is only safe when peek_cmd() returned \c true.
140 * Otherwise the behavior of this method is undefined.
141 * \param out The variable to write the value to.
142 */
143 void receive(RelayCommand *out);
144
145 /**
146 * Receive an uint8_t.
147 * \warning Calling this method is only safe when peek_uint8_t() returned \c true.
148 * Otherwise the behavior of this method is undefined.
149 * \param out The variable to write the value to.
150 */
151 void receive(uint8_t *out);
152
153 /**
154 * Receive a string.
155 * \warning Calling this method is only safe when peek_string() returned \c true.
156 * Otherwise the behavior of this method is undefined.
157 * \param out The variable to write the value to.
158 */
159 void receive(std::string *out);
160
161 /**
162 * Receive a RecvPacket.
163 * \warning Calling this method is only safe when peek_recvpacket() returned \c true.
164 * Otherwise the behavior of this method is undefined.
165 * \param out The variable to write the value to.
166 */
167 void receive(RecvPacket *out);
168
169 /**
170 * Sends a relay command.
171 * Calling this on a closed connection will silently fail.
172 * \param data The data to send.
173 */
174 void send(RelayCommand data);
175
176 /**
177 * Sends an uint8_t.
178 * Calling this on a closed connection will silently fail.
179 * \param data The data to send.
180 */
181 void send(uint8_t data);
182
183 /**
184 * Sends a string.
185 * Calling this on a closed connection will silently fail.
186 * \param data The data to send.
187 */
188 void send(const std::string& data);
189
190 /**
191 * Sends a packet.
192 * Calling this on a closed connection will silently fail.
193 * \param data The data to send.
194 */
195 void send(const SendPacket& data);
196
197 // Temporary method, will be removed.
198 // Removes a message from type kRoundTripTimeResponse from the buffer.
199 void ignore_rtt_response();
200
201private:
202 /**
203 * Tries to establish a connection to the given host.
204 * If the connection attempt failed, is_connected() will return \c false.
205 * \param host The host to connect to.
206 */
207 explicit NetRelayConnection(const NetAddress& host);
208
209 /**
210 * Reads data from network.
211 * \return \c False if an error occurred.
212 */
213 bool try_network_receive();
214
215 /// An io_service needed by boost.asio. Primarily needed for asynchronous operations.
216 boost::asio::io_service io_service_;
217
218 /// The socket that connects us to the relay.
219 boost::asio::ip::tcp::socket socket_;
220
221 /// Buffer for arriving data. We need to store it until we have enough to return the required type.
222 std::deque<unsigned char> buffer_;
223};
224
225#endif // end of include guard: WL_NETWORK_NETRELAYCONNECTION_H
0226
=== modified file 'src/network/network.cc'
--- src/network/network.cc 2017-07-05 19:21:57 +0000
+++ src/network/network.cc 2017-12-05 20:38:52 +0000
@@ -171,6 +171,8 @@
171 if (buffer.empty()) {171 if (buffer.empty()) {
172 buffer.push_back(0); // this will finally be the length of the packet172 buffer.push_back(0); // this will finally be the length of the packet
173 buffer.push_back(0);173 buffer.push_back(0);
174 // Attention! These bytes are also used by the network relay protocol.
175 // So if they are removed the protocol has to be updated
174 }176 }
175177
176 for (size_t idx = 0; idx < size; ++idx)178 for (size_t idx = 0; idx < size; ++idx)
@@ -199,7 +201,6 @@
199}201}
200202
201/*** class RecvPacket ***/203/*** class RecvPacket ***/
202
203size_t RecvPacket::data(void* const packet_data, size_t const bufsize) {204size_t RecvPacket::data(void* const packet_data, size_t const bufsize) {
204 if (index_ + bufsize > buffer.size())205 if (index_ + bufsize > buffer.size())
205 throw wexception("Packet too short");206 throw wexception("Packet too short");
206207
=== modified file 'src/network/network.h'
--- src/network/network.h 2017-08-09 17:55:34 +0000
+++ src/network/network.h 2017-12-05 20:38:52 +0000
@@ -171,6 +171,7 @@
171171
172private:172private:
173 friend class Deserializer;173 friend class Deserializer;
174 friend class NetRelayConnection;
174 std::vector<uint8_t> buffer;175 std::vector<uint8_t> buffer;
175 size_t index_ = 0U;176 size_t index_ = 0U;
176};177};
177178
=== added file 'src/network/relay_protocol.h'
--- src/network/relay_protocol.h 1970-01-01 00:00:00 +0000
+++ src/network/relay_protocol.h 2017-12-05 20:38:52 +0000
@@ -0,0 +1,242 @@
1/*
2 * Copyright (C) 2012-2017 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_NETWORK_RELAY_PROTOCOL_H
21#define WL_NETWORK_RELAY_PROTOCOL_H
22
23/**
24 * Before the internet gaming relay was added, (a) NetHost and (multiple) NetClient established a direct
25 * connection and exchanged data packets created and processed by the GameHost/GameClient. With the
26 * introduction of the relay this changed: GameHost/GameClient still create and process the data packets.
27 * For LAN games, they still pass them to the NetHost/NetClient.
28 * For internet games, the traffic is relayed by the wlnr binary in the metaserver repository.
29 * GameHost/GameClient pass their packets to the new classes NetHostProxy/NetClientProxy.
30 * Those no longer have a direct connection but are both connected to the relay on the metaserver.
31 * When they want to exchange messages, they send their packets to the relay which forwards them to the
32 * intended recipient.
33 * The relay only transport the packets, it does not run any game logic. The idea of this is that the
34 * relay runs on an globally reachable computer (i.e. the one of the metaserver) and simplifies
35 * connectivity for the users (i.e. no more port forwarding required).
36 *
37 * Below are the command codes used in the relay protocol. They are used on the connection
38 * between NetHostProxy/NetClientProxy and relay.
39 *
40 * An example of a typical session:
41 * 1) A user wants to start an internet game. The Widelands instance tells the metaserver about it.
42 * 2) A relay instance is started by the metaserver. On startup of the relay, the nonce of the player is set
43 * as password for the game-host position in the new game. Additionally, the name of the hosted game
44 * is stored. The IP address of the relay is send to the Widelands instance by the metaserver.
45 * 3) The Widelands instance of the user connects to the relay.
46 * 4) Now there is a reachable relay/game running somewhere. The open game can be listed in the lobby.
47 * 5) Clients get the address and port of the relay by the metaserver and can connect to the game.
48 * When enough clients have connected, the GameHost can start the game.
49 * 6) When a client wants to send a packet (e.g. user input) to the GameHost, its GameClient packs the packet,
50 * passes it to the local NetClientProxy which sends it to the relay. The relay relays the packet to the
51 * NetHostProxy which passes it to the GameHost.
52 * 7) When the GameHost wants to send a packet to one or all clients, it also packs it and passes it to its
53 * NetHostProxy which sends it to the relay, where it is forwarded to the client(s).
54 */
55
56
57/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
58 * CONSTANTS
59 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
60
61/**
62 * The current version of the network protocol.
63 * Protocol versions must match on all systems.
64 * Used versions:
65 * 1: Initial version introduced between build 19 and build 20
66 */
67constexpr uint8_t kRelayProtocolVersion = 1;
68
69/**
70 * The commands used by the relay.
71 *
72 * If a command is followed by parameters, they are listed in the description.
73 * Most of these commands do not require any reaction by the receiver.
74 * Exceptions are described at the respective commands.
75 *
76 * Parameter types:
77 *
78 * Strings are NULL-terminated.
79 *
80 * Transmitting data packets:
81 *
82 * The main work of the relay consists of passing opaque data packets between GameHost
83 * and GameClients. They send SendPacket's over the network and expect to receive RecvPacket's
84 * (see network.h). Those packets basically consists of an array of uint8_t with the first two bytes
85 * coding the length of the packet.
86 * When in the following documentation the \c packet datatype is listed as payload data, such a packet
87 * is meant. Since they are already containing their own length, no explicit length field or separator
88 * is required to pack them. When sending they are just written out. When receiving, the Deserializer
89 * has to read the length first and read the appropriate amount of bytes afterwards.
90 */
91/**
92 * This protocol is a binary protocol (as in: not human readable). The receiver reads only the first byte
93 * of the received data to determine which command has been send. Based on that, it knows whether to expect
94 * a string / an uint8 / a SendPacket. Based on this, it can read until NULL / one byte / as much
95 * as the next two bytes specify. When all parameters have been read the next command starts
96 * without any separator.
97 */
98
99// If anyone removes a command: Please leave a comment which command with which value was removed
100enum class RelayCommand : uint8_t {
101 // Value 0 should not be used
102
103 /**
104 * Commands send by/to all participants.
105 */
106 /// \{
107 /**
108 * Sent by the NetHostProxy or NetClientProxy on connect to the relay.
109 * Has the following payload:
110 * \li unsigned_8: Protocol version.
111 * \li string: Game name.
112 * \li string: For the host: Password that was set on start of the relay.
113 * For clients/observers: String "client".
114 *
115 * Is answered by kWelcome or kDisconnect (if a parameter is wrong/unknown).
116 */
117 kHello = 1,
118
119 /**
120 * Send by the relay to answer a kHello command.
121 * Confirms the successful connection with the relay. In the case of a connecting NetClientProxy,
122 * this does not mean that it can participate on the game. This decision is up to the GameHost as
123 * soon as it learns about the new client.
124 * Payload:
125 * \li unsigned_8: The protocol version. Should be the same as the one send in the kHello.
126 * \li string: The name of the hosted game as shown in the internet lobby.
127 * The client might want to check this.
128 *
129 * This command and its parameters are not really necessary. But it might be nice to have at least some
130 * confirmation that we have a connection to a (and the right) relay and not to some other server.
131 */
132 kWelcome = 2,
133
134 /**
135 * Can be sent by any participant.
136 * Might be the result of a protocol violation, an invalid password on connect of the NetHostProxy
137 * or a regular disconnect (e.g. the game is over).
138 * After sending or receiving this command, the TCP connection should be closed.
139 * \note When the game host sends its kDisconnect message, the relay will shut down.
140 * Payload:
141 * \li string: An error code describing the reason of the disconnect. Valid values:
142 * NORMAL: Regular disconnect (game has ended, host leaves, client leaves, ...);
143 * PROTOCOL_VIOLATION: Some protocol error (unknown command, invalid parameters, ...);
144 * WRONG_VERSION: The version in the kHello packet is not supported;
145 * GAME_UNKNOWN: Game name provided in kHello packet is unknown;
146 * NO_HOST: No host is connected to the relay yet;
147 * INVALID_CLIENT: Host tried to send a message to a non-existing client
148 */
149 kDisconnect = 3,
150
151 /**
152 * The relay sends this message to check for presence and to measure the round-trip-time.
153 * Has to be answered by kPong immediately.
154 * Payload:
155 * \li unsigned_8: A sequence number for this ping request.
156 */
157 kPing = 4,
158
159 /**
160 * Send to the relay to answer a kPing message.
161 * Payload:
162 * \li unsigned_8: Should be the sequence number found in the ping request.
163 */
164 kPong = 5,
165
166 /**
167 * Send to the relay to request the newest ping results.
168 * No payload.
169 */
170 kRoundTripTimeRequest = 6,
171
172 /**
173 * Send by the relay as an answer to the kRoundTripTimeRequest with the following payload:
174 * \li unsigned_8: Length of the list.
175 * A list of
176 * \li unsigned_8: Id of the client.
177 * \li unsigned_8: The RTT in milliseconds. Capped to max. 255ms.
178 * \li unsigned_8: Seconds since the last kPong has been received by the relay. Capped to max. 255ms.
179 */
180 kRoundTripTimeResponse = 7,
181 /// \}
182
183 /**
184 * Communication between relay and NetHostProxy.
185 */
186 /// \{
187
188 /**
189 * Send by the relay to the NetHostProxy to inform that a client established a connection to the relay.
190 * Payload:
191 * \li unsigned_8: An id to represent the new client.
192 */
193 kConnectClient = 11,
194
195 /**
196 * If send by the NetHostProxy, tells the relay to close the connection to a client.
197 * If send by the relay, informs the NetHostProxy that the connection to a client has been lost.
198 * Payload:
199 * \li unsigned_8: The id of the client.
200 */
201 kDisconnectClient = 12,
202
203 /**
204 * The NetHostProxy sends a message to a connected client over the relay.
205 * Payload:
206 * \li NULL terminated list of unsigned_8: The ids of the clients.
207 * \li packet: The SendPacket to relay.
208 */
209 kToClients = 13,
210
211 /**
212 * The relay transmits a packet from a client to the NetHostProxy.
213 * Payload:
214 * \li unsigned_8: The id of the client.
215 * \li packet: The SendPacket to relay.
216 */
217 kFromClient = 14,
218 /// \}
219
220 /**
221 * Communication between relay and NetHostProxy.
222 */
223 /// \{
224
225 /**
226 * The NetClientProxy sends a message to the NetHostProxy over the relay.
227 * Direct communication between clients is not supported.
228 * Payload:
229 * \li packet: The SendPacket to relay.
230 */
231 kToHost = 21,
232
233 /**
234 * The relay transmits a packet from a NetHostProxy to the NetClientProxy.
235 * Payload:
236 * \li packet: The SendPacket to relay.
237 */
238 kFromHost = 22
239 /// \}
240};
241
242#endif // end of include guard: WL_NETWORK_RELAY_PROTOCOL_H
0243
=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc 2017-11-25 20:29:26 +0000
+++ src/ui_fsmenu/internet_lobby.cc 2017-12-05 20:38:52 +0000
@@ -193,7 +193,7 @@
193void FullscreenMenuInternetLobby::connect_to_metaserver() {193void FullscreenMenuInternetLobby::connect_to_metaserver() {
194 Section& s = g_options.pull_section("global");194 Section& s = g_options.pull_section("global");
195 const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());195 const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
196 uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);196 uint32_t port = s.get_natural("metaserverport", kInternetGamingPort);
197 std::string auth = is_registered_ ? password_ : s.get_string("uuid");197 std::string auth = is_registered_ ? password_ : s.get_string("uuid");
198 assert(!auth.empty());198 assert(!auth.empty());
199 InternetGaming::ref().login(nickname_, auth, is_registered_, metaserver, port);199 InternetGaming::ref().login(nickname_, auth, is_registered_, metaserver, port);
@@ -352,30 +352,40 @@
352 }352 }
353}353}
354354
355bool FullscreenMenuInternetLobby::wait_for_ip() {
356 // Wait until the metaserver provided us with an IP address
357 uint32_t const secs = time(nullptr);
358 while (!InternetGaming::ref().ips().first.is_valid()) {
359 InternetGaming::ref().handle_metaserver_communication();
360 // give some time for the answer + for a relogin, if a problem occurs.
361 if ((kInternetGamingTimeout * 5 / 3) < time(nullptr) - secs) {
362 // Show a popup warning message
363 const std::string warning(
364 _("Widelands was unable to get the IP address of the server in time. "
365 "There seems to be a network problem, either on your side or on the side "
366 "of the server.\n"));
367 UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
368 UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
369 mmb.run<UI::Panel::Returncodes>();
370 InternetGaming::ref().set_error();
371 return false;
372 }
373 }
374 return true;
375}
376
355/// called when the 'join game' button was clicked377/// called when the 'join game' button was clicked
356void FullscreenMenuInternetLobby::clicked_joingame() {378void FullscreenMenuInternetLobby::clicked_joingame() {
357 if (opengames_list_.has_selection()) {379 if (opengames_list_.has_selection()) {
358 InternetGaming::ref().join_game(opengames_list_.get_selected().name);380 InternetGaming::ref().join_game(opengames_list_.get_selected().name);
359381
360 uint32_t const secs = time(nullptr);382 if (!wait_for_ip()) {
361 while (!InternetGaming::ref().ips().first.is_valid()) {383 return;
362 InternetGaming::ref().handle_metaserver_communication();
363 // give some time for the answer + for a relogin, if a problem occurs.
364 if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) {
365 // Show a popup warning message
366 const std::string warning(
367 _("Widelands was unable to get the IP address of the server in time.\n"
368 "There seems to be a network problem, either on your side or on the side\n"
369 "of the server.\n"));
370 UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
371 UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
372 mmb.run<UI::Panel::Returncodes>();
373 return InternetGaming::ref().set_error();
374 }
375 }384 }
376 const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips();385 const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips();
377386
378 GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), true);387 GameClient netgame(ips, InternetGaming::ref().get_local_clientname(),
388 true, opengames_list_.get_selected().name);
379 netgame.run();389 netgame.run();
380 } else390 } else
381 throw wexception("No server selected! That should not happen!");391 throw wexception("No server selected! That should not happen!");
@@ -399,6 +409,16 @@
399409
400 // Start the game410 // Start the game
401 try {411 try {
412
413 // Tell the metaserver about it
414 InternetGaming::ref().open_game();
415
416 // Wait for his response with the IPs of the relay server
417 if (!wait_for_ip()) {
418 return;
419 }
420
421 // Start our relay host
402 GameHost netgame(InternetGaming::ref().get_local_clientname(), true);422 GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
403 netgame.run();423 netgame.run();
404 } catch (...) {424 } catch (...) {
405425
=== modified file 'src/ui_fsmenu/internet_lobby.h'
--- src/ui_fsmenu/internet_lobby.h 2017-01-25 18:55:59 +0000
+++ src/ui_fsmenu/internet_lobby.h 2017-12-05 20:38:52 +0000
@@ -75,6 +75,7 @@
75 void server_doubleclicked();75 void server_doubleclicked();
7676
77 void change_servername();77 void change_servername();
78 bool wait_for_ip();
78 void clicked_joingame();79 void clicked_joingame();
79 void clicked_hostgame();80 void clicked_hostgame();
8081
8182
=== modified file 'src/ui_fsmenu/multiplayer.cc'
--- src/ui_fsmenu/multiplayer.cc 2017-11-25 20:29:26 +0000
+++ src/ui_fsmenu/multiplayer.cc 2017-12-05 20:38:52 +0000
@@ -123,7 +123,7 @@
123123
124 // Try to connect to the metaserver124 // Try to connect to the metaserver
125 const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());125 const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
126 uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);126 uint32_t port = s.get_natural("metaserverport", kInternetGamingPort);
127 std::string auth = register_ ? password_ : s.get_string("uuid");127 std::string auth = register_ ? password_ : s.get_string("uuid");
128 assert(!auth.empty());128 assert(!auth.empty());
129 InternetGaming::ref().login(nickname_, auth, register_, meta, port);129 InternetGaming::ref().login(nickname_, auth, register_, meta, port);

Subscribers

People subscribed via source and target branches

to status/vote changes: