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

Proposed by Notabilis on 2017-05-20
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 on 2017-06-07
Klaus Halfmann compile, test, small review 2017-05-20 Approve on 2017-06-04
Tino Resubmit on 2017-06-01
Notabilis Resubmit on 2017-05-26
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.
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.

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.

8370. By Notabilis on 2017-05-21

Made LAN games prefer IPv6 over IPv4.

8371. By Notabilis on 2017-05-21

Addressing code review.

Notabilis (notabilis27) wrote :

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

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.

8372. By Notabilis on 2017-05-23

Merged trunk.

8373. By Notabilis on 2017-05-23

Feeble attempt to make appveyor happy / fix windows builds.

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.

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.

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.

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?

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?

8374. By Klaus Halfmann \<<email address hidden>\> on 2017-05-25

Cleanup struct/class confusion in editor_game_base.h and network.h use correct (?) ifa_broadaddr in network_lan_promotion.cc

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.

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

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)
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.

8375. By Notabilis on 2017-05-26

Merged trunk.

8376. By Notabilis on 2017-05-26

Fixed aborting broadcasting when only one protocol failed.

8377. By Notabilis on 2017-05-26

Introduced bug due to code structure before, fixing it now.

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: Resubmit
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 ....

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?

8378. By Notabilis on 2017-05-26

Added log output about network status.

8379. By Notabilis on 2017-05-26

New! Error logs now with error 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.

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.

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 :-)

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]

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)
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.

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.

8380. By Notabilis on 2017-05-28

Addressed code review.

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.

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)
8381. By Notabilis on 2017-05-28

Added some really ugly test code while trying to fix UDP multicasts for OSX.

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.

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.

8382. By Notabilis on 2017-05-28

Removed header includes to make Appveyor happy.

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.

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.

GunChleoc (gunchleoc) wrote :

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

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

GunChleoc (gunchleoc) wrote :

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

Notabilis (notabilis27) wrote :

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

8383. By Notabilis on 2017-05-31

Prettied up apple specific code and added some documentation.

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?

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.

8384. By Notabilis on 2017-06-01

Removed unneeded include of boost asio.

GunChleoc (gunchleoc) wrote :

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

8385. By Tino on 2017-06-01

avoid unnecessary includes via windows.h

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: Resubmit
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...

8386. By Notabilis on 2017-06-01

Fixed broken ifdef.

Notabilis (notabilis27) wrote :

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

8387. By Notabilis on 2017-06-03

Removed more windows includes.

8388. By Notabilis on 2017-06-03

Merged trunk.

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?

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?

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.

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.

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.

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)
8389. By Notabilis on 2017-06-04

Simple fix for crash on connection loss to metaserver.

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.

8390. By Notabilis on 2017-06-04

Fixed bug with assert(state_ == OFFLINE).

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

Klaus Halfmann (klaus-halfmann) wrote :

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

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.

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?

GunChleoc (gunchleoc) wrote :

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

GunChleoc (gunchleoc) wrote :

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

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
8391. By Notabilis on 2017-06-07

Merged trunk.

8392. By Notabilis on 2017-06-07

