Skip to content

Instantly share code, notes, and snippets.

@jkilpatr
Created December 19, 2019 21:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkilpatr/0d004bdbd1618ca73d5581c3f8baa0ae to your computer and use it in GitHub Desktop.
Save jkilpatr/0d004bdbd1618ca73d5581c3f8baa0ae to your computer and use it in GitHub Desktop.
package com.althea.althea_android;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.Random;
// todo, do we actually need a routing table in this implementation? We will have at most one neighbor
// this depends on how we need to configure the next hop
public class Babel implements Runnable {
private static final short babel_port = 6696;
private static final String babel_addr = "ff02:0:0:0:0:0:1:6";
private int our_seqno = 0;
private byte[] our_routerId = getRouterID();
private int our_hello_seqno = 0;
private InetAddress own_address;
@Override
public void run() {
try {
InetAddress babel_peer_discovery_address = InetAddress.getByName(babel_addr);
NetworkInterface ni = NetworkInterface.getByName("wlan0");
getOwnAddress(ni.getInetAddresses());
MulticastSocket babel_multicast_socket = new MulticastSocket(babel_port);
babel_multicast_socket.setBroadcast(true);
babel_multicast_socket.joinGroup(babel_peer_discovery_address);
babel_multicast_socket.setNetworkInterface(ni);
babel_multicast_socket.setTrafficClass(0xc0);
babel_multicast_socket.setReuseAddress(true);
babel_multicast_socket.setTimeToLive(1);
// shortest possible timeout, in theory we can just block because if there is no neighbor
// to talk to us we don't need to be active, but lets try and avoid that anyways, no we
// can't use the proper async sockets in Java because they are on a much later Android target API
babel_multicast_socket.setSoTimeout(1000);
System.out.println("Bound to socket and starting babel loop");
while(true) {
parsePacket(babel_peer_discovery_address, babel_multicast_socket, babel_port);
Thread.sleep(4000);
}
}
catch (UnknownHostException e) {
System.out.println("Error in Babel service, Unknown Host");
e.printStackTrace();
}
catch(IOException e) {
System.out.println("Error in Babel service, IO exception");
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("Babel thread interrupted, terminating");
e.printStackTrace();
}
}
private void getOwnAddress(Enumeration<InetAddress> addresses) {
while (addresses.hasMoreElements()) {
InetAddress val = addresses.nextElement();
System.out.println(val.getHostAddress());
if(isv6RoutablePrivate(val)) {
System.out.println("Our address is:");
System.out.println(val.getHostAddress());
own_address = val;
}
}
// we could return inline, but the system seems to use the last
// valid address, so we have to finish iterating to get it
if(own_address == null) {
System.out.println("Could not get our own address!");
System.exit(1);
}
return;
}
private LinkedList<Byte> start_packet() {
LinkedList<Byte> packet = new LinkedList<Byte>();
// The 'magic' see RFC 6126 section 4.2
packet.add((byte) 42);
// todo double check this might not be the same as the althea fork
packet.add((byte) 2);
// set size to zero it will be filled in by end_packet
packet.add((byte) 0);
packet.add((byte) 0);
return packet;
}
private void end_packet(LinkedList<Byte> packet) {
System.out.println("ending packet");
int len = packet.size() - 4;
packet.set(2, (byte) (len >> 8));
packet.set(3, (byte) len);
}
private void hello_bytes(LinkedList<Byte> packet) {
System.out.println("added Hello");
// hello tlv identifier
packet.add((byte) 4);
// length of the hello tlv after the header, or 6 bytes
packet.add((byte) 6);
// reserved short field
packet.add((byte) 0);
packet.add((byte) 0);
// the hello sequence number (not the same as the babel seqno), please read the spec before messing with it
packet.add((byte) (our_hello_seqno >> 8));
packet.add((byte) our_hello_seqno);
incrementHelloSeqno();
// up to five seconds until the next hello
packet.add((byte) 1);
packet.add((byte) 245);
}
private void router_id_bytes(LinkedList<Byte> packet, byte[] routerId) {
System.out.println("added Router ID");
// router id tlv identifier
packet.add((byte) 6);
// length of the router id tlv after the header or 10 bytes
packet.add((byte) 10);
// reserved short field
packet.add((byte) 0);
packet.add((byte) 0);
// this is a fixed 8 byte field, array used for brevity
for(int i = 0; i < routerId.length; i++){
packet.add(routerId[i]);
}
}
private void update_bytes(LinkedList<Byte> packet, InetAddress prefix, byte plen, int interval_seconds, int seqno, short metric) {
System.out.println("added update");
byte[] address = addressToEncodedArray(prefix);
// update tlv identifier
packet.add((byte) 8);
// length of the update tlv after the header, ten bytes plus encoded address length
// not the same as prefix length!
packet.add((byte) (14 + address[1]));
// address encoding
packet.add(address[0]);
// flags, currently hardcoded to zero
packet.add((byte) 0);
// prefix length, 192.168.10.1/24 the 24 is the 'prefix length'
packet.add((byte) plen);
// this field is used for compressing prefixes if we send multiple updates within that prefix
// not implemented here
packet.add((byte) 0);
// interval until the next update
byte[] interval = secondsToInverval(interval_seconds);
packet.add(interval[0]);
packet.add(interval[1]);
// the babel sequence number, please read the spec before messing with it
packet.add((byte) (seqno >> 8));
packet.add((byte) seqno);
// the route metric,
packet.add((byte) (metric >> 8));
packet.add((byte) metric);
// the route price,
packet.add((byte) 0);
packet.add((byte) 0);
packet.add((byte) 0);
packet.add((byte) 0);
// the actual prefix
for(int i = 2; i < address.length; i++){
packet.add(address[i]);
}
}
private void IHU_bytes(LinkedList<Byte> packet, InetAddress neigh_address) {
System.out.println("added IHU");
// the encoded neighbor address
byte[] encoded_address = addressToEncodedArray(neigh_address);
// IHU tlv identifier
packet.add((byte) 5);
// length of the IHU tlv after the header
packet.add((byte) (6 + encoded_address[1]));
// address encoding
packet.add(encoded_address[0]);
// reserved byte field
packet.add((byte) 0);
// rx cost, since we currently have no route computation system at all, it's zero
packet.add((byte) 0);
packet.add((byte) 0);
// up to five seconds until the next IHU
byte[] interval = secondsToInverval(5);
packet.add(interval[0]);
packet.add(interval[1]);
// add the encoded address
for(int i = 2; i < encoded_address.length; i++) {
packet.add(encoded_address[i]);
}
}
private void sendStandaloneHello(InetAddress address, MulticastSocket hello_send_socket, int port) {
LinkedList<Byte> packet = start_packet();
hello_bytes(packet);
end_packet(packet);
try {
DatagramPacket send_packet = new DatagramPacket(packet_list_to_bytes(packet), 0, packet.size(), address, port);
hello_send_socket.send(send_packet);
System.out.println("Hello sent");
} catch (SocketException e) {
System.out.println("Could not bind to babel hello send socket");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Cloud not send babel Hello");
e.printStackTrace();
}
}
private void sendPacket(LinkedList<Byte> packet, InetAddress broadcast_address, MulticastSocket m, short port) {
try {
DatagramPacket send_packet = new DatagramPacket(packet_list_to_bytes(packet), 0, packet.size(), broadcast_address, port);
m.send(send_packet);
} catch (SocketException e) {
System.out.println("Could not bind to babel IHU send socket");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Cloud not send babel Hello");
e.printStackTrace();
}
}
private void parsePacket(InetAddress broadcast_address, MulticastSocket m, short port) {
byte[] buf = new byte[1024];
// used to keep track of hellos we have seen so we don't produce duplicate IHU's
HashSet<InetAddress> hello_list = new HashSet();
// remember this is the raw socket output so it will contain potentially many
// HELLO/IHU messages
DatagramPacket hellos = new DatagramPacket(buf, buf.length);
LinkedList<Byte> response_packet = start_packet();
// always add a hello
hello_bytes(response_packet);
// send our own route
router_id_bytes(response_packet, our_routerId);
update_bytes(response_packet, own_address,(byte) 128, 10, our_seqno,(short) 0);
try {
// since each call to receive gets only a single packet we need to call it over
// and over again until we hit an exception
while(true) {
m.receive(hellos);
System.out.println("Got Babel packet");
byte[] data = hellos.getData();
if(data[0] != 42 || data[1] != 2) {
System.out.println("Got non babel packet!");
continue;
}
else if(addressEqual(m.getInterface(),hellos.getAddress())) {
System.out.println("Got a packet from ourselves, ignoring");
continue;
}
// the third and fourth bytes represent the body length of the packet
// we do some bitshifting to assemble it into a single integer, here we assume
// an int is at least 32 bytes so we don't get into any signed BS
int babel_packet_length = 0;
babel_packet_length = (data[2] << 8);
babel_packet_length = babel_packet_length | data[3];
int index = 4;
while (index < babel_packet_length) {
//System.out.println(String.format("index %d len %d", index, babel_packet_length));
if(data[index] == 4) {
byte size = data[++index];
//System.out.println(String.format("This is a hello len %d", size));
InetAddress neigh_address = hellos.getAddress();
if(!hello_list.contains(neigh_address)) {
IHU_bytes(response_packet, neigh_address);
hello_list.add(neigh_address);
}
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 5) {
//System.out.println("this is a IHU");
byte size = data[++index];
// todo update reach metrics?
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 6) {
//System.out.println("this is a Router ID");
byte size = data[++index];
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 7) {
//System.out.println("this is a Next Hop");
byte size = data[++index];
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 8) {
//System.out.println("this is an update");
byte size = data[++index];
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 9) {
//System.out.println("this is a route request");
// todo we MUST respond to this
byte size = data[++index];
index += unsignedToBytes(size) + 1;
}
else if(data[index] == 10) {
//System.out.println("this is a seqno request");
byte size = data[++index];
index += unsignedToBytes(size) + 1;
} else if(data[index] == 0) {
//System.out.println("this is a pad1");
index++;
} else if(data[index] == 1) {
byte size = data[++index];
index += unsignedToBytes(size) + 1;
} else {
System.out.println(String.format("Unknown TLV! %d", unsignedToBytes(data[index])));
break;
}
}
}
} catch (IOException e) {
if(response_packet.size() > 0) {
System.out.println(String.format("sending packet with hello seqno %d", our_hello_seqno));
end_packet(response_packet);
sendPacket(response_packet, broadcast_address, m, port);
}
System.out.println("Failed to receive, probably nothing on the socket");
}
}
// converts a linked list of Bytes into a primitive array of bytes
private byte[] packet_list_to_bytes(LinkedList<Byte> val) {
byte[] ret = new byte[val.size()];
// iterate over the linked list in reverse to fix byteorder issues
// but also maintain dev readability.
for (int i = 0; i < val.size(); i++) {
ret[i] = val.get(i);
}
return ret;
}
// the fact that bytes are signed in java is an abomination
public static int unsignedToBytes(byte b) {
return b & 0xFF;
}
// Takes an address returns a byte array that is that address encoded
// into the correct address format, the first byte is the AE encoding type
// as specified in section 4.1.3 of the Babel spec the second byte is the
// length of this encoding, the remaining bytes are the encoded address
public byte[] addressToEncodedArray(InetAddress addr) {
if(addr.getClass() == Inet4Address.class){
byte[] ret = new byte[6];
ret[0] = 1;
ret[1] = 4;
// since we insert everything else backwards we have to flip these too
ret[2] = addr.getAddress()[0];
ret[3] = addr.getAddress()[1];
ret[4] = addr.getAddress()[2];
ret[5] = addr.getAddress()[3];
return ret;
} else if (addr.getClass() == Inet6Address.class) {
if(isMCLinkLocal(addr)) {
byte[] ret = new byte[10];
ret[0] = 3;
ret[1] = 8;
// since we insert everything else backwards we have to flip these too
for(int i = 2; i < 10; i++) {
ret[i] = addr.getAddress()[i+6];
}
return ret;
}
else {
byte[] ret = new byte[18];
ret[0] = 2;
ret[1] = 16;
for(int i = 2; i < 18; i++) {
ret[i] = addr.getAddress()[i-2];
}
return ret;
}
}
// error program will crash, in theory totally unreachable
return new byte[0];
}
private String printHex(byte[] in) {
String ret = "";
for(int i = 0; i < in.length; i++) {
ret += String.format("%02x", in[i]);
}
return ret;
}
private Boolean isMCLinkLocal(InetAddress a) {
byte one = a.getAddress()[0];
byte two = a.getAddress()[1];
if (a.getClass() != Inet6Address.class) {
return false;
} else if (one == -2 && two == -128) {
return true;
}
return false;
}
private Boolean isv6RoutablePrivate(InetAddress a) {
byte one = a.getAddress()[0];
byte two = a.getAddress() [1];
System.out.println(two);
if (a.getClass() != Inet6Address.class) {
return false;
} else if (one == -3) {
return true;
} else if (one == -2 && two != -128) {
// this allows the android emulator which provides fe::/10 addresses
return true;
}
return false;
}
private String printHex(LinkedList<Byte> in) {
String ret = "";
for(int i = 0; i < in.size(); i++) {
ret += String.format("%02x", in.get(i));
}
return ret;
}
private Boolean addressEqual(InetAddress a, InetAddress b) {
if(a.getClass() != b.getClass()) {
return false;
}
else if(a.getClass() == Inet6Address.class) {
for(int i = 0; i < 16; i++) {
if(a.getAddress()[i] != b.getAddress()[i]) {
return false;
}
}
return true;
}
else if(a.getClass() == Inet4Address.class) {
for(int i = 0; i < 4; i++) {
if(a.getAddress()[i] != b.getAddress()[i]) {
return false;
}
}
return true;
}
System.out.println("Error in addressEqual");
System.exit(1);
return false;
}
// the router ID SHOULD be derived from the MAC address but that's difficult to get in Android
// you need location permissions and such, so for now it's just random on startup, computing it
// properly would result in faster network re-convergence in some cases
private byte[] getRouterID() {
byte[] rid = new byte[8];
Random r = new Random();
r.nextBytes(rid);
return rid;
}
// converts a integer number of seconds into two bytes of interval
private byte[] secondsToInverval(int seconds) {
int centiseconds = seconds * 100;
byte[] r = new byte[2];
r[0] =(byte) (centiseconds >> 8);
r[1] =(byte) centiseconds;
return r;
}
private void incrementHelloSeqno() {
if(our_hello_seqno > 65536) {
our_hello_seqno = 0;
}
else {
our_hello_seqno++;
}
}
private void incrementSeqno() {
if(our_seqno > 65536) {
our_seqno = 0;
}
else {
our_seqno++;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment