offbynull / portmapper

Java library that maps ports on NAT-enabled routers (supported protocols: UPnP-IGD/NAT-PMP/PCP).
Apache License 2.0
88 stars 17 forks source link

java.net.SocketException: Permission denied #12

Closed Azhrei closed 8 years ago

Azhrei commented 8 years ago

This report is against commit b5fb9fcfbd75f2c81a479db9f98b7e7492c4b2d8 (the last prior to the "WIP" message). Maybe I should go all the way back to the 1.0.0 tag?

I'm getting the above exception while trying to write a small example of how to use your library. I'm guessing it has something to do with my Unix system (Mac OS X) limiting which ports can be bound by an ordinary user, but I have the following in my java.policy file:

grant { 
    // allows anyone to listen on un-privileged ports
    permission java.net.SocketPermission "*:1024-", "listen";
    permission java.net.SocketPermission "*",       "accept,connect";

    // allows anyone to listen on dynamic ports
    permission java.net.SocketPermission "localhost:0", "listen";
};

(The last paragraph in the policy file, above, is part of the standard installation and I copied it here for documentation purposes.)

I'm going to begin stepping through this to determine exactly what might be causing the problem, but I thought it might be helpful to report it even before I find out that I screwed something up. ;)

Here's the stack trace that goes with the exception. The second exception references my test code, sampleCode.java, and the call to PortMapperFactory.create() that triggers the exception. That code is included below the exception trace, although it's almost identical to what is in the README.md file:

Exception in thread "Thread-2" java.lang.RuntimeException: java.net.SocketException: Permission denied
    at com.google.common.base.Throwables.propagate(Throwables.java:160)
    at com.google.common.util.concurrent.AbstractExecutionThreadService$1$2.run(AbstractExecutionThreadService.java:77)
    at com.google.common.util.concurrent.Callables$3.run(Callables.java:95)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.SocketException: Permission denied
    at sun.nio.ch.DatagramChannelImpl.send0(Native Method)
    at sun.nio.ch.DatagramChannelImpl.sendFromNativeBuffer(DatagramChannelImpl.java:521)
    at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:498)
    at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:462)
    at com.offbynull.portmapper.common.UdpCommunicator.run(UdpCommunicator.java:162)
    at com.google.common.util.concurrent.AbstractExecutionThreadService$1$2.run(AbstractExecutionThreadService.java:60)
    ... 2 more

Exception in thread "main" java.lang.IllegalStateException: Expected the service to be TERMINATED, but the service has FAILED
    at com.google.common.util.concurrent.AbstractService.checkCurrentState(AbstractService.java:285)
    at com.google.common.util.concurrent.AbstractService.awaitTerminated(AbstractService.java:255)
    at com.google.common.util.concurrent.AbstractExecutionThreadService.awaitTerminated(AbstractExecutionThreadService.java:211)
    at com.offbynull.portmapper.pcp.PcpDiscovery.discoverGateways(PcpDiscovery.java:187)
    at com.offbynull.portmapper.pcp.PcpDiscovery.discover(PcpDiscovery.java:60)
    at com.offbynull.portmapper.PortMapperFactory.create(PortMapperFactory.java:49)
    at sample.SampleCode.main(SampleCode.java:32)
Caused by: java.net.SocketException: Permission denied
    at sun.nio.ch.DatagramChannelImpl.send0(Native Method)
    at sun.nio.ch.DatagramChannelImpl.sendFromNativeBuffer(DatagramChannelImpl.java:521)
    at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:498)
    at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:462)
    at com.offbynull.portmapper.common.UdpCommunicator.run(UdpCommunicator.java:162)
    at com.google.common.util.concurrent.AbstractExecutionThreadService$1$2.run(AbstractExecutionThreadService.java:60)
    at com.google.common.util.concurrent.Callables$3.run(Callables.java:95)
    at java.lang.Thread.run(Thread.java:745)

This is the contents of sampleCode.java with all comments removed:

package sample;

import com.offbynull.portmapper.MappedPort;
import com.offbynull.portmapper.PortMapper;
import com.offbynull.portmapper.PortMapperEventListener;
import com.offbynull.portmapper.PortMapperFactory;
import com.offbynull.portmapper.PortType;

public class SampleCode {
    public static void main(String[] args) throws Throwable {
        PortMapperEventListener listener = x -> System.err.println(
            "Port mapping listener activated: " + x);

        try (PortMapper pm = PortMapperFactory.create(listener)) {
            MappedPort mp = pm.mapPort(PortType.TCP, 54321, 10);
            System.out.println("Port mapping added: "
                    + mp.getInternalPort() + " to "
                    + mp.getExternalPort() + " on "
                    + mp.getExternalAddress());

            int count = 4;
            while (--count >= 0) {
                mp = pm.refreshPort(mp, mp.getLifetime() / 2L);
                System.out.println("Port mapping refreshed: "
                    + mp.getInternalPort() + " to "
                    + mp.getExternalPort() + " on "
                    + mp.getExternalAddress());
                Thread.sleep(mp.getLifetime() * 1000L);
            }
            pm.unmapPort(mp);
        }
    }
}
offbynull commented 8 years ago

Hey,

