Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UPnP support for peer discovery. Resolves #1036 #1088

Merged
merged 5 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 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,7 @@ dependencyVerification {
'net.jcip:jcip-annotations:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.apache.commons:commons-lang3:8ac96fc686512d777fca85e144f196cd7cfe0c0aec23127229497d1a38ff651c',
'org.awaitility:awaitility:a02982e89585a52c1c84296a895bfeb86ea250cca1a53bcfc8a14092fffa87c4',
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bclcrypto-jdk15on:7d03ba37df4d0ddc4ea40d56554324c6f18062a930edadb0a1b3acbbbea28efc',
'org.ethereum:leveldbjni-all:18da00444c77080d4422b16c9d4750c4addabda350b702b4a6d628b86658e585',
'org.fusesource.hawtjni:hawtjni-runtime:74fe9764e1fb1ef20b159dbca2d29abd6de292082ce3fcf538f81ac912390416',
Expand Down
35 changes: 30 additions & 5 deletions 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 @@ -235,6 +236,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 @@ -771,12 +773,11 @@ public List<InternalService> buildInternalServices() {
if (rpcWebSocketEnabled) {
internalServices.add(getWeb3WebSocketServer());
}
if (getRskSystemProperties().isPeerDiscoveryByUpnpEnabled()) {
internalServices.add(getUpnpService());
}
if (getRskSystemProperties().isPeerDiscoveryEnabled()) {
internalServices.add(new UDPServer(
getRskSystemProperties().getBindAddress().getHostAddress(),
getRskSystemProperties().getPeerPort(),
getPeerExplorer()
));
internalServices.add(getUDPServer());
}
if (getRskSystemProperties().isSyncEnabled()) {
internalServices.add(getSyncPool());
Expand Down Expand Up @@ -1626,6 +1627,30 @@ private MinerClock getMinerClock() {
return minerClock;
}

private UDPServer getUDPServer() {
if (getRskSystemProperties().isPeerDiscoveryByUpnpEnabled()) {
return new UDPServer(
getRskSystemProperties().getBindAddress().getHostAddress(),
getRskSystemProperties().getPeerPort(),
getPeerExplorer(),
getUpnpService()
);
} else {
return new UDPServer(
getRskSystemProperties().getBindAddress().getHostAddress(),
getRskSystemProperties().getPeerPort(),
getPeerExplorer()
);
}
}

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 @@ -35,6 +37,7 @@
*/
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 +46,17 @@ public class UDPServer implements InternalService {
private volatile boolean shutdown = false;

private PeerExplorer peerExplorer;
private UpnpService upnpService;

public UDPServer(String address, int port, PeerExplorer peerExplorer) {
this(address, port, peerExplorer, null);
}

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

@Override
Expand All @@ -59,6 +68,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 +96,18 @@ public void startUDPServer() throws InterruptedException {
group.shutdownGracefully().sync();
}

private void doPortMappingIfEnabled() {
if (upnpService != null) {
upnpService.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 synchronized 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 synchronized 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