Created
December 19, 2019 21:02
-
-
Save jkilpatr/0d004bdbd1618ca73d5581c3f8baa0ae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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