koraktor / steam-condenser

A multi-language library for querying the Steam Community, Source, GoldSrc servers and Steam master servers
https://koraktor.de/steam-condenser
Other
356 stars 67 forks source link

RCON causes IllegalBlockingModeException #93

Closed daparker closed 13 years ago

daparker commented 13 years ago

I am attempting to use Steam Condenser on an Android app. I have a simple test class which calls rconAuth() with the RCON password and then rconExec() to execute the status command. However, the call to rconAuth() causes an IllegalBlockingModeException and the app crashes. I am not sure if this is a bug in Steam Condenser, or perhaps an issue with the way sockets work on the Android platform. Any advice would be appreciated.

daparker commented 13 years ago

I have narrowed it down a little further. The IllegalBlockingModeException occurs at this call:

this.rconSocket.getReply();

This is in the rconAuth() method of the SourceServer class.

koraktor commented 13 years ago

I read your mail, so here's a first quick answer. Seems like Android doesn't allow blocking sockets or something similar.

Steam Condenser doesn't really support Android, but I'd love to get it working on this platform as well. Maybe I should just start and get an SDK...

daparker commented 13 years ago

Thanks Koraktor. It would be great if this worked someday, and I was able to bring RCON support to the app.

The Android documentation seems to indicate that blocking is supported:

http://developer.android.com/reference/java/nio/channels/SelectableChannel.html

Is there a change I can make to Steam Condenser to explicitly set the blocking mode? Or, is there something I can do to make Steam Condenser work with non-blocking sockets?

koraktor commented 13 years ago

Maybe it has been broken by this change: https://github.com/koraktor/steam-condenser/commit/ca87811f1ad69bedd530796561b6d0c555208312

You can test and change back the specific lines in the Java code.

daparker commented 13 years ago

Thanks for the tip. It looks like the only change in that version that would affect this is the removal of line 41 in the send() method of RCONSocket.java:

this.channel.configureBlocking(false);

I put this back in, but now I get a NullPointerException at the same place I was getting the IllegalBlockingModeException before. I will investigate this further and try to track down exactly what is causing this exception.

daparker commented 13 years ago

At this point, line 105 in SteamSocket.java appears to be the problem:

Selector selector = Selector.open();

This triggers an unhandled exception:

java.net.SocketException: Bad address family

This causes the subsequent NullPointerException on line 109:

selector.close();

Still working on tracing it further. Any advice or suggestions would be greatly appreciated. Thanks.

daparker commented 13 years ago

I found a forum thread online that said this problem is caused by the NIO library attempting to use IPv6, and Android 2.2 not supporting IPv6. I was able to solve the NullPointerException by adding this line just above the call to Selector.open():

System.setProperty("java.net.preferIPv6Addresses", "false");

Now, the app connects to the server and rconAuth() sends the password, but I get a "Bad password" rcon error in the SRCDS console each time. I even tried unsetting the password on the server and sending an empty string, but it still says bad password. The server I am testing against is a TF2 server.

Any ideas about this?

koraktor commented 13 years ago

Unsetting the RCON password on the server disables RCON completely, so you have to set one. If you set one and use it in rconAuth() you should be able to send commands via RCON.

daparker commented 13 years ago

Thanks! I'm not sure what the problem was. I was definitely using the correct password, but I was getting password errors on the TF2 server console. I restarted the server and now it works.

This is off topic for this thread, but does Steam Condenser handle a SERVERDATA_AUTH_RESPONSE for a bad password differently from a SERVERDATA_AUTH_RESPONSE for no password? Basically, is it possible to know whether the password sent with rconAuth() was bad before sending a command?

daparker commented 13 years ago

And back on topic...

The System.setProperty() fix I posted above only works for Source servers. I get an IllegalBlockingModeException when I call rconExec() with a GoldSrcServer.

koraktor commented 13 years ago

Did you add the line this.channel.configureBlocking(false); in QuerySocket.java, too? I'll revert ca87811f1ad69bedd530796561b6d0c555208312 for Java soon.

Your password problems may be caused by servers banning IPs which failed to authenticate several times. SourceServer#rconAuth() gives a boolean, so you know if the password was correct or not. GoldSrcServer doesn't have this (#rconAuth() always returns true), because the protocol authenticates every single command and not the whole connection.

koraktor commented 13 years ago

Fixed by c0de01f4cace0906352709cb83ee479e519bd5f2.

