Merge lp:~widelands-dev/widelands/net-boost-asio into lp:widelands

Proposed by Notabilis
Status: Merged
Merged at revision: 8377
Proposed branch: lp:~widelands-dev/widelands/net-boost-asio
Merge into: lp:widelands
Diff against target: 2058 lines (+887/-493)
26 files modified
CMakeLists.txt (+4/-3)
CREDITS (+0/-1)
appveyor.yml (+1/-1)
cmake/Modules/FindSDL2_net.cmake (+0/-88)
cmake/WlFunctions.cmake (+0/-6)
src/network/CMakeLists.txt (+0/-2)
src/network/gameclient.cc (+4/-9)
src/network/gameclient.h (+5/-7)
src/network/gamehost.cc (+0/-1)
src/network/internet_gaming.cc (+10/-5)
src/network/internet_gaming.h (+1/-6)
src/network/netclient.cc (+57/-37)
src/network/netclient.h (+26/-21)
src/network/nethost.cc (+142/-42)
src/network/nethost.h (+46/-22)
src/network/network.cc (+55/-1)
src/network/network.h (+55/-1)
src/network/network_lan_promotion.cc (+348/-107)
src/network/network_lan_promotion.h (+101/-23)
src/network/network_system.h (+0/-59)
src/ui_fsmenu/internet_lobby.cc (+12/-11)
src/ui_fsmenu/netsetup_lan.cc (+8/-11)
src/ui_fsmenu/netsetup_lan.h (+3/-7)
src/wlapplication.cc (+9/-20)
utils/macos/build_app.sh (+0/-1)
utils/win32/innosetup/Widelands.iss (+0/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/net-boost-asio
Reviewer Review Type Date Requested Status
GunChleoc Approve
Klaus Halfmann compile, test, small review Approve
Tino Needs Resubmitting
Notabilis Needs Resubmitting
Review via email: mp+324364@code.launchpad.net

Commit message

Replaced SDL-Net with Boost.Asio.
Added IPv6 support for non-metaserver games.

Description of the change

Replaced the SDL-Net network code with Boost.Asio. This removes SDL-Net as a required library, unfortunately we now require boost-system.

Also added support for IPv6. As of now, this only works for games in the local network, since the metaserver host does not support IPv6 at the moment.

Warning: This branch is not yet ready for merge. I removed the sdl-net library but I don't know / can't test how to add the boost requirements to the build process for Windows and MacOS. For Linux I only had to add the option to link with boost-system in CMakeList.txt. It would be great if someone could test the other operating systems and, if needed, add the required linking for them. The commit where I removed the sdl-net references is:
http://bazaar.launchpad.net/~widelands-dev/widelands/net-boost-asio/revision/8365

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

Continuous integration builds have changed state:

Travis build 2223. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/234386018.
Appveyor build 2058. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2058.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I have done a code review anyway. Since I don't know that much about networking, it's for code style only.

I have also given it a quick spin and found that IPv4 is preferred over IPv6, I think it should be the other way around if possible, since v6 is the more modern protocol.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the review! I included all your suggestions, even though the "prefer IPv6" part is somewhere else in the code.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2236. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/234606690.
Appveyor build 2071. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2071.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2241. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/235398138.
Appveyor build 2076. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2076.

Revision history for this message
Notabilis (notabilis27) wrote :

Seems like its building on Windows (or at least on Appveyor) now. A confirmation and a short test would be appreciated, though. Also, building and testing on MacOS is still missing.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

tried to compile this an it failed me with:

net-boost-asio/src/network/network_lan_promotion.cc:90:26: error: no member named
      'ifa_ifu' in 'ifaddrs'
    s = getnameinfo(ifa->ifa_ifu.ifu_broadaddr, sizeof(struct sockaddr_in),

So Do _you_ use some implementation detail, or do I have to chnange something?

Can you give my some link to what this Boost.Asio is?

Ill as the usal suspects in the internet meanwhile.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

OK checked some things,

I found Asio 1.10.6 / Boost 1.58 as part of the boost library
provided by MacPorts (https://www.macports.org/)

most recent is Asio 1.10.9 / Boost 1.64

I assume you are on Ubuntu, so I assume you are somehwere in the middle of these versions.

Can anybody tell use the windows boost versions we use?

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

OK you use some struct ifaddrs from
$FreeBSD: src/include/ifaddrs.h,v 1.3.32.1.4.1 2010/06/14 02:09:06 kensmith Exp $
As OSX is bases on Freebsd no surprise here.
man getifaddr tells me

     The ifaddrs structure contains at least the following entries:

         struct ifaddrs *ifa_next; /* Pointer to next struct */
         char *ifa_name; /* Interface name */
         u_int ifa_flags; /* Interface flags */
         struct sockaddr *ifa_addr; /* Interface address */
         struct sockaddr *ifa_netmask; /* Interface netmask */
         struct sockaddr *ifa_dstaddr; /* P2P interface destination */
         void *ifa_data; /* Address specific data */

...
    Note that as a convenience, ifa_broadaddr is defined by a compiler
    #define directive to be the same as ifa_dstaddr.

On linux this is:

         struct ifaddrs *ifa_next; /* Next item in list */
         char *ifa_name; /* Name of interface */
         unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
         struct sockaddr *ifa_addr; /* Address of interface */
         struct sockaddr *ifa_netmask; /* Netmask of interface */
         union {
             struct sockaddr *ifu_broadaddr;
                              /* Broadcast address of interface */
             struct sockaddr *ifu_dstaddr;
                              /* Point-to-point destination address */
         } ifa_ifu;
     #define ifa_broadaddr ifa_ifu.ifu_broadaddr
     #define ifa_dstaddr ifa_ifu.ifu_dstaddr
         void *ifa_data; /* Address-specific data */

No ifa_ifu here, but as of the Note for BSD this should be ifa_broadaddr ...

Ok, this did the trick, will cehck in this chanage and some struct/class
confusions my compiler complains about.

Notabilis: will you merge with trunk as Gun has commited some major change today?

Revision history for this message
GunChleoc (gunchleoc) wrote :

> I found Asio 1.10.6 / Boost 1.58 as part of the boost library
> provided by MacPorts (https://www.macports.org/)
>
> most recent is Asio 1.10.9 / Boost 1.64

From CMakelists.txt:

find_package(Boost 1.48
  COMPONENTS
    unit_test_framework
    regex
  REQUIRED
    system)

So, we require version 1.48 as our lowest boost version. If we need a
higher version for the ASIO stuff, we will need to change that line.

The lowest Ubuntu version that we support is Trusty (14.04), which
already comes with Boost version 1.54. Travis is also on Boost 1.54

AppVeyor is pulling Boost version 1.63.

We should go over
http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/history.html to
see if we need to require a higher Boost version.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Trying an itenratgame now gives me:

InternetGaming: Client opened a game with the name BugHasi.
[NetHost]: Opening a listening IPv4 socket on TCP port 7396
[NetHost]: Opening a listening IPv6 socket on TCP port 7396
[LAN] Started an IPv6 socket on UDP port 7395
[LAN] Started an IPv4 socket on UDP port 7395
[LAN] Will broadcast to 192.168.254.255
[LAN] Will broadcast for IPv6
[LAN] Closing an IPv6 socket.
[NetHost]: Closing a listening IPv4 socket
[NetHost]: Closing a listening IPv6 socket
[LAN] Closing an IPv4 socket.

Warning: Failed to use the local network!
Widelands was unable to use the local network. Maybe some other process is already running a server on port 7394, 7395 or 7396 or your network setup is broken.

Any idea what this is about?
> netstat -a | fgrep 139
does not show me any such port. going to debug this now

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

* I can connect to the metaserver, so networking basically works.

* With a second try I now died with:

Assertion failed: (state_ == OFFLINE), function login,
file /Users/klaus/develop/widelands-repo/net-boost-asio/src/network/internet_gaming.cc, line 118.
But I think this is already adressed in some other fix.

* Hmm, You use
   d->promoter = new LanGamePromoter()
with a BroadcastAddr even for an internet game.

And this then fails here:

boost::asio::ip::udp::endpoint destination(boost::asio::ip::address::from_string("ff02::1"), port);
socket_v6.send_to(boost::asio::buffer(buf, len), destination, 0, ec);

please fix:
 * no Brodcast for a pure Internet game.
 * in case one of IVP4 _or_ IPV6 broadcast can be used,
   do not fail but log a warning only.

I will not try a local network game until this is fixed.
I assume it will fail in the same way.

Thanks for your work nonetheless, you may find me in the Lobby between
18:00 and 20:00 CET some times, so we may further discuss my findings

review: Needs Fixing (compile, test)
Revision history for this message
GunChleoc (gunchleoc) wrote :

I ran a LAN game with Windows host and Linux client, no problem.

Will do more testing when the issues raised by Klaus have been addressed.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks a lot for testing! Sorry, somehow missed this yesterday.
- Thanks for fixing the find-broadcast code, it compiles and works on my system, too.
- Trunk is merged, luckily without conflicts.
- I went through the boost history, seems like version 1.48 is good enough. A lot of fixes but no changes that should be relevant for us.
- Fixed the too early abort when only one broadcast failed. Does your system has an IPv6 (address)? Its strange that earlier the code detected IPv6 on your system but later on is unable to send using it.
- Whether to remove the broadcast for internet games is up to discussion. It was already in the code before I worked on it. Since it allows LAN clients to connect to our game I don't see any harm in it.
- Regarding the failed assertion: You connected to the metaserver, clicked "back" and connected again without restarting the game? Have there been any error messages before the assert? I am not sure whether this is fixed, when it happens again let me know.

review: Needs Resubmitting
Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

* Hello Notabalis: I will proceed as follows:
 * Try "normal" network games with another instance as host and vice versa
 * Try local network gaming (assuming its IPv4)
 * Find out what wrong with IPv6 on this Mac, I have seen the error code
   (I hope you log it), So i can ivestigate there.
  * Either my Firewall needs tweaking
  * Or IPv6 Broadcasting on Darvin/OSX is different
  * or what else...

* Local Broadcasting
 - Pollutes the local network with unneeded traffic
 + Allows local computers to connect not knowing the server IP
 * it should not make normal network games fail :-)
* I think the Errror after a failed connect is already adressed in some other bug/branch
  https://bugs.launchpad.net/widelands/+bug/1690649 but this was merged to trunk
  so maybe your merge fixed it.

Lest go compiling ....

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Basi Network gamig now works,
I get
[NetHost]: Opening a listening IPv6 socket on TCP port 7396
[LAN] Started an IPv6 socket on UDP port 7395
[LAN] Started an IPv4 socket on UDP port 7395
[LAN] Will broadcast to 192.168.254.255
[LAN] Will broadcast for IPv6
[LAN] Closing an IPv6 socket.

Wee need more loggin why the socket is closed, well.

Did some basic Testing with Gun,
-> but we should play perhpas 30 Minutes.

Connection to localhost form a second instance works.
Found the Broadcasted local Game, OK,

-> Will do som cross test vs. trunk next

looks god so far when IPv4 is ok this can go to trunk
we can work on IPv6 issues on some other branch.

Cann we do some testing this weekend?

Revision history for this message
Notabilis (notabilis27) wrote :

Added log outputs for all errors, hopefully this will help finding out whats going on.
I have some dates tomorrow, but I will be in the lobby as much as possible. Sunday would work, too.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2257. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/236536300.
Appveyor build 2092. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2092.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

* Hmm, here is the IPv6 Error:
[LAN] Started an IPv6 socket on UDP port 7394.
...
[LAN] Will broadcast for IPv6.
[LAN] Error when broadcasting on IPv6 socket, closing it: No route to host.
[LAN] Closing an IPv6 socket.

Must use debugger/consult docs what this is about.
Looks Broadcast adresses in IPv6 need some differetn aproach.

* Testing lcoal network (OSX)
  server <- client
  asio <- asio game is not visible in the local network browser (but is fast)
            but can connect via localhost
  old <- asio game is visible in the local network browser (but slow setting this up)
            connection works as expected
  asio <- old game is visible in the local network browser
            connection works as expected

Now I am puzzeld an will need some debugging to find this.
(And we will need some Linux, OSX, Windows ... LAN Party for the final checks :-)

Notabilis please give some hint where to check.

As its is bsiaclly working I would like to see some > 30 min Internet game,
for stabilit, we then vould write seom bugs for our findings.

I will start the code review now, while wating for some victim in the lobby for testing :-)

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Travis has Problme with just CLANG_VERSION="4.0" BUILD_TYPE="Debug"
all others are ok?

Looks like some transient error, llvm.or had a bad time?

sudo apt-get install -qq --force-yes -y clang-4.0
E: Failed to fetch http://llvm.org/apt/trusty/pool/main/l/llvm-toolchain-4.0/libllvm4.0_4.0~svn303688-1~exp1_amd64.deb 404 Not Found [IP: 151.101.128.204 80]

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Did a review now,
 * there is quite some duplicate code for 4/6 that should be avoided
 * Think about a common errro handling, that will save a lot of code, like
   if (hasError(ec)) { // implies logging

check the comments inline.

we still could get his in and improved this later.

Got some "visitors" from Libre Game Night, so my review is imcomplete sorry.

Can approve, and mabye fix the Issues later

review: Approve (review, comoiel)
Revision history for this message
GunChleoc (gunchleoc) wrote :

Re Travis: Yes, that was just Travis unable to get a package. So, it doesn't count as an error for our code.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the review, I will address most of it.
I commented some of your comments, on two points I disagree with you and would prefer the way it currently is. Feel free to insist if you think I am wrong.

Revision history for this message
Notabilis (notabilis27) wrote :

Review is dealt with. The code looks prettier now, thanks.

I have to do some more reading about IPv6 and OSX. I fear we will need some platform specific code there. I will try to push something for testing in the next days.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Code looks much better, no more 4/6 duplicates.
Will now play a bit, again.

review: Approve (code review, compile)
Revision history for this message
Notabilis (notabilis27) wrote :

Here, I have something more to play with for you. ;-)

When I understand it right, with the current version you are always getting the log message "closing IPv6 socket" when entering the LAN lobby, right? With my newest push this message hopefully does not appear any longer. And if it works really good, two computers should be able to find their games over the LAN with IPv6.

When trying, make sure the __APPLE__ branch of the code is compiled. It should also print a comment on console when entering the LAN lobby. But if you don't have to better don't look at the code, its very ugly.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2260. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/236944779.
Appveyor build 2095. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2095.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Codecheck from travis fails, but for some compilers?
grep '^[/_.a-zA-Z]\+:[0-9]\+:' codecheck.out

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:225: Use log() from base/log.h instead of printf.

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:238: Comments need to start with a space.

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:261: Old C-Style cast. Change to static_cast<Type>(var) or similar!

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:264: Use log() from base/log.h instead of printf.

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:271: Use log() from base/log.h instead of printf.

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:275: Comments need to start with a space.

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:280: Old C-Style cast. Change to static_cast<Type>(var) or similar!

/home/travis/build/widelands/widelands/src/network/network_lan_promotion.cc:283: Use log() from base/log.h instead of printf.

+echo 'You have codecheck warnings (see above) Please fix.'

You have codecheck warnings (see above) Please fix.

Revision history for this message
Notabilis (notabilis27) wrote :

Travis is only complaining about my coding style, which I have to agree with. The current apple-test-code is totally ugly. ;-)
The failing builds are the debug builds, seems like release builds aren't doing codechecks.

When I understood it right, the new code worked on your system, or? If you want to try with only IPv6 enabled, add "close_socket(&socket_v4);" at the end of LanBase::LanBase(uint16_t port).
If the code is working, I will keep it in and clean it up.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Exactly, codecheck is only run with debug builds. So, it's failing all the debug builds, but not the release builds.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Will not have much time the next 3 weeks I guess.
I aon call duty for my job and the weekends are stuffed
with other thing I must care for.

Lets fix the ugly apple code and then lets get this in.
I like having smaller steps and the history of this branch
is long enought ...

sorry

Revision history for this message
GunChleoc (gunchleoc) wrote :

Don't worry, RL always has to come first. Thanks for all this extensive testing!

Revision history for this message
Notabilis (notabilis27) wrote :

Fine by me, I will try to do so.
Thanks a lot for all the testing, it was a great help!

Revision history for this message
Notabilis (notabilis27) wrote :

So, code is cleaned up. A review of the new apple specific parts and maybe another short test, then it can go?

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2267. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/238067343.
Appveyor build 2102. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2102.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Sounds like a plan - AppVeyor builds are broken though, so we still need to make Windows happy.

Revision history for this message
Tino (tino79) wrote :

Windows.h includes winsock.h, which clashes with winsock2 and boost asio.
The additional define WIN32_LEAN_AND_MEAN avoids including winsock.h and some other headers.

review: Needs Resubmitting
Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Oops, now this does not compile on OSX:

net-boost-asio/src/network/network_lan_promotion.cc:78:29: error: member initializer
      'interface_indices_v6' does not name a non-static data member or base class
                broadcast_addresses_v4(), interface_indices_v6()

in the .h files this is:

#ifndef __APPLE__
        /// Apple forces us to define which interface to broadcast through.
        std::set<unsigned int> interface_indices_v6;
#endif // __APPLE__

-> need another #ifdef then...

Revision history for this message
Notabilis (notabilis27) wrote :

Uh uh, my fault. This should have actually been an #ifdef. Is fixed now.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

After playing quite a qhile (> 1hour) I now got a crash:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 widelands 0x000000010a7c2d9c boost::asio::basic_io_object<boost::asio::stream_socket_service<boost::asio::ip::tcp>, true>::get_service() const + 12 (basic_io_object.hpp:214)
1 widelands 0x000000010a7c03ef boost::asio::basic_socket<boost::asio::ip::tcp, boost::asio::stream_socket_service<boost::asio::ip::tcp> >::is_open() const + 31 (basic_socket.hpp:338)
2 widelands 0x000000010a7bceb9 NetClient::is_connected() const + 25 (netclient.cc:24)
3 widelands 0x000000010a7bd609 NetClient::send(SendPacket const&) + 41 (netclient.cc:67)
4 widelands 0x000000010a7526c1 InternetGaming::logout(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) + 161 (internet_gaming.cc:225)
5 widelands 0x000000010a795f1c GameHost::run() + 9932 (gamehost.cc:749)

So the interal asio service object was broken :-(

should this be our long searched for bug in trunk?

Revision history for this message
Notabilis (notabilis27) wrote :

See the positive side: You were able to compile the branch! ;-)

Seems like some kind of memory corruption though I am not sure where it is coming from. The line in GameHost indicates that the server crashed. What were you doing when it happened? Could it be that your network went offline (just a wild guess)?

Which bug in trunk do you mean? Random crashes or something network related? (The asio service does not exist in trunk but a similar problem could probably happen there, too.)

An unrelated question to whomever: I am a bit worried that Appveyor isn't saying anything. Is it just slow or does that mean that the code is still broken?

Revision history for this message
Tino (tino79) wrote :

It seems the Bazaar->Github-Bridge is broken. Both Appveyor und Travis do only build on new commits to the Github repo.

Revision history for this message
SirVer (sirver) wrote :

Sorry for that. Bunnybot did not restart after a computer reboot and I did not even notice the computer rebooted. Should work now again.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2272. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/239238741.
Appveyor build 2107. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_boost_asio-2107.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

One nit inline - no need to fix this.

We should get this in now, otherwise we will never get enouht testing coverage.

Notablilis: try pulling the cable at several stepts in the game,
maybe there is one error left, I am no 100% sure.

FAPP tthis is OK for me.

review: Approve (compile, test, small review)
Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the explanation and fixing Bunnybot! Good to see that it is working again.

I did a short test with two network interfaces on Linux. With the current code one packet is sent on both interfaces, so the kernel (or so) is already dealing with duplicating them. With the apple-code, two packets with the same contents are sent on both interfaces. So as long as it doesn't make any problems, I would keep the current code.

Removing/Adding the network cable did not change anything but some time later I also got the exception. The problem is/was that the metaserver-connection gets disconnected, so the client does a DNS lookup to get its IP. When that fails (i.e. currently no network) an exception is thrown. In the handler of this exception the metaserver is disconnected which crashes since we aren't connected anyway (and so have no socket). I pushed a simple fix.

But... is it my system or is trunk (and this branch) currently broken? When ingame the view is permanently scrolling down, as if the arrow-down key is pressed.

Revision history for this message
Notabilis (notabilis27) wrote :

Pushed a fix for an assert(state_ == OFFLINE) bug which I encountered. The problem was that on crash of a hosted game the logic jumped back to the main menu without closing the connection to the metaserver. So when the player tries to connect again, he already is connected to the metaserver (and not offline).

To reproduce the bug:
- Login to the metaserver
- Cut your wifi cable
- Try to host a game. Should fail and jump you back into the main menu
- Try to connect to the metaserver again (I did this one without network)
- Game crashes due to the assert

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Ahh. As I have an untrustbale network cable I have seen this already.
You changes are fine with me.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> When ingame the view is permanently scrolling down, as if the arrow-down key is pressed.

I have never heard of that one.

Revision history for this message
Notabilis (notabilis27) wrote :

Today it does not happen for me anymore. Really strange effect, no idea what happened there.

So, if no-one has any requests or suggestions left, lets merge this?

Revision history for this message
GunChleoc (gunchleoc) wrote :

I'd like to give it a quick spin on Windows first, I just haven't gotten around to it yet.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I had another look at the code and added 9 small code style/English language nits. Fix as you see fit.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Testing on Windows was successful with LAN + internet game.

Feel free to merge any time - don't forget to set a commit message on top of this page first though.

review: Approve
Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for testing and the review. I fixed all your nits except the iterator. A foreach-loop would not work since I need it to get the key of the std::map and also since I am modifying the map while running through it. The temporary key-variable (for it->first) could be probably removed when I write code like "it++->first". But I at least prefer the better readable version with the variable.

I will give Bunnybot another chance to run, then this will go in.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Well, this should work:

    for (auto& client: clients_) {
        ...
        ConnectionId id_to_remove = client.first;
        ...
        client.second.deserializer.read_data(buffer, length);
        ...
    }

You don't need an explicit iterator to access the first and second element of the pair.

Revision history for this message
Notabilis (notabilis27) wrote :

Oh, thanks for the hint! I wasn't aware that this loop allows access to the key. It does not solve the removal-problem, however. When I remove the client while in the loop the behavior of the rest of the loop will be undefined.
I could add a local std::set and store the to-be-removed ids in there, removing them when the loop is done. Do you prefer this to the iterator?

    std::set to_remove;
    for (auto& client: clients_) {
        ...
        if (error) {
            to_remove.add(client.first);
            continue;
        }
        ...
    }
    for (auto id: to_remove)
        close(id);

Revision history for this message
GunChleoc (gunchleoc) wrote :

Basically, that loop will give you the elements in the container, so you can do all the stuff with them that you would do if it was a simple variable.

I wasn't aware that close() will remove an element from the container. Best stay with the iterator after all then - adding a second loop + container would make the code unnecessarily complicated.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Let's get this in now. Thanks for the great change :)