Addressed review.

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.

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.

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);

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.

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
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2017-02-28 08:31:53 +0000
3+++ CMakeLists.txt 2017-06-07 18:50:40 +0000
4@@ -41,6 +41,7 @@
5
6 if (WIN32)
7 set (Boost_USE_STATIC_LIBS ON)
8+ link_libraries(wsock32 ws2_32)
9 else()
10 set (Boost_USE_STATIC_LIBS OFF)
11 endif()
12@@ -50,7 +51,8 @@
13 COMPONENTS
14 unit_test_framework
15 regex
16- REQUIRED)
17+ REQUIRED
18+ system)
19
20 find_package (PythonInterp REQUIRED)
21
22@@ -60,7 +62,6 @@
23 find_package(SDL2 REQUIRED)
24 find_package(SDL2_image REQUIRED)
25 find_package(SDL2_mixer REQUIRED)
26-find_package(SDL2_net REQUIRED)
27 find_package(SDL2_ttf REQUIRED)
28 find_package(ZLIB REQUIRED)
29 find_package(ICU REQUIRED)
30@@ -181,7 +182,7 @@
31 wl_add_flag(WL_COMPILE_DIAGNOSTICS "-Werror=uninitialized")
32
33 IF (WIN32)
34- add_definitions(-DMINGW_HAS_SECURE_API)
35+ add_definitions(-DMINGW_HAS_SECURE_API -DWIN32_LEAN_AND_MEAN)
36 if (CMAKE_SIZEOF_VOID_P EQUAL 4)
37 set (CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware" CACHE STRING "Set by widelands CMakeLists.txt" FORCE)
38 message (STATUS "Enabled large address awareness on mingw32")
39
40=== modified file 'CREDITS'
41--- CREDITS 2014-10-13 15:04:50 +0000
42+++ CREDITS 2017-06-07 18:50:40 +0000
43@@ -10,7 +10,6 @@
44
45 libSDL2
46 libSDL2_mixer
47- libSDL2_net
48 libSDL2_image
49 libSDL2_ttf
50 * All files from SDL2-Project
51
52=== modified file 'appveyor.yml'
53--- appveyor.yml 2016-10-06 20:34:46 +0000
54+++ appveyor.yml 2017-06-07 18:50:40 +0000
55@@ -19,7 +19,7 @@
56 - cmd: "bash --login -c \"pacman -Su --noconfirm\""
57 - cmd: "bash --login -c \"pacman -Su --noconfirm\""
58 # Installed required libs
59- - 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\""
60+ - 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\""
61
62 shallow_clone: true
63
64
65=== removed file 'cmake/Modules/FindSDL2_net.cmake'
66--- cmake/Modules/FindSDL2_net.cmake 2014-10-13 15:04:50 +0000
67+++ cmake/Modules/FindSDL2_net.cmake 1970-01-01 00:00:00 +0000
68@@ -1,88 +0,0 @@
69-# - Locate SDL2_net library
70-# This module defines:
71-# SDL2_NET_LIBRARIES, the name of the library to link against
72-# SDL2_NET_INCLUDE_DIRS, where to find the headers
73-# SDL2_NET_FOUND, if false, do not try to link against
74-# SDL2_NET_VERSION_STRING - human-readable string containing the version of SDL2_net
75-#
76-# For backward compatiblity the following variables are also set:
77-# SDL2NET_LIBRARY (same value as SDL2_NET_LIBRARIES)
78-# SDL2NET_INCLUDE_DIR (same value as SDL2_NET_INCLUDE_DIRS)
79-# SDL2NET_FOUND (same value as SDL2_NET_FOUND)
80-#
81-# $SDL2DIR is an environment variable that would
82-# correspond to the ./configure --prefix=$SDL2DIR
83-# used in building SDL2.
84-#
85-# Created by Eric Wing. This was influenced by the FindSDL2.cmake
86-# module, but with modifications to recognize OS X frameworks and
87-# additional Unix paths (FreeBSD, etc).
88-
89-#=============================================================================
90-# Copyright 2005-2009 Kitware, Inc.
91-# Copyright 2012 Benjamin Eikel
92-#
93-# Distributed under the OSI-approved BSD License (the "License");
94-# see accompanying file Copyright.txt for details.
95-#
96-# This software is distributed WITHOUT ANY WARRANTY; without even the
97-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
98-# See the License for more information.
99-#=============================================================================
100-# (To distribute this file outside of CMake, substitute the full
101-# License text for the above reference.)
102-
103-if(NOT SDL2_NET_INCLUDE_DIR AND SDL2NET_INCLUDE_DIR)
104- set(SDL2_NET_INCLUDE_DIR ${SDL2NET_INCLUDE_DIR} CACHE PATH "directory cache
105-entry initialized from old variable name")
106-endif()
107-find_path(SDL2_NET_INCLUDE_DIR SDL_net.h
108- HINTS
109- ENV SDL2NETDIR
110- ENV SDL2DIR
111- PATH_SUFFIXES include/SDL2 include
112-)
113-
114-if(NOT SDL2_NET_LIBRARY AND SDL2NET_LIBRARY)
115- set(SDL2_NET_LIBRARY ${SDL2NET_LIBRARY} CACHE FILEPATH "file cache entry
116-initialized from old variable name")
117-endif()
118-find_library(SDL2_NET_LIBRARY
119- NAMES SDL2_net
120- HINTS
121- ENV SDL2NETDIR
122- ENV SDL2DIR
123- PATH_SUFFIXES lib
124-)
125-
126-if(SDL2_NET_INCLUDE_DIR AND EXISTS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h")
127- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+[0-9]+$")
128- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+[0-9]+$")
129- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+[0-9]+$")
130- string(REGEX REPLACE "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MAJOR "${SDL_NET_VERSION_MAJOR_LINE}")
131- string(REGEX REPLACE "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MINOR "${SDL_NET_VERSION_MINOR_LINE}")
132- string(REGEX REPLACE "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_PATCH "${SDL_NET_VERSION_PATCH_LINE}")
133- set(SDL2_NET_VERSION_STRING ${SDL_NET_VERSION_MAJOR}.${SDL_NET_VERSION_MINOR}.${SDL_NET_VERSION_PATCH})
134- unset(SDL_NET_VERSION_MAJOR_LINE)
135- unset(SDL_NET_VERSION_MINOR_LINE)
136- unset(SDL_NET_VERSION_PATCH_LINE)
137- unset(SDL_NET_VERSION_MAJOR)
138- unset(SDL_NET_VERSION_MINOR)
139- unset(SDL_NET_VERSION_PATCH)
140-endif()
141-
142-set(SDL2_NET_LIBRARIES ${SDL2_NET_LIBRARY})
143-set(SDL2_NET_INCLUDE_DIRS ${SDL2_NET_INCLUDE_DIR})
144-
145-# include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
146-
147-FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_net
148- REQUIRED_VARS SDL2_NET_LIBRARIES SDL2_NET_INCLUDE_DIRS
149- VERSION_VAR SDL2_NET_VERSION_STRING)
150-
151-# for backward compatiblity
152-set(SDL2NET_LIBRARY ${SDL2_NET_LIBRARIES})
153-set(SDL2NET_INCLUDE_DIR ${SDL2_NET_INCLUDE_DIRS})
154-set(SDL2NET_FOUND ${SDL2_NET_FOUND})
155-
156-mark_as_advanced(SDL2_NET_LIBRARY SDL2_NET_INCLUDE_DIR)
157
158=== modified file 'cmake/WlFunctions.cmake'
159--- cmake/WlFunctions.cmake 2016-02-06 16:17:23 +0000
160+++ cmake/WlFunctions.cmake 2017-06-07 18:50:40 +0000
161@@ -12,7 +12,6 @@
162 USES_SDL2
163 USES_SDL2_IMAGE
164 USES_SDL2_MIXER
165- USES_SDL2_NET
166 USES_SDL2_TTF
167 USES_ZLIB
168 USES_ICU
169@@ -127,11 +126,6 @@
170 target_link_libraries(${NAME} ${SDL2MIXER_LIBRARY})
171 endif()
172
173- if(ARG_USES_SDL2_NET)
174- wl_include_system_directories(${NAME} ${SDL2NET_INCLUDE_DIR})
175- target_link_libraries(${NAME} ${SDL2NET_LIBRARY})
176- endif()
177-
178 if(ARG_USES_SDL2_IMAGE)
179 wl_include_system_directories(${NAME} ${SDL2IMAGE_INCLUDE_DIR})
180 target_link_libraries(${NAME} ${SDL2IMAGE_LIBRARY})
181
182=== modified file 'src/network/CMakeLists.txt'
183--- src/network/CMakeLists.txt 2017-05-07 20:27:21 +0000
184+++ src/network/CMakeLists.txt 2017-06-07 18:50:40 +0000
185@@ -23,8 +23,6 @@
186 network_player_settings_backend.cc
187 network_player_settings_backend.h
188 network_protocol.h
189- network_system.h
190- USES_SDL2_NET
191 DEPENDS
192 ai
193 base_exceptions
194
195=== modified file 'src/network/gameclient.cc'
196--- src/network/gameclient.cc 2017-05-11 10:45:44 +0000
197+++ src/network/gameclient.cc 2017-06-07 18:50:40 +0000
198@@ -42,7 +42,6 @@
199 #include "network/internet_gaming.h"
200 #include "network/network_gaming_messages.h"
201 #include "network/network_protocol.h"
202-#include "network/network_system.h"
203 #include "scripting/lua_interface.h"
204 #include "scripting/lua_table.h"
205 #include "ui_basic/messagebox.h"
206@@ -89,17 +88,13 @@
207 std::vector<ChatMessage> chatmessages;
208 };
209
210-GameClient::GameClient(const std::string& host,
211- const uint16_t port,
212- const std::string& playername,
213- bool internet)
214+GameClient::GameClient(const NetAddress& host, const std::string& playername, bool internet)
215 : d(new GameClientImpl), internet_(internet) {
216- d->net = NetClient::connect(host, port);
217+ d->net = NetClient::connect(host);
218 if (!d->net || !d->net->is_connected()) {
219 throw WLWarning(_("Could not establish connection to host"),
220- _("Widelands could not establish a connection to the given "
221- "address.\n"
222- "Either no Widelands server was running at the supposed port or\n"
223+ _("Widelands could not establish a connection to the given address. "
224+ "Either no Widelands server was running at the supposed port or "
225 "the server shut down as you tried to connect."));
226 }
227
228
229=== modified file 'src/network/gameclient.h'
230--- src/network/gameclient.h 2017-05-11 10:45:44 +0000
231+++ src/network/gameclient.h 2017-06-07 18:50:40 +0000
232@@ -36,13 +36,11 @@
233 * launch, as well as dealing with the actual network protocol.
234 */
235 struct GameClient : public GameController,
236- public GameSettingsProvider,
237- private SyncCallback,
238- public ChatProvider {
239- GameClient(const std::string& host,
240- const uint16_t port,
241- const std::string& playername,
242- bool internet = false);
243+ public GameSettingsProvider,
244+ private SyncCallback,
245+ public ChatProvider {
246+ GameClient(const NetAddress& host, const std::string& playername, bool internet = false);
247+
248 virtual ~GameClient();
249
250 void run();
251
252=== modified file 'src/network/gamehost.cc'
253--- src/network/gamehost.cc 2017-05-11 10:45:44 +0000
254+++ src/network/gamehost.cc 2017-06-07 18:50:40 +0000
255@@ -54,7 +54,6 @@
256 #include "network/network_lan_promotion.h"
257 #include "network/network_player_settings_backend.h"
258 #include "network/network_protocol.h"
259-#include "network/network_system.h"
260 #include "profile/profile.h"
261 #include "scripting/lua_interface.h"
262 #include "ui_basic/progresswindow.h"
263
264=== modified file 'src/network/internet_gaming.cc'
265--- src/network/internet_gaming.cc 2017-05-16 18:29:06 +0000
266+++ src/network/internet_gaming.cc 2017-06-07 18:50:40 +0000
267@@ -95,7 +95,10 @@
268 void InternetGaming::initialize_connection() {
269 // First of all try to connect to the metaserver
270 log("InternetGaming: Connecting to the metaserver.\n");
271- net = NetClient::connect(meta_, port_);
272+ NetAddress addr;
273+ net.reset();
274+ if (NetAddress::resolve_to_v4(&addr, meta_, port_))
275+ net = NetClient::connect(addr);
276 if (!net || !net->is_connected())
277 throw WLWarning(_("Could not establish connection to host"),
278 _("Widelands could not establish a connection to the given address.\n"
279@@ -216,10 +219,12 @@
280 void InternetGaming::logout(const std::string& msgcode) {
281
282 // Just in case the metaserver is listening on the socket - tell him we break up with him ;)
283- SendPacket s;
284- s.string(IGPCMD_DISCONNECT);
285- s.string(msgcode);
286- net->send(s);
287+ if (net && net->is_connected()) {
288+ SendPacket s;
289+ s.string(IGPCMD_DISCONNECT);
290+ s.string(msgcode);
291+ net->send(s);
292+ }
293
294 const std::string& msg = InternetGamingMessages::get_message(msgcode);
295 log("InternetGaming: logout(%s)\n", msg.c_str());
296
297=== modified file 'src/network/internet_gaming.h'
298--- src/network/internet_gaming.h 2017-05-09 19:17:12 +0000
299+++ src/network/internet_gaming.h 2017-06-07 18:50:40 +0000
300@@ -24,11 +24,6 @@
301 #include <string>
302 #include <vector>
303
304-#ifdef _WIN32
305-#include <io.h>
306-#include <winsock2.h>
307-#endif
308-
309 #include "build_info.h"
310 #include "chat/chat.h"
311 #include "network/internet_gaming_protocol.h"
312@@ -170,7 +165,7 @@
313 std::string pwd_;
314 bool reg_;
315 std::string meta_;
316- uint32_t port_;
317+ uint16_t port_;
318
319 /// local clients name and rights
320 std::string clientname_;
321
322=== modified file 'src/network/netclient.cc'
323--- src/network/netclient.cc 2017-05-11 10:45:44 +0000
324+++ src/network/netclient.cc 2017-06-07 18:50:40 +0000
325@@ -4,8 +4,9 @@
326
327 #include "base/log.h"
328
329-std::unique_ptr<NetClient> NetClient::connect(const std::string& ip_address, const uint16_t port) {
330- std::unique_ptr<NetClient> ptr(new NetClient(ip_address, port));
331+std::unique_ptr<NetClient> NetClient::connect(const NetAddress& host) {
332+
333+ std::unique_ptr<NetClient> ptr(new NetClient(host));
334 if (ptr->is_connected()) {
335 return ptr;
336 } else {
337@@ -17,63 +18,82 @@
338 NetClient::~NetClient() {
339 if (is_connected())
340 close();
341- if (sockset_ != nullptr)
342- SDLNet_FreeSocketSet(sockset_);
343 }
344
345 bool NetClient::is_connected() const {
346- return sock_ != nullptr;
347+ return socket_.is_open();
348 }
349
350 void NetClient::close() {
351 if (!is_connected())
352 return;
353- SDLNet_TCP_DelSocket(sockset_, sock_);
354- SDLNet_TCP_Close(sock_);
355- sock_ = nullptr;
356+ boost::system::error_code ec;
357+ boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec);
358+ if (!ec) {
359+ log("[NetClient] Closing network socket connected to %s:%i.\n",
360+ remote.address().to_string().c_str(), remote.port());
361+ } else {
362+ log("[NetClient] Closing network socket.\n");
363+ }
364+ socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
365+ socket_.close(ec);
366 }
367
368 bool NetClient::try_receive(RecvPacket* packet) {
369 if (!is_connected())
370 return false;
371
372- uint8_t buffer[512];
373- while (SDLNet_CheckSockets(sockset_, 0) > 0) {
374-
375- const int32_t bytes = SDLNet_TCP_Recv(sock_, buffer, sizeof(buffer));
376- if (bytes <= 0) {
377- // Error while receiving
378- close();
379- return false;
380- }
381-
382- deserializer_.read_data(buffer, bytes);
383+ uint8_t buffer[kNetworkBufferSize];
384+ boost::system::error_code ec;
385+ size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
386+ if (!ec) {
387+ assert(length > 0);
388+ assert(length <= kNetworkBufferSize);
389+ // Has read something
390+ deserializer_.read_data(buffer, length);
391+ }
392+
393+ if (ec && ec != boost::asio::error::would_block) {
394+ // Connection closed or some error, close the socket
395+ log("[NetClient] Error when trying to receive some data: %s.\n", ec.message().c_str());
396+ close();
397+ return false;
398 }
399 // Get one packet from the deserializer
400 return deserializer_.write_packet(packet);
401 }
402
403 void NetClient::send(const SendPacket& packet) {
404- if (is_connected()) {
405- SDLNet_TCP_Send(sock_, packet.get_data(), packet.get_size());
406- }
407-}
408-
409-NetClient::NetClient(const std::string& ip_address, const uint16_t port)
410- : sock_(nullptr), sockset_(nullptr), deserializer_() {
411-
412- IPaddress addr;
413- if (SDLNet_ResolveHost(&addr, ip_address.c_str(), port) != 0) {
414- log("[Client]: Failed to resolve host address %s:%u.\n", ip_address.c_str(), port);
415+ if (!is_connected())
416 return;
417+
418+ boost::system::error_code ec;
419+ size_t written = boost::asio::write(socket_,
420+ boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
421+ // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
422+ // If it doesn't, set the socket to blocking before writing
423+ // If it does, remove this comment after build 20
424+ assert(ec != boost::asio::error::would_block);
425+ assert(written == packet.get_size() || ec);
426+ if (ec) {
427+ log("[NetClient] Error when trying to send some data: %s.\n", ec.message().c_str());
428+ close();
429 }
430- log("[Client]: Trying to connect to %s:%u ... ", ip_address.c_str(), port);
431- sock_ = SDLNet_TCP_Open(&addr);
432- if (is_connected()) {
433- log("success\n");
434- sockset_ = SDLNet_AllocSocketSet(1);
435- SDLNet_TCP_AddSocket(sockset_, sock_);
436+}
437+
438+NetClient::NetClient(const NetAddress& host)
439+ : io_service_(), socket_(io_service_), deserializer_() {
440+
441+ assert(host.is_valid());
442+ const boost::asio::ip::tcp::endpoint destination(host.ip, host.port);
443+
444+ log("[NetClient]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port);
445+ boost::system::error_code ec;
446+ socket_.connect(destination, ec);
447+ if (!ec && is_connected()) {
448+ log("success.\n");
449+ socket_.non_blocking(true);
450 } else {
451- log("failed\n");
452+ log("failed.\n");
453 }
454 }
455
456=== modified file 'src/network/netclient.h'
457--- src/network/netclient.h 2017-05-11 10:45:44 +0000
458+++ src/network/netclient.h 2017-06-07 18:50:40 +0000
459@@ -22,23 +22,23 @@
460
461 #include <memory>
462
463-#include <SDL_net.h>
464-
465 #include "network/network.h"
466
467 /**
468 * NetClient manages the network connection for a network game in which this computer
469 * participates as a client.
470+ * This class only tries to create a single socket, either for IPv4 and IPv6.
471+ * Which is used depends on what kind of address is given on call to connect().
472 */
473 class NetClient {
474 public:
475+
476 /**
477 * Tries to establish a connection to the given host.
478- * @param ip_address A hostname or an IPv4 address as string.
479- * @param port The port to connect to.
480- * @return A pointer to a connected \c NetClient object or a nullptr if the connection failed.
481+ * \param host The host to connect to.
482+ * \return A pointer to a connected \c NetClient object or a \c nullptr if the connection failed.
483 */
484- static std::unique_ptr<NetClient> connect(const std::string& ip_address, const uint16_t port);
485+ static std::unique_ptr<NetClient> connect(const NetAddress& host);
486
487 /**
488 * Closes the connection.
489@@ -48,7 +48,7 @@
490
491 /**
492 * Returns whether the client is connected.
493- * @return \c true if the connection is open, \c false otherwise.
494+ * \return \c true if the connection is open, \c false otherwise.
495 */
496 bool is_connected() const;
497
498@@ -60,30 +60,35 @@
499
500 /**
501 * Tries to receive a packet.
502- * @param packet A packet that should be overwritten with the received data.
503- * @return \c true if a packet is available, \c false otherwise.
504+ * \param packet A packet that should be overwritten with the received data.
505+ * \return \c true if a packet is available, \c false otherwise.
506 * The given packet is only modified when \c true is returned.
507 * Calling this on a closed connection will return false.
508 */
509- bool try_receive(RecvPacket* packet);
510+ bool try_receive(RecvPacket *packet);
511
512 /**
513 * Sends a packet.
514 * Calling this on a closed connection will silently fail.
515- * @param packet The packet to send.
516+ * \param packet The packet to send.
517 */
518- void send(const SendPacket& packet);
519+ void send(const SendPacket& packet);
520
521 private:
522- NetClient(const std::string& ip_address, const uint16_t port);
523-
524- /// The socket that connects us to the host
525- TCPsocket sock_;
526-
527- /// Socket set used for selection
528- SDLNet_SocketSet sockset_;
529-
530- /// Deserializer acts as a buffer for packets (reassembly/splitting up)
531+ /**
532+ * Tries to establish a connection to the given host.
533+ * If the connection attempt failed, is_connected() will return \c false.
534+ * \param host The host to connect to.
535+ */
536+ NetClient(const NetAddress& host);
537+
538+ /// An io_service needed by boost.asio. Primarily needed for asynchronous operations.
539+ boost::asio::io_service io_service_;
540+
541+ /// The socket that connects us to the host.
542+ boost::asio::ip::tcp::socket socket_;
543+
544+ /// Deserializer acts as a buffer for packets (splitting stream to packets)
545 Deserializer deserializer_;
546 };
547
548
549=== modified file 'src/network/nethost.cc'
550--- src/network/nethost.cc 2017-05-11 10:45:44 +0000
551+++ src/network/nethost.cc 2017-06-07 18:50:40 +0000
552@@ -4,7 +4,25 @@
553
554 #include "base/log.h"
555
556-NetHost::Client::Client(TCPsocket sock) : socket(sock), deserializer() {
557+namespace {
558+
559+ /**
560+ * Returns the IP version.
561+ * \param acceptor The acceptor socket to get the IP version for.
562+ * \return Either 4 or 6, depending on the version of the given acceptor.
563+ */
564+ int get_ip_version(const boost::asio::ip::tcp::acceptor& acceptor) {
565+ assert(acceptor.is_open());
566+ if (acceptor.local_endpoint().protocol() == boost::asio::ip::tcp::v4()) {
567+ return 4;
568+ } else {
569+ return 6;
570+ }
571+ }
572+}
573+
574+
575+NetHost::Client::Client(boost::asio::ip::tcp::socket&& sock) : socket(std::move(sock)), deserializer() {
576 }
577
578 std::unique_ptr<NetHost> NetHost::listen(const uint16_t port) {
579@@ -22,11 +40,10 @@
580 while (!clients_.empty()) {
581 close(clients_.begin()->first);
582 }
583- SDLNet_FreeSocketSet(sockset_);
584 }
585
586 bool NetHost::is_listening() const {
587- return svrsock_ != nullptr;
588+ return acceptor_v4_.is_open() || acceptor_v6_.is_open();
589 }
590
591 bool NetHost::is_connected(const ConnectionId id) const {
592@@ -34,11 +51,17 @@
593 }
594
595 void NetHost::stop_listening() {
596- if (!is_listening())
597- return;
598- SDLNet_TCP_DelSocket(sockset_, svrsock_);
599- SDLNet_TCP_Close(svrsock_);
600- svrsock_ = nullptr;
601+ static const auto do_stop = [](boost::asio::ip::tcp::acceptor& acceptor) {
602+ boost::system::error_code ec;
603+ if (acceptor.is_open()) {
604+ log("[NetHost]: Closing a listening IPv%d socket.\n", get_ip_version(acceptor));
605+ acceptor.close(ec);
606+ }
607+ // Ignore errors
608+ };
609+
610+ do_stop(acceptor_v4_);
611+ do_stop(acceptor_v6_);
612 }
613
614 void NetHost::close(const ConnectionId id) {
615@@ -47,68 +70,145 @@
616 // Not connected anyway
617 return;
618 }
619- SDLNet_TCP_DelSocket(sockset_, iter_client->second.socket);
620- SDLNet_TCP_Close(iter_client->second.socket);
621+ boost::system::error_code ec;
622+ boost::asio::ip::tcp::endpoint remote = iter_client->second.socket.remote_endpoint(ec);
623+ if (!ec) {
624+ log("[NetHost] Closing network connection to %s:%i.\n",
625+ remote.address().to_string().c_str(), remote.port());
626+ } else {
627+ log("[NetHost] Closing network connection to some client.\n");
628+ }
629+ iter_client->second.socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
630+ iter_client->second.socket.close(ec);
631 clients_.erase(iter_client);
632 }
633
634 bool NetHost::try_accept(ConnectionId* new_id) {
635 if (!is_listening())
636 return false;
637-
638- TCPsocket sock = SDLNet_TCP_Accept(svrsock_);
639- // No client wants to connect
640- if (sock == nullptr)
641+ boost::asio::ip::tcp::socket socket(io_service_);
642+
643+ const auto do_try_accept = [&socket](boost::asio::ip::tcp::acceptor& acceptor) {
644+ boost::system::error_code ec;
645+ if (acceptor.is_open()) {
646+ acceptor.accept(socket, ec);
647+ if (ec == boost::asio::error::would_block) {
648+ // No client wants to connect
649+ // New socket doesn't need to be closed since it isn't open yet
650+ } else if (ec) {
651+ // Some other error, close the acceptor
652+ log("[NetHost] No longer listening for IPv%d connections due to error: %s.\n",
653+ get_ip_version(acceptor), ec.message().c_str());
654+ acceptor.close(ec);
655+ } else {
656+ log("[NetHost]: Accepting IPv%d connection from %s.\n",
657+ get_ip_version(acceptor), socket.remote_endpoint().address().to_string().c_str());
658+ }
659+ }
660+ };
661+
662+ do_try_accept(acceptor_v4_);
663+ if (!socket.is_open())
664+ do_try_accept(acceptor_v6_);
665+
666+ if (!socket.is_open()) {
667+ // No new connection
668 return false;
669- SDLNet_TCP_AddSocket(sockset_, sock);
670+ }
671+
672+ socket.non_blocking(true);
673+
674 ConnectionId id = next_id_++;
675 assert(id > 0);
676 assert(clients_.count(id) == 0);
677- clients_.insert(std::make_pair(id, Client(sock)));
678+ clients_.insert(std::make_pair(id, Client(std::move(socket))));
679 assert(clients_.count(id) == 1);
680 *new_id = id;
681 return true;
682 }
683
684 bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) {
685-
686 // Always read all available data into buffers
687- uint8_t buffer[512];
688- while (SDLNet_CheckSockets(sockset_, 0) > 0) {
689- for (auto& e : clients_) {
690- if (SDLNet_SocketReady(e.second.socket)) {
691- const int32_t bytes = SDLNet_TCP_Recv(e.second.socket, buffer, sizeof(buffer));
692- if (bytes <= 0) {
693- // Error while receiving
694- close(e.first);
695- // We have to run the for-loop again since we modified the map
696- break;
697- }
698+ uint8_t buffer[kNetworkBufferSize];
699
700- e.second.deserializer.read_data(buffer, bytes);
701- }
702+ boost::system::error_code ec;
703+ for (auto it = clients_.begin(); it != clients_.end(); ) {
704+ size_t length = it->second.socket.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
705+ if (ec == boost::asio::error::would_block) {
706+ // Nothing to read
707+ assert(length == 0);
708+ ++it;
709+ continue;
710+ } else if (ec) {
711+ assert(length == 0);
712+ // Connection closed or some error, close the socket
713+ log("[NetHost] Error when receiving from a client, closing connection: %s.\n",
714+ ec.message().c_str());
715+ // close() will remove the client from the map so we have to increment the iterator first.
716+ // Otherwise, it will point to unallocated memory after close() so we can't increase it
717+ ConnectionId id_to_remove = it->first;
718+ ++it;
719+ close(id_to_remove);
720+ continue;
721 }
722+ assert(length > 0);
723+ assert(length <= kNetworkBufferSize);
724+ // Read something
725+ it->second.deserializer.read_data(buffer, length);
726+ ++it;
727 }
728
729 // Now check whether there is data for the requested client
730 if (!is_connected(id))
731 return false;
732
733- // Get one packet from the deserializer
734+ // Try to get one packet from the deserializer
735 return clients_.at(id).deserializer.write_packet(packet);
736 }
737
738 void NetHost::send(const ConnectionId id, const SendPacket& packet) {
739+ boost::system::error_code ec;
740 if (is_connected(id)) {
741- SDLNet_TCP_Send(clients_.at(id).socket, packet.get_data(), packet.get_size());
742- }
743-}
744-
745-NetHost::NetHost(const uint16_t port) : svrsock_(nullptr), sockset_(nullptr), next_id_(1) {
746-
747- IPaddress myaddr;
748- SDLNet_ResolveHost(&myaddr, nullptr, port);
749- svrsock_ = SDLNet_TCP_Open(&myaddr);
750- // Maximal 16 sockets! This mean we can have at most 15 clients_ in our game (+ metaserver)
751- sockset_ = SDLNet_AllocSocketSet(16);
752+ size_t written = boost::asio::write(clients_.at(id).socket,
753+ boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
754+ // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
755+ // If it doesn't, set the socket to blocking before writing
756+ // If it does, remove this comment after build 20
757+ assert(ec != boost::asio::error::would_block);
758+ assert(written == packet.get_size() || ec);
759+ if (ec) {
760+ log("[NetHost] Error when sending to a client, closing connection: %s.\n", ec.message().c_str());
761+ close(id);
762+ }
763+ }
764+}
765+
766+NetHost::NetHost(const uint16_t port)
767+ : clients_(), next_id_(1), io_service_(), acceptor_v4_(io_service_), acceptor_v6_(io_service_) {
768+
769+ if (open_acceptor(&acceptor_v4_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))) {
770+ log("[NetHost]: Opening a listening IPv4 socket on TCP port %u\n", port);
771+ }
772+ if (open_acceptor(&acceptor_v6_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), port))) {
773+ log("[NetHost]: Opening a listening IPv6 socket on TCP port %u\n", port);
774+ }
775+}
776+
777+bool NetHost::open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
778+ const boost::asio::ip::tcp::endpoint& endpoint) {
779+ try {
780+ acceptor->open(endpoint.protocol());
781+ acceptor->non_blocking(true);
782+ const boost::asio::socket_base::reuse_address option_reuse(true);
783+ acceptor->set_option(option_reuse);
784+ if (endpoint.protocol() == boost::asio::ip::tcp::v6()) {
785+ const boost::asio::ip::v6_only option_v6only(true);
786+ acceptor->set_option(option_v6only);
787+ }
788+ acceptor->bind(endpoint);
789+ acceptor->listen(boost::asio::socket_base::max_connections);
790+ return true;
791+ } catch (const boost::system::system_error&) {
792+ return false;
793+ }
794 }
795
796=== modified file 'src/network/nethost.h'
797--- src/network/nethost.h 2017-05-11 10:45:44 +0000
798+++ src/network/nethost.h 2017-06-07 18:50:40 +0000
799@@ -23,22 +23,22 @@
800 #include <map>
801 #include <memory>
802
803-#include <SDL_net.h>
804-
805 #include "network/network.h"
806
807 /**
808 * NetHost manages the client connections of a network game in which this computer
809 * participates as a server.
810+ * This class tries to create sockets for IPv4 and IPv6.
811 */
812 class NetHost {
813 public:
814+ /// IDs used to enumerate the clients.
815 using ConnectionId = uint32_t;
816
817 /**
818 * Tries to listen on the given port.
819- * @param port The port to listen on.
820- * @return A pointer to a listening \c NetHost object or a nullptr if the connection failed.
821+ * \param port The port to listen on.
822+ * \return A pointer to a listening \c NetHost object or a nullptr if the connection failed.
823 */
824 static std::unique_ptr<NetHost> listen(const uint16_t port);
825
826@@ -49,14 +49,14 @@
827
828 /**
829 * Returns whether the server is started and is listening.
830- * @return \c true if the server is listening, \c false otherwise.
831+ * \return \c true if the server is listening, \c false otherwise.
832 */
833 bool is_listening() const;
834
835 /**
836 * Returns whether the given client is connected.
837- * @param The id of the client to check.
838- * @return \c true if the connection is open, \c false otherwise.
839+ * \param The id of the client to check.
840+ * \return \c true if the connection is open, \c false otherwise.
841 */
842 bool is_connected(ConnectionId id) const;
843
844@@ -67,14 +67,14 @@
845
846 /**
847 * Closes the connection to the given client.
848- * @param id The id of the client to close the connection to.
849+ * \param id The id of the client to close the connection to.
850 */
851 void close(ConnectionId id);
852
853 /**
854 * Tries to accept a new client.
855- * @param new_id The connection id of the new client will be stored here.
856- * @return \c true if a client has connected, \c false otherwise.
857+ * \param new_id The connection id of the new client will be stored here.
858+ * \return \c true if a client has connected, \c false otherwise.
859 * The given id is only modified when \c true is returned.
860 * Calling this on a closed server will return false.
861 * The returned id is always greater than 0.
862@@ -83,9 +83,9 @@
863
864 /**
865 * Tries to receive a packet.
866- * @param id The connection id of the client that should be received.
867- * @param packet A packet that should be overwritten with the received data.
868- * @return \c true if a packet is available, \c false otherwise.
869+ * \param id The connection id of the client that should be received.
870+ * \param packet A packet that should be overwritten with the received data.
871+ * \return \c true if a packet is available, \c false otherwise.
872 * The given packet is only modified when \c true is returned.
873 * Calling this on a closed connection will return false.
874 */
875@@ -94,26 +94,50 @@
876 /**
877 * Sends a packet.
878 * Calling this on a closed connection will silently fail.
879- * @param id The connection id of the client that should be sent to.
880- * @param packet The packet to send.
881+ * \param id The connection id of the client that should be sent to.
882+ * \param packet The packet to send.
883 */
884 void send(ConnectionId id, const SendPacket& packet);
885
886 private:
887+ /**
888+ * Tries to listen on the given port.
889+ * If it fails, is_listening() will return \c false.
890+ * \param port The port to listen on.
891+ */
892 NetHost(const uint16_t port);
893
894- class Client {
895- public:
896- Client(TCPsocket sock);
897-
898- TCPsocket socket;
899+ bool open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
900+ const boost::asio::ip::tcp::endpoint& endpoint);
901+
902+ /**
903+ * Helper structure to store variables about a connected client.
904+ */
905+ struct Client {
906+ /**
907+ * Initializes the structure with the given socket.
908+ * \param sock The socket to listen on. The socket is moved by this
909+ * constructor so the given socket is no longer valid.
910+ */
911+ Client(boost::asio::ip::tcp::socket&& sock);
912+
913+ /// The socket to send/receive with.
914+ boost::asio::ip::tcp::socket socket;
915+ /// The deserializer to feed the received data to. It will transform it into data packets.
916 Deserializer deserializer;
917 };
918
919- TCPsocket svrsock_;
920- SDLNet_SocketSet sockset_;
921+ /// A map linking client ids to the respective data about the clients.
922+ /// Client ids not in this map should be considered invalid.
923 std::map<NetHost::ConnectionId, Client> clients_;
924+ /// The next client id that will be used
925 NetHost::ConnectionId next_id_;
926+ /// An io_service needed by boost.asio. Primary needed for async operations.
927+ boost::asio::io_service io_service_;
928+ /// The acceptor we get IPv4 connection requests to.
929+ boost::asio::ip::tcp::acceptor acceptor_v4_;
930+ /// The acceptor we get IPv6 connection requests to.
931+ boost::asio::ip::tcp::acceptor acceptor_v6_;
932 };
933
934 #endif // end of include guard: WL_NETWORK_NETHOST_H
935
936=== modified file 'src/network/network.cc'
937--- src/network/network.cc 2017-05-14 14:40:24 +0000
938+++ src/network/network.cc 2017-06-07 18:50:40 +0000
939@@ -19,8 +19,62 @@
940
941 #include "network/network.h"
942
943+#include <SDL.h>
944+
945 #include "base/log.h"
946
947+
948+namespace {
949+
950+bool do_resolve(const boost::asio::ip::tcp& protocol, NetAddress *addr, const std::string& hostname, uint16_t port) {
951+ assert(addr != nullptr);
952+ try {
953+ boost::asio::io_service io_service;
954+ boost::asio::ip::tcp::resolver resolver(io_service);
955+ boost::asio::ip::tcp::resolver::query query(protocol, hostname, boost::lexical_cast<std::string>(port));
956+ boost::asio::ip::tcp::resolver::iterator iter = resolver.resolve(query);
957+ if (iter == boost::asio::ip::tcp::resolver::iterator()) {
958+ // Resolution failed
959+ return false;
960+ }
961+ addr->ip = iter->endpoint().address();
962+ addr->port = port;
963+ return true;
964+ } catch (const boost::system::system_error& ec) {
965+ // Resolution failed
966+ log("Could not resolve network name: %s", ec.what());
967+ return false;
968+ }
969+}
970+
971+}
972+
973+bool NetAddress::resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port) {
974+ return do_resolve(boost::asio::ip::tcp::v4(), addr, hostname, port);
975+}
976+
977+bool NetAddress::resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port) {
978+ return do_resolve(boost::asio::ip::tcp::v6(), addr, hostname, port);
979+}
980+
981+bool NetAddress::parse_ip(NetAddress *addr, const std::string& ip, uint16_t port) {
982+ boost::system::error_code ec;
983+ boost::asio::ip::address new_addr = boost::asio::ip::address::from_string(ip, ec);
984+ if (ec)
985+ return false;
986+ addr->ip = new_addr;
987+ addr->port = port;
988+ return true;
989+}
990+
991+bool NetAddress::is_ipv6() const {
992+ return ip.is_v6();
993+}
994+
995+bool NetAddress::is_valid() const {
996+ return port != 0 && !ip.is_unspecified();
997+}
998+
999 CmdNetCheckSync::CmdNetCheckSync(uint32_t const dt, SyncCallback* const cb)
1000 : Command(dt), callback_(cb) {
1001 }
1002@@ -184,7 +238,7 @@
1003 }
1004
1005 DisconnectException::DisconnectException(const char* fmt, ...) {
1006- char buffer[512];
1007+ char buffer[kNetworkBufferSize];
1008 {
1009 va_list va;
1010 va_start(va, fmt);
1011
1012=== modified file 'src/network/network.h'
1013--- src/network/network.h 2017-05-23 21:07:47 +0000
1014+++ src/network/network.h 2017-06-07 18:50:40 +0000
1015@@ -24,7 +24,7 @@
1016 #include <string>
1017 #include <vector>
1018
1019-#include <SDL_net.h>
1020+#include <boost/asio.hpp>
1021 #include <boost/lexical_cast.hpp>
1022
1023 #include "base/wexception.h"
1024@@ -36,6 +36,60 @@
1025 class Deserializer;
1026 class FileRead;
1027
1028+constexpr size_t kNetworkBufferSize = 512;
1029+
1030+/**
1031+ * Simple structure to hold the IP address and port of a server.
1032+ * This structure must not contain a hostname but only IP addresses.
1033+ */
1034+struct NetAddress {
1035+ /**
1036+ * Tries to resolve the given hostname to an IPv4 address.
1037+ * \param[out] addr A NetAddress structure to write the result to,
1038+ * if resolution succeeds.
1039+ * \param hostname The name of the host.
1040+ * \param port The port on the host.
1041+ * \return \c True if the resolution succeeded, \c false otherwise.
1042+ */
1043+ static bool resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port);
1044+
1045+ /**
1046+ * Tries to resolve the given hostname to an IPv6 address.
1047+ * \param[out] addr A NetAddress structure to write the result to,
1048+ * if resolution succeeds.
1049+ * \param hostname The name of the host.
1050+ * \param port The port on the host.
1051+ * \return \c True if the resolution succeeded, \c false otherwise.
1052+ */
1053+ static bool resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port);
1054+
1055+ /**
1056+ * Parses the given string to an IP address.
1057+ * \param[out] addr A NetAddress structure to write the result to,
1058+ * if parsing succeeds.
1059+ * \param ip An IP address as string.
1060+ * \param port The port on the host.
1061+ * \return \c True if the parsing succeeded, \c false otherwise.
1062+ */
1063+ static bool parse_ip(NetAddress *addr, const std::string& ip, uint16_t port);
1064+
1065+ /**
1066+ * Returns whether the stored IP is in IPv6 format.
1067+ * @return \c true if the stored IP is in IPv6 format, \c false otherwise.
1068+ * If it isn't an IPv6 address, it is an IPv4 address.
1069+ */
1070+ bool is_ipv6() const;
1071+
1072+ /**
1073+ * Returns whether valid IP address and port are stored.
1074+ * @return \c true if valid, \c false otherwise.
1075+ */
1076+ bool is_valid() const;
1077+
1078+ boost::asio::ip::address ip;
1079+ uint16_t port;
1080+};
1081+
1082 struct SyncCallback {
1083 virtual ~SyncCallback() {
1084 }
1085
1086=== modified file 'src/network/network_lan_promotion.cc'
1087--- src/network/network_lan_promotion.cc 2017-01-25 18:55:59 +0000
1088+++ src/network/network_lan_promotion.cc 2017-06-07 18:50:40 +0000
1089@@ -19,116 +19,349 @@
1090
1091 #include "network/network_lan_promotion.h"
1092
1093-#include <cstdio>
1094-#include <cstring>
1095+#ifndef _WIN32
1096+#include <ifaddrs.h>
1097+#endif
1098
1099+#include "base/i18n.h"
1100 #include "base/log.h"
1101-#include "base/macros.h"
1102+#include "base/warning.h"
1103 #include "build_info.h"
1104 #include "network/constants.h"
1105
1106+namespace {
1107+
1108+ /**
1109+ * Returns the IP version.
1110+ * \param addr The address object to get the IP version for.
1111+ * \return Either 4 or 6, depending on the version of the given address.
1112+ */
1113+ int get_ip_version(const boost::asio::ip::address& addr) {
1114+ assert(!addr.is_unspecified());
1115+ if (addr.is_v4()) {
1116+ return 4;
1117+ } else {
1118+ assert(addr.is_v6());
1119+ return 6;
1120+ }
1121+ }
1122+
1123+ /**
1124+ * Returns the IP version.
1125+ * \param version A whatever object to get the IP version for.
1126+ * \return Either 4 or 6, depending on the version of the given address.
1127+ */
1128+ int get_ip_version(const boost::asio::ip::udp& version) {
1129+ if (version == boost::asio::ip::udp::v4()) {
1130+ return 4;
1131+ } else {
1132+ assert(version == boost::asio::ip::udp::v6());
1133+ return 6;
1134+ }
1135+ }
1136+}
1137+
1138 /*** class LanBase ***/
1139-
1140-LanBase::LanBase() {
1141-
1142- sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); // open the socket
1143-
1144- int32_t opt = 1;
1145- // the cast to char* is because microsoft wants it that way
1146- setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char*>(&opt), sizeof(opt));
1147+/**
1148+ * \internal
1149+ * In an ideal world, we would use the same code with boost asio for all three operating systems.
1150+ * Unfortunately, it isn't that easy and we need some platform specific code.
1151+ * For IPv4, windows needs a special case: For Linux and Apple we have to iterate over all assigned IPv4
1152+ * addresses (e.g. 192.168.1.68), transform them to broadcast addresses (e.g. 192.168.1.255) and send our
1153+ * packets to those addresses. For windows, we simply can send to 255.255.255.255.
1154+ * For IPv6, Apple requires special handling. On the other two operating systems we can send to the multicast
1155+ * address ff02::1 (kind of a local broadcast) without specifying over which interface we want to send.
1156+ * On Apple we have to specify the interface, forcing us to send our message over all interfaces we can find.
1157+ */
1158+LanBase::LanBase(uint16_t port)
1159+ : io_service(), socket_v4(io_service), socket_v6(io_service) {
1160
1161 #ifndef _WIN32
1162-
1163- // get a list of all local broadcast addresses
1164- struct if_nameindex* ifnames = if_nameindex();
1165- struct ifreq ifr;
1166-
1167- for (int32_t i = 0; ifnames[i].if_index; ++i) {
1168- strncpy(ifr.ifr_name, ifnames[i].if_name, IFNAMSIZ);
1169-
1170- DIAG_OFF("-Wold-style-cast")
1171- if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0)
1172- continue;
1173-
1174- if (!(ifr.ifr_flags & IFF_BROADCAST))
1175- continue;
1176-
1177- if (ioctl(sock, SIOCGIFBRDADDR, &ifr) < 0)
1178- continue;
1179- DIAG_ON("-Wold-style-cast")
1180-
1181- broadcast_addresses.push_back(
1182- reinterpret_cast<sockaddr_in*>(&ifr.ifr_broadaddr)->sin_addr.s_addr);
1183- }
1184-
1185- if_freenameindex(ifnames);
1186+ // Iterate over all interfaces. If they support IPv4, store the broadcast-address
1187+ // of the interface and try to start the socket. If they support IPv6, just start
1188+ // the socket. There is one fixed broadcast-address for IPv6 (well, actually multicast)
1189+
1190+ // Adapted example out of "man getifaddrs"
1191+ // TODO(Notabilis): I don't like this part. But boost is not able to iterate over
1192+ // the local IPs and interfaces at this time. If they ever add it, replace this code
1193+ struct ifaddrs *ifaddr, *ifa;
1194+ int s, n;
1195+ char host[NI_MAXHOST];
1196+ if (getifaddrs(&ifaddr) == -1) {
1197+ perror("getifaddrs");
1198+ exit(EXIT_FAILURE);
1199+ }
1200+ for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) {
1201+ if (ifa->ifa_addr == nullptr)
1202+ continue;
1203+ if (!(ifa->ifa_flags & IFF_BROADCAST) && !(ifa->ifa_flags & IFF_MULTICAST))
1204+ continue;
1205+ switch (ifa->ifa_addr->sa_family) {
1206+ case AF_INET:
1207+ s = getnameinfo(ifa->ifa_broadaddr, sizeof(struct sockaddr_in),
1208+ host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
1209+ if (s == 0) {
1210+ start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
1211+ broadcast_addresses_v4.insert(host);
1212+ }
1213+ break;
1214+ case AF_INET6:
1215+#ifdef __APPLE__
1216+ interface_indices_v6.insert(if_nametoindex(ifa->ifa_name));
1217+#endif
1218+ start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
1219+ // No address to store here. There is only one "broadcast" address for IPv6
1220+ break;
1221+ }
1222+ }
1223+ freeifaddrs(ifaddr);
1224+
1225 #else
1226 // As Microsoft does not seem to support if_nameindex, we just broadcast to
1227 // INADDR_BROADCAST.
1228- broadcast_addresses.push_back(INADDR_BROADCAST);
1229+ broadcast_addresses_v4.insert("255.255.255.255");
1230 #endif
1231+
1232+ if (!is_open()) {
1233+ // Hm, not good. Just try to open them and hope for the best
1234+ log("[LAN] Trying to open both sockets.\n");
1235+ start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
1236+ start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
1237+ }
1238+
1239+ if (!is_open()) {
1240+ // Still not open? Go back to main menu.
1241+ log("[LAN] Error: No sockets could be opened.\n");
1242+ report_network_error();
1243+ }
1244+
1245+ for (const std::string& ip : broadcast_addresses_v4)
1246+ log("[LAN] Will broadcast to %s.\n", ip.c_str());
1247+ if (socket_v6.is_open())
1248+ log("[LAN] Will broadcast for IPv6.\n");
1249 }
1250
1251 LanBase::~LanBase() {
1252- closesocket(sock);
1253-}
1254-
1255-void LanBase::bind(uint16_t port) {
1256- sockaddr_in addr;
1257-
1258- DIAG_OFF("-Wold-style-cast")
1259- addr.sin_family = AF_INET;
1260- addr.sin_addr.s_addr = INADDR_ANY;
1261- addr.sin_port = htons(port);
1262- DIAG_ON("-Wold-style-cast")
1263-
1264- ::bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
1265-}
1266-
1267-bool LanBase::avail() {
1268- fd_set fds;
1269- timeval tv;
1270-
1271- DIAG_OFF("-Wold-style-cast")
1272- FD_ZERO(&fds);
1273- FD_SET(sock, &fds);
1274- DIAG_ON("-Wold-style-cast")
1275-
1276- tv.tv_sec = 0;
1277- tv.tv_usec = 0;
1278-
1279- return select(sock + 1, &fds, nullptr, nullptr, &tv) == 1;
1280-}
1281-
1282-ssize_t LanBase::receive(void* const buf, size_t const len, sockaddr_in* const addr) {
1283- socklen_t addrlen = sizeof(sockaddr_in);
1284- return recvfrom(
1285- sock, static_cast<DATATYPE*>(buf), len, 0, reinterpret_cast<sockaddr*>(addr), &addrlen);
1286-}
1287-
1288-void LanBase::send(void const* const buf, size_t const len, sockaddr_in const* const addr) {
1289- sendto(sock, static_cast<const DATATYPE*>(buf), len, 0, reinterpret_cast<const sockaddr*>(addr),
1290- sizeof(sockaddr_in));
1291-}
1292-
1293-void LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
1294- for (const in_addr_t& temp_address : broadcast_addresses) {
1295- sockaddr_in addr;
1296- addr.sin_family = AF_INET;
1297- addr.sin_addr.s_addr = temp_address;
1298- DIAG_OFF("-Wold-style-cast")
1299- addr.sin_port = htons(port);
1300- DIAG_ON("-Wold-style-cast")
1301-
1302- sendto(sock, static_cast<const DATATYPE*>(buf), len, 0,
1303- reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));
1304+ close_socket(&socket_v4);
1305+ close_socket(&socket_v6);
1306+}
1307+
1308+bool LanBase::is_available() {
1309+ const auto do_is_available = [this](boost::asio::ip::udp::socket& socket) -> bool {
1310+ boost::system::error_code ec;
1311+ bool available = (socket.is_open() && socket.available(ec) > 0);
1312+ if (ec) {
1313+ log("[LAN] Error when checking whether data is available on IPv%d socket, closing it: %s.\n",
1314+ get_ip_version(socket.local_endpoint().protocol()), ec.message().c_str());
1315+ close_socket(&socket);
1316+ return false;
1317+ }
1318+ return available;
1319+ };
1320+
1321+ return do_is_available(socket_v4) || do_is_available(socket_v6);
1322+}
1323+
1324+bool LanBase::is_open() {
1325+ return socket_v4.is_open() || socket_v6.is_open();
1326+}
1327+
1328+ssize_t LanBase::receive(void* const buf, size_t const len, NetAddress *addr) {
1329+ assert(buf != nullptr);
1330+ assert(addr != nullptr);
1331+ size_t recv_len = 0;
1332+
1333+ const auto do_receive
1334+ = [this, &buf, &len, &recv_len, addr](boost::asio::ip::udp::socket& socket) -> bool {
1335+ if (socket.is_open()) {
1336+ try {
1337+ if (socket.available() > 0) {
1338+ boost::asio::ip::udp::endpoint sender_endpoint;
1339+ recv_len = socket.receive_from(boost::asio::buffer(buf, len), sender_endpoint);
1340+ *addr = NetAddress{sender_endpoint.address(), sender_endpoint.port()};
1341+ assert(recv_len <= len);
1342+ return true;
1343+ }
1344+ } catch (const boost::system::system_error& ec) {
1345+ // Some network error. Close the socket
1346+ log("[LAN] Error when receiving data on IPv%d socket, closing it: %s.\n",
1347+ get_ip_version(socket.local_endpoint().protocol()), ec.what());
1348+ close_socket(&socket);
1349+ }
1350+ }
1351+ // Nothing received
1352+ return false;
1353+ };
1354+
1355+ // Try to receive something somewhere
1356+ if (!do_receive(socket_v4))
1357+ do_receive(socket_v6);
1358+
1359+ // Return how much has been received, might be 0
1360+ return recv_len;
1361+}
1362+
1363+bool LanBase::send(void const* const buf, size_t const len, const NetAddress& addr) {
1364+ boost::system::error_code ec;
1365+ assert(addr.is_valid());
1366+ // If this assert failed, then there is some bug in the code. NetAddress should only be filled
1367+ // with valid IP addresses (e.g. no hostnames)
1368+ assert(!ec);
1369+ boost::asio::ip::udp::endpoint destination(addr.ip, addr.port);
1370+ boost::asio::ip::udp::socket *socket = nullptr;
1371+ if (destination.address().is_v4()) {
1372+ socket = &socket_v4;
1373+ } else if (destination.address().is_v6()) {
1374+ socket = &socket_v6;
1375+ } else {
1376+ NEVER_HERE();
1377+ }
1378+ assert(socket != nullptr);
1379+ if (!socket->is_open()) {
1380+ // I think this shouldn't happen normally. It might happen, though, if we receive
1381+ // a broadcast and learn the IP, then our sockets goes down, then we try to send
1382+ log("[LAN] Error: trying to send to an IPv%d address but socket is not open.\n",
1383+ get_ip_version(addr.ip));
1384+ return false;
1385+ }
1386+ socket->send_to(boost::asio::buffer(buf, len), destination, 0, ec);
1387+ if (ec) {
1388+ log("[LAN] Error when trying to send something over IPv%d, closing socket: %s.\n",
1389+ get_ip_version(addr.ip), ec.message().c_str());
1390+ close_socket(socket);
1391+ return false;
1392+ }
1393+ return true;
1394+}
1395+
1396+bool LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
1397+
1398+ const auto do_broadcast
1399+ = [this, buf, len, port](boost::asio::ip::udp::socket& socket, const std::string& address) -> bool {
1400+ if (socket.is_open()) {
1401+ boost::system::error_code ec;
1402+ boost::asio::ip::udp::endpoint destination(boost::asio::ip::address::from_string(address), port);
1403+ socket.send_to(boost::asio::buffer(buf, len), destination, 0, ec);
1404+ if (!ec) {
1405+ return true;
1406+ }
1407+#ifdef __APPLE__
1408+ if (get_ip_version(destination.address()) == 4) {
1409+#endif // __APPLE__
1410+ log("[LAN] Error when broadcasting on IPv%d socket to %s, closing it: %s.\n",
1411+ get_ip_version(destination.address()), address.c_str(), ec.message().c_str());
1412+ close_socket(&socket);
1413+#ifdef __APPLE__
1414+ } else {
1415+ log("[LAN] Error when broadcasting on IPv6 socket to %s: %s.\n",
1416+ address.c_str(), ec.message().c_str());
1417+ }
1418+#endif // __APPLE__
1419+ }
1420+ return false;
1421+ };
1422+
1423+ bool one_success = false;
1424+
1425+ // IPv4 broadcasting is the same for all
1426+ for (const std::string& address : broadcast_addresses_v4) {
1427+ one_success |= do_broadcast(socket_v4, address);
1428+ }
1429+#ifndef __APPLE__
1430+ // For IPv6 on Linux and Windows just send on an undefined network interface
1431+ one_success |= do_broadcast(socket_v6, "ff02::1");
1432+#else // __APPLE__
1433+
1434+ // Apple forces us to define which interface we want to send through
1435+ for (auto it = interface_indices_v6.begin(); it != interface_indices_v6.end(); ) {
1436+ socket_v6.set_option(boost::asio::ip::multicast::outbound_interface(*it));
1437+ bool success = do_broadcast(socket_v6, "ff02::1");
1438+ one_success |= success;
1439+ if (!success) {
1440+ // Remove this interface id from the set
1441+ it = interface_indices_v6.erase(it);
1442+ if (interface_indices_v6.empty()) {
1443+ log("[LAN] Warning: No more multicast capable IPv6 interfaces."
1444+ "Other LAN players won't find your game.\n");
1445+ }
1446+ } else {
1447+ ++it;
1448+ }
1449+ }
1450+#endif // __APPLE__
1451+ return one_success;
1452+}
1453+
1454+void LanBase::start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port) {
1455+
1456+ if (socket->is_open())
1457+ return;
1458+
1459+ boost::system::error_code ec;
1460+ // Try to open the socket
1461+ socket->open(version, ec);
1462+ if (ec) {
1463+ log("[LAN] Failed to start an IPv%d socket: %s.\n",
1464+ get_ip_version(version), ec.message().c_str());
1465+ return;
1466+ }
1467+
1468+ const boost::asio::socket_base::broadcast option_broadcast(true);
1469+ socket->set_option(option_broadcast, ec);
1470+ if (ec) {
1471+ log("[LAN] Error setting options for IPv%d socket, closing socket: %s.\n",
1472+ get_ip_version(version), ec.message().c_str());
1473+ // Retrieve the error code to avoid throwing but ignore it
1474+ close_socket(socket);
1475+ return;
1476+ }
1477+
1478+ const boost::asio::socket_base::reuse_address option_reuse(true);
1479+ socket->set_option(option_reuse, ec);
1480+ // This one isn't really needed so ignore the error
1481+
1482+
1483+ if (version == boost::asio::ip::udp::v6()) {
1484+ const boost::asio::ip::v6_only option_v6only(true);
1485+ socket->set_option(option_v6only, ec);
1486+ // This one might not be needed, ignore the error and see whether we fail on bind()
1487+ }
1488+
1489+ socket->bind(boost::asio::ip::udp::endpoint(version, port), ec);
1490+ if (ec) {
1491+ log("[LAN] Error binding IPv%d socket to UDP port %d, closing socket: %s.\n",
1492+ get_ip_version(version), port, ec.message().c_str());
1493+ close_socket(socket);
1494+ return;
1495+ }
1496+
1497+ log("[LAN] Started an IPv%d socket on UDP port %d.\n", get_ip_version(version), port);
1498+}
1499+
1500+void LanBase::report_network_error() {
1501+ // No socket open? Sorry, but we can't continue this way
1502+ throw WLWarning(_("Failed to use the local network!"),
1503+ _("Widelands was unable to use the local network. "
1504+ "Maybe some other process is already running a server on port %d, %d or %d "
1505+ "or your network setup is broken."),
1506+ WIDELANDS_LAN_DISCOVERY_PORT, WIDELANDS_LAN_PROMOTION_PORT, WIDELANDS_PORT);
1507+}
1508+
1509+void LanBase::close_socket(boost::asio::ip::udp::socket *socket) {
1510+ boost::system::error_code ec;
1511+ if (socket->is_open()) {
1512+ const boost::asio::ip::udp::endpoint& endpoint = socket->local_endpoint(ec);
1513+ if (!ec)
1514+ log("[LAN] Closing an IPv%d socket.\n", get_ip_version(endpoint.protocol()));
1515+ socket->shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
1516+ socket->close(ec);
1517 }
1518 }
1519
1520 /*** class LanGamePromoter ***/
1521
1522-LanGamePromoter::LanGamePromoter() {
1523- bind(WIDELANDS_LAN_PROMOTION_PORT);
1524+LanGamePromoter::LanGamePromoter()
1525+ : LanBase(WIDELANDS_LAN_PROMOTION_PORT) {
1526
1527 needupdate = true;
1528
1529@@ -140,12 +373,13 @@
1530
1531 strncpy(gameinfo.gameversion, build_id().c_str(), sizeof(gameinfo.gameversion));
1532
1533- gethostname(gameinfo.hostname, sizeof(gameinfo.hostname));
1534+ strncpy(gameinfo.hostname, boost::asio::ip::host_name().c_str(), sizeof(gameinfo.hostname));
1535 }
1536
1537 LanGamePromoter::~LanGamePromoter() {
1538 gameinfo.state = LAN_GAME_CLOSED;
1539
1540+ // Don't care about errors at this point
1541 broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);
1542 }
1543
1544@@ -153,20 +387,25 @@
1545 if (needupdate) {
1546 needupdate = false;
1547
1548- broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);
1549+ if (!broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT)) {
1550+ report_network_error();
1551+ }
1552 }
1553
1554- while (avail()) {
1555+ while (is_available()) {
1556 char magic[8];
1557- sockaddr_in addr;
1558+ NetAddress addr;
1559
1560 if (receive(magic, 8, &addr) < 8)
1561 continue;
1562
1563- log("Received %s packet\n", magic);
1564+ log("Received %s packet from %s\n", magic, addr.ip.to_string().c_str());
1565
1566- if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION)
1567- send(&gameinfo, sizeof(gameinfo), &addr);
1568+ if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION) {
1569+ if (!send(&gameinfo, sizeof(gameinfo), addr)) {
1570+ report_network_error();
1571+ }
1572+ }
1573 }
1574 }
1575
1576@@ -178,8 +417,8 @@
1577
1578 /*** class LanGameFinder ***/
1579
1580-LanGameFinder::LanGameFinder() : callback(nullptr) {
1581- bind(WIDELANDS_LAN_DISCOVERY_PORT);
1582+LanGameFinder::LanGameFinder()
1583+ : LanBase(WIDELANDS_LAN_DISCOVERY_PORT), callback(nullptr) {
1584
1585 reset();
1586 }
1587@@ -192,18 +431,19 @@
1588 strncpy(magic, "QUERY", 8);
1589 magic[6] = LAN_PROMOTION_PROTOCOL_VERSION;
1590
1591- broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT);
1592+ if (!broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT))
1593+ report_network_error();
1594 }
1595
1596 void LanGameFinder::run() {
1597- while (avail()) {
1598+ while (is_available()) {
1599 NetGameInfo info;
1600- sockaddr_in addr;
1601+ NetAddress addr;
1602
1603 if (receive(&info, sizeof(info), &addr) < static_cast<int32_t>(sizeof(info)))
1604 continue;
1605
1606- log("Received %s packet\n", info.magic);
1607+ log("Received %s packet from %s\n", info.magic, addr.ip.to_string().c_str());
1608
1609 if (strncmp(info.magic, "GAME", 6))
1610 continue;
1611@@ -217,6 +457,9 @@
1612 for (NetOpenGame* opengame : opengames) {
1613 if (0 == strncmp(opengame->info.hostname, info.hostname, 128)) {
1614 opengame->info = info;
1615+ if (!opengame->address.is_ipv6() && addr.is_ipv6()) {
1616+ opengame->address.ip = addr.ip;
1617+ }
1618 callback(GameUpdated, opengame, userdata);
1619 was_in_list = true;
1620 break;
1621@@ -225,10 +468,8 @@
1622
1623 if (!was_in_list) {
1624 opengames.push_back(new NetOpenGame);
1625- DIAG_OFF("-Wold-style-cast")
1626- opengames.back()->address = addr.sin_addr.s_addr;
1627- opengames.back()->port = htons(WIDELANDS_PORT);
1628- DIAG_ON("-Wold-style-cast")
1629+ addr.port = WIDELANDS_PORT;
1630+ opengames.back()->address = addr;
1631 opengames.back()->info = info;
1632 callback(GameOpened, opengames.back(), userdata);
1633 break;
1634
1635=== modified file 'src/network/network_lan_promotion.h'
1636--- src/network/network_lan_promotion.h 2017-05-07 20:27:21 +0000
1637+++ src/network/network_lan_promotion.h 2017-06-07 18:50:40 +0000
1638@@ -21,21 +21,15 @@
1639 #define WL_NETWORK_NETWORK_LAN_PROMOTION_H
1640
1641 #include <list>
1642-
1643-#ifndef _WIN32
1644-#include <sys/socket.h>
1645-#endif
1646-#include <sys/types.h>
1647-
1648-#include "network/network_system.h"
1649+#include <set>
1650+
1651+#include "network/network.h"
1652
1653 #define LAN_PROMOTION_PROTOCOL_VERSION 1
1654
1655 #define LAN_GAME_CLOSED 0
1656 #define LAN_GAME_OPEN 1
1657
1658-// TODO(Notabilis): Update file for IPv6
1659-
1660 struct NetGameInfo {
1661 char magic[6];
1662 uint8_t version;
1663@@ -47,31 +41,112 @@
1664 };
1665
1666 struct NetOpenGame {
1667- in_addr_t address;
1668- in_port_t port;
1669+ NetAddress address;
1670 NetGameInfo info;
1671 };
1672
1673+/**
1674+ * Base class for UDP networking.
1675+ * This class is used by derived classes to find open games on the
1676+ * local network and to announce a just opened game on the local network.
1677+ * This class tries to create sockets for IPv4 and IPv6.
1678+ */
1679 struct LanBase {
1680 protected:
1681- LanBase();
1682+ /**
1683+ * Tries to start a socket on the given port.
1684+ * Sockets for IPv4 and IPv6 are started.
1685+ * When both fail, report_network_error() is called.
1686+ * \param port The port to listen on.
1687+ */
1688+ LanBase(uint16_t port);
1689+
1690 ~LanBase();
1691
1692- void bind(uint16_t);
1693-
1694- bool avail();
1695-
1696- ssize_t receive(void*, size_t, sockaddr_in*);
1697-
1698- void send(void const*, size_t, sockaddr_in const*);
1699- void broadcast(void const*, size_t, uint16_t);
1700+ /**
1701+ * Returns whether data is available to be read.
1702+ * \return \c True when receive() will return data, \c false otherwise.
1703+ */
1704+ bool is_available();
1705+
1706+ /**
1707+ * Returns whether at least one of the sockets is open.
1708+ * If this returns \c false, you probably have a problem.
1709+ * \return \c True when a socket is ready, \c false otherwise.
1710+ */
1711+ bool is_open();
1712+
1713+ /**
1714+ * Tries to receive some data.
1715+ * \param[out] buf The buffer to read data into.
1716+ * \param len The length of the buffer.
1717+ * \param[out] addr The address we received data from. Since UDP is a connection-less
1718+ * protocol, each receive() might receive data from another address.
1719+ * \return How many bytes have been written to \c buf. If 0 is returned there either was no data
1720+ * available (check before with avail()) or there was some error (check with is_open())
1721+ */
1722+ ssize_t receive(void *buf, size_t len, NetAddress *addr);
1723+
1724+ /**
1725+ * Sends data to a specified address.
1726+ * \param buf The data to send.
1727+ * \param len The length of the buffer.
1728+ * \param addr The address to send to.
1729+ */
1730+ bool send(void const *buf, size_t len, const NetAddress& addr);
1731+
1732+ /**
1733+ * Broadcast some data in the local network.
1734+ * \param buf The data to send.
1735+ * \param len The length of the buffer.
1736+ * \param port The port to send to. No address is required.
1737+ */
1738+ bool broadcast(void const* buf, size_t len, uint16_t port);
1739+
1740+ /**
1741+ * Throws a WLWarning exception to jump back to the main menu.
1742+ * Calling this on network errors is in the responsibility of derived classes.
1743+ * (Most of the time, aborting makes sense when an error occurred. But e.g. in
1744+ * the destructor simply ignoring the error is okay.)
1745+ */
1746+ void report_network_error();
1747
1748 private:
1749- int32_t sock;
1750-
1751- std::list<in_addr_t> broadcast_addresses;
1752+
1753+ /**
1754+ * Opens a listening UDP socket.
1755+ * \param[out] The socket to open. The object has to be created but the socket not opened before.
1756+ * If it already has been opened before, nothing will be done.
1757+ * \param version Whether a IPv4 or IPv6 socket should be opened.
1758+ * \param port The port to listen on.
1759+ */
1760+ void start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port);
1761+
1762+ /**
1763+ * Closes the given socket.
1764+ * Does nothing if the socket already has been closed.
1765+ * \param socket The socket to close.
1766+ */
1767+ void close_socket(boost::asio::ip::udp::socket *socket);
1768+
1769+ /// No idea what this does. I think it is only really used when asynchronous operations are done.
1770+ boost::asio::io_service io_service;
1771+ /// The socket for IPv4.
1772+ boost::asio::ip::udp::socket socket_v4;
1773+ /// The socket for IPv6.
1774+ boost::asio::ip::udp::socket socket_v6;
1775+ /// The found broadcast addresses for IPv4.
1776+ /// No addresses for v6, there is only one fixed address.
1777+ std::set<std::string> broadcast_addresses_v4;
1778+#ifdef __APPLE__
1779+ /// Apple forces us to define which interface to broadcast through.
1780+ std::set<unsigned int> interface_indices_v6;
1781+#endif // __APPLE__
1782 };
1783
1784+/**
1785+ * Used to promote opened games locally.
1786+ */
1787 struct LanGamePromoter : public LanBase {
1788 LanGamePromoter();
1789 ~LanGamePromoter();
1790@@ -85,6 +160,9 @@
1791 bool needupdate;
1792 };
1793
1794+/**
1795+ * Used to listen for open games while in the LAN-screen.
1796+ */
1797 struct LanGameFinder : LanBase {
1798 enum { GameOpened, GameClosed, GameUpdated };
1799
1800
1801=== removed file 'src/network/network_system.h'
1802--- src/network/network_system.h 2017-01-25 18:55:59 +0000
1803+++ src/network/network_system.h 1970-01-01 00:00:00 +0000
1804@@ -1,59 +0,0 @@
1805-/*
1806- * Copyright (C) 2004-2017 by the Widelands Development Team
1807- *
1808- * This program is free software; you can redistribute it and/or
1809- * modify it under the terms of the GNU General Public License
1810- * as published by the Free Software Foundation; either version 2
1811- * of the License, or (at your option) any later version.
1812- *
1813- * This program is distributed in the hope that it will be useful,
1814- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1815- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1816- * GNU General Public License for more details.
1817- *
1818- * You should have received a copy of the GNU General Public License
1819- * along with this program; if not, write to the Free Software
1820- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1821- *
1822- */
1823-
1824-#ifndef WL_NETWORK_NETWORK_SYSTEM_H
1825-#define WL_NETWORK_NETWORK_SYSTEM_H
1826-
1827-#include <stdint.h>
1828-#ifndef _WIN32
1829-// These includes work on Linux and should be fine on any other Unix-alike.
1830-// If not, this is the right place to conditionally include what is needed.
1831-#include <net/if.h>
1832-#include <netdb.h>
1833-#include <netinet/in.h>
1834-#include <sys/ioctl.h>
1835-#include <sys/socket.h>
1836-#include <sys/types.h>
1837-#include <unistd.h>
1838-
1839-// be compatible to microsoft
1840-#define closesocket close
1841-#define DATATYPE void
1842-
1843-#else
1844-
1845-// This is the header to include according to the documentation
1846-// at msdn.microsoft.com
1847-#include <winsock2.h>
1848-
1849-#define DATATYPE char
1850-// microsoft doesn't have these
1851-using in_port_t = uint16_t;
1852-using in_addr_t = uint32_t;
1853-
1854-#ifndef s_addr
1855-#define s_addr S_addr
1856-#endif
1857-
1858-// This is no typedef on purpose
1859-#define socklen_t int32_t
1860-
1861-#endif
1862-
1863-#endif // end of include guard: WL_NETWORK_NETWORK_SYSTEM_H
1864
1865=== modified file 'src/ui_fsmenu/internet_lobby.cc'
1866--- src/ui_fsmenu/internet_lobby.cc 2017-05-11 06:31:35 +0000
1867+++ src/ui_fsmenu/internet_lobby.cc 2017-06-07 18:50:40 +0000
1868@@ -373,15 +373,10 @@
1869 }
1870 std::string ip = InternetGaming::ref().ip();
1871
1872- // TODO(Notabilis): Change this for IPv6
1873- // convert IPv6 addresses returned by the metaserver to IPv4 addresses.
1874- // At the moment SDL_net does not support IPv6 anyways.
1875- if (!ip.compare(0, 7, "::ffff:")) {
1876- ip = ip.substr(7);
1877- log("InternetGaming: cut IPv6 address: %s\n", ip.c_str());
1878- }
1879-
1880- GameClient netgame(ip, WIDELANDS_PORT, InternetGaming::ref().get_local_clientname(), true);
1881+ NetAddress addr;
1882+ NetAddress::parse_ip(&addr, ip, WIDELANDS_PORT);
1883+ assert(addr.is_valid());
1884+ GameClient netgame(addr, InternetGaming::ref().get_local_clientname(), true);
1885 netgame.run();
1886 } else
1887 throw wexception("No server selected! That should not happen!");
1888@@ -404,6 +399,12 @@
1889 InternetGaming::ref().set_local_servername(servername_ui);
1890
1891 // Start the game
1892- GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
1893- netgame.run();
1894+ try {
1895+ GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
1896+ netgame.run();
1897+ } catch (...) {
1898+ // Log out before going back to the main menu
1899+ InternetGaming::ref().logout("SERVER_CRASHED");
1900+ throw;
1901+ }
1902 }
1903
1904=== modified file 'src/ui_fsmenu/netsetup_lan.cc'
1905--- src/ui_fsmenu/netsetup_lan.cc 2017-02-27 13:45:46 +0000
1906+++ src/ui_fsmenu/netsetup_lan.cc 2017-06-07 18:50:40 +0000
1907@@ -135,7 +135,7 @@
1908 discovery.run();
1909 }
1910
1911-bool FullscreenMenuNetSetupLAN::get_host_address(uint32_t& addr, uint16_t& port) {
1912+bool FullscreenMenuNetSetupLAN::get_host_address(NetAddress *addr) {
1913 const std::string& host = hostname.text();
1914
1915 const uint32_t opengames_size = opengames.size();
1916@@ -143,20 +143,17 @@
1917 const NetOpenGame& game = *opengames[i];
1918
1919 if (!strcmp(game.info.hostname, host.c_str())) {
1920- addr = game.address;
1921- port = game.port;
1922+ *addr = game.address;
1923 return true;
1924 }
1925 }
1926
1927- if (hostent* const he = gethostbyname(host.c_str())) {
1928- addr = (reinterpret_cast<in_addr*>(he->h_addr_list[0]))->s_addr;
1929- DIAG_OFF("-Wold-style-cast")
1930- port = htons(WIDELANDS_PORT);
1931- DIAG_ON("-Wold-style-cast")
1932- return true;
1933- } else
1934- return false;
1935+ // The user probably entered a hostname on his own. Try to resolve it
1936+ if (NetAddress::resolve_to_v6(addr, host, WIDELANDS_PORT))
1937+ return true;
1938+ if (NetAddress::resolve_to_v4(addr, host, WIDELANDS_PORT))
1939+ return true;
1940+ return false;
1941 }
1942
1943 const std::string& FullscreenMenuNetSetupLAN::get_playername() {
1944
1945=== modified file 'src/ui_fsmenu/netsetup_lan.h'
1946--- src/ui_fsmenu/netsetup_lan.h 2017-05-07 20:27:21 +0000
1947+++ src/ui_fsmenu/netsetup_lan.h 2017-06-07 18:50:40 +0000
1948@@ -34,8 +34,6 @@
1949 struct NetOpenGame;
1950 struct NetGameInfo;
1951
1952-// TODO(Notabilis): Update for IPv6
1953-
1954 class FullscreenMenuNetSetupLAN : public FullscreenMenuBase {
1955 public:
1956 FullscreenMenuNetSetupLAN();
1957@@ -43,12 +41,10 @@
1958 void think() override;
1959
1960 /**
1961- * \param[out] addr filled in with the IP address of the chosen server
1962- * \param[out] port filled in with the port of the chosen server
1963- * \return \c true if a valid server has been chosen. If \c false is
1964- * returned, the values of \p addr and \p port are undefined.
1965+ * \param[out] addr filled in with the host name or IP address and port of the chosen server
1966+ * \return \c True if the address is valid, \c false otherwise. In that case \c addr is not modified
1967 */
1968- bool get_host_address(uint32_t& addr, uint16_t& port);
1969+ bool get_host_address(NetAddress *addr);
1970
1971 /**
1972 * \return the name chosen by the player
1973
1974=== modified file 'src/wlapplication.cc'
1975--- src/wlapplication.cc 2017-05-13 13:14:29 +0000
1976+++ src/wlapplication.cc 2017-06-07 18:50:40 +0000
1977@@ -350,9 +350,6 @@
1978 // This might grab the input.
1979 refresh_graphics();
1980
1981- if (SDLNet_Init() == -1)
1982- throw wexception("SDLNet_Init failed: %s\n", SDLNet_GetError());
1983-
1984 // seed random number generator used for random tribe selection
1985 std::srand(time(nullptr));
1986
1987@@ -378,8 +375,6 @@
1988 delete UI::g_fh1;
1989 UI::g_fh1 = nullptr;
1990
1991- SDLNet_Quit();
1992-
1993 TTF_Quit(); // TODO(unknown): not here
1994
1995 assert(g_fs);
1996@@ -1164,10 +1159,6 @@
1997 FullscreenMenuNetSetupLAN ns;
1998 menu_result = ns.run<FullscreenMenuBase::MenuTarget>();
1999 std::string playername = ns.get_playername();
2000- // TODO(Notabilis): This has to be updated for IPv6
2001- uint32_t addr;
2002- uint16_t port;
2003- bool const host_address = ns.get_host_address(addr, port);
2004
2005 switch (menu_result) {
2006 case FullscreenMenuBase::MenuTarget::kHostgame: {
2007@@ -1176,18 +1167,16 @@
2008 break;
2009 }
2010 case FullscreenMenuBase::MenuTarget::kJoingame: {
2011- if (!host_address)
2012- throw WLWarning(
2013- "Invalid Address", "%s", "The address of the game server is invalid");
2014+ NetAddress addr;
2015+ if (!ns.get_host_address(&addr)) {
2016+ UI::WLMessageBox mmb(&ns, _("Invalid address"),
2017+ _("The entered hostname or address is invalid and can’t be connected to."),
2018+ UI::WLMessageBox::MBoxType::kOk);
2019+ mmb.run<UI::Panel::Returncodes>();
2020+ break;
2021+ }
2022
2023- // TODO(Notabilis): Make this prettier. I am aware that this is quite ugly but it should
2024- // work
2025- // for now and will be removed shortly when we switch to boost.asio
2026- char ip_str[] = {"255.255.255.255"};
2027- sprintf(ip_str, "%d.%d.%d.%d", (addr & 0x000000ff), (addr & 0x0000ff00) >> 8,
2028- (addr & 0x00ff0000) >> 16, (addr & 0xff000000) >> 24);
2029- port = (port >> 8) | ((port & 0xFF) << 8);
2030- GameClient netgame(ip_str, port, playername);
2031+ GameClient netgame(addr, playername);
2032 netgame.run();
2033 break;
2034 }
2035
2036=== modified file 'utils/macos/build_app.sh'
2037--- utils/macos/build_app.sh 2016-11-30 00:18:17 +0000
2038+++ utils/macos/build_app.sh 2017-06-07 18:50:40 +0000
2039@@ -109,7 +109,6 @@
2040 export SDL2IMAGEDIR="$(brew --prefix sdl2_image)"
2041 export SDL2MIXERDIR="$(brew --prefix sdl2_mixer)"
2042 export SDL2TTFDIR="$(brew --prefix sdl2_ttf)"
2043- export SDL2NETDIR="$(brew --prefix sdl2_net)"
2044 export BOOST_ROOT="$(brew --prefix boost)"
2045 export ICU_ROOT="$(brew --prefix icu4c)"
2046
2047
2048=== modified file 'utils/win32/innosetup/Widelands.iss'
2049--- utils/win32/innosetup/Widelands.iss 2017-02-27 08:52:41 +0000
2050+++ utils/win32/innosetup/Widelands.iss 2017-06-07 18:50:40 +0000
2051@@ -131,7 +131,6 @@
2052 Source: {#DLLFolder}\SDL2.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2053 Source: {#DLLFolder}\SDL2_image.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2054 Source: {#DLLFolder}\libSDL2_mixer-2-0-0.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2055-Source: {#DLLFolder}\SDL2_net.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2056 Source: {#DLLFolder}\SDL2_ttf.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2057 Source: {#DLLFolder}\zlib1.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
2058 Source: {#DLLFolder}\libFLAC-8.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"