/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library 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.
*
* The ethereumJ library 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 the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.net.rlpx.discover;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import org.ethereum.config.SystemProperties;
import org.ethereum.crypto.ECKey;
import org.ethereum.net.rlpx.Node;
import org.ethereum.net.server.WireTrafficStats;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.net.BindException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.ethereum.crypto.HashUtil.sha3;
@Component
public class UDPListener {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover");
private int port;
private String address;
private String[] bootPeers;
@Autowired
private NodeManager nodeManager;
@Autowired
SystemProperties config = SystemProperties.getDefault();
@Autowired
WireTrafficStats stats;
private Channel channel;
private volatile boolean shutdown = false;
private DiscoveryExecutor discoveryExecutor;
@Autowired
public UDPListener(final SystemProperties config, final NodeManager nodeManager) {
this.config = config;
this.nodeManager = nodeManager;
this.address = config.bindIp();
port = config.listenPort();
if (config.peerDiscovery()) {
bootPeers = config.peerDiscoveryIPList().toArray(new String[0]);
}
if (config.peerDiscovery()) {
if (port == 0) {
logger.error("Discovery can't be started while listen port == 0");
} else {
new Thread("UDPListener") {
@Override
public void run() {
try {
UDPListener.this.start(bootPeers);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}.start();
}
}
}
public UDPListener(String address, int port) {
this.address = address;
this.port = port;
}
public static Node parseNode(String s) {
int idx1 = s.indexOf('@');
int idx2 = s.indexOf(':');
String id = s.substring(0, idx1);
String host = s.substring(idx1 + 1, idx2);
int port = Integer.parseInt(s.substring(idx2+1));
return new Node(Hex.decode(id), host, port);
}
public void start(String[] args) throws Exception {
logger.info("Discovery UDPListener started");
NioEventLoopGroup group = new NioEventLoopGroup(1);
final List<Node> bootNodes = new ArrayList<>();
for (String boot: args) {
// since discover IP list has no NodeIds we will generate random but persistent
bootNodes.add(Node.instanceOf(boot));
}
nodeManager.setBootNodes(bootNodes);
try {
discoveryExecutor = new DiscoveryExecutor(nodeManager);
discoveryExecutor.start();
while (!shutdown) {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
public void initChannel(NioDatagramChannel ch)
throws Exception {
ch.pipeline().addLast(stats.udp);
ch.pipeline().addLast(new PacketDecoder());
MessageHandler messageHandler = new MessageHandler(ch, nodeManager);
nodeManager.setMessageSender(messageHandler);
ch.pipeline().addLast(messageHandler);
}
});
channel = b.bind(address, port).sync().channel();
channel.closeFuture().sync();
if (shutdown) {
logger.info("Shutdown discovery UDPListener");
break;
}
logger.warn("UDP channel closed. Recreating after 5 sec pause...");
Thread.sleep(5000);
}
} catch (Exception e) {
if (e instanceof BindException && e.getMessage().contains("Address already in use")) {
logger.error("Port " + port + " is busy. Check if another instance is running with the same port.");
} else {
logger.error("Can't start discover: ", e);
}
} finally {
group.shutdownGracefully().sync();
}
}
public void close() {
logger.info("Closing UDPListener...");
shutdown = true;
if (channel != null) {
try {
channel.close().await(10, TimeUnit.SECONDS);
} catch (Exception e) {
logger.warn("Problems closing UDPListener", e);
}
}
if (discoveryExecutor != null) {
try {
discoveryExecutor.close();
} catch (Exception e) {
logger.warn("Problems closing DiscoveryExecutor", e);
}
}
}
public static void main(String[] args) throws Exception {
String address = "0.0.0.0";
int port = 30303;
if (args.length >= 2) {
address = args[0];
port = Integer.parseInt(args[1]);
}
new UDPListener(address, port).start(Arrays.copyOfRange(args, 2, args.length));
}
}