Files
briar/components/net/sf/briar/plugins/socket/LanSocketPlugin.java
2011-10-30 20:50:55 +00:00

195 lines
6.0 KiB
Java

package net.sf.briar.plugins.socket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.api.plugins.StreamPluginCallback;
import net.sf.briar.api.transport.StreamTransportConnection;
import net.sf.briar.util.ByteUtils;
/** A socket plugin that supports exchanging invitations over a LAN. */
public class LanSocketPlugin extends SimpleSocketPlugin {
private static final Logger LOG =
Logger.getLogger(LanSocketPlugin.class.getName());
LanSocketPlugin(Executor executor, StreamPluginCallback callback,
long pollingInterval) {
super(executor, callback, pollingInterval);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public StreamTransportConnection sendInvitation(int code, long timeout) {
// Calculate the group address and port from the invitation code
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
// Bind a multicast socket for receiving packets
MulticastSocket ms;
try {
ms = new MulticastSocket(mcast.getPort());
ms.setInterface(chooseInterface());
ms.joinGroup(mcast.getAddress());
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
return null;
}
// Listen until a valid packet is received or the timeout occurs
byte[] buffer = new byte[2];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
long now = System.currentTimeMillis();
long end = now + timeout;
try {
while(now < end) {
try {
ms.setSoTimeout((int) (end - now));
ms.receive(packet);
byte[] b = packet.getData();
int off = packet.getOffset();
int len = packet.getLength();
int port = parsePacket(b, off, len);
if(port >= 32768 && port < 65536) {
try {
// Connect back on the advertised TCP port
Socket s = new Socket(packet.getAddress(), port);
// Close the multicast socket
ms.leaveGroup(mcast.getAddress());
ms.close();
return new SocketTransportConnection(s);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
}
}
} catch(SocketTimeoutException e) {
break;
}
now = System.currentTimeMillis();
}
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
return null;
}
private InetSocketAddress convertInvitationCodeToMulticastGroup(int code) {
Random r = new Random(code);
byte[] b = new byte[5];
r.nextBytes(b);
// The group address is 239.random.random.random, excluding 0 and 255
byte[] group = new byte[4];
group[0] = (byte) 239;
group[1] = legalAddressByte(b[0]);
group[2] = legalAddressByte(b[1]);
group[3] = legalAddressByte(b[2]);
// The port is random in the range 32768 - 65535, inclusive
int port = ByteUtils.readUint16(b, 3);
if(port < 32768) port += 32768;
InetAddress address;
try {
address = InetAddress.getByAddress(group);
} catch(UnknownHostException badAddressLength) {
throw new RuntimeException(badAddressLength);
}
return new InetSocketAddress(address, port);
}
private byte legalAddressByte(byte b) {
if(b == 0) return 1;
if(b == (byte) 255) return (byte) 254;
return b;
}
private InetAddress chooseInterface() throws IOException {
// Try to find a LAN interface that supports multicast
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
for(NetworkInterface iface : Collections.list(ifaces)) {
if(iface.supportsMulticast()) {
Enumeration<InetAddress> addrs = iface.getInetAddresses();
for(InetAddress addr : Collections.list(addrs)) {
if(addr.isLinkLocalAddress() || addr.isSiteLocalAddress())
return addr;
}
}
}
// Bind to 0.0.0.0
return InetAddress.getByName("0.0.0.0");
}
private int parsePacket(byte[] b, int off, int len) {
if(len != 2) return 0;
return ByteUtils.readUint16(b, off);
}
@Override
public StreamTransportConnection acceptInvitation(int code, long timeout) {
// Calculate the group address and port from the invitation code
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
// Bind a TCP socket for receiving connections
ServerSocket ss;
try {
ss = new ServerSocket();
ss.bind(new InetSocketAddress(chooseInterface(), 0));
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
return null;
}
// Bind a multicast socket for sending packets
MulticastSocket ms;
try {
ms = new MulticastSocket();
ms.setInterface(chooseInterface());
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
return null;
}
// Send packets until a connection is received or the timeout expires
byte[] buffer = new byte[2];
ByteUtils.writeUint16(ss.getLocalPort(), buffer, 0);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
packet.setAddress(mcast.getAddress());
packet.setPort(mcast.getPort());
long now = System.currentTimeMillis();
long end = now + timeout;
long interval = 1000;
long nextPacket = now + 1;
try {
while(now < end) {
try {
int wait = (int) (Math.min(end, nextPacket) - now);
ss.setSoTimeout(wait < 1 ? 1 : wait);
return new SocketTransportConnection(ss.accept());
} catch(SocketTimeoutException e) {
if(System.currentTimeMillis() < end) {
ms.send(packet);
now = System.currentTimeMillis();
nextPacket = now + interval;
interval += 1000;
}
}
}
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
return null;
}
}