The binding port probably isn't the issue here. It looks like it's failing at the device discovery stage for PCP. The PCP discovery mechanism attempts to bind to a wildcard port and address. It could be that it's failing because it's attempting to bind to 'new InetSocketAddress(0)' instead of 'null' -- but I suspect that 'null' would be wrong here as well. It's tough to say for certain without actually having a Mac to test on (I have one available but I can't use it for anything personal).

The project is going through major refactoring at the moment. If you can wait around for 3 months or so, version 2 should be available then. Version 2's code will be much easier to follow + it'll have much more testing + it'll remove many dependencies (including guava and regex/SOAP/XML libs) + it'll be more fault tolerant + it'll have support for IPv6 and UPnP-IGD 2.0.

If you can't wait, the only other thing I can do is maybe a hangouts session over the weekend to try and debug the issue with you. But, keep in mind that the code throwing the exception slated for removal. It's being re-written from scratch for 2.0.

Azhrei commented 8 years ago

Thanks for the quick response. :)

I can wait in terms of a production-ready library, but I'm probably going to stub out an API class and I'll just fill it with static data. I'm currently using the UPNP-only library known as sbbi-upnp with some modifications to make it compatible with XML namespaces (the original library from sbbi wasn't). But it only does UPNP so a single library that can handle all three would be ideal.

I presume it works for you on the Windows platform -- is that correct? If so, I might try wrapping the code inside a doPrivileged() block but I don't expect that will help.

I may try it under Linux as well. I've had problems before in the way that OS X handles the multicast address aspect; I fixed a similar problem with the sbbi-upnp library. Hm, I'll go back and see what the issue was there; maybe it'll be the same problem.

I'll come back and report it if I find the problem and I've starred this project to come back and check again in the future.

Thanks for your effort! :)

offbynull commented 8 years ago

Yep. I'm on Windows 7. If all you care about is UPnP, you might want to take a look at weupnp as well. I don't think it supports anything other than 1.0/IPv4, but it seems to be fairly stable.

I'll close this ticket once 2.0 is released.

Azhrei commented 8 years ago

I've started digging into the code... It looks like you've got some code in NetworkUtils that executes netstat -rn and then tries to parse the IP addresses it finds?! Am I correct in assuming that the rewrite will change that to a broadcast on active interfaces followed by listening for replies from gateway devices?

Thanks again for working on this library. :)

offbynull commented 8 years ago

The UPNP portion uses normal SSDP broadcast to find the device. The portion of code you're looking at is for PCP and NAT-PMP. There is no broadcast discovery mechanism for these two protocols. The RFC just tells you to talk to the gateway -- there is no way in Java to get the gateway associated with an interface.

There is an RFC draft in place to fix this: https://tools.ietf.org/html/draft-ietf-pcp-anycast-08, but there are already too many routers in the wild that won't implement this spec.

offbynull commented 8 years ago

It may be best to target your test against https://github.com/offbynull/portmapper/commit/174900e1c634a3bc17947cda61e6604bbf1dfeac

Everything after that point is WIP for 2.0

Azhrei commented 8 years ago

Thanks for the commit marker. :)

Hm, bummer about finding the gateway device. I guess one could send a packet to google.com with a TTL of 2 (the first hop is your local NIC, right?) and then see what IP address produces the error. ;-)

EDIT: I see in the RFC that they're planning ahead such that the first hop doesn't need to be the gateway. However, in most residential/consumer cases, "in the end, there can be only one." (Heh-heh.)

Oh well, I'll work on other things for now and come back to this later. Thanks again!

offbynull commented 8 years ago

This may work? I think there are issues with Java when it comes to sending ICMP packets if you aren't running as root.

EDIT: Probably won't work if you're under a carrier-grade NAT. But that's out of scope for now / will be resolved once that RFC stabilizes.

I had a similar thought in https://github.com/offbynull/portmapper/issues/5

In any event, 1.0 is a bit of a mess. This exception will go away in 2.0.

offbynull commented 8 years ago

Do you have a moment to try the latest commit? https://github.com/offbynull/portmapper/commit/8400d1292aa3e5df867dcbb433722c9b5fadfa06

Some of the tests are currently bound to my network setup, so you have to disable test when running maven by using -DskipTests. For example mvn -DskipTests=true clean install

Azhrei commented 8 years ago

Thanks for the note. I'll checkout the latest build before I get on a plane tonight, but I won't be able to test to test it until Saturday.

offbynull commented 8 years ago

Updated commit to try... https://github.com/offbynull/portmapper/commit/3ce95b0e1ab60a3ca9d703e87e70e53e639dc1e2

// start network gateway
Gateway network = NetworkGateway.create();
Bus networkBus = network.getBus();

// start process gateway
Gateway process = ProcessGateway.create();
Bus processBus = process.getBus();

// discover mappers
List<PortMapper> mappers = PortMapperFactory.discover(networkBus, processBus);

// kill the process gateway after discovery finishes
processBus.send(new KillProcessRequest());

// ...
// your application code here, possibly with some port mapping functionality
// ...

// kill the network gateway once your application completes
networkBus.send(new KillNetworkRequest());
offbynull commented 8 years ago

Verified working on Mac.