Skip to content

Commit

Permalink
Add UPnP support for peer discovery. Resolves rsksmart#1036
Browse files Browse the repository at this point in the history
  • Loading branch information
CoeJoder committed Nov 8, 2019
1 parent d380ec4 commit 87cb336
Show file tree
Hide file tree
Showing 16 changed files with 1,060 additions and 3 deletions.
5 changes: 5 additions & 0 deletions rskj-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ext {
logbackVersion = '1.2.2'
bitcoinjVersion = '0.14.4-rsk-8'
nettyVersion = '4.0.56.Final'
weupnpVersion = '0.1.4'
}

dependencies {
Expand All @@ -84,6 +85,7 @@ dependencies {
compile "org.mapdb:mapdb:2.0-beta13"
compile "co.rsk.bitcoinj:bitcoinj-thin:${bitcoinjVersion}"
compile 'com.github.briandilley.jsonrpc4j:jsonrpc4j:1.5.1'
compile "org.bitlet:weupnp:${weupnpVersion}"

runtime "org.slf4j:log4j-over-slf4j:${slf4jVersion}"
runtime "ch.qos.logback:logback-classic:${logbackVersion}"
Expand Down Expand Up @@ -131,6 +133,9 @@ dependencyVerification {
'net.jcip:jcip-annotations:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.apache.commons:commons-lang3:8ac96fc686512d777fca85e144f196cd7cfe0c0aec23127229497d1a38ff651c',
'org.awaitility:awaitility:a02982e89585a52c1c84296a895bfeb86ea250cca1a53bcfc8a14092fffa87c4',
// TODO should be updated with hash from reproducible build, once that .jar/.pom is uploaded
// see: https://github.com/rsksmart/reproducible-builds/pull/19
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bclcrypto-jdk15on:7d03ba37df4d0ddc4ea40d56554324c6f18062a930edadb0a1b3acbbbea28efc',
'org.ethereum:leveldbjni-all:18da00444c77080d4422b16c9d4750c4addabda350b702b4a6d628b86658e585',
'org.fusesource.hawtjni:hawtjni-runtime:74fe9764e1fb1ef20b159dbca2d29abd6de292082ce3fcf538f81ac912390416',
Expand Down
16 changes: 15 additions & 1 deletion rskj-core/src/main/java/co/rsk/RskContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import co.rsk.net.eth.WriterMessageRecorder;
import co.rsk.net.sync.PeersInformation;
import co.rsk.net.sync.SyncConfiguration;
import co.rsk.net.discovery.upnp.UpnpService;
import co.rsk.peg.BridgeSupportFactory;
import co.rsk.peg.BtcBlockStoreWithCache;
import co.rsk.peg.RepositoryBtcBlockStoreWithCache;
Expand Down Expand Up @@ -234,6 +235,7 @@ public class RskContext implements NodeBootstrapper {
private StatusResolver statusResolver;
private Web3InformationRetriever web3InformationRetriever;
private BootstrapImporter bootstrapImporter;
private UpnpService upnpService;

public RskContext(String[] args) {
this(new CliArgs.Parser<>(
Expand Down Expand Up @@ -780,10 +782,15 @@ public List<InternalService> buildInternalServices() {
internalServices.add(getWeb3WebSocketServer());
}
if (getRskSystemProperties().isPeerDiscoveryEnabled()) {
boolean isUpnpEnabled = getRskSystemProperties().isPeerDiscoveryByUpnpEnabled();
if (isUpnpEnabled) {
internalServices.add(getUpnpService());
}
internalServices.add(new UDPServer(
getRskSystemProperties().getBindAddress().getHostAddress(),
getRskSystemProperties().getPeerPort(),
getPeerExplorer()
getPeerExplorer(),
isUpnpEnabled ? Optional.of(getUpnpService()) : Optional.empty()
));
}
if (getRskSystemProperties().isSyncEnabled()) {
Expand Down Expand Up @@ -1607,6 +1614,13 @@ private MinerClock getMinerClock() {
return minerClock;
}

private UpnpService getUpnpService() {
if (upnpService == null) {
upnpService = new UpnpService();
}
return upnpService;
}

public org.ethereum.db.BlockStore buildBlockStore(String databaseDir) {
File blockIndexDirectory = new File(databaseDir + "/blocks/");
File dbFile = new File(blockIndexDirectory, "index");
Expand Down
22 changes: 22 additions & 0 deletions rskj-core/src/main/java/co/rsk/net/discovery/UDPServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package co.rsk.net.discovery;

import co.rsk.config.InternalService;
import co.rsk.net.discovery.upnp.UpnpProtocol;
import co.rsk.net.discovery.upnp.UpnpService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
Expand All @@ -28,13 +30,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
* Created by mario on 10/02/17.
*/
public class UDPServer implements InternalService {
private static final Logger logger = LoggerFactory.getLogger(UDPServer.class);
private static final String PEER_DISCOVERY_PORT_MAPPING_DESCRIPTION = "RSK peer discovery";

private int port;
private String address;
Expand All @@ -43,11 +47,17 @@ public class UDPServer implements InternalService {
private volatile boolean shutdown = false;

private PeerExplorer peerExplorer;
private final Optional<UpnpService> upnpService;

public UDPServer(String address, int port, PeerExplorer peerExplorer) {
this(address, port, peerExplorer, Optional.empty());
}

public UDPServer(String address, int port, PeerExplorer peerExplorer, Optional<UpnpService> upnpService) {
this.address = address;
this.port = port;
this.peerExplorer = peerExplorer;
this.upnpService = upnpService;
}

@Override
Expand All @@ -59,6 +69,7 @@ public void start() {
@Override
public void run() {
try {
UDPServer.this.doPortMappingIfEnabled();
UDPServer.this.startUDPServer();
} catch (Exception e) {
logger.error("Discovery can't be started. ", e);
Expand Down Expand Up @@ -86,6 +97,17 @@ public void startUDPServer() throws InterruptedException {
group.shutdownGracefully().sync();
}

private void doPortMappingIfEnabled() {
upnpService
.flatMap(service -> service.findGateway(address))
.ifPresent(gateway -> gateway.addPortMapping(
port,
port,
UpnpProtocol.UDP,
PEER_DISCOVERY_PORT_MAPPING_DESCRIPTION
));
}

@Override
public void stop() {
logger.info("Closing UDPListener...");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
* This file is part of RskJ
* Copyright (C) 2019 RSK Labs Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package co.rsk.net.discovery.upnp;

import org.bitlet.weupnp.GatewayDevice;
import org.bitlet.weupnp.PortMappingEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;

/**
* Provides a UPnP interface for a particular Internet Gateway Device:
* <ul>
* <li>Add/remove port mappings.</li>
* <li>Find the gateway's external IP address.</li>
* </ul>
*/
public class UpnpGatewayManager {

private static final Logger logger = LoggerFactory.getLogger(UpnpGatewayManager.class);
private static final int PORT_MAPPINGS_INITIAL_CAPACITY = 3;

private final GatewayDevice gateway;
private final List<PortMappingEntry> portMappings = new ArrayList<>(PORT_MAPPINGS_INITIAL_CAPACITY);

/**
* Package-private constructor. Called by {@link UpnpService#findGateway()}.
*
* @param gateway a valid gateway with a WAN connection.
*/
UpnpGatewayManager(GatewayDevice gateway) {
this.gateway = gateway;
}

/**
* Gets the external IP address of the gateway.
*
* @return the external IP address of the gateway, or empty if failure.
*/
public Optional<String> getExternalIPAddress() {
try {
return Optional.ofNullable(gateway.getExternalIPAddress());
} catch (Exception e) {
logger.error("Failed to get external IP address.", e);
return Optional.empty();
}
}

/**
* Forwards a port on the gateway to this node. The port mapping will be deleted
* on {@link UpnpService#stop()}.
*
* @param externalPort the external port to be forwarded.
* @param internalPort the destination port being forwarded to.
* @param protocol the protocol to use.
* @param description describes the purpose of this port mapping.
* @return true if successful.
*/
public boolean addPortMapping(int externalPort, int internalPort, UpnpProtocol protocol, String description) {
String strProtocol = protocol.toString();
String strLocalAddress = gateway.getLocalAddress().getHostAddress();

if (addPortMapping(externalPort, internalPort, strLocalAddress, strProtocol, description)) {
logger.info(
"Added port mapping of {} port {} to {}:{} for \"{}\".",
strProtocol,
externalPort,
strLocalAddress,
internalPort,
description
);
// saved here for release on service shutdown
portMappings.add(createPortMappingEntryObject(
strProtocol,
externalPort,
strLocalAddress,
internalPort,
description
));
return true;
} else {
logger.error(getPortForwardingExceptionMessage(
strProtocol,
externalPort,
strLocalAddress,
internalPort,
description
));
return false;
}
}

private boolean addPortMapping(
int externalPort,
int internalPort,
String strLocalAddress,
String strProtocol,
String description) {

try {
return gateway.addPortMapping(
externalPort,
internalPort,
strLocalAddress,
strProtocol,
description
);
} catch (Exception e) {
logger.error(getPortForwardingExceptionMessage(
strProtocol,
externalPort,
strLocalAddress,
internalPort,
description
), e);
return false;
}
}

private static PortMappingEntry createPortMappingEntryObject(
String protocol,
int externalPort,
String localAddress,
int internalPort,
String description) {

PortMappingEntry pm = new PortMappingEntry();
pm.setProtocol(protocol);
pm.setExternalPort(externalPort);
pm.setInternalClient(localAddress);
pm.setInternalPort(internalPort);
pm.setPortMappingDescription(description);
return pm;
}

private static String getPortForwardingExceptionMessage(
String strProtocol,
int externalPort,
String strLocalAddress,
int internalPort,
String description) {
return String.format(
"Failed to forward %s port %s to %s:%s for %s.",
strProtocol,
externalPort,
strLocalAddress,
internalPort,
description
);
}

/**
* Deletes all port mappings which were created by calls to
* {@link #addPortMapping(int, int, UpnpProtocol, String)}.
*/
public void deleteAllPortMappings() {
ListIterator<PortMappingEntry> iter = portMappings.listIterator();
while (iter.hasNext()) {
PortMappingEntry entry = iter.next();
int externalPort = entry.getExternalPort();
String protocol = entry.getProtocol();

if (deletePortMapping(externalPort, protocol)) {
logger.info(
"Deleted port mapping of {} port {}.",
protocol,
externalPort
);
iter.remove();
}
}
}

private boolean deletePortMapping(int externalPort, String protocol) {
try {
return gateway.deletePortMapping(externalPort, protocol);
} catch (Exception e) {
logger.error(String.format(
"Failed to delete port mapping of %s port %s",
protocol,
externalPort
), e);
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This file is part of RskJ
* Copyright (C) 2019 RSK Labs Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package co.rsk.net.discovery.upnp;

public enum UpnpProtocol {
TCP, UDP
}
Loading

0 comments on commit 87cb336

Please sign in to comment.