package freenet.io.comm;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Random;
import freenet.io.AddressTracker;
import freenet.io.comm.Peer.LocalAddressException;
import freenet.node.Node;
import freenet.node.PrioRunnable;
import freenet.support.Logger;
import freenet.support.io.NativeThread;
import freenet.support.transport.ip.IPUtil;
public class UdpSocketHandler implements PrioRunnable, PacketSocketHandler, PortForwardSensitiveSocketHandler {
private final DatagramSocket _sock;
private final InetAddress _bindTo;
private final AddressTracker tracker;
private IncomingPacketFilter lowLevelFilter;
/** RNG for debugging, used with _dropProbability.
* NOT CRYPTO SAFE. DO NOT USE FOR THINGS THAT NEED CRYPTO SAFE RNG!
*/
private Random dropRandom;
/** If >0, 1 in _dropProbability chance of dropping a packet; for debugging */
private int _dropProbability;
// Icky layer violation, but we need to know the Node to work around the EvilJVMBug.
private final Node node;
private static volatile boolean logMINOR;
private static volatile boolean logDEBUG;
private boolean _isDone;
private volatile boolean _active = true;
private final int listenPort;
private final String title;
private boolean _started;
private long startTime;
private final IOStatisticCollector collector;
static {
Logger.registerClass(UdpSocketHandler.class);
}
public UdpSocketHandler(int listenPort, InetAddress bindto, Node node, long startupTime, String title, IOStatisticCollector collector) throws SocketException {
this.node = node;
this.collector = collector;
this.title = title;
_bindTo = bindto;
// Keep the Updater code in, just commented out, for now
// We may want to be able to do on-line updates.
// if (Updater.hasResource()) {
// _sock = (DatagramSocket) Updater.getResource();
// } else {
this.listenPort = listenPort;
_sock = new DatagramSocket(listenPort, bindto);
int sz = _sock.getReceiveBufferSize();
if(sz < 65536) {
_sock.setReceiveBufferSize(65536);
}
try {
// Exit reasonably quickly
_sock.setReuseAddress(true);
} catch (SocketException e) {
throw new RuntimeException(e);
}
try {
_sock.setTrafficClass(node.getTrafficClass().value);
} catch (SocketException e) {
Logger.error(this, "Failed to setTrafficClass with "+node.getTrafficClass().value,e);
}
// }
// Only used for debugging, no need to seed from Yarrow
dropRandom = node.fastWeakRandom;
tracker = AddressTracker.create(node.lastBootID, node.runDir(), listenPort);
tracker.startSend(startupTime);
}
/** Must be called, or we will NPE in run() */
@Override
public void setLowLevelFilter(IncomingPacketFilter f) {
lowLevelFilter = f;
}
public InetAddress getBindTo() {
return _bindTo;
}
public String getTitle() {
return title;
}
@Override
public void run() { // Listen for packets
tracker.startReceive(System.currentTimeMillis());
try {
runLoop();
} catch (Throwable t) {
// Impossible? It keeps on exiting. We get the below,
// but not this...
try {
System.err.print(t.getClass().getName());
System.err.println();
} catch (Throwable tt) {}
try {
System.err.print(t.getMessage());
System.err.println();
} catch (Throwable tt) {}
try {
System.gc();
System.runFinalization();
System.gc();
System.runFinalization();
} catch (Throwable tt) {}
try {
Runtime r = Runtime.getRuntime();
System.err.print(r.freeMemory());
System.err.println();
System.err.print(r.totalMemory());
System.err.println();
} catch (Throwable tt) {}
try {
t.printStackTrace();
} catch (Throwable tt) {}
} finally {
System.err.println("run() exiting for UdpSocketHandler on port "+_sock.getLocalPort());
Logger.error(this, "run() exiting for UdpSocketHandler on port "+_sock.getLocalPort());
synchronized (this) {
_isDone = true;
notifyAll();
}
}
}
private void runLoop() {
byte[] buf = new byte[MAX_RECEIVE_SIZE];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while (_active) {
try {
realRun(packet);
} catch (Throwable t) {
System.err.println("Caught "+t);
t.printStackTrace(System.err);
Logger.error(this, "Caught " + t, t);
}
}
}
private void realRun(DatagramPacket packet) {
// Single receiving thread
boolean gotPacket = getPacket(packet);
long now = System.currentTimeMillis();
if (gotPacket) {
long startTime = System.currentTimeMillis();
Peer peer = new Peer(packet.getAddress(), packet.getPort());
tracker.receivedPacketFrom(peer);
long endTime = System.currentTimeMillis();
if(endTime - startTime > 50) {
if(endTime-startTime > 3000) {
Logger.error(this, "packet creation took "+(endTime-startTime)+"ms");
} else {
if(logMINOR) Logger.minor(this, "packet creation took "+(endTime-startTime)+"ms");
}
}
byte[] data = packet.getData();
int offset = packet.getOffset();
int length = packet.getLength();
try {
if(logMINOR) Logger.minor(this, "Processing packet of length "+length+" from "+peer);
startTime = System.currentTimeMillis();
lowLevelFilter.process(data, offset, length, peer, now);
endTime = System.currentTimeMillis();
if(endTime - startTime > 50) {
if(endTime-startTime > 3000) {
Logger.error(this, "processing packet took "+(endTime-startTime)+"ms");
} else {
if(logMINOR) Logger.minor(this, "processing packet took "+(endTime-startTime)+"ms");
}
}
if(logMINOR) Logger.minor(this,
"Successfully handled packet length " + length);
} catch (Throwable t) {
Logger.error(this, "Caught " + t + " from "
+ lowLevelFilter, t);
}
} else {
if(logDEBUG) Logger.debug(this, "No packet received");
}
}
private static final int MAX_RECEIVE_SIZE = 1500;
private boolean getPacket(DatagramPacket packet) {
try {
_sock.receive(packet);
InetAddress address = packet.getAddress();
boolean isLocal = !IPUtil.isValidAddress(address, false);
collector.addInfo(address, packet.getPort(),
getHeadersLength(address) + packet.getLength(), 0, isLocal);
} catch (SocketTimeoutException e1) {
return false;
} catch (IOException e2) {
if (!_active) { // closed, just return silently
return false;
} else {
throw new RuntimeException(e2);
}
}
if(logMINOR) Logger.minor(this, "Received packet");
return true;
}
/**
* Send a block of encoded bytes to a peer. This is called by
* send, and by IncomingPacketFilter.processOutgoing(..).
* @param blockToSend The data block to send.
* @param destination The peer to send it to.
*/
@Override
public void sendPacket(byte[] blockToSend, Peer destination, boolean allowLocalAddresses) throws LocalAddressException {
assert(blockToSend != null);
if(!_active) {
Logger.error(this, "Trying to send packet but no longer active");
// It is essential that for recording accurate AddressTracker data that we don't send any more
// packets after shutdown.
return;
}
// there should be no DNS needed here, but go ahead if we can, but complain doing it
if( destination.getAddress(false, allowLocalAddresses) == null ) {
Logger.error(this, "Tried sending to destination without pre-looked up IP address(needs a real Peer.getHostname()): null:" + destination.getPort(), new Exception("error"));
if( destination.getAddress(true, allowLocalAddresses) == null ) {
Logger.error(this, "Tried sending to bad destination address: null:" + destination.getPort(), new Exception("error"));
return;
}
}
if (_dropProbability > 0) {
if (dropRandom.nextInt() % _dropProbability == 0) {
Logger.normal(this, "DROPPED: " + _sock.getLocalPort() + " -> " + destination.getPort());
return;
}
}
InetAddress address = destination.getAddress(false, allowLocalAddresses);
assert(address != null);
int port = destination.getPort();
DatagramPacket packet = new DatagramPacket(blockToSend, blockToSend.length);
packet.setAddress(address);
packet.setPort(port);
try {
_sock.send(packet);
tracker.sentPacketTo(destination);
boolean isLocal = (!IPUtil.isValidAddress(address, false)) && (IPUtil.isValidAddress(address, true));
collector.addInfo(address, port, 0, getHeadersLength(address) + blockToSend.length, isLocal);
if(logMINOR) Logger.minor(this, "Sent packet length "+blockToSend.length+" to "+address+':'+port);
} catch (IOException e) {
if(packet.getAddress() instanceof Inet6Address) {
Logger.normal(this, "Error while sending packet to IPv6 address: "+destination+": "+e);
} else {
Logger.error(this, "Error while sending packet to " + destination+": "+e, e);
}
}
}
// CompuServe use 1400 MTU; AOL claim 1450; DFN@home use 1448.
// http://info.aol.co.uk/broadband/faqHomeNetworking.adp
// http://www.compuserve.de/cso/hilfe/linux/hilfekategorien/installation/contentview.jsp?conid=385700
// http://www.studenten-ins-netz.net/inhalt/service_faq.html
// officially GRE is 1476 and PPPoE is 1492.
// unofficially, PPPoE is often 1472 (seen in the wild). Also PPPoATM is sometimes 1472.
static final int MAX_ALLOWED_MTU = 1280;
static final int UDPv4_HEADERS_LENGTH = 28;
static final int UDPv6_HEADERS_LENGTH = 48;
// conservative estimation when AF is not known
public static final int UDP_HEADERS_LENGTH = UDPv6_HEADERS_LENGTH;
static final int MIN_IPv4_MTU = 576;
static final int MIN_IPv6_MTU = 1280;
// conservative estimation when AF is not known
public static final int MIN_MTU = MIN_IPv4_MTU;
private volatile int maxPacketSize = MAX_ALLOWED_MTU;
/**
* @return The maximum packet size supported by this SocketManager, not including transport (UDP/IP) headers.
*/
@Override
public int getMaxPacketSize() {
return maxPacketSize;
}
public int calculateMaxPacketSize() {
int oldSize = maxPacketSize;
int newSize = innerCalculateMaxPacketSize();
maxPacketSize = newSize;
if(oldSize != newSize)
System.out.println("Max packet size: "+newSize);
return maxPacketSize;
}
/** Recalculate the maximum packet size */
int innerCalculateMaxPacketSize() { //FIXME: what about passing a peerNode though and doing it on a per-peer basis? How? PMTU would require JNI, although it might be worth it...
final int minAdvertisedMTU = node.getMinimumMTU();
return maxPacketSize = Math.min(MAX_ALLOWED_MTU, minAdvertisedMTU) - UDP_HEADERS_LENGTH;
}
@Override
public int getPacketSendThreshold() {
return getMaxPacketSize() - 100;
}
public void start() {
if(!_active) return;
synchronized(this) {
_started = true;
startTime = System.currentTimeMillis();
}
node.executor.execute(this, "UdpSocketHandler for port "+listenPort);
}
public void close() {
Logger.normal(this, "Closing.", new Exception("error"));
synchronized (this) {
_active = false;
_sock.close();
if(!_started) return;
while (!_isDone) {
try {
wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
tracker.storeData(node.bootID, node.runDir(), listenPort);
}
public int getDropProbability() {
return _dropProbability;
}
public void setDropProbability(int dropProbability) {
_dropProbability = dropProbability;
}
public int getPortNumber() {
return _sock.getLocalPort();
}
@Override
public String toString() {
return _sock.getLocalAddress() + ":" + _sock.getLocalPort();
}
@Override
public int getHeadersLength() {
return UDP_HEADERS_LENGTH;
}
@Override
public int getHeadersLength(Peer peer) {
return getHeadersLength(peer.getAddress(false));
}
int getHeadersLength(InetAddress addr) {
return addr == null || addr instanceof Inet6Address ? UDPv6_HEADERS_LENGTH : UDPv4_HEADERS_LENGTH;
}
public AddressTracker getAddressTracker() {
return tracker;
}
@Override
public void rescanPortForward() {
tracker.rescan();
}
@Override
public AddressTracker.Status getDetectedConnectivityStatus() {
return tracker.getPortForwardStatus();
}
@Override
public int getPriority() {
return NativeThread.MAX_PRIORITY;
}
public long getStartTime() {
return startTime;
}
}