@bunnybot merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2017-02-28 08:31:53 +0000
+++ CMakeLists.txt 2017-06-07 18:50:40 +0000
@@ -41,6 +41,7 @@
4141
42if (WIN32)42if (WIN32)
43 set (Boost_USE_STATIC_LIBS ON)43 set (Boost_USE_STATIC_LIBS ON)
44 link_libraries(wsock32 ws2_32)
44else()45else()
45 set (Boost_USE_STATIC_LIBS OFF)46 set (Boost_USE_STATIC_LIBS OFF)
46endif()47endif()
@@ -50,7 +51,8 @@
50 COMPONENTS51 COMPONENTS
51 unit_test_framework52 unit_test_framework
52 regex53 regex
53 REQUIRED)54 REQUIRED
55 system)
5456
55find_package (PythonInterp REQUIRED)57find_package (PythonInterp REQUIRED)
5658
@@ -60,7 +62,6 @@
60find_package(SDL2 REQUIRED)62find_package(SDL2 REQUIRED)
61find_package(SDL2_image REQUIRED)63find_package(SDL2_image REQUIRED)
62find_package(SDL2_mixer REQUIRED)64find_package(SDL2_mixer REQUIRED)
63find_package(SDL2_net REQUIRED)
64find_package(SDL2_ttf REQUIRED)65find_package(SDL2_ttf REQUIRED)
65find_package(ZLIB REQUIRED)66find_package(ZLIB REQUIRED)
66find_package(ICU REQUIRED)67find_package(ICU REQUIRED)
@@ -181,7 +182,7 @@
181wl_add_flag(WL_COMPILE_DIAGNOSTICS "-Werror=uninitialized")182wl_add_flag(WL_COMPILE_DIAGNOSTICS "-Werror=uninitialized")
182183
183IF (WIN32)184IF (WIN32)
184 add_definitions(-DMINGW_HAS_SECURE_API)185 add_definitions(-DMINGW_HAS_SECURE_API -DWIN32_LEAN_AND_MEAN)
185 if (CMAKE_SIZEOF_VOID_P EQUAL 4)186 if (CMAKE_SIZEOF_VOID_P EQUAL 4)
186 set (CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware" CACHE STRING "Set by widelands CMakeLists.txt" FORCE)187 set (CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware" CACHE STRING "Set by widelands CMakeLists.txt" FORCE)
187 message (STATUS "Enabled large address awareness on mingw32")188 message (STATUS "Enabled large address awareness on mingw32")
188189
=== modified file 'CREDITS'
--- CREDITS 2014-10-13 15:04:50 +0000
+++ CREDITS 2017-06-07 18:50:40 +0000
@@ -10,7 +10,6 @@
1010
11 libSDL211 libSDL2
12 libSDL2_mixer12 libSDL2_mixer
13 libSDL2_net
14 libSDL2_image13 libSDL2_image
15 libSDL2_ttf14 libSDL2_ttf
16 * All files from SDL2-Project15 * All files from SDL2-Project
1716
=== modified file 'appveyor.yml'
--- appveyor.yml 2016-10-06 20:34:46 +0000
+++ appveyor.yml 2017-06-07 18:50:40 +0000
@@ -19,7 +19,7 @@
19 - cmd: "bash --login -c \"pacman -Su --noconfirm\""19 - cmd: "bash --login -c \"pacman -Su --noconfirm\""
20 - cmd: "bash --login -c \"pacman -Su --noconfirm\""20 - cmd: "bash --login -c \"pacman -Su --noconfirm\""
21 # Installed required libs21 # Installed required libs
22 - cmd: "bash --login -c \"pacman --noconfirm -S mingw-w64-%MINGWSUFFIX%-ninja mingw-w64-%MINGWSUFFIX%-boost mingw-w64-%MINGWSUFFIX%-SDL2_net mingw-w64-%MINGWSUFFIX%-SDL2_ttf mingw-w64-%MINGWSUFFIX%-SDL2_mixer mingw-w64-%MINGWSUFFIX%-SDL2_image mingw-w64-%MINGWSUFFIX%-glbinding\""22 - cmd: "bash --login -c \"pacman --noconfirm -S mingw-w64-%MINGWSUFFIX%-ninja mingw-w64-%MINGWSUFFIX%-boost mingw-w64-%MINGWSUFFIX%-SDL2_ttf mingw-w64-%MINGWSUFFIX%-SDL2_mixer mingw-w64-%MINGWSUFFIX%-SDL2_image mingw-w64-%MINGWSUFFIX%-glbinding\""
2323
24shallow_clone: true24shallow_clone: true
2525
2626
=== removed file 'cmake/Modules/FindSDL2_net.cmake'
--- cmake/Modules/FindSDL2_net.cmake 2014-10-13 15:04:50 +0000
+++ cmake/Modules/FindSDL2_net.cmake 1970-01-01 00:00:00 +0000
@@ -1,88 +0,0 @@
1# - Locate SDL2_net library
2# This module defines:
3# SDL2_NET_LIBRARIES, the name of the library to link against
4# SDL2_NET_INCLUDE_DIRS, where to find the headers
5# SDL2_NET_FOUND, if false, do not try to link against
6# SDL2_NET_VERSION_STRING - human-readable string containing the version of SDL2_net
7#
8# For backward compatiblity the following variables are also set:
9# SDL2NET_LIBRARY (same value as SDL2_NET_LIBRARIES)
10# SDL2NET_INCLUDE_DIR (same value as SDL2_NET_INCLUDE_DIRS)
11# SDL2NET_FOUND (same value as SDL2_NET_FOUND)
12#
13# $SDL2DIR is an environment variable that would
14# correspond to the ./configure --prefix=$SDL2DIR
15# used in building SDL2.
16#
17# Created by Eric Wing. This was influenced by the FindSDL2.cmake
18# module, but with modifications to recognize OS X frameworks and
19# additional Unix paths (FreeBSD, etc).
20
21#=============================================================================
22# Copyright 2005-2009 Kitware, Inc.
23# Copyright 2012 Benjamin Eikel
24#
25# Distributed under the OSI-approved BSD License (the "License");
26# see accompanying file Copyright.txt for details.
27#
28# This software is distributed WITHOUT ANY WARRANTY; without even the
29# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
30# See the License for more information.
31#=============================================================================
32# (To distribute this file outside of CMake, substitute the full
33# License text for the above reference.)
34
35if(NOT SDL2_NET_INCLUDE_DIR AND SDL2NET_INCLUDE_DIR)
36 set(SDL2_NET_INCLUDE_DIR ${SDL2NET_INCLUDE_DIR} CACHE PATH "directory cache
37entry initialized from old variable name")
38endif()
39find_path(SDL2_NET_INCLUDE_DIR SDL_net.h
40 HINTS
41 ENV SDL2NETDIR
42 ENV SDL2DIR
43 PATH_SUFFIXES include/SDL2 include
44)
45
46if(NOT SDL2_NET_LIBRARY AND SDL2NET_LIBRARY)
47 set(SDL2_NET_LIBRARY ${SDL2NET_LIBRARY} CACHE FILEPATH "file cache entry
48initialized from old variable name")
49endif()
50find_library(SDL2_NET_LIBRARY
51 NAMES SDL2_net
52 HINTS
53 ENV SDL2NETDIR
54 ENV SDL2DIR
55 PATH_SUFFIXES lib
56)
57
58if(SDL2_NET_INCLUDE_DIR AND EXISTS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h")
59 file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+[0-9]+$")
60 file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+[0-9]+$")
61 file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+[0-9]+$")
62 string(REGEX REPLACE "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MAJOR "${SDL_NET_VERSION_MAJOR_LINE}")
63 string(REGEX REPLACE "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MINOR "${SDL_NET_VERSION_MINOR_LINE}")
64 string(REGEX REPLACE "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_PATCH "${SDL_NET_VERSION_PATCH_LINE}")
65 set(SDL2_NET_VERSION_STRING ${SDL_NET_VERSION_MAJOR}.${SDL_NET_VERSION_MINOR}.${SDL_NET_VERSION_PATCH})
66 unset(SDL_NET_VERSION_MAJOR_LINE)
67 unset(SDL_NET_VERSION_MINOR_LINE)
68 unset(SDL_NET_VERSION_PATCH_LINE)
69 unset(SDL_NET_VERSION_MAJOR)
70 unset(SDL_NET_VERSION_MINOR)
71 unset(SDL_NET_VERSION_PATCH)
72endif()
73
74set(SDL2_NET_LIBRARIES ${SDL2_NET_LIBRARY})
75set(SDL2_NET_INCLUDE_DIRS ${SDL2_NET_INCLUDE_DIR})
76
77# include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
78
79FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_net
80 REQUIRED_VARS SDL2_NET_LIBRARIES SDL2_NET_INCLUDE_DIRS
81 VERSION_VAR SDL2_NET_VERSION_STRING)
82
83# for backward compatiblity
84set(SDL2NET_LIBRARY ${SDL2_NET_LIBRARIES})
85set(SDL2NET_INCLUDE_DIR ${SDL2_NET_INCLUDE_DIRS})
86set(SDL2NET_FOUND ${SDL2_NET_FOUND})
87
88mark_as_advanced(SDL2_NET_LIBRARY SDL2_NET_INCLUDE_DIR)
890
=== modified file 'cmake/WlFunctions.cmake'
--- cmake/WlFunctions.cmake 2016-02-06 16:17:23 +0000
+++ cmake/WlFunctions.cmake 2017-06-07 18:50:40 +0000
@@ -12,7 +12,6 @@
12 USES_SDL212 USES_SDL2
13 USES_SDL2_IMAGE13 USES_SDL2_IMAGE
14 USES_SDL2_MIXER14 USES_SDL2_MIXER
15 USES_SDL2_NET
16 USES_SDL2_TTF15 USES_SDL2_TTF
17 USES_ZLIB16 USES_ZLIB
18 USES_ICU17 USES_ICU
@@ -127,11 +126,6 @@
127 target_link_libraries(${NAME} ${SDL2MIXER_LIBRARY})126 target_link_libraries(${NAME} ${SDL2MIXER_LIBRARY})
128 endif()127 endif()
129128
130 if(ARG_USES_SDL2_NET)
131 wl_include_system_directories(${NAME} ${SDL2NET_INCLUDE_DIR})
132 target_link_libraries(${NAME} ${SDL2NET_LIBRARY})
133 endif()
134
135 if(ARG_USES_SDL2_IMAGE)129 if(ARG_USES_SDL2_IMAGE)
136 wl_include_system_directories(${NAME} ${SDL2IMAGE_INCLUDE_DIR})130 wl_include_system_directories(${NAME} ${SDL2IMAGE_INCLUDE_DIR})
137 target_link_libraries(${NAME} ${SDL2IMAGE_LIBRARY})131 target_link_libraries(${NAME} ${SDL2IMAGE_LIBRARY})
138132
=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt 2017-05-07 20:27:21 +0000
+++ src/network/CMakeLists.txt 2017-06-07 18:50:40 +0000
@@ -23,8 +23,6 @@
23 network_player_settings_backend.cc23 network_player_settings_backend.cc
24 network_player_settings_backend.h24 network_player_settings_backend.h
25 network_protocol.h25 network_protocol.h
26 network_system.h
27 USES_SDL2_NET
28 DEPENDS26 DEPENDS
29 ai27 ai
30 base_exceptions28 base_exceptions
3129
=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc 2017-05-11 10:45:44 +0000
+++ src/network/gameclient.cc 2017-06-07 18:50:40 +0000
@@ -42,7 +42,6 @@
42#include "network/internet_gaming.h"42#include "network/internet_gaming.h"
43#include "network/network_gaming_messages.h"43#include "network/network_gaming_messages.h"
44#include "network/network_protocol.h"44#include "network/network_protocol.h"
45#include "network/network_system.h"
46#include "scripting/lua_interface.h"45#include "scripting/lua_interface.h"
47#include "scripting/lua_table.h"46#include "scripting/lua_table.h"
48#include "ui_basic/messagebox.h"47#include "ui_basic/messagebox.h"
@@ -89,17 +88,13 @@
89 std::vector<ChatMessage> chatmessages;88 std::vector<ChatMessage> chatmessages;
90};89};
9190
92GameClient::GameClient(const std::string& host,91GameClient::GameClient(const NetAddress& host, const std::string& playername, bool internet)
93 const uint16_t port,
94 const std::string& playername,
95 bool internet)
96 : d(new GameClientImpl), internet_(internet) {92 : d(new GameClientImpl), internet_(internet) {
97 d->net = NetClient::connect(host, port);93 d->net = NetClient::connect(host);
98 if (!d->net || !d->net->is_connected()) {94 if (!d->net || !d->net->is_connected()) {
99 throw WLWarning(_("Could not establish connection to host"),95 throw WLWarning(_("Could not establish connection to host"),
100 _("Widelands could not establish a connection to the given "96 _("Widelands could not establish a connection to the given address. "
101 "address.\n"97 "Either no Widelands server was running at the supposed port or "
102 "Either no Widelands server was running at the supposed port or\n"
103 "the server shut down as you tried to connect."));98 "the server shut down as you tried to connect."));
104 }99 }
105100
106101
=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h 2017-05-11 10:45:44 +0000
+++ src/network/gameclient.h 2017-06-07 18:50:40 +0000
@@ -36,13 +36,11 @@
36 * launch, as well as dealing with the actual network protocol.36 * launch, as well as dealing with the actual network protocol.
37 */37 */
38struct GameClient : public GameController,38struct GameClient : public GameController,
39 public GameSettingsProvider,39 public GameSettingsProvider,
40 private SyncCallback,40 private SyncCallback,
41 public ChatProvider {41 public ChatProvider {
42 GameClient(const std::string& host,42 GameClient(const NetAddress& host, const std::string& playername, bool internet = false);
43 const uint16_t port,43
44 const std::string& playername,
45 bool internet = false);
46 virtual ~GameClient();44 virtual ~GameClient();
4745
48 void run();46 void run();
4947
=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc 2017-05-11 10:45:44 +0000
+++ src/network/gamehost.cc 2017-06-07 18:50:40 +0000
@@ -54,7 +54,6 @@
54#include "network/network_lan_promotion.h"54#include "network/network_lan_promotion.h"
55#include "network/network_player_settings_backend.h"55#include "network/network_player_settings_backend.h"
56#include "network/network_protocol.h"56#include "network/network_protocol.h"
57#include "network/network_system.h"
58#include "profile/profile.h"57#include "profile/profile.h"
59#include "scripting/lua_interface.h"58#include "scripting/lua_interface.h"
60#include "ui_basic/progresswindow.h"59#include "ui_basic/progresswindow.h"
6160
=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc 2017-05-16 18:29:06 +0000
+++ src/network/internet_gaming.cc 2017-06-07 18:50:40 +0000
@@ -95,7 +95,10 @@
95void InternetGaming::initialize_connection() {95void InternetGaming::initialize_connection() {
96 // First of all try to connect to the metaserver96 // First of all try to connect to the metaserver
97 log("InternetGaming: Connecting to the metaserver.\n");97 log("InternetGaming: Connecting to the metaserver.\n");
98 net = NetClient::connect(meta_, port_);98 NetAddress addr;
99 net.reset();
100 if (NetAddress::resolve_to_v4(&addr, meta_, port_))
101 net = NetClient::connect(addr);
99 if (!net || !net->is_connected())102 if (!net || !net->is_connected())
100 throw WLWarning(_("Could not establish connection to host"),103 throw WLWarning(_("Could not establish connection to host"),
101 _("Widelands could not establish a connection to the given address.\n"104 _("Widelands could not establish a connection to the given address.\n"
@@ -216,10 +219,12 @@
216void InternetGaming::logout(const std::string& msgcode) {219void InternetGaming::logout(const std::string& msgcode) {
217220
218 // Just in case the metaserver is listening on the socket - tell him we break up with him ;)221 // Just in case the metaserver is listening on the socket - tell him we break up with him ;)
219 SendPacket s;222 if (net && net->is_connected()) {
220 s.string(IGPCMD_DISCONNECT);223 SendPacket s;
221 s.string(msgcode);224 s.string(IGPCMD_DISCONNECT);
222 net->send(s);225 s.string(msgcode);
226 net->send(s);
227 }
223228
224 const std::string& msg = InternetGamingMessages::get_message(msgcode);229 const std::string& msg = InternetGamingMessages::get_message(msgcode);
225 log("InternetGaming: logout(%s)\n", msg.c_str());230 log("InternetGaming: logout(%s)\n", msg.c_str());
226231
=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h 2017-05-09 19:17:12 +0000
+++ src/network/internet_gaming.h 2017-06-07 18:50:40 +0000
@@ -24,11 +24,6 @@
24#include <string>24#include <string>
25#include <vector>25#include <vector>
2626
27#ifdef _WIN32
28#include <io.h>
29#include <winsock2.h>
30#endif
31
32#include "build_info.h"27#include "build_info.h"
33#include "chat/chat.h"28#include "chat/chat.h"
34#include "network/internet_gaming_protocol.h"29#include "network/internet_gaming_protocol.h"
@@ -170,7 +165,7 @@
170 std::string pwd_;165 std::string pwd_;
171 bool reg_;166 bool reg_;
172 std::string meta_;167 std::string meta_;
173 uint32_t port_;168 uint16_t port_;
174169
175 /// local clients name and rights170 /// local clients name and rights
176 std::string clientname_;171 std::string clientname_;
177172
=== modified file 'src/network/netclient.cc'
--- src/network/netclient.cc 2017-05-11 10:45:44 +0000
+++ src/network/netclient.cc 2017-06-07 18:50:40 +0000
@@ -4,8 +4,9 @@
44
5#include "base/log.h"5#include "base/log.h"
66
7std::unique_ptr<NetClient> NetClient::connect(const std::string& ip_address, const uint16_t port) {7std::unique_ptr<NetClient> NetClient::connect(const NetAddress& host) {
8 std::unique_ptr<NetClient> ptr(new NetClient(ip_address, port));8
9 std::unique_ptr<NetClient> ptr(new NetClient(host));
9 if (ptr->is_connected()) {10 if (ptr->is_connected()) {
10 return ptr;11 return ptr;
11 } else {12 } else {
@@ -17,63 +18,82 @@
17NetClient::~NetClient() {18NetClient::~NetClient() {
18 if (is_connected())19 if (is_connected())
19 close();20 close();
20 if (sockset_ != nullptr)
21 SDLNet_FreeSocketSet(sockset_);
22}21}
2322
24bool NetClient::is_connected() const {23bool NetClient::is_connected() const {
25 return sock_ != nullptr;24 return socket_.is_open();
26}25}
2726
28void NetClient::close() {27void NetClient::close() {
29 if (!is_connected())28 if (!is_connected())
30 return;29 return;
31 SDLNet_TCP_DelSocket(sockset_, sock_);30 boost::system::error_code ec;
32 SDLNet_TCP_Close(sock_);31 boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec);
33 sock_ = nullptr;32 if (!ec) {
33 log("[NetClient] Closing network socket connected to %s:%i.\n",
34 remote.address().to_string().c_str(), remote.port());
35 } else {
36 log("[NetClient] Closing network socket.\n");
37 }
38 socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
39 socket_.close(ec);
34}40}
3541
36bool NetClient::try_receive(RecvPacket* packet) {42bool NetClient::try_receive(RecvPacket* packet) {
37 if (!is_connected())43 if (!is_connected())
38 return false;44 return false;
3945
40 uint8_t buffer[512];46 uint8_t buffer[kNetworkBufferSize];
41 while (SDLNet_CheckSockets(sockset_, 0) > 0) {47 boost::system::error_code ec;
4248 size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
43 const int32_t bytes = SDLNet_TCP_Recv(sock_, buffer, sizeof(buffer));49 if (!ec) {
44 if (bytes <= 0) {50 assert(length > 0);
45 // Error while receiving51 assert(length <= kNetworkBufferSize);
46 close();52 // Has read something
47 return false;53 deserializer_.read_data(buffer, length);
48 }54 }
4955
50 deserializer_.read_data(buffer, bytes);56 if (ec && ec != boost::asio::error::would_block) {
57 // Connection closed or some error, close the socket
58 log("[NetClient] Error when trying to receive some data: %s.\n", ec.message().c_str());
59 close();
60 return false;
51 }61 }
52 // Get one packet from the deserializer62 // Get one packet from the deserializer
53 return deserializer_.write_packet(packet);63 return deserializer_.write_packet(packet);
54}64}
5565
56void NetClient::send(const SendPacket& packet) {66void NetClient::send(const SendPacket& packet) {
57 if (is_connected()) {67 if (!is_connected())
58 SDLNet_TCP_Send(sock_, packet.get_data(), packet.get_size());
59 }
60}
61
62NetClient::NetClient(const std::string& ip_address, const uint16_t port)
63 : sock_(nullptr), sockset_(nullptr), deserializer_() {
64
65 IPaddress addr;
66 if (SDLNet_ResolveHost(&addr, ip_address.c_str(), port) != 0) {
67 log("[Client]: Failed to resolve host address %s:%u.\n", ip_address.c_str(), port);
68 return;68 return;
69
70 boost::system::error_code ec;
71 size_t written = boost::asio::write(socket_,
72 boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
73 // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
74 // If it doesn't, set the socket to blocking before writing
75 // If it does, remove this comment after build 20
76 assert(ec != boost::asio::error::would_block);
77 assert(written == packet.get_size() || ec);
78 if (ec) {
79 log("[NetClient] Error when trying to send some data: %s.\n", ec.message().c_str());
80 close();
69 }81 }
70 log("[Client]: Trying to connect to %s:%u ... ", ip_address.c_str(), port);82}
71 sock_ = SDLNet_TCP_Open(&addr);83
72 if (is_connected()) {84NetClient::NetClient(const NetAddress& host)
73 log("success\n");85 : io_service_(), socket_(io_service_), deserializer_() {
74 sockset_ = SDLNet_AllocSocketSet(1);86
75 SDLNet_TCP_AddSocket(sockset_, sock_);87 assert(host.is_valid());
88 const boost::asio::ip::tcp::endpoint destination(host.ip, host.port);
89
90 log("[NetClient]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port);
91 boost::system::error_code ec;
92 socket_.connect(destination, ec);
93 if (!ec && is_connected()) {
94 log("success.\n");
95 socket_.non_blocking(true);
76 } else {96 } else {
77 log("failed\n");97 log("failed.\n");
78 }98 }
79}99}
80100
=== modified file 'src/network/netclient.h'
--- src/network/netclient.h 2017-05-11 10:45:44 +0000
+++ src/network/netclient.h 2017-06-07 18:50:40 +0000
@@ -22,23 +22,23 @@
2222
23#include <memory>23#include <memory>
2424
25#include <SDL_net.h>
26
27#include "network/network.h"25#include "network/network.h"
2826
29/**27/**
30 * NetClient manages the network connection for a network game in which this computer28 * NetClient manages the network connection for a network game in which this computer
31 * participates as a client.29 * participates as a client.
30 * This class only tries to create a single socket, either for IPv4 and IPv6.
31 * Which is used depends on what kind of address is given on call to connect().
32 */32 */
33class NetClient {33class NetClient {
34public:34public:
35
35 /**36 /**
36 * Tries to establish a connection to the given host.37 * Tries to establish a connection to the given host.
37 * @param ip_address A hostname or an IPv4 address as string.38 * \param host The host to connect to.
38 * @param port The port to connect to.39 * \return A pointer to a connected \c NetClient object or a \c nullptr if the connection failed.
39 * @return A pointer to a connected \c NetClient object or a nullptr if the connection failed.
40 */40 */
41 static std::unique_ptr<NetClient> connect(const std::string& ip_address, const uint16_t port);41 static std::unique_ptr<NetClient> connect(const NetAddress& host);
4242
43 /**43 /**
44 * Closes the connection.44 * Closes the connection.
@@ -48,7 +48,7 @@
4848
49 /**49 /**
50 * Returns whether the client is connected.50 * Returns whether the client is connected.
51 * @return \c true if the connection is open, \c false otherwise.51 * \return \c true if the connection is open, \c false otherwise.
52 */52 */
53 bool is_connected() const;53 bool is_connected() const;
5454
@@ -60,30 +60,35 @@
6060
61 /**61 /**
62 * Tries to receive a packet.62 * Tries to receive a packet.
63 * @param packet A packet that should be overwritten with the received data.63 * \param packet A packet that should be overwritten with the received data.
64 * @return \c true if a packet is available, \c false otherwise.64 * \return \c true if a packet is available, \c false otherwise.
65 * The given packet is only modified when \c true is returned.65 * The given packet is only modified when \c true is returned.
66 * Calling this on a closed connection will return false.66 * Calling this on a closed connection will return false.
67 */67 */
68 bool try_receive(RecvPacket* packet);68 bool try_receive(RecvPacket *packet);
6969
70 /**70 /**
71 * Sends a packet.71 * Sends a packet.
72 * Calling this on a closed connection will silently fail.72 * Calling this on a closed connection will silently fail.
73 * @param packet The packet to send.73 * \param packet The packet to send.
74 */74 */
75 void send(const SendPacket& packet);75 void send(const SendPacket& packet);
7676
77private:77private:
78 NetClient(const std::string& ip_address, const uint16_t port);78 /**
7979 * Tries to establish a connection to the given host.
80 /// The socket that connects us to the host80 * If the connection attempt failed, is_connected() will return \c false.
81 TCPsocket sock_;81 * \param host The host to connect to.
8282 */
83 /// Socket set used for selection83 NetClient(const NetAddress& host);
84 SDLNet_SocketSet sockset_;84
8585 /// An io_service needed by boost.asio. Primarily needed for asynchronous operations.
86 /// Deserializer acts as a buffer for packets (reassembly/splitting up)86 boost::asio::io_service io_service_;
87
88 /// The socket that connects us to the host.
89 boost::asio::ip::tcp::socket socket_;
90
91 /// Deserializer acts as a buffer for packets (splitting stream to packets)
87 Deserializer deserializer_;92 Deserializer deserializer_;
88};93};
8994
9095
=== modified file 'src/network/nethost.cc'
--- src/network/nethost.cc 2017-05-11 10:45:44 +0000
+++ src/network/nethost.cc 2017-06-07 18:50:40 +0000
@@ -4,7 +4,25 @@
44
5#include "base/log.h"5#include "base/log.h"
66
7NetHost::Client::Client(TCPsocket sock) : socket(sock), deserializer() {7namespace {
8
9 /**
10 * Returns the IP version.
11 * \param acceptor The acceptor socket to get the IP version for.
12 * \return Either 4 or 6, depending on the version of the given acceptor.
13 */
14 int get_ip_version(const boost::asio::ip::tcp::acceptor& acceptor) {
15 assert(acceptor.is_open());
16 if (acceptor.local_endpoint().protocol() == boost::asio::ip::tcp::v4()) {
17 return 4;
18 } else {
19 return 6;
20 }
21 }
22}
23
24
25NetHost::Client::Client(boost::asio::ip::tcp::socket&& sock) : socket(std::move(sock)), deserializer() {
8}26}
927
10std::unique_ptr<NetHost> NetHost::listen(const uint16_t port) {28std::unique_ptr<NetHost> NetHost::listen(const uint16_t port) {
@@ -22,11 +40,10 @@
22 while (!clients_.empty()) {40 while (!clients_.empty()) {
23 close(clients_.begin()->first);41 close(clients_.begin()->first);
24 }42 }
25 SDLNet_FreeSocketSet(sockset_);
26}43}
2744
28bool NetHost::is_listening() const {45bool NetHost::is_listening() const {
29 return svrsock_ != nullptr;46 return acceptor_v4_.is_open() || acceptor_v6_.is_open();
30}47}
3148
32bool NetHost::is_connected(const ConnectionId id) const {49bool NetHost::is_connected(const ConnectionId id) const {
@@ -34,11 +51,17 @@
34}51}
3552
36void NetHost::stop_listening() {53void NetHost::stop_listening() {
37 if (!is_listening())54 static const auto do_stop = [](boost::asio::ip::tcp::acceptor& acceptor) {
38 return;55 boost::system::error_code ec;
39 SDLNet_TCP_DelSocket(sockset_, svrsock_);56 if (acceptor.is_open()) {
40 SDLNet_TCP_Close(svrsock_);57 log("[NetHost]: Closing a listening IPv%d socket.\n", get_ip_version(acceptor));
41 svrsock_ = nullptr;58 acceptor.close(ec);
59 }
60 // Ignore errors
61 };
62
63 do_stop(acceptor_v4_);
64 do_stop(acceptor_v6_);
42}65}
4366
44void NetHost::close(const ConnectionId id) {67void NetHost::close(const ConnectionId id) {
@@ -47,68 +70,145 @@
47 // Not connected anyway70 // Not connected anyway
48 return;71 return;
49 }72 }
50 SDLNet_TCP_DelSocket(sockset_, iter_client->second.socket);73 boost::system::error_code ec;
51 SDLNet_TCP_Close(iter_client->second.socket);74 boost::asio::ip::tcp::endpoint remote = iter_client->second.socket.remote_endpoint(ec);
75 if (!ec) {
76 log("[NetHost] Closing network connection to %s:%i.\n",
77 remote.address().to_string().c_str(), remote.port());
78 } else {
79 log("[NetHost] Closing network connection to some client.\n");
80 }
81 iter_client->second.socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
82 iter_client->second.socket.close(ec);
52 clients_.erase(iter_client);83 clients_.erase(iter_client);
53}84}
5485
55bool NetHost::try_accept(ConnectionId* new_id) {86bool NetHost::try_accept(ConnectionId* new_id) {
56 if (!is_listening())87 if (!is_listening())
57 return false;88 return false;
5889 boost::asio::ip::tcp::socket socket(io_service_);
59 TCPsocket sock = SDLNet_TCP_Accept(svrsock_);90
60 // No client wants to connect91 const auto do_try_accept = [&socket](boost::asio::ip::tcp::acceptor& acceptor) {
61 if (sock == nullptr)92 boost::system::error_code ec;
93 if (acceptor.is_open()) {
94 acceptor.accept(socket, ec);
95 if (ec == boost::asio::error::would_block) {
96 // No client wants to connect
97 // New socket doesn't need to be closed since it isn't open yet
98 } else if (ec) {
99 // Some other error, close the acceptor
100 log("[NetHost] No longer listening for IPv%d connections due to error: %s.\n",
101 get_ip_version(acceptor), ec.message().c_str());
102 acceptor.close(ec);
103 } else {
104 log("[NetHost]: Accepting IPv%d connection from %s.\n",
105 get_ip_version(acceptor), socket.remote_endpoint().address().to_string().c_str());
106 }
107 }
108 };
109
110 do_try_accept(acceptor_v4_);
111 if (!socket.is_open())
112 do_try_accept(acceptor_v6_);
113
114 if (!socket.is_open()) {
115 // No new connection
62 return false;116 return false;
63 SDLNet_TCP_AddSocket(sockset_, sock);117 }
118
119 socket.non_blocking(true);
120
64 ConnectionId id = next_id_++;121 ConnectionId id = next_id_++;
65 assert(id > 0);122 assert(id > 0);
66 assert(clients_.count(id) == 0);123 assert(clients_.count(id) == 0);
67 clients_.insert(std::make_pair(id, Client(sock)));124 clients_.insert(std::make_pair(id, Client(std::move(socket))));
68 assert(clients_.count(id) == 1);125 assert(clients_.count(id) == 1);
69 *new_id = id;126 *new_id = id;
70 return true;127 return true;
71}128}
72129
73bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) {130bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) {
74
75 // Always read all available data into buffers131 // Always read all available data into buffers
76 uint8_t buffer[512];132 uint8_t buffer[kNetworkBufferSize];
77 while (SDLNet_CheckSockets(sockset_, 0) > 0) {
78 for (auto& e : clients_) {
79 if (SDLNet_SocketReady(e.second.socket)) {
80 const int32_t bytes = SDLNet_TCP_Recv(e.second.socket, buffer, sizeof(buffer));
81 if (bytes <= 0) {
82 // Error while receiving
83 close(e.first);
84 // We have to run the for-loop again since we modified the map
85 break;
86 }
87133
88 e.second.deserializer.read_data(buffer, bytes);134 boost::system::error_code ec;
89 }135 for (auto it = clients_.begin(); it != clients_.end(); ) {
136 size_t length = it->second.socket.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
137 if (ec == boost::asio::error::would_block) {
138 // Nothing to read
139 assert(length == 0);
140 ++it;
141 continue;
142 } else if (ec) {
143 assert(length == 0);
144 // Connection closed or some error, close the socket
145 log("[NetHost] Error when receiving from a client, closing connection: %s.\n",
146 ec.message().c_str());
147 // close() will remove the client from the map so we have to increment the iterator first.
148 // Otherwise, it will point to unallocated memory after close() so we can't increase it
149 ConnectionId id_to_remove = it->first;
150 ++it;
151 close(id_to_remove);
152 continue;
90 }153 }
154 assert(length > 0);
155 assert(length <= kNetworkBufferSize);
156 // Read something
157 it->second.deserializer.read_data(buffer, length);
158 ++it;
91 }159 }
92160
93 // Now check whether there is data for the requested client161 // Now check whether there is data for the requested client
94 if (!is_connected(id))162 if (!is_connected(id))
95 return false;163 return false;
96164
97 // Get one packet from the deserializer165 // Try to get one packet from the deserializer
98 return clients_.at(id).deserializer.write_packet(packet);166 return clients_.at(id).deserializer.write_packet(packet);
99}167}
100168
101void NetHost::send(const ConnectionId id, const SendPacket& packet) {169void NetHost::send(const ConnectionId id, const SendPacket& packet) {
170 boost::system::error_code ec;
102 if (is_connected(id)) {171 if (is_connected(id)) {
103 SDLNet_TCP_Send(clients_.at(id).socket, packet.get_data(), packet.get_size());172 size_t written = boost::asio::write(clients_.at(id).socket,
104 }173 boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
105}174 // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
106175 // If it doesn't, set the socket to blocking before writing
107NetHost::NetHost(const uint16_t port) : svrsock_(nullptr), sockset_(nullptr), next_id_(1) {176 // If it does, remove this comment after build 20
108177 assert(ec != boost::asio::error::would_block);
109 IPaddress myaddr;178 assert(written == packet.get_size() || ec);
110 SDLNet_ResolveHost(&myaddr, nullptr, port);179 if (ec) {
111 svrsock_ = SDLNet_TCP_Open(&myaddr);180 log("[NetHost] Error when sending to a client, closing connection: %s.\n", ec.message().c_str());
112 // Maximal 16 sockets! This mean we can have at most 15 clients_ in our game (+ metaserver)181 close(id);
113 sockset_ = SDLNet_AllocSocketSet(16);182 }
183 }
184}
185
186NetHost::NetHost(const uint16_t port)
187 : clients_(), next_id_(1), io_service_(), acceptor_v4_(io_service_), acceptor_v6_(io_service_) {
188
189 if (open_acceptor(&acceptor_v4_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))) {
190 log("[NetHost]: Opening a listening IPv4 socket on TCP port %u\n", port);
191 }
192 if (open_acceptor(&acceptor_v6_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), port))) {
193 log("[NetHost]: Opening a listening IPv6 socket on TCP port %u\n", port);
194 }
195}
196
197bool NetHost::open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
198 const boost::asio::ip::tcp::endpoint& endpoint) {
199 try {
200 acceptor->open(endpoint.protocol());
201 acceptor->non_blocking(true);
202 const boost::asio::socket_base::reuse_address option_reuse(true);
203 acceptor->set_option(option_reuse);
204 if (endpoint.protocol() == boost::asio::ip::tcp::v6()) {
205 const boost::asio::ip::v6_only option_v6only(true);
206 acceptor->set_option(option_v6only);
207 }
208 acceptor->bind(endpoint);
209 acceptor->listen(boost::asio::socket_base::max_connections);
210 return true;
211 } catch (const boost::system::system_error&) {
212 return false;
213 }
114}214}
115215
=== modified file 'src/network/nethost.h'
--- src/network/nethost.h 2017-05-11 10:45:44 +0000
+++ src/network/nethost.h 2017-06-07 18:50:40 +0000
@@ -23,22 +23,22 @@
23#include <map>23#include <map>
24#include <memory>24#include <memory>
2525
26#include <SDL_net.h>
27
28#include "network/network.h"26#include "network/network.h"
2927
30/**28/**
31 * NetHost manages the client connections of a network game in which this computer29 * NetHost manages the client connections of a network game in which this computer
32 * participates as a server.30 * participates as a server.
31 * This class tries to create sockets for IPv4 and IPv6.
33 */32 */
34class NetHost {33class NetHost {
35public:34public:
35 /// IDs used to enumerate the clients.
36 using ConnectionId = uint32_t;36 using ConnectionId = uint32_t;
3737
38 /**38 /**
39 * Tries to listen on the given port.39 * Tries to listen on the given port.
40 * @param port The port to listen on.40 * \param port The port to listen on.
41 * @return A pointer to a listening \c NetHost object or a nullptr if the connection failed.41 * \return A pointer to a listening \c NetHost object or a nullptr if the connection failed.
42 */42 */
43 static std::unique_ptr<NetHost> listen(const uint16_t port);43 static std::unique_ptr<NetHost> listen(const uint16_t port);
4444
@@ -49,14 +49,14 @@
4949
50 /**50 /**
51 * Returns whether the server is started and is listening.51 * Returns whether the server is started and is listening.
52 * @return \c true if the server is listening, \c false otherwise.52 * \return \c true if the server is listening, \c false otherwise.
53 */53 */
54 bool is_listening() const;54 bool is_listening() const;
5555
56 /**56 /**
57 * Returns whether the given client is connected.57 * Returns whether the given client is connected.
58 * @param The id of the client to check.58 * \param The id of the client to check.
59 * @return \c true if the connection is open, \c false otherwise.59 * \return \c true if the connection is open, \c false otherwise.
60 */60 */
61 bool is_connected(ConnectionId id) const;61 bool is_connected(ConnectionId id) const;
6262
@@ -67,14 +67,14 @@
6767
68 /**68 /**
69 * Closes the connection to the given client.69 * Closes the connection to the given client.
70 * @param id The id of the client to close the connection to.70 * \param id The id of the client to close the connection to.
71 */71 */
72 void close(ConnectionId id);72 void close(ConnectionId id);
7373
74 /**74 /**
75 * Tries to accept a new client.75 * Tries to accept a new client.
76 * @param new_id The connection id of the new client will be stored here.76 * \param new_id The connection id of the new client will be stored here.
77 * @return \c true if a client has connected, \c false otherwise.77 * \return \c true if a client has connected, \c false otherwise.
78 * The given id is only modified when \c true is returned.78 * The given id is only modified when \c true is returned.
79 * Calling this on a closed server will return false.79 * Calling this on a closed server will return false.
80 * The returned id is always greater than 0.80 * The returned id is always greater than 0.
@@ -83,9 +83,9 @@
8383
84 /**84 /**
85 * Tries to receive a packet.85 * Tries to receive a packet.
86 * @param id The connection id of the client that should be received.86 * \param id The connection id of the client that should be received.
87 * @param packet A packet that should be overwritten with the received data.87 * \param packet A packet that should be overwritten with the received data.
88 * @return \c true if a packet is available, \c false otherwise.88 * \return \c true if a packet is available, \c false otherwise.
89 * The given packet is only modified when \c true is returned.89 * The given packet is only modified when \c true is returned.
90 * Calling this on a closed connection will return false.90 * Calling this on a closed connection will return false.
91 */91 */
@@ -94,26 +94,50 @@
94 /**94 /**
95 * Sends a packet.95 * Sends a packet.
96 * Calling this on a closed connection will silently fail.96 * Calling this on a closed connection will silently fail.
97 * @param id The connection id of the client that should be sent to.97 * \param id The connection id of the client that should be sent to.
98 * @param packet The packet to send.98 * \param packet The packet to send.
99 */99 */
100 void send(ConnectionId id, const SendPacket& packet);100 void send(ConnectionId id, const SendPacket& packet);
101101
102private:102private:
103 /**
104 * Tries to listen on the given port.
105 * If it fails, is_listening() will return \c false.
106 * \param port The port to listen on.
107 */
103 NetHost(const uint16_t port);108 NetHost(const uint16_t port);
104109
105 class Client {110 bool open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
106 public:111 const boost::asio::ip::tcp::endpoint& endpoint);
107 Client(TCPsocket sock);112
108113 /**
109 TCPsocket socket;114 * Helper structure to store variables about a connected client.
115 */
116 struct Client {
117 /**
118 * Initializes the structure with the given socket.
119 * \param sock The socket to listen on. The socket is moved by this
120 * constructor so the given socket is no longer valid.
121 */
122 Client(boost::asio::ip::tcp::socket&& sock);
123
124 /// The socket to send/receive with.
125 boost::asio::ip::tcp::socket socket;
126 /// The deserializer to feed the received data to. It will transform it into data packets.
110 Deserializer deserializer;127 Deserializer deserializer;
111 };128 };
112129
113 TCPsocket svrsock_;130 /// A map linking client ids to the respective data about the clients.
114 SDLNet_SocketSet sockset_;131 /// Client ids not in this map should be considered invalid.
115 std::map<NetHost::ConnectionId, Client> clients_;132 std::map<NetHost::ConnectionId, Client> clients_;
133 /// The next client id that will be used
116 NetHost::ConnectionId next_id_;134 NetHost::ConnectionId next_id_;
135 /// An io_service needed by boost.asio. Primary needed for async operations.
136 boost::asio::io_service io_service_;
137 /// The acceptor we get IPv4 connection requests to.
138 boost::asio::ip::tcp::acceptor acceptor_v4_;
139 /// The acceptor we get IPv6 connection requests to.
140 boost::asio::ip::tcp::acceptor acceptor_v6_;
117};141};
118142
119#endif // end of include guard: WL_NETWORK_NETHOST_H143#endif // end of include guard: WL_NETWORK_NETHOST_H
120144
=== modified file 'src/network/network.cc'
--- src/network/network.cc 2017-05-14 14:40:24 +0000
+++ src/network/network.cc 2017-06-07 18:50:40 +0000
@@ -19,8 +19,62 @@
1919
20#include "network/network.h"20#include "network/network.h"
2121
22#include <SDL.h>
23
22#include "base/log.h"24#include "base/log.h"
2325
26
27namespace {
28
29bool do_resolve(const boost::asio::ip::tcp& protocol, NetAddress *addr, const std::string& hostname, uint16_t port) {
30 assert(addr != nullptr);
31 try {
32 boost::asio::io_service io_service;
33 boost::asio::ip::tcp::resolver resolver(io_service);
34 boost::asio::ip::tcp::resolver::query query(protocol, hostname, boost::lexical_cast<std::string>(port));
35 boost::asio::ip::tcp::resolver::iterator iter = resolver.resolve(query);
36 if (iter == boost::asio::ip::tcp::resolver::iterator()) {
37 // Resolution failed
38 return false;
39 }
40 addr->ip = iter->endpoint().address();
41 addr->port = port;
42 return true;
43 } catch (const boost::system::system_error& ec) {
44 // Resolution failed
45 log("Could not resolve network name: %s", ec.what());
46 return false;
47 }
48}
49
50}
51
52bool NetAddress::resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port) {
53 return do_resolve(boost::asio::ip::tcp::v4(), addr, hostname, port);
54}
55
56bool NetAddress::resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port) {
57 return do_resolve(boost::asio::ip::tcp::v6(), addr, hostname, port);
58}
59
60bool NetAddress::parse_ip(NetAddress *addr, const std::string& ip, uint16_t port) {
61 boost::system::error_code ec;
62 boost::asio::ip::address new_addr = boost::asio::ip::address::from_string(ip, ec);
63 if (ec)
64 return false;
65 addr->ip = new_addr;
66 addr->port = port;
67 return true;
68}
69
70bool NetAddress::is_ipv6() const {
71 return ip.is_v6();
72}
73
74bool NetAddress::is_valid() const {
75 return port != 0 && !ip.is_unspecified();
76}
77
24CmdNetCheckSync::CmdNetCheckSync(uint32_t const dt, SyncCallback* const cb)78CmdNetCheckSync::CmdNetCheckSync(uint32_t const dt, SyncCallback* const cb)
25 : Command(dt), callback_(cb) {79 : Command(dt), callback_(cb) {
26}80}
@@ -184,7 +238,7 @@
184}238}
185239
186DisconnectException::DisconnectException(const char* fmt, ...) {240DisconnectException::DisconnectException(const char* fmt, ...) {
187 char buffer[512];241 char buffer[kNetworkBufferSize];
188 {242 {
189 va_list va;243 va_list va;
190 va_start(va, fmt);244 va_start(va, fmt);
191245
=== modified file 'src/network/network.h'
--- src/network/network.h 2017-05-23 21:07:47 +0000
+++ src/network/network.h 2017-06-07 18:50:40 +0000
@@ -24,7 +24,7 @@
24#include <string>24#include <string>
25#include <vector>25#include <vector>
2626
27#include <SDL_net.h>27#include <boost/asio.hpp>
28#include <boost/lexical_cast.hpp>28#include <boost/lexical_cast.hpp>
2929
30#include "base/wexception.h"30#include "base/wexception.h"
@@ -36,6 +36,60 @@
36class Deserializer;36class Deserializer;
37class FileRead;37class FileRead;
3838
39constexpr size_t kNetworkBufferSize = 512;
40
41/**
42 * Simple structure to hold the IP address and port of a server.
43 * This structure must not contain a hostname but only IP addresses.
44 */
45struct NetAddress {
46 /**
47 * Tries to resolve the given hostname to an IPv4 address.
48 * \param[out] addr A NetAddress structure to write the result to,
49 * if resolution succeeds.
50 * \param hostname The name of the host.
51 * \param port The port on the host.
52 * \return \c True if the resolution succeeded, \c false otherwise.
53 */
54 static bool resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port);
55
56 /**
57 * Tries to resolve the given hostname to an IPv6 address.
58 * \param[out] addr A NetAddress structure to write the result to,
59 * if resolution succeeds.
60 * \param hostname The name of the host.
61 * \param port The port on the host.
62 * \return \c True if the resolution succeeded, \c false otherwise.
63 */
64 static bool resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port);
65
66 /**
67 * Parses the given string to an IP address.
68 * \param[out] addr A NetAddress structure to write the result to,
69 * if parsing succeeds.
70 * \param ip An IP address as string.
71 * \param port The port on the host.
72 * \return \c True if the parsing succeeded, \c false otherwise.
73 */
74 static bool parse_ip(NetAddress *addr, const std::string& ip, uint16_t port);
75
76 /**
77 * Returns whether the stored IP is in IPv6 format.
78 * @return \c true if the stored IP is in IPv6 format, \c false otherwise.
79 * If it isn't an IPv6 address, it is an IPv4 address.
80 */
81 bool is_ipv6() const;
82
83 /**
84 * Returns whether valid IP address and port are stored.
85 * @return \c true if valid, \c false otherwise.
86 */
87 bool is_valid() const;
88
89 boost::asio::ip::address ip;
90 uint16_t port;
91};
92
39struct SyncCallback {93struct SyncCallback {
40 virtual ~SyncCallback() {94 virtual ~SyncCallback() {
41 }95 }
4296
=== modified file 'src/network/network_lan_promotion.cc'
--- src/network/network_lan_promotion.cc 2017-01-25 18:55:59 +0000
+++ src/network/network_lan_promotion.cc 2017-06-07 18:50:40 +0000
@@ -19,116 +19,349 @@
1919
20#include "network/network_lan_promotion.h"20#include "network/network_lan_promotion.h"
2121
22#include <cstdio>22#ifndef _WIN32
23#include <cstring>23#include <ifaddrs.h>
24#endif
2425
26#include "base/i18n.h"
25#include "base/log.h"27#include "base/log.h"
26#include "base/macros.h"28#include "base/warning.h"
27#include "build_info.h"29#include "build_info.h"
28#include "network/constants.h"30#include "network/constants.h"
2931
32namespace {
33
34 /**
35 * Returns the IP version.
36 * \param addr The address object to get the IP version for.
37 * \return Either 4 or 6, depending on the version of the given address.
38 */
39 int get_ip_version(const boost::asio::ip::address& addr) {
40 assert(!addr.is_unspecified());
41 if (addr.is_v4()) {
42 return 4;
43 } else {
44 assert(addr.is_v6());
45 return 6;
46 }
47 }
48
49 /**
50 * Returns the IP version.
51 * \param version A whatever object to get the IP version for.
52 * \return Either 4 or 6, depending on the version of the given address.
53 */
54 int get_ip_version(const boost::asio::ip::udp& version) {
55 if (version == boost::asio::ip::udp::v4()) {
56 return 4;
57 } else {
58 assert(version == boost::asio::ip::udp::v6());
59 return 6;
60 }
61 }
62}
63
30/*** class LanBase ***/64/*** class LanBase ***/
3165/**
32LanBase::LanBase() {66 * \internal
3367 * In an ideal world, we would use the same code with boost asio for all three operating systems.
34 sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); // open the socket68 * Unfortunately, it isn't that easy and we need some platform specific code.
3569 * For IPv4, windows needs a special case: For Linux and Apple we have to iterate over all assigned IPv4
36 int32_t opt = 1;70 * addresses (e.g. 192.168.1.68), transform them to broadcast addresses (e.g. 192.168.1.255) and send our
37 // the cast to char* is because microsoft wants it that way71 * packets to those addresses. For windows, we simply can send to 255.255.255.255.
38 setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char*>(&opt), sizeof(opt));72 * For IPv6, Apple requires special handling. On the other two operating systems we can send to the multicast
73 * address ff02::1 (kind of a local broadcast) without specifying over which interface we want to send.
74 * On Apple we have to specify the interface, forcing us to send our message over all interfaces we can find.
75 */
76LanBase::LanBase(uint16_t port)
77 : io_service(), socket_v4(io_service), socket_v6(io_service) {
3978
40#ifndef _WIN3279#ifndef _WIN32
4180 // Iterate over all interfaces. If they support IPv4, store the broadcast-address
42 // get a list of all local broadcast addresses81 // of the interface and try to start the socket. If they support IPv6, just start
43 struct if_nameindex* ifnames = if_nameindex();82 // the socket. There is one fixed broadcast-address for IPv6 (well, actually multicast)
44 struct ifreq ifr;83
4584 // Adapted example out of "man getifaddrs"
46 for (int32_t i = 0; ifnames[i].if_index; ++i) {85 // TODO(Notabilis): I don't like this part. But boost is not able to iterate over
47 strncpy(ifr.ifr_name, ifnames[i].if_name, IFNAMSIZ);86 // the local IPs and interfaces at this time. If they ever add it, replace this code
4887 struct ifaddrs *ifaddr, *ifa;
49 DIAG_OFF("-Wold-style-cast")88 int s, n;
50 if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0)89 char host[NI_MAXHOST];
51 continue;90 if (getifaddrs(&ifaddr) == -1) {
5291 perror("getifaddrs");
53 if (!(ifr.ifr_flags & IFF_BROADCAST))92 exit(EXIT_FAILURE);
54 continue;93 }
5594 for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) {
56 if (ioctl(sock, SIOCGIFBRDADDR, &ifr) < 0)95 if (ifa->ifa_addr == nullptr)
57 continue;96 continue;
58 DIAG_ON("-Wold-style-cast")97 if (!(ifa->ifa_flags & IFF_BROADCAST) && !(ifa->ifa_flags & IFF_MULTICAST))
5998 continue;
60 broadcast_addresses.push_back(99 switch (ifa->ifa_addr->sa_family) {
61 reinterpret_cast<sockaddr_in*>(&ifr.ifr_broadaddr)->sin_addr.s_addr);100 case AF_INET:
62 }101 s = getnameinfo(ifa->ifa_broadaddr, sizeof(struct sockaddr_in),
63102 host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
64 if_freenameindex(ifnames);103 if (s == 0) {
104 start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
105 broadcast_addresses_v4.insert(host);
106 }
107 break;
108 case AF_INET6:
109#ifdef __APPLE__
110 interface_indices_v6.insert(if_nametoindex(ifa->ifa_name));
111#endif
112 start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
113 // No address to store here. There is only one "broadcast" address for IPv6
114 break;
115 }
116 }
117 freeifaddrs(ifaddr);
118
65#else119#else
66 // As Microsoft does not seem to support if_nameindex, we just broadcast to120 // As Microsoft does not seem to support if_nameindex, we just broadcast to
67 // INADDR_BROADCAST.121 // INADDR_BROADCAST.
68 broadcast_addresses.push_back(INADDR_BROADCAST);122 broadcast_addresses_v4.insert("255.255.255.255");
69#endif123#endif
124
125 if (!is_open()) {
126 // Hm, not good. Just try to open them and hope for the best
127 log("[LAN] Trying to open both sockets.\n");
128 start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
129 start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
130 }
131
132 if (!is_open()) {
133 // Still not open? Go back to main menu.
134 log("[LAN] Error: No sockets could be opened.\n");
135 report_network_error();
136 }
137
138 for (const std::string& ip : broadcast_addresses_v4)
139 log("[LAN] Will broadcast to %s.\n", ip.c_str());
140 if (socket_v6.is_open())
141 log("[LAN] Will broadcast for IPv6.\n");
70}142}
71143
72LanBase::~LanBase() {144LanBase::~LanBase() {
73 closesocket(sock);145 close_socket(&socket_v4);
74}146 close_socket(&socket_v6);
75147}
76void LanBase::bind(uint16_t port) {148
77 sockaddr_in addr;149bool LanBase::is_available() {
78150 const auto do_is_available = [this](boost::asio::ip::udp::socket& socket) -> bool {
79 DIAG_OFF("-Wold-style-cast")151 boost::system::error_code ec;
80 addr.sin_family = AF_INET;152 bool available = (socket.is_open() && socket.available(ec) > 0);
81 addr.sin_addr.s_addr = INADDR_ANY;153 if (ec) {
82 addr.sin_port = htons(port);154 log("[LAN] Error when checking whether data is available on IPv%d socket, closing it: %s.\n",
83 DIAG_ON("-Wold-style-cast")155 get_ip_version(socket.local_endpoint().protocol()), ec.message().c_str());
84156 close_socket(&socket);
85 ::bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));157 return false;
86}158 }
87159 return available;
88bool LanBase::avail() {160 };
89 fd_set fds;161
90 timeval tv;162 return do_is_available(socket_v4) || do_is_available(socket_v6);
91163}
92 DIAG_OFF("-Wold-style-cast")164
93 FD_ZERO(&fds);165bool LanBase::is_open() {
94 FD_SET(sock, &fds);166 return socket_v4.is_open() || socket_v6.is_open();
95 DIAG_ON("-Wold-style-cast")167}
96168
97 tv.tv_sec = 0;169ssize_t LanBase::receive(void* const buf, size_t const len, NetAddress *addr) {
98 tv.tv_usec = 0;170 assert(buf != nullptr);
99171 assert(addr != nullptr);
100 return select(sock + 1, &fds, nullptr, nullptr, &tv) == 1;172 size_t recv_len = 0;
101}173
102174 const auto do_receive
103ssize_t LanBase::receive(void* const buf, size_t const len, sockaddr_in* const addr) {175 = [this, &buf, &len, &recv_len, addr](boost::asio::ip::udp::socket& socket) -> bool {
104 socklen_t addrlen = sizeof(sockaddr_in);176 if (socket.is_open()) {
105 return recvfrom(177 try {
106 sock, static_cast<DATATYPE*>(buf), len, 0, reinterpret_cast<sockaddr*>(addr), &addrlen);178 if (socket.available() > 0) {
107}179 boost::asio::ip::udp::endpoint sender_endpoint;
108180 recv_len = socket.receive_from(boost::asio::buffer(buf, len), sender_endpoint);
109void LanBase::send(void const* const buf, size_t const len, sockaddr_in const* const addr) {181 *addr = NetAddress{sender_endpoint.address(), sender_endpoint.port()};
110 sendto(sock, static_cast<const DATATYPE*>(buf), len, 0, reinterpret_cast<const sockaddr*>(addr),182 assert(recv_len <= len);
111 sizeof(sockaddr_in));183 return true;
112}184 }
113185 } catch (const boost::system::system_error& ec) {
114void LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {186 // Some network error. Close the socket
115 for (const in_addr_t& temp_address : broadcast_addresses) {187 log("[LAN] Error when receiving data on IPv%d socket, closing it: %s.\n",
116 sockaddr_in addr;188 get_ip_version(socket.local_endpoint().protocol()), ec.what());
117 addr.sin_family = AF_INET;189 close_socket(&socket);
118 addr.sin_addr.s_addr = temp_address;190 }
119 DIAG_OFF("-Wold-style-cast")191 }
120 addr.sin_port = htons(port);192 // Nothing received
121 DIAG_ON("-Wold-style-cast")193 return false;
122194 };
123 sendto(sock, static_cast<const DATATYPE*>(buf), len, 0,195
124 reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));196 // Try to receive something somewhere
197 if (!do_receive(socket_v4))
198 do_receive(socket_v6);
199
200 // Return how much has been received, might be 0
201 return recv_len;
202}
203
204bool LanBase::send(void const* const buf, size_t const len, const NetAddress& addr) {
205 boost::system::error_code ec;
206 assert(addr.is_valid());
207 // If this assert failed, then there is some bug in the code. NetAddress should only be filled
208 // with valid IP addresses (e.g. no hostnames)
209 assert(!ec);
210 boost::asio::ip::udp::endpoint destination(addr.ip, addr.port);
211 boost::asio::ip::udp::socket *socket = nullptr;
212 if (destination.address().is_v4()) {
213 socket = &socket_v4;
214 } else if (destination.address().is_v6()) {
215 socket = &socket_v6;
216 } else {
217 NEVER_HERE();
218 }
219 assert(socket != nullptr);
220 if (!socket->is_open()) {
221 // I think this shouldn't happen normally. It might happen, though, if we receive
222 // a broadcast and learn the IP, then our sockets goes down, then we try to send
223 log("[LAN] Error: trying to send to an IPv%d address but socket is not open.\n",
224 get_ip_version(addr.ip));
225 return false;
226 }
227 socket->send_to(boost::asio::buffer(buf, len), destination, 0, ec);
228 if (ec) {
229 log("[LAN] Error when trying to send something over IPv%d, closing socket: %s.\n",
230 get_ip_version(addr.ip), ec.message().c_str());
231 close_socket(socket);
232 return false;
233 }
234 return true;
235}
236
237bool LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
238
239 const auto do_broadcast
240 = [this, buf, len, port](boost::asio::ip::udp::socket& socket, const std::string& address) -> bool {
241 if (socket.is_open()) {
242 boost::system::error_code ec;
243 boost::asio::ip::udp::endpoint destination(boost::asio::ip::address::from_string(address), port);
244 socket.send_to(boost::asio::buffer(buf, len), destination, 0, ec);
245 if (!ec) {
246 return true;
247 }
248#ifdef __APPLE__
249 if (get_ip_version(destination.address()) == 4) {
250#endif // __APPLE__
251 log("[LAN] Error when broadcasting on IPv%d socket to %s, closing it: %s.\n",
252 get_ip_version(destination.address()), address.c_str(), ec.message().c_str());
253 close_socket(&socket);
254#ifdef __APPLE__
255 } else {
256 log("[LAN] Error when broadcasting on IPv6 socket to %s: %s.\n",
257 address.c_str(), ec.message().c_str());
258 }
259#endif // __APPLE__
260 }
261 return false;
262 };
263
264 bool one_success = false;
265
266 // IPv4 broadcasting is the same for all
267 for (const std::string& address : broadcast_addresses_v4) {
268 one_success |= do_broadcast(socket_v4, address);
269 }
270#ifndef __APPLE__
271 // For IPv6 on Linux and Windows just send on an undefined network interface
272 one_success |= do_broadcast(socket_v6, "ff02::1");
273#else // __APPLE__
274
275 // Apple forces us to define which interface we want to send through
276 for (auto it = interface_indices_v6.begin(); it != interface_indices_v6.end(); ) {
277 socket_v6.set_option(boost::asio::ip::multicast::outbound_interface(*it));
278 bool success = do_broadcast(socket_v6, "ff02::1");
279 one_success |= success;
280 if (!success) {
281 // Remove this interface id from the set
282 it = interface_indices_v6.erase(it);
283 if (interface_indices_v6.empty()) {
284 log("[LAN] Warning: No more multicast capable IPv6 interfaces."
285 "Other LAN players won't find your game.\n");
286 }
287 } else {
288 ++it;
289 }
290 }
291#endif // __APPLE__
292 return one_success;
293}
294
295void LanBase::start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port) {
296
297 if (socket->is_open())
298 return;
299
300 boost::system::error_code ec;
301 // Try to open the socket
302 socket->open(version, ec);
303 if (ec) {
304 log("[LAN] Failed to start an IPv%d socket: %s.\n",
305 get_ip_version(version), ec.message().c_str());
306 return;
307 }
308
309 const boost::asio::socket_base::broadcast option_broadcast(true);
310 socket->set_option(option_broadcast, ec);
311 if (ec) {
312 log("[LAN] Error setting options for IPv%d socket, closing socket: %s.\n",
313 get_ip_version(version), ec.message().c_str());
314 // Retrieve the error code to avoid throwing but ignore it
315 close_socket(socket);
316 return;
317 }
318
319 const boost::asio::socket_base::reuse_address option_reuse(true);
320 socket->set_option(option_reuse, ec);
321 // This one isn't really needed so ignore the error
322
323
324 if (version == boost::asio::ip::udp::v6()) {
325 const boost::asio::ip::v6_only option_v6only(true);
326 socket->set_option(option_v6only, ec);
327 // This one might not be needed, ignore the error and see whether we fail on bind()
328 }
329
330 socket->bind(boost::asio::ip::udp::endpoint(version, port), ec);
331 if (ec) {
332 log("[LAN] Error binding IPv%d socket to UDP port %d, closing socket: %s.\n",
333 get_ip_version(version), port, ec.message().c_str());
334 close_socket(socket);
335 return;
336 }
337
338 log("[LAN] Started an IPv%d socket on UDP port %d.\n", get_ip_version(version), port);
339}
340
341void LanBase::report_network_error() {
342 // No socket open? Sorry, but we can't continue this way
343 throw WLWarning(_("Failed to use the local network!"),
344 _("Widelands was unable to use the local network. "
345 "Maybe some other process is already running a server on port %d, %d or %d "
346 "or your network setup is broken."),
347 WIDELANDS_LAN_DISCOVERY_PORT, WIDELANDS_LAN_PROMOTION_PORT, WIDELANDS_PORT);
348}
349
350void LanBase::close_socket(boost::asio::ip::udp::socket *socket) {
351 boost::system::error_code ec;
352 if (socket->is_open()) {
353 const boost::asio::ip::udp::endpoint& endpoint = socket->local_endpoint(ec);
354 if (!ec)
355 log("[LAN] Closing an IPv%d socket.\n", get_ip_version(endpoint.protocol()));
356 socket->shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
357 socket->close(ec);
125 }358 }
126}359}
127360
128/*** class LanGamePromoter ***/361/*** class LanGamePromoter ***/
129362
130LanGamePromoter::LanGamePromoter() {363LanGamePromoter::LanGamePromoter()
131 bind(WIDELANDS_LAN_PROMOTION_PORT);364 : LanBase(WIDELANDS_LAN_PROMOTION_PORT) {
132365
133 needupdate = true;366 needupdate = true;
134367
@@ -140,12 +373,13 @@
140373
141 strncpy(gameinfo.gameversion, build_id().c_str(), sizeof(gameinfo.gameversion));374 strncpy(gameinfo.gameversion, build_id().c_str(), sizeof(gameinfo.gameversion));
142375
143 gethostname(gameinfo.hostname, sizeof(gameinfo.hostname));376 strncpy(gameinfo.hostname, boost::asio::ip::host_name().c_str(), sizeof(gameinfo.hostname));
144}377}
145378
146LanGamePromoter::~LanGamePromoter() {379LanGamePromoter::~LanGamePromoter() {
147 gameinfo.state = LAN_GAME_CLOSED;380 gameinfo.state = LAN_GAME_CLOSED;
148381
382 // Don't care about errors at this point
149 broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);383 broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);
150}384}
151385
@@ -153,20 +387,25 @@
153 if (needupdate) {387 if (needupdate) {
154 needupdate = false;388 needupdate = false;
155389
156 broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);390 if (!broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT)) {
391 report_network_error();
392 }
157 }393 }
158394
159 while (avail()) {395 while (is_available()) {
160 char magic[8];396 char magic[8];
161 sockaddr_in addr;397 NetAddress addr;
162398
163 if (receive(magic, 8, &addr) < 8)399 if (receive(magic, 8, &addr) < 8)
164 continue;400 continue;
165401
166 log("Received %s packet\n", magic);402 log("Received %s packet from %s\n", magic, addr.ip.to_string().c_str());
167403
168 if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION)404 if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION) {
169 send(&gameinfo, sizeof(gameinfo), &addr);405 if (!send(&gameinfo, sizeof(gameinfo), addr)) {
406 report_network_error();
407 }
408 }
170 }409 }
171}410}
172411
@@ -178,8 +417,8 @@
178417
179/*** class LanGameFinder ***/418/*** class LanGameFinder ***/
180419
181LanGameFinder::LanGameFinder() : callback(nullptr) {420LanGameFinder::LanGameFinder()
182 bind(WIDELANDS_LAN_DISCOVERY_PORT);421 : LanBase(WIDELANDS_LAN_DISCOVERY_PORT), callback(nullptr) {
183422
184 reset();423 reset();
185}424}
@@ -192,18 +431,19 @@
192 strncpy(magic, "QUERY", 8);431 strncpy(magic, "QUERY", 8);
193 magic[6] = LAN_PROMOTION_PROTOCOL_VERSION;432 magic[6] = LAN_PROMOTION_PROTOCOL_VERSION;
194433
195 broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT);434 if (!broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT))
435 report_network_error();
196}436}
197437
198void LanGameFinder::run() {438void LanGameFinder::run() {
199 while (avail()) {439 while (is_available()) {
200 NetGameInfo info;440 NetGameInfo info;
201 sockaddr_in addr;441 NetAddress addr;
202442
203 if (receive(&info, sizeof(info), &addr) < static_cast<int32_t>(sizeof(info)))443 if (receive(&info, sizeof(info), &addr) < static_cast<int32_t>(sizeof(info)))
204 continue;444 continue;
205445
206 log("Received %s packet\n", info.magic);446 log("Received %s packet from %s\n", info.magic, addr.ip.to_string().c_str());
207447
208 if (strncmp(info.magic, "GAME", 6))448 if (strncmp(info.magic, "GAME", 6))
209 continue;449 continue;
@@ -217,6 +457,9 @@
217 for (NetOpenGame* opengame : opengames) {457 for (NetOpenGame* opengame : opengames) {
218 if (0 == strncmp(opengame->info.hostname, info.hostname, 128)) {458 if (0 == strncmp(opengame->info.hostname, info.hostname, 128)) {
219 opengame->info = info;459 opengame->info = info;
460 if (!opengame->address.is_ipv6() && addr.is_ipv6()) {
461 opengame->address.ip = addr.ip;
462 }
220 callback(GameUpdated, opengame, userdata);463 callback(GameUpdated, opengame, userdata);
221 was_in_list = true;464 was_in_list = true;
222 break;465 break;
@@ -225,10 +468,8 @@
225468
226 if (!was_in_list) {469 if (!was_in_list) {
227 opengames.push_back(new NetOpenGame);470 opengames.push_back(new NetOpenGame);
228 DIAG_OFF("-Wold-style-cast")471 addr.port = WIDELANDS_PORT;
229 opengames.back()->address = addr.sin_addr.s_addr;472 opengames.back()->address = addr;
230 opengames.back()->port = htons(WIDELANDS_PORT);
231 DIAG_ON("-Wold-style-cast")
232 opengames.back()->info = info;473 opengames.back()->info = info;
233 callback(GameOpened, opengames.back(), userdata);474 callback(GameOpened, opengames.back(), userdata);
234 break;475 break;
235476
=== modified file 'src/network/network_lan_promotion.h'
--- src/network/network_lan_promotion.h 2017-05-07 20:27:21 +0000
+++ src/network/network_lan_promotion.h 2017-06-07 18:50:40 +0000
@@ -21,21 +21,15 @@
21#define WL_NETWORK_NETWORK_LAN_PROMOTION_H21#define WL_NETWORK_NETWORK_LAN_PROMOTION_H
2222
23#include <list>23#include <list>
2424#include <set>
25#ifndef _WIN3225
26#include <sys/socket.h>26#include "network/network.h"
27#endif
28#include <sys/types.h>
29
30#include "network/network_system.h"
3127
32#define LAN_PROMOTION_PROTOCOL_VERSION 128#define LAN_PROMOTION_PROTOCOL_VERSION 1
3329
34#define LAN_GAME_CLOSED 030#define LAN_GAME_CLOSED 0
35#define LAN_GAME_OPEN 131#define LAN_GAME_OPEN 1
3632
37// TODO(Notabilis): Update file for IPv6
38
39struct NetGameInfo {33struct NetGameInfo {
40 char magic[6];34 char magic[6];
41 uint8_t version;35 uint8_t version;
@@ -47,31 +41,112 @@
47};41};
4842
49struct NetOpenGame {43struct NetOpenGame {
50 in_addr_t address;44 NetAddress address;
51 in_port_t port;
52 NetGameInfo info;45 NetGameInfo info;
53};46};
5447
48/**
49 * Base class for UDP networking.
50 * This class is used by derived classes to find open games on the
51 * local network and to announce a just opened game on the local network.
52 * This class tries to create sockets for IPv4 and IPv6.
53 */
55struct LanBase {54struct LanBase {
56protected:55protected:
57 LanBase();56 /**
57 * Tries to start a socket on the given port.
58 * Sockets for IPv4 and IPv6 are started.
59 * When both fail, report_network_error() is called.
60 * \param port The port to listen on.
61 */
62 LanBase(uint16_t port);
63
58 ~LanBase();64 ~LanBase();
5965
60 void bind(uint16_t);66 /**
6167 * Returns whether data is available to be read.
62 bool avail();68 * \return \c True when receive() will return data, \c false otherwise.
6369 */
64 ssize_t receive(void*, size_t, sockaddr_in*);70 bool is_available();
6571
66 void send(void const*, size_t, sockaddr_in const*);72 /**
67 void broadcast(void const*, size_t, uint16_t);73 * Returns whether at least one of the sockets is open.
74 * If this returns \c false, you probably have a problem.
75 * \return \c True when a socket is ready, \c false otherwise.
76 */
77 bool is_open();
78
79 /**
80 * Tries to receive some data.
81 * \param[out] buf The buffer to read data into.
82 * \param len The length of the buffer.
83 * \param[out] addr The address we received data from. Since UDP is a connection-less
84 * protocol, each receive() might receive data from another address.
85 * \return How many bytes have been written to \c buf. If 0 is returned there either was no data
86 * available (check before with avail()) or there was some error (check with is_open())
87 */
88 ssize_t receive(void *buf, size_t len, NetAddress *addr);
89
90 /**
91 * Sends data to a specified address.
92 * \param buf The data to send.
93 * \param len The length of the buffer.
94 * \param addr The address to send to.
95 */
96 bool send(void const *buf, size_t len, const NetAddress& addr);
97
98 /**
99 * Broadcast some data in the local network.
100 * \param buf The data to send.
101 * \param len The length of the buffer.
102 * \param port The port to send to. No address is required.
103 */
104 bool broadcast(void const* buf, size_t len, uint16_t port);
105
106 /**
107 * Throws a WLWarning exception to jump back to the main menu.
108 * Calling this on network errors is in the responsibility of derived classes.
109 * (Most of the time, aborting makes sense when an error occurred. But e.g. in
110 * the destructor simply ignoring the error is okay.)
111 */
112 void report_network_error();
68113
69private:114private:
70 int32_t sock;115
71116 /**
72 std::list<in_addr_t> broadcast_addresses;117 * Opens a listening UDP socket.
118 * \param[out] The socket to open. The object has to be created but the socket not opened before.
119 * If it already has been opened before, nothing will be done.
120 * \param version Whether a IPv4 or IPv6 socket should be opened.
121 * \param port The port to listen on.
122 */
123 void start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port);
124
125 /**
126 * Closes the given socket.
127 * Does nothing if the socket already has been closed.
128 * \param socket The socket to close.
129 */
130 void close_socket(boost::asio::ip::udp::socket *socket);
131
132 /// No idea what this does. I think it is only really used when asynchronous operations are done.
133 boost::asio::io_service io_service;
134 /// The socket for IPv4.
135 boost::asio::ip::udp::socket socket_v4;
136 /// The socket for IPv6.
137 boost::asio::ip::udp::socket socket_v6;
138 /// The found broadcast addresses for IPv4.
139 /// No addresses for v6, there is only one fixed address.
140 std::set<std::string> broadcast_addresses_v4;
141#ifdef __APPLE__
142 /// Apple forces us to define which interface to broadcast through.
143 std::set<unsigned int> interface_indices_v6;
144#endif // __APPLE__
73};145};
74146
147/**
148 * Used to promote opened games locally.
149 */
75struct LanGamePromoter : public LanBase {150struct LanGamePromoter : public LanBase {
76 LanGamePromoter();151 LanGamePromoter();
77 ~LanGamePromoter();152 ~LanGamePromoter();
@@ -85,6 +160,9 @@
85 bool needupdate;160 bool needupdate;
86};161};
87162
163/**
164 * Used to listen for open games while in the LAN-screen.
165 */
88struct LanGameFinder : LanBase {166struct LanGameFinder : LanBase {
89 enum { GameOpened, GameClosed, GameUpdated };167 enum { GameOpened, GameClosed, GameUpdated };
90168
91169
=== removed file 'src/network/network_system.h'
--- src/network/network_system.h 2017-01-25 18:55:59 +0000
+++ src/network/network_system.h 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
1/*
2 * Copyright (C) 2004-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_NETWORK_SYSTEM_H
21#define WL_NETWORK_NETWORK_SYSTEM_H
22
23#include <stdint.h>
24#ifndef _WIN32
25// These includes work on Linux and should be fine on any other Unix-alike.
26// If not, this is the right place to conditionally include what is needed.
27#include <net/if.h>
28#include <netdb.h>
29#include <netinet/in.h>
30#include <sys/ioctl.h>
31#include <sys/socket.h>
32#include <sys/types.h>
33#include <unistd.h>
34
35// be compatible to microsoft
36#define closesocket close
37#define DATATYPE void
38
39#else
40
41// This is the header to include according to the documentation
42// at msdn.microsoft.com
43#include <winsock2.h>
44
45#define DATATYPE char
46// microsoft doesn't have these
47using in_port_t = uint16_t;
48using in_addr_t = uint32_t;
49
50#ifndef s_addr
51#define s_addr S_addr
52#endif
53
54// This is no typedef on purpose
55#define socklen_t int32_t
56
57#endif
58
59#endif // end of include guard: WL_NETWORK_NETWORK_SYSTEM_H
600
=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc 2017-05-11 06:31:35 +0000
+++ src/ui_fsmenu/internet_lobby.cc 2017-06-07 18:50:40 +0000
@@ -373,15 +373,10 @@
373 }373 }
374 std::string ip = InternetGaming::ref().ip();374 std::string ip = InternetGaming::ref().ip();
375375
376 // TODO(Notabilis): Change this for IPv6376 NetAddress addr;
377 // convert IPv6 addresses returned by the metaserver to IPv4 addresses.377 NetAddress::parse_ip(&addr, ip, WIDELANDS_PORT);
378 // At the moment SDL_net does not support IPv6 anyways.378 assert(addr.is_valid());
379 if (!ip.compare(0, 7, "::ffff:")) {379 GameClient netgame(addr, InternetGaming::ref().get_local_clientname(), true);
380 ip = ip.substr(7);
381 log("InternetGaming: cut IPv6 address: %s\n", ip.c_str());
382 }
383
384 GameClient netgame(ip, WIDELANDS_PORT, InternetGaming::ref().get_local_clientname(), true);
385 netgame.run();380 netgame.run();
386 } else381 } else
387 throw wexception("No server selected! That should not happen!");382 throw wexception("No server selected! That should not happen!");
@@ -404,6 +399,12 @@
404 InternetGaming::ref().set_local_servername(servername_ui);399 InternetGaming::ref().set_local_servername(servername_ui);
405400
406 // Start the game401 // Start the game
407 GameHost netgame(InternetGaming::ref().get_local_clientname(), true);402 try {
408 netgame.run();403 GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
404 netgame.run();
405 } catch (...) {
406 // Log out before going back to the main menu
407 InternetGaming::ref().logout("SERVER_CRASHED");
408 throw;
409 }
409}410}
410411
=== modified file 'src/ui_fsmenu/netsetup_lan.cc'
--- src/ui_fsmenu/netsetup_lan.cc 2017-02-27 13:45:46 +0000
+++ src/ui_fsmenu/netsetup_lan.cc 2017-06-07 18:50:40 +0000
@@ -135,7 +135,7 @@
135 discovery.run();135 discovery.run();
136}136}
137137
138bool FullscreenMenuNetSetupLAN::get_host_address(uint32_t& addr, uint16_t& port) {138bool FullscreenMenuNetSetupLAN::get_host_address(NetAddress *addr) {
139 const std::string& host = hostname.text();139 const std::string& host = hostname.text();
140140
141 const uint32_t opengames_size = opengames.size();141 const uint32_t opengames_size = opengames.size();
@@ -143,20 +143,17 @@
143 const NetOpenGame& game = *opengames[i];143 const NetOpenGame& game = *opengames[i];
144144
145 if (!strcmp(game.info.hostname, host.c_str())) {145 if (!strcmp(game.info.hostname, host.c_str())) {
146 addr = game.address;146 *addr = game.address;
147 port = game.port;
148 return true;147 return true;
149 }148 }
150 }149 }
151150
152 if (hostent* const he = gethostbyname(host.c_str())) {151 // The user probably entered a hostname on his own. Try to resolve it
153 addr = (reinterpret_cast<in_addr*>(he->h_addr_list[0]))->s_addr;152 if (NetAddress::resolve_to_v6(addr, host, WIDELANDS_PORT))
154 DIAG_OFF("-Wold-style-cast")153 return true;
155 port = htons(WIDELANDS_PORT);154 if (NetAddress::resolve_to_v4(addr, host, WIDELANDS_PORT))
156 DIAG_ON("-Wold-style-cast")155 return true;
157 return true;156 return false;
158 } else
159 return false;
160}157}
161158
162const std::string& FullscreenMenuNetSetupLAN::get_playername() {159const std::string& FullscreenMenuNetSetupLAN::get_playername() {
163160
=== modified file 'src/ui_fsmenu/netsetup_lan.h'
--- src/ui_fsmenu/netsetup_lan.h 2017-05-07 20:27:21 +0000
+++ src/ui_fsmenu/netsetup_lan.h 2017-06-07 18:50:40 +0000
@@ -34,8 +34,6 @@
34struct NetOpenGame;34struct NetOpenGame;
35struct NetGameInfo;35struct NetGameInfo;
3636
37// TODO(Notabilis): Update for IPv6
38
39class FullscreenMenuNetSetupLAN : public FullscreenMenuBase {37class FullscreenMenuNetSetupLAN : public FullscreenMenuBase {
40public:38public:
41 FullscreenMenuNetSetupLAN();39 FullscreenMenuNetSetupLAN();
@@ -43,12 +41,10 @@
43 void think() override;41 void think() override;
4442
45 /**43 /**
46 * \param[out] addr filled in with the IP address of the chosen server44 * \param[out] addr filled in with the host name or IP address and port of the chosen server
47 * \param[out] port filled in with the port of the chosen server45 * \return \c True if the address is valid, \c false otherwise. In that case \c addr is not modified
48 * \return \c true if a valid server has been chosen. If \c false is
49 * returned, the values of \p addr and \p port are undefined.
50 */46 */
51 bool get_host_address(uint32_t& addr, uint16_t& port);47 bool get_host_address(NetAddress *addr);
5248
53 /**49 /**
54 * \return the name chosen by the player50 * \return the name chosen by the player
5551
=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc 2017-05-13 13:14:29 +0000
+++ src/wlapplication.cc 2017-06-07 18:50:40 +0000
@@ -350,9 +350,6 @@
350 // This might grab the input.350 // This might grab the input.
351 refresh_graphics();351 refresh_graphics();
352352
353 if (SDLNet_Init() == -1)
354 throw wexception("SDLNet_Init failed: %s\n", SDLNet_GetError());
355
356 // seed random number generator used for random tribe selection353 // seed random number generator used for random tribe selection
357 std::srand(time(nullptr));354 std::srand(time(nullptr));
358355
@@ -378,8 +375,6 @@
378 delete UI::g_fh1;375 delete UI::g_fh1;
379 UI::g_fh1 = nullptr;376 UI::g_fh1 = nullptr;
380377
381 SDLNet_Quit();
382
383 TTF_Quit(); // TODO(unknown): not here378 TTF_Quit(); // TODO(unknown): not here
384379
385 assert(g_fs);380 assert(g_fs);
@@ -1164,10 +1159,6 @@
1164 FullscreenMenuNetSetupLAN ns;1159 FullscreenMenuNetSetupLAN ns;
1165 menu_result = ns.run<FullscreenMenuBase::MenuTarget>();1160 menu_result = ns.run<FullscreenMenuBase::MenuTarget>();
1166 std::string playername = ns.get_playername();1161 std::string playername = ns.get_playername();
1167 // TODO(Notabilis): This has to be updated for IPv6
1168 uint32_t addr;
1169 uint16_t port;
1170 bool const host_address = ns.get_host_address(addr, port);
11711162
1172 switch (menu_result) {1163 switch (menu_result) {
1173 case FullscreenMenuBase::MenuTarget::kHostgame: {1164 case FullscreenMenuBase::MenuTarget::kHostgame: {
@@ -1176,18 +1167,16 @@
1176 break;1167 break;
1177 }1168 }
1178 case FullscreenMenuBase::MenuTarget::kJoingame: {1169 case FullscreenMenuBase::MenuTarget::kJoingame: {
1179 if (!host_address)1170 NetAddress addr;
1180 throw WLWarning(1171 if (!ns.get_host_address(&addr)) {
1181 "Invalid Address", "%s", "The address of the game server is invalid");1172 UI::WLMessageBox mmb(&ns, _("Invalid address"),
1173 _("The entered hostname or address is invalid and can’t be connected to."),
1174 UI::WLMessageBox::MBoxType::kOk);
1175 mmb.run<UI::Panel::Returncodes>();
1176 break;
1177 }
11821178
1183 // TODO(Notabilis): Make this prettier. I am aware that this is quite ugly but it should1179 GameClient netgame(addr, playername);
1184 // work
1185 // for now and will be removed shortly when we switch to boost.asio
1186 char ip_str[] = {"255.255.255.255"};
1187 sprintf(ip_str, "%d.%d.%d.%d", (addr & 0x000000ff), (addr & 0x0000ff00) >> 8,
1188 (addr & 0x00ff0000) >> 16, (addr & 0xff000000) >> 24);
1189 port = (port >> 8) | ((port & 0xFF) << 8);
1190 GameClient netgame(ip_str, port, playername);
1191 netgame.run();1180 netgame.run();
1192 break;1181 break;
1193 }1182 }
11941183
=== modified file 'utils/macos/build_app.sh'
--- utils/macos/build_app.sh 2016-11-30 00:18:17 +0000
+++ utils/macos/build_app.sh 2017-06-07 18:50:40 +0000
@@ -109,7 +109,6 @@
109 export SDL2IMAGEDIR="$(brew --prefix sdl2_image)"109 export SDL2IMAGEDIR="$(brew --prefix sdl2_image)"
110 export SDL2MIXERDIR="$(brew --prefix sdl2_mixer)"110 export SDL2MIXERDIR="$(brew --prefix sdl2_mixer)"
111 export SDL2TTFDIR="$(brew --prefix sdl2_ttf)"111 export SDL2TTFDIR="$(brew --prefix sdl2_ttf)"
112 export SDL2NETDIR="$(brew --prefix sdl2_net)"
113 export BOOST_ROOT="$(brew --prefix boost)"112 export BOOST_ROOT="$(brew --prefix boost)"
114 export ICU_ROOT="$(brew --prefix icu4c)"113 export ICU_ROOT="$(brew --prefix icu4c)"
115114
116115
=== modified file 'utils/win32/innosetup/Widelands.iss'
--- utils/win32/innosetup/Widelands.iss 2017-02-27 08:52:41 +0000
+++ utils/win32/innosetup/Widelands.iss 2017-06-07 18:50:40 +0000
@@ -131,7 +131,6 @@
131Source: {#DLLFolder}\SDL2.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"131Source: {#DLLFolder}\SDL2.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
132Source: {#DLLFolder}\SDL2_image.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"132Source: {#DLLFolder}\SDL2_image.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
133Source: {#DLLFolder}\libSDL2_mixer-2-0-0.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"133Source: {#DLLFolder}\libSDL2_mixer-2-0-0.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
134Source: {#DLLFolder}\SDL2_net.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
135Source: {#DLLFolder}\SDL2_ttf.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"134Source: {#DLLFolder}\SDL2_ttf.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
136Source: {#DLLFolder}\zlib1.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"135Source: {#DLLFolder}\zlib1.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
137Source: {#DLLFolder}\libFLAC-8.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"136Source: {#DLLFolder}\libFLAC-8.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"

Subscribers

People subscribed via source and target branches

to status/vote changes: