diff --git a/mphlr.h b/mphlr.h index e3808f01..cf8a8601 100644 --- a/mphlr.h +++ b/mphlr.h @@ -591,3 +591,16 @@ extern int overlay_interface_count; /* Userland overlay mesh packet codes */ #define OF_SELFANNOUNCE 0x01 + +#define OVERLAY_ADDRESS_CACHE_SIZE 1024 +int overlay_abbreviate_address(unsigned char *in,char *out,int *ofs); +int overlay_abbreviate_expand_address(unsigned char *in,int *inofs,unsigned char *out,int *ofs); +int overlay_address_cache_address(unsigned char *sid); +int overlay_abbreviate_cache_lookup(unsigned char *in,unsigned char *out,int *ofs, + int prefix_bytes,int index_bytes); +int overlay_abbreviate_remember_index(int index_byte_count,unsigned char *in,unsigned char *index_bytes); + + +#define OA_RESOLVED 0 /* We expanded the abbreviation */ +#define OA_PLEASEEXPLAIN 1 /* We need the sender to explain their abbreviation */ +#define OA_UNSUPPORTED 2 /* We cannot expand the abbreviation as we do not understand this code */ diff --git a/overlay_abbreviations.c b/overlay_abbreviations.c new file mode 100644 index 00000000..e5372776 --- /dev/null +++ b/overlay_abbreviations.c @@ -0,0 +1,361 @@ +#include "mphlr.h" + +/* + We use 256bit Curve25519 addresses in the overlay mesh. This means we have very large + addresses to send around the place on a regular basis, which is bad. + + We allow addresses to be shortened to save bandwidth, especially for low-bandwidth links. + + For this purpose we have special address prefixes 0x00 - 0x0f which are not allowed to + occur in unabbreviated addresses. + + 0x00 = reserved + 0x01-0x02 = one to two byte address by index. + 0x03 = same as last address. + 0x04 = address matches sender (not currently implemented). + 0x05 = address by prefix of three bytes. + 0x06 = address by prefix of seven bytes. + 0x07 = address by prefix of eleven bytes. + 0x08 = full address followed by one-byte index allocation. + 0x09-0x0b = same as 0x05-0x07, but assign single byte index. + 0x0c = reserved. + 0x0d = same as 0x07, but assign two byte index. + 0x0e = full address followed by two-byte index allocation. + 0x0f = broadcast. + + However, in this implementation we not include support for the two-byte + index code, as it requires 64Kx32=2MB storage, which is too much to ask + of a mesh potato or a cheap smart phone. + + All indexed abbreviations may reference a token which is used to keep + track of the epoch of the abbreviation table. + + This allows us to maintain multiple abbreviation tables at the same time, so + that neighbours we have not spoken to for some time will not be so easily + confused, as any one epoch will remain valid for some time after it has + ceased to be ammended. + + This also allows us to effectively have multiple 256-entry pages, which will + be more efficient than using a 16-bit index table in most instances, especially + since we have the flexibility to reorder frames in an ensemble to minimise the + total length. + + One table is maintained for all interfaces, as we may have neighbours who are + reachable via multiple interfaces. It also helps to minimise memory usage. + + A cache of recently seen addresses is also desirable for conclusively resolving + abbreviated addresses. Failure will allow the birthday paradox to cause us problems + and also allow an attack based on searching for private keys that yield colliding + public key prefixes. This would allow an attacker to mess with the routing and + divert the traffic of other nodes to themselves. Thus some cache or similar policy + is strongly recommended if a node is going to accept address prefixes, especially if + it accepts the shorter prefixes. Seven and eleven byte prefixes should be reasonably + resistant to this attack. + + There is no reason why the address abbreviation table cannot be used as the cache, + excepting for the question of efficiency, as a naive implementation would require + a linear search. However, adding a simple index table can mitigate this, and thus + presents as a sensible solution. + + If a node receives a packet with an abbreviation that it cannot resolve an + abbreviation can ask for clarification, including indicating if it does + not support the abbreviation mode specified. + + Abbreviations are link-local, but clarifications will always be requested by + attempting to contact the abbreviator via normal mesh routing rules. + + We need to take some care so as to allow other crypto-systems, and thus storing the + crypto-system ID byte, although we still limit addresses to 256 bits, so we always need + no more than 33 bytes. In any case, indicating the crypto system is not the problem of + this section, as we are just concerned with abbreviating and expanding the addresses. + + To decode abbreviations supplied by other nodes we need to try to replicate a copy of + their abbreviation table. This is where things get memory intensive (about 8KB per node). + However, we only need the table of one-hop neighbours, and it is reasonable to have a + constrained set of such tables, with a random replacement algorithm. This allows the + structure to be scaled to accomodate varying memory sizes of devices. + + Very memory constrained devices may elect to cache individual entries rather than tables + of individual nodes, but we haven't implemented that here. + + If we send only a prefix, then we need to be careful to make sure that receivers + get some chance to learn the full address if they need it. The question is whether + abbreviation clarification is more or less efficient than sometimes sending the + full address. Given that we already have need for the clarification process, let's + go that way, and change the policy later if appropriate. + +*/ + +typedef struct overlay_address_table { + unsigned char epoch; + char sids[256][SID_SIZE]; + /* 0x00 = not set, which thus limits us to using only 255 (0x01-0xff) of the indexes for + storing addresses. + By spending an extra 256 bytes we reduce, but not eliminate the problem of collisions. + Will think about a complete solution later. + */ + unsigned char byfirstbyte[256][2]; + /* next free entry in sid[] */ + unsigned char next_free; +} overlay_address_table; + +typedef struct sid { + unsigned char b[SID_SIZE]; +} sid; + +typedef struct overlay_address_cache { + int size; + int shift; /* Used to calculat lookup function, which is (b[0].b[1].b[2]>>shift) */ + sid *sids; /* one entry per bucket, to keep things simple. */ + /* XXX Should have a means of changing the hash function so that naughty people can't try + to force our cache to flush with duplicate addresses? + But we must use only the first 24 bits of the address due to abbreviation policies, + so our options are limited. + For now the hash will be the first k bits. + */ +} overlay_address_cache; + +overlay_address_table *abbrs=NULL; +overlay_address_cache *cache=NULL; + +sid overlay_abbreviate_previous_address={{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; + +/* The address of the sender of the current frame. + The ID is used to lookup the abbreviation indexes. + If the ID is -1, then this means that the sender SID has been set, but not looked up. + This just saves some time instead of doing the *_id determination each time, when we might not need + it on most occassions. +*/ +sid overlay_abbreviate_current_sender={{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; +int overlay_abbreviate_current_sender_id=-1; + +int overlay_abbreviate_cache_address(unsigned char *sid) +{ + if ((!cache)&&OVERLAY_ADDRESS_CACHE_SIZE) + { + if (OVERLAY_ADDRESS_CACHE_SIZE>0x1000000) + exit(WHY("OVERLAY_ADDRESS_CACHE_SIZE must be no larger than 2^24.")); + if (OVERLAY_ADDRESS_CACHE_SIZE<0) + exit(WHY("OVERLAY_ADDRESS_CACHE_SIZE must be larger than 0.")); + + /* Allocate cache */ + cache=calloc(sizeof(overlay_address_cache),1); + if (!cache) return 0; + + /* Allocate address cache */ + cache->size=OVERLAY_ADDRESS_CACHE_SIZE; + cache->sids=calloc(sizeof(sid),cache->size); + if (!cache->sids) { free(cache); return 0; } + + /* Work out the number of bits to shift */ + cache->shift=0; + while(cache->size>>cache->shift) cache->shift++; + + if ((1<shift)!=cache->size) + exit(WHY("OVERLAY_ADDRESS_CACHE_SIZE must be a power of two.")); + } + + if (!cache) return 0; + + /* Work out the index in the cache where this address would go */ + int index=((sid[0]<<16)|(sid[0]<<8)|sid[2])>>cache->shift; + + /* Does the stored address match the one we have been passed? */ + if (!bcmp(sid,&cache->sids[index].b[0],SID_SIZE)) + /* Address is already in cache, so return and let the caller know. */ + return 1; + + /* Not yet in cache, so store it */ + bcopy(sid,&cache->sids[index].b[0],SID_SIZE); + + return 0; +} + +int overlay_abbreviate_try_byindex(unsigned char *in,char *out,int *ofs,int index) +{ + if(!bcmp(in,abbrs->sids[index],SID_SIZE)) + { + /* We can encode this address with one byte */ + out[(*ofs)++]=0x01; + out[(*ofs)++]=index; + return 0; + } + else + /* Cannot find index in our node list. */ + return 1; +} + +int overlay_abbreviate_address(unsigned char *in,char *out,int *ofs) +{ + int i; + int wasInCachedP=overlay_abbreviate_cache_address(in); + + if (!in) return WHY("in==NULL"); + if (in[0]<0x10) return WHY("Invalid address - 0x00-0x0f are reserved prefixes."); + + if (!abbrs) { + // Abbreviation table not setup, so allocate it. + // Epoch starts at zero. + // XXX We have only one simultaneous epoch here, not that it is a problem. + abbrs=calloc(sizeof(overlay_address_table),1); + if (!abbrs) + { + // Could not allocate abbreviation table, so just output full-length address. + WHY("calloc() failed."); + bcopy(in,&out[*ofs],SID_SIZE); + (*ofs)+=SID_SIZE; + return 0; + } + abbrs->next_free=1; + } + + /* Try abbreviating by index + XXX should search backwards through old epochs + XXX If we do, we need a way to indicate a reference to an old epoch */ + for(i=0;i<2;i++) + if (abbrs->byfirstbyte[in[0]][i]) + { if (!overlay_abbreviate_try_byindex(in,out,ofs,i)) return 0; } + else break; + if (i<2&&abbrs->next_free) { + // There is a spare slot to abbreviate this address by storing it in an index if we + // wish. So let's store it, then send the full address along with the newly allocated + // index. + + /* Remember this address */ + bcopy(in,abbrs->sids[abbrs->next_free],SID_SIZE); + abbrs->byfirstbyte[in[0]][i]=abbrs->next_free; + + /* Write address out with index code */ + out[(*ofs)++]=0x08; + bcopy(in,&out[(*ofs)],SID_SIZE); + (*ofs)+=SID_SIZE; + out[(*ofs)++]=abbrs->next_free; + + /* Tidy things up and return triumphant. */ + abbrs->next_free++; + + return 0; + } + + /* No space in our table, so either send address verbatim, or send only a prefix. + Go for prefix. Next question is length of prefix. Seven bytes is probably about + right as an simple initial policy. */ + if (wasInCachedP) { + /* Prefix addresses that have been seen recently */ + out[(*ofs)++]=0x06; + bcopy(in,&out[(*ofs)],7); + (*ofs)+=7; + return 0; + } else { + /* But send full address for those we haven't seen before */ + bcopy(in,&out[*ofs],SID_SIZE); + (*ofs)+=SID_SIZE; + return 0; + } + +} + +int overlay_abbreviate_expand_address(unsigned char *in,int *inofs,unsigned char *out,int *ofs) +{ + int bytes=0; + switch(in[0]) + { + case 0x00: case 0x02: case 0x04: case 0x0c: + /* Unsupported codes, so tell the sender + if the frame was addressed to us as next-hop */ + (*inofs)++; + return OA_UNSUPPORTED; + case 0x01: /* single byte index look up */ + exit(WHY("Unimplemented address abbreviation code")); + break; + case 0x03: /* Same as last address */ + (*inofs)++; + bcopy(&overlay_abbreviate_previous_address.b[0],&out[*ofs],SID_SIZE); + (*ofs)+=SID_SIZE; + return OA_RESOLVED; + break; + case 0x05: case 0x09: /* 3-byte prefix */ + if (in[0]==0x09) bytes=1; + (*inofs)+=3+bytes; + return overlay_abbreviate_cache_lookup(in,out,ofs,3,bytes); + case 0x06: case 0x0a: /* 7-byte prefix */ + if (in[0]==0x0a) bytes=1; + (*inofs)+=7+bytes; + return overlay_abbreviate_cache_lookup(in,out,ofs,7,bytes); + case 0x07: case 0x0b: case 0x0d: /* 11-byte prefix */ + if (in[0]==0x0b) bytes=1; + if (in[0]==0x0d) bytes=2; + (*inofs)+=11+bytes; + return overlay_abbreviate_cache_lookup(in,out,ofs,11,bytes); + case 0x0f: /* broadcast */ + memset(&out[*ofs],0xff,SID_SIZE); + (*inofs)++; + return 0; + case 0x08: case 0x0e: default: /* Full address, optionally followed by index for us to remember */ + if (in[0]==0x08) bytes=1; + if (in[0]==0x0e) bytes=2; + bcopy(in,&out[*ofs],SID_SIZE); + if (bytes) overlay_abbreviate_remember_index(bytes,in,&in[SID_SIZE]); + (*inofs)+=SID_SIZE+bytes; + return 0; + } +} + +int overlay_abbreviate_remember_index(int index_byte_count,unsigned char *in,unsigned char *index_bytes) +{ + int zero=0; + char sid[SID_SIZE*2+1]; + int index=index_bytes[0]; + if (index_byte_count>1) index=(index<<8)|index_bytes[1]; + + sid[0]=0; extractSid(in,&zero,sid); + fprintf(stderr,"We need to remember that the sender #%d has assigned index #%d to the following:\n [%s]\n", + overlay_abbreviate_current_sender_id,index,sid); + return WHY("Not implemented"); +} + +int overlay_abbreviate_cache_lookup(unsigned char *in,unsigned char *out,int *ofs, + int prefix_bytes,int index_bytes) +{ + /* Lookup this entry from the cache, and also assign it the specified prefix */ + + /* Work out the index in the cache where this address would live */ + int index=((in[0]<<16)|(in[0]<<8)|in[2])>>cache->shift; + + /* So is it there? */ + if (bcmp(in,&cache->sids[index].b[0],prefix_bytes)) + { + /* No, it isn't in the cache. */ + return OA_PLEASEEXPLAIN; + } + + /* XXX We should implement associativity in the address cache so that we can spot + colliding prefixes and ask the sender to resolve them for us */ + + /* It is here, so let's return it */ + bcopy(&cache->sids[index].b[0],&out[(*ofs)],SID_SIZE); + (*ofs)+=SID_SIZE; + if (index_bytes) { + /* We need to remember it as well, so do that. + If this process fails, it is okay, as we can still resolve the address now. + It will probably result in waste later though when we get asked to look it up, + however the alternative definitely wastes bandwidth now, so let us defer the + corrective action in case it is never required. + */ + overlay_abbreviate_remember_index(index_bytes,&cache->sids[index].b[0],&in[prefix_bytes]); + } + return OA_RESOLVED; +} + +int overlay_abbreviate_set_current_sender(char *in) +{ + bcopy(in,&overlay_abbreviate_current_sender.b[0],SID_SIZE); + overlay_abbreviate_current_sender_id=-1; + return 0; +} + +int overlay_abbreviate_set_most_recent_address(char *in) +{ + bcopy(in,&overlay_abbreviate_previous_address.b[0],SID_SIZE); + return 0; +}