daparker commented 13 years ago

Thanks. Adding that line to QuerySocket.java fixed the problem for GoldSource servers, but it seems to cause Source Servers to wait indefinitely for a response to rconAuth().

koraktor commented 13 years ago

That's no possible. Source server RCON communication uses RCONSocket (TCP) whereas GoldSrc uses QuerySocket (UDP). So a change to QuerySocket can't interfere with Source RCON.

daparker commented 13 years ago

Sorry, you're right. This is strange. This worked perfectly last night with my TF2 server, but now it gets stuck waiting for something at this line in rconAuth() method of SourceServer.java:

RCONAuthResponse reply = (RCONAuthResponse) this.rconSocket.getReply();

I'm really puzzled. The getReply() method called by this line is returning, but for some reason the program waits at this point and does not move on to the return on the next line. I can see packets hitting the server, including an RCON auth exchange, but then the traffic stops and the app just sits there at this point.

koraktor commented 13 years ago

If getReply() returns, I can't see a way that rconAuth() could block you application. It must be stuck in another place. Did you change anything in the code to debug?

daparker commented 13 years ago

I have debugged it several different ways, and it truly seems to get stuck after that line. I have no explanation for what's happening. It executes that line but does not move on the the return below it. I set breakpoints in RCONAuthResponse.java and RCONSocket.java but it did not get stuck at any of those points.

Is there anything I can attach which would help with this?

Thanks.

daparker commented 13 years ago

I think I solved it. In the rconAuth() method of SourceServer.java I changed this:

this.rconSocket.send(new RCONAuthRequestPacket(this.rconRequestId, password));
this.rconSocket.getReply();
RCONAuthResponse reply = (RCONAuthResponse) this.rconSocket.getReply();
return (reply.getRequestId() == this.rconRequestId);

To this:

this.rconSocket.send(new RCONAuthRequestPacket(this.rconRequestId, password));
RCONPacket reply = this.rconSocket.getReply();
return (reply.getRequestId() == this.rconRequestId);

Removing the first call to getReply() and the cast to RCONAuthResponse made everything work.

koraktor commented 13 years ago

I always wondered why that second call was required, we should really check if this is a Android thing or if an update changed that. What game server are you querying?

daparker commented 13 years ago

I think I spoke too soon. Those changes made it work in the emulator, but I got a different result on my actual phone. When using RCON with a Source engine server, the first call to rconExec() after making a new connection would always throw an RCONNoAuthException, even though a reply came back from the server and was waiting to be read on the socket. The next call to rconExec() would return the results of both commands.

After fighting with it for a while, I was able to fix it by making a change to the rconExec() method in SourceServer.java. I changed this:

if(responsePacket instanceof RCONAuthResponse) {
    throw new RCONNoAuthException();
}

To this:

if(responsePacket instanceof RCONAuthResponse) {
    responsePacket = this.rconSocket.getReply();
    if(responsePacket instanceof RCONAuthResponse) {
        throw new RCONNoAuthException();
    }
}

This double-check causes it to ignore the first RCONAuthResponse and read the socket again, which gets the result from the command.

Also, rconAuth() in SourceServer.java always seems to return true. I don't know if this is a result of removing the cast to RCONAuthResponse. I tried testing with a bad RCON password and I continued to get true from rconAuth() even though I could see "Bad password" errors on the server console.

daparker commented 13 years ago

I'm testing this with a TF2 server that I admin.

koraktor commented 13 years ago

Your first change caused your second one.

I'll try to reproduce your problem, but I can't imagine the RCON protocol changed dramatically.

daparker commented 13 years ago

I'm not sure why I ran into the problem on the device (Motorola Droid running Android 2.2.1) and not in the emulator using the same OS. I know this solution is a bit of a hack, but it makes RCON work on the phone, so it's a step in the right direction. The only thing that doesn't work now is the return value of rconAuth(), and this only affects Source engine servers. GoldSource RCON is working fine as far as I can tell.

I tried putting that extra call to this.rconSocket.getReply() back into rconAuth() instead of changing rconExec(), but that led to the app hanging like it did before. This seems strange, because the issue seems to be that the RCON auth response needs to be consumed from the socket before the results of the command can be read, and it seems like that extra getReply() would do that. I still have not found an explanation for the app hanging like that.

If you are interested, I can send you the code for the app and you could test it yourself. You would need the Android SDK installed.