Compare commits

...

57 Commits

Author SHA1 Message Date
Tristan B. Velloza Kildaire 1419e99f29 Router
- Don't encrypt payloads destined to ourselvges
- This reworks the `sendData(byte[] payload, string to)` method
2024-01-24 16:08:32 +02:00
Tristan B. Velloza Kildaire a9575e5149 Router
- `sendData(byte[] payload, string to)` now encrypts data to the destination address
- Added `getPrivateKey()`
- `handle_DATA(Link link, string srcAddr, Message recvMesg)` now will decrypt data destined to it before passing it up the the user message handler

Router (unittests)

- Fixed to use new `Router` API which makes use of `Identity`
- We now generate the identities before instantiating the routers
2024-01-24 15:58:52 +02:00
Tristan B. Velloza Kildaire 645dc31ff2 Keys
- Added encryption and decryption methods
2024-01-24 15:47:09 +02:00
Tristan B. Velloza Kildaire c3e8098604 Router
- No need for `const` there
2024-01-24 15:27:05 +02:00
Tristan B. Velloza Kildaire 13317a6f96 Router
- Now takes in an `Identity`

App

- Generate an `Identity`
2024-01-24 15:26:24 +02:00
Tristan B. Velloza Kildaire 1a086f3d51 Identity
- Marked as `const` (nothing should ever change herein)
2024-01-24 15:26:19 +02:00
Tristan B. Velloza Kildaire fb61fcf4cd Merge branch 'master' into feature/crypto 2024-01-24 15:21:47 +02:00
Tristan B. Velloza Kildaire a3fc8b4f5e LLInterface
- Formatted
2024-01-24 15:21:21 +02:00
Tristan B. Velloza Kildaire 15b13b6e58 App
- Broadcast to `"desktop"` (as a test) if identity is `"laptop"`
2024-01-23 21:40:03 +02:00
Tristan B. Velloza Kildaire ec8e155eb7 LLInterface
- `getAddress()` no longer returns the `InterfaceInfo`'s address and no port, no we want the finally-bound address hence we derive this from the `peerSock` and APPEND that to the `InterfaceInfo`'s address (this also means we get the port then)
- Implemented `getAddress_fromStringWithKak(string addr)`
- `transmit(byte[] xmit, string addr)` now fixed when transmitting as we needed to remove the `[]` from the addresses and then extract the port and return an `Address`
- `broadcast(byte[] xmit)` we send using the `peerSock` to the broadcast address (although it's probably fine to use either)
- `mcastLoop()` now passes all received broadcasts up to the attached `Receiver`(s)
- `peerLoop()` now handles incoming peer-destined data and passes it up to the attached `Receiver`(s)
2024-01-23 21:39:46 +02:00
Tristan B. Velloza Kildaire d64677895d Docs
- Updated
2024-01-23 21:32:20 +02:00
Tristan B. Velloza Kildaire ada91b2355 Link
- Documented methods
2024-01-23 19:38:19 +02:00
Tristan B. Velloza Kildaire 2e1108ea31 Docs
- Fixed typo
2024-01-23 19:33:52 +02:00
Tristan B. Velloza Kildaire da114e1793 Receiver
- Documented interface
2024-01-23 19:29:41 +02:00
Tristan B. Velloza Kildaire 29940a90b0 Docs
- Added information on the `Receiver` interface
2024-01-23 19:29:24 +02:00
Tristan B. Velloza Kildaire 8bb3801548 Docs
- Updated PDF
2024-01-23 19:20:42 +02:00
Tristan B. Velloza Kildaire deb4d52a80 Docs
- Added information on links and driver implementation
2024-01-23 19:20:31 +02:00
Tristan B. Velloza Kildaire 1c629681a2 Docs
- Updated
2024-01-22 21:48:23 +02:00
Tristan B. Velloza Kildaire 56bd7501f6 Doc
- Added information of available methods and how route equality works
2024-01-22 21:40:35 +02:00
Tristan B. Velloza Kildaire a8f30228ed Route
- Commented fields
2024-01-22 21:27:35 +02:00
Tristan B. Velloza Kildaire 89d8025e98 DOcs
- Added some information on the `Route`
2024-01-22 21:27:23 +02:00
Tristan B. Velloza Kildaire 1d46a76e7d Router
- Fixed `routeSweep()`
2024-01-22 18:46:08 +02:00
Tristan B. Velloza Kildaire 6c765450e6 Router
- Added `routeSweep()`
- `advertiseLoop()` now calls `routeSweep()`
2024-01-22 18:41:01 +02:00
Tristan B. Velloza Kildaire 0d8d2ec242 Route
- Finished all documentation
2024-01-21 15:28:59 +02:00
Tristan B. Velloza Kildaire 311f4d388f Route
- Documented
2024-01-21 14:15:02 +02:00
Tristan B. Velloza Kildaire ffef54d98f Route
- Removed `const`
2024-01-21 14:09:53 +02:00
Tristan B. Velloza Kildaire 7c38b56384 Route
- Documented
2024-01-21 14:09:37 +02:00
Tristan B. Velloza Kildaire 3acc85093b Docs
- Removed TODO
2024-01-21 12:16:56 +02:00
Tristan B. Velloza Kildaire 5501d5f23b Docs
- Added more information about arp
2024-01-21 12:14:57 +02:00
Tristan B. Velloza Kildaire a431aaad60 Router
- Documented `getRoutes()`
2024-01-20 23:42:33 +02:00
Tristan B. Velloza Kildaire 9cbadcbc30 Router
- Documented `installSelfRoute()`
2024-01-20 23:41:57 +02:00
Tristan B. Velloza Kildaire 51d9d0bff2 Route
- Documented `handle_ADV(Link link, Message recvMesg)`
2024-01-20 23:41:17 +02:00
Tristan B. Velloza Kildaire 5a7eac1584 Router
- Documented `dumpRoutes()`
2024-01-20 23:40:33 +02:00
Tristan B. Velloza Kildaire 301309a8fd Route
- Documented `sendData(byte[] payload, string to)`
2024-01-20 23:37:47 +02:00
Tristan B. Velloza Kildaire 5081758ab6 Route
- Documented `onReceive(Link link, byte[] data, string srcAddr)`
2024-01-20 23:25:37 +02:00
Tristan B. Velloza Kildaire 60e0cffff0 Router
- Documented `handle_ARP(Link link, string srcAddr, Message recvMesg)`
2024-01-20 23:21:36 +02:00
Tristan B. Velloza Kildaire e5f296a8fd Router
- Documented methods `process(Link link, byte[] data, string srcAddr)`, `attemptForward(Data dataPkt)` and `handle_DATA(Link link, string srcAddr, Message recvMesg)`
2024-01-20 23:11:14 +02:00
Tristan B. Velloza Kildaire 1aeade2a4d Router
- Documented some more methods
2024-01-20 23:08:13 +02:00
Tristan B. Velloza Kildaire f012d65753 ArpManager
- Added logging statement to `onReceive(Link src, byte[] data, string srcAddr)`
2024-01-20 22:48:43 +02:00
Tristan B. Velloza Kildaire ce131f09dc UserDataPkt
- Documented
2024-01-20 22:46:53 +02:00
Tristan B. Velloza Kildaire 393f975556 DataCallbackFunction
- Added definition

Router

- Documented class
- Removed `ProcMesg` declaration
- Allowed setting of the advertisement frequency
- Added a constructor which accepts a `DataCallbackFunction`
- Added more debug logs in `handle_ARP(Link link, string srcAddr, Message recvMesg)`

Router (unittests)

- Updated to use the new `Router` API
2024-01-18 22:11:17 +02:00
Tristan B. Velloza Kildaire adbbcc478e Docs
- Fixed formatting and typos
2024-01-18 22:05:21 +02:00
Tristan B. Velloza Kildaire 4e33bfbdf3 Router
- Documented `installRoute(Route route)`
2024-01-17 22:23:15 +02:00
Tristan B. Velloza Kildaire 8375ae8abe Arp (unittests)
- Cleaned up
2024-01-17 21:52:45 +02:00
Tristan B. Velloza Kildaire 47315f7c50 Arp
- Cleaned up imports
2024-01-17 21:52:00 +02:00
Tristan B. Velloza Kildaire 2595a46df7 ArpEntry
- Documented

ArpManager

- Documented

Arp

- Documented
2024-01-17 21:51:46 +02:00
Tristan B. Velloza Kildaire ee0f62f090 ArpManager
- Documented constructor
2024-01-17 21:39:32 +02:00
Tristan B. Velloza Kildaire 653994e050 Docs
- Updated title sizes
2024-01-17 21:35:19 +02:00
Tristan B. Velloza Kildaire 76c49258a8 Router
- Fixed compilation error for `installRoute(Route route)`, a dereference required
2024-01-17 18:35:58 +02:00
Tristan B. Velloza Kildaire b2860f3bac Route
- Updated `installRoute(Route route)` such that if the matched route, `cr`, is the same as incoming route then simply refresh the current one (`cr`)
2024-01-17 18:35:08 +02:00
Tristan B. Velloza Kildaire 0191a16817 Route
- Implemented `isSameRoute(Route r1, Route r2)`
- Overrided equality `==` behavior
2024-01-17 18:32:29 +02:00
Tristan B. Velloza Kildaire 6fec5c4046 Arp (unittests)
- Moved to `ArpManager` so it is associated with it
2024-01-17 18:26:36 +02:00
Tristan B. Velloza Kildaire c0d1d8d515 ArpManager
- Added more documentation
- Removed method `test_stop()`
2024-01-17 18:25:54 +02:00
Tristan B. Velloza Kildaire da5319e4f6 Target
- Documented
2024-01-17 18:13:41 +02:00
Tristan B. Velloza Kildaire 64415d9177 Docs
- Updated `arp.md`
- Built new pdf
2024-01-17 17:54:21 +02:00
Tristan B. Velloza Kildaire d67912768d ArpManager
- Fixed todo
2024-01-17 16:05:01 +02:00
Tristan B. Velloza Kildaire e060ad4325 Docs
- Initial work done on documentation
2024-01-17 16:04:45 +02:00
13 changed files with 1452 additions and 159 deletions

4
.gitignore vendored
View File

@ -1,7 +1,6 @@
.dub
docs.json
__dummy.html
docs/
/twine
twine.so
twine.dylib
@ -15,3 +14,6 @@ twine-test-*
*.obj
*.lst
dub.selections.json
doc/texput.aux
doc/texput.log
doc/texput.toc

475
doc/arp.md Normal file
View File

@ -0,0 +1,475 @@
Address resolution protocol
===========================
The _address resolution protocol_ or **ARP** is a standalone module
which performs the mapping of a given layer-3 address $addr_{NL}$ to
another address, the link-layer address, which we will call $addr_{LL}$.
## Why do we need this?
The reason that we require this $addr_{LL}$ is because when we need
to send data to a host we do so over a link which is indicated in the
routing table for said packet.
However, links don't speak the same network-layer protocol as twine -
they speak whatever protocol they implement - i.e. Ethernet via `LIInterface`
or the in-memory `PipedLink`. Needless to say there is also always a
requirement of such a mapping mechanism because several links may be
backed by a different link-layer protocols in their `Link` implementation
and therefore we cannot marry ourselves to only one link-layer protocol
- _we need something dynamic_.
## The mapping function
We now head over to the technical side of things. Before we jump directly
into an analysis of the source code it is worth considering what this
procedure means in a mathematical sense because at a high-level this is
what the code is modeled on.
If we have a router $r_1$ which has a set of links $L= \{ l_1, l_2 \}$
and we wanted to send a packet to a host addresses $h_1$ and $h_2$ which
are accessible over $l_1$ and $l_2$ respectively then the mapping function
would appear as such:
$$
(h_1, l_1) \rightarrow addr_{LL_1}
$$
$$
(h_2, l_2) \rightarrow addr_{LL_2}
$$
On the right hand side the $addr_{LL_1}$ and $addr_{LL_2}$ are the resolved
link-layer addresses.
$$
(h_i, l_i) \rightarrow addr_{LL_i}
$$
Therefore we discover that we have the above mapping function which requires
the network-layer $h_i$ address you wish to resolve and the link $l_i$ over
which the resolution must be done, this then mapping to a single scalar -
the link-layer address, $addr_{LL_i}$.
## Implementation
We will begin the examination of the code at the deepest level which models
this mathematical function earlier, first, after which we will then consider
the code which calls it and how that works.
### The entry type
Firstly let us begin with the definition of the in-memory data type which
holds the mapping details. this is known as the `ArpEntry` struct and it
is shown in part below:
```{.numberLines .d}
public struct ArpEntry
{
private string l3Addr;
private string l2Addr;
...
public bool isEmpty()
{
return this.l3Addr == "" && this.l2Addr == "";
}
public static ArpEntry empty()
{
return ArpEntry("", "");
}
```
Please note the methods `isEmpty()`. An entry is considered empty if both
its network-layer and link-layer fields have an empty string in them, this
is normally accomplished by calling the `empty()` static method in order
to construct such an `ArpEntry`.
### Making an ARP request
The code to make an ARP request is in the `regen(Target target)` method
and we will now go through it line by line.
#### Setting up the request and link
Firstly we are provided with a `Target`, this is encapsulates the network-layer
address and the `Link` instance we want to request over. We now extract both
of those items into their own variables:
```{.numberLines .d}
// use this link
Link link = target.getLink();
// address we want to resolve
string addr = target.getAddr();
```
Before we make the request we will need a way to receive the response,
therefore we attach ourselves, the `ArpManager`, as a `Receiver` to
the link:
```{.numberLines .d}
// attach as a receiver to this link
link.attachReceiver(this);
logger.dbg("attach done");
```
This provides us with a callback method which will be called by the `Link`
whenever it receives _any_ traffic. It is worth noting that such a method
will not run on the thread concerning the code we are looking at now but
rather on the thread of the `Link`'s - we will discuss later how will filter
it and deliver the result to us, _but for now - back to the code_.
#### Encoding and sending the request
Now that we know what we want to request and over which link we can go
ahead and encode the ARP request message and broadcast it over the link:
```{.numberLines .d}
// generate the message and send request
Arp arpReq = Arp.newRequest(addr);
Message msg;
if(toMessage(arpReq, msg))
{
link.broadcast(msg.encode());
logger.dbg("arp req sent");
}
else
{
logger.error("Arp failed but oh boy, at the encoding level");
}
```
As you can see we make use of the `broadcast(byte[])` method, this is
handled by the link's implementation according to its link-layer protocol.
#### Waiting for a response
We now have to wait for a response and not just any response. It has to be
an ARP reply for the particular network-layer address we requested earlier.
This is done with the following code:
```{.numberLines .d}
// wait for reply
string llAddr;
bool status = waitForLLAddr(addr, llAddr);
...
```
As you can see we have this call to a method called `waitForLLAddr(addr, llAddr)`.
This method will block for us and can wake up if it is signaled to by
the callback method running on the `Link`'s thread (as mentioned previously).
----
```{.numberLines .d}
StopWatch timer = StopWatch(AutoStart.yes);
// todo, make timeout-able (todo, make configurable)
while(timer.peek() < this.timeout)
{
this.waitLock.lock();
scope(exit)
{
this.waitLock.unlock();
}
this.waitSig.wait(dur!("msecs")(500)); // todo, duty cycle if missed notify but also helps with checking for the timeout
// scan if we have it
string* llAddr = l3Addr in this.addrIncome;
if(llAddr !is null)
{
string llAddrRet = *llAddr;
this.addrIncome.remove(l3Addr);
llAddrOut = llAddrRet; // set result
return true; // did not timeout
}
}
return false; // timed out
```
Because it is implemented using a condition variable, it could potentially
miss a signal from the calling `notify()` if we only call `wait()` on our
thread _after_ the link's thread has called `notify()`. Therefore, we make
our `wait()` wake up every now and then by using a timed-wait, to check if
the data has been filled in by the other thread.
Second of all, what we do after retrying from `wait(Duration)` is check if
the _requested network-layer address_ has been resolved or not - this is that
filtering I was mentioning earlier. This is important as we don't want to
wake up for _any_ ARP response, but only the one which matches our `addr`
requested.
Thirdly, this also gives us a chance to check the while-loop's condition
so that we can see if we have timed out (waited long enough) for an ARP
response.
---
After all is done, the resulting entry is placed in a globally accessible
`string[string] addrIncome` which is protected by the `waitLock` for
both threads contending it. We then continue:
```{.numberLines .d}
...
// if timed out
if(!status)
{
logger.warn("Arp failed for target: ", target);
return ArpEntry.empty();
}
// on success
else
{
ArpEntry arpEntry = ArpEntry(addr, llAddr);
logger.info("Arp request completed: ", arpEntry);
return arpEntry;
}
```
We now check, as I said, if the entry is valid or not. If we timed-out then
we would have returned `false`. Now, as we shall see later, we will still have
to return _some_ `ArpEntry` because that is the signature of our method,
`regen(Target target)`. Thus, if we failed t get an `ArpEntry` we then return
one generated by `ArpEntry.empty()`, else we return the actual entry that
we received.
#### Catching responses
I have mentioned that the thread which waits for a matching ARP response
to come in (the one which calls the `wait(Duration)`) above. So then,
the question is - which thread is the one calling `notify()` on the
condition variable and under which scenarios?
---
Recall that we attached the `ArpManager` as a `Receiver` to the `Link`
object which was passed into the `regen(Target)` method:
```{.d}
// use this link
Link link = target.getLink();
// address we want to resolve
string addr = target.getAddr();
// attach as a receiver to this link
link.attachReceiver(this);
logger.dbg("attach done");
```
---
Now the reason for this is that whenever traffic is received on a `Link`
it will copy the `byte[]` containing the payload to each attached `Receiver`.
This means that the `ArpManager` will receive all packets from a given
link, the question is - which ones to we react to? Well that's easy. Below
I show you the `onReceive(Link src, byte[] data, string srcAddr)` method
which the arp manager overrides. This is called every time a given link
receives data:
```{.numberLines .d}
/**
* Called by the `Link` which received a packet which
* may be of interest to us
*
* Params:
* src = the `Link` from where the packet came from
* data = the packet's data
* srcAddr = the link-layer source address
*/
public override void onReceive(Link src, byte[] data, string srcAddr)
{
Message recvMesg;
if(Message.decode(data, recvMesg))
{
// payload type
if(recvMesg.getType() == MType.ARP)
{
Arp arpMesg;
if(recvMesg.decodeAs(arpMesg))
{
logger.dbg("arpMesg, received: ", arpMesg, "from: ", srcAddr);
ArpReply reply;
if(arpMesg.getReply(reply))
{
logger.info("ArpReply: ", reply);
// place and wakeup waiters
placeLLAddr(reply.networkAddr(), reply.llAddr());
}
...
...
...
...
}
```
What we do here is we attempt to decode each incoming packet
into our `Message` type, then further check if it is an ARP-typed
message. If this is the case then we check if it is an ARP request
(because as we have seen, ARP requests are **not** handled here).
```{.numberLines .d}
/**
* Called by the thread which has an ARP response
* it would like to pass off to the thread waiting
* on the condition variable
*
* Params:
* l3Addr = the network layer address
* llAddr = the link-layer address
*/
private void placeLLAddr(string l3Addr, string llAddr)
{
this.waitLock.lock();
scope(exit)
{
this.waitLock.unlock();
}
this.waitSig.notify(); // todo, more than one or never?
this.addrIncome[l3Addr] = llAddr;
}
```
If this is the case then we will place the link-layer address into
a key-value map where the key is the network-layer address and the
value is the link-layer address. After this we wake up the sleeping
thread by calling `notify()`.
### Caching
I mentioned that there is caching involved. The involvement is that all
`ArpEntry`'s are stored in a `CacheMap!(ArpEntry)` which means that they
will exist in there for some period of time and then be evicted.
If an entry has not yet been cached-in then it is created on demand when
you do `map.get(Target)`. Now remember the `regen(Target)` method? Well,
thats the regeneration method that we supply this cache map upon
instantiation - therefore it works as expected.
## The API
We have now discussed the gritty internals which aid us in creating requests,
awaiting replies and then returning the matched entry. We now must move over
to the publicly facing API of the `ArpManager`. This really just contains
a single method:
```{.d}
Optional!(ArpEntry) resolve(string networkAddr, Link onLink)
```
The way this method works is that it will return an `Optional!(ArpEntry)`,
meaning that you can test to see if the arp resolution process succeeded
or failed (i.e. timed-out for example) using code that looks akin
to what shall follow.
---
I have prepared an example which can illustrate the usage of the `ArpManager`.
In fact this example is part of a unittest which tests the various scenarios
that can occur with the manager itself.
### Mock links
Firstly we setup a pseudo-link. This is a sub-class of the `Link` class
which is specifically configured to respond **only** to ARP requests
and only to those which a mapping exists for.
In this example I configure two mappings of network-layer addresses to
link-layer addresses:
$$
(host_{A_{l3}}, dummyLink) \rightarrow host_{A_{l2}}
$$
$$
(host_{B_{l3}}, dummyLink) \rightarrow host_{B_{l2}}
$$
The code to do this is as follows:
```{.numberLines .d}
// Map some layer 3 -> layer 2 addresses
string[string] mappings;
mappings["hostA:l3"] = "hostA:l2";
mappings["hostB:l3"] = "hostB:l2";
// create a dummy link that responds with those mappings
ArpRespondingLink dummyLink = new ArpRespondingLink(mappings);
```
### Resolution
We then must create an `ArpManager` we can use for the resolution process:
```{.d}
ArpManager man = new ArpManager();
```
Now we are ready to attempt resolution. I first try to resolve the link-layer
address of the network-layer address `hostA:l3` by specifying it along with
the mock link, `dummyLink`, which we created earlier:
```{.numberLines .d}
// try resolve address `hostA:l3` over the `dummyLink` link (should PASS)
Optional!(ArpEntry) entry = man.resolve("hostA:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostA:l3"]);
```
In the above case the mapping succeeds and we get an `ArpEntry` returned
from `entry.get()`, upon which I extract the link-layer address by calling
`llAddr()` on it and comparing it to what I expected, `mappings["hostA:l3"]` -
which maps to `hostA:l2`.
---
We do a similar example for the other host:
```{.numberLines .d}
// try resolve address `hostB:l3` over the `dummyLink` link (should PASS)
entry = man.resolve("hostB:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostB:l3"]);
```
---
Lastly, I wanted to show what a failure would look like. With this we
expect that `entry.isPresent()` would return `false` and therefore stop
right there:
```{.numberLines .d}
// try top resolve `hostC:l3` over the `dummyLink` link (should FAIL)
entry = man.resolve("hostC:l3", dummyLink);
assert(entry.isPresent() == false);
```
This resolution fails because our `ArpRespondingLink`, our _dummy link_,
doesn't respond to mapping requests of the kind $(host_{B_{l3}}, dummyLink)$.
### Shutting it down
We need to shut down the `ArpManager` when we shut down the whole system,
this is then accomplished by running its destructor:
```{.d}
// shut down the arp manager
destroy(man);
```

3
doc/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
pandoc -f markdown --number-sections --toc *.md -s -t latex --highlight-style kate
pandoc -f markdown --number-sections --toc *.md -s -t latex --highlight-style kate | pdflatex

105
doc/links.md Normal file
View File

@ -0,0 +1,105 @@
Links and Receivers
===================
So-called _links_ are _receivers_ are terms referred to throughout the documentation
and understanding what they are and how they relate to each other is important for what
is to follow.
## The `Receiver`
A _receiver_ is a relatively simple type, the interface is defined as follows:
```{.numberLines .d}
/**
* A subscriber could be a router that wants
* to subscribe to data coming in from this
* interface
*/
public interface Receiver
{
/**
* On reception of the provided data from
* the given link-layer address over
* the given `Link`
*
* Params:
* source = the source `Link`
* recv = the received data
* srcAddr = the source link-layer address
*/
public void onReceive(Link source, byte[] recv, string srcAddr);
}
```
As you can probably understand from the just of it, it is basically a handler for _ingress_
traffic whereby the first argument is the data itself and the second must be the link-layer
address the traffic is sourced from. Any class which implements the `Receiver` interface may
be (as you will see later) attached to a `Link` such that it can have data passed to it.
## The `Link`
A _Link_ is provides us with a method to send data to a destination link-layer address
and be notified when we receive packets from link-layer addressed hosts over said link.
A _link_ is composed out of a few things:
1. A list of _receivers_
* These are the currently attached receivers which is to be called
serially (one after the other) whenever a data packet arrives over
this link.
* Given a link with two `Receiver`(s) attached, then in an example
whereby the bytes `[66, 65, 65, 66]` arrive over the link then that
that byte array would be copied to the attached
2. A _source address_
* We must have a way to determine the source address of the link
such that it can be used for various procedures such as ARP
3. A _transmission_ and _broadcast_ mechanism
* We need a way to send unicast (traffic directed to a singular _given_
host) and also to broadcast to all those attached to the link
### Concrete methods
There are a few methods which relate to the `Receiver`(s). These are shown below and essentially are for adding,
removing and enumerating receivers for this link:
| Method name | Description |
|------------------------------------------|-------------------------------------------------------------------------------------------|
| `attachReceiver(Receiver receiver)` | This attaches the given receiver to this `Link`, meaning packets will be copied to it |
| `removeReceiver(Receiver receiver)` | Removes the given receiver from this `Link` meaning that packets will no longer be copied to it |
| `auto getRecvCnt()` | Returns the number of receivers attached to this `Link` |
#### Implementing your driver
As part of implementing your driver, i.e. by method of extending/sub-classing the `Link` class, you will implement the mechanism (however
you go about it) by which will extract data from your link-layer and extract the network-layer part (the twine data payload of your
link-layer packet)
> and then what do you do with it?
Well, you will want to make this data available to any of the `Receiver`(s) which are attached currently. you want to _pass it up_ to the handlers. This can be safely done by calling the `receive(...)` method as shown below:
| Method name | Description |
|------------------------------------------|-------------------------------------------------------------------------------------------|
| `receive(byte[] recv, string srcAddr)` | This is to be called when the `Link` sub-class (implementation) has network-layer traffic to provide |
Calling this method iterates over every attached `Receiver` and calls their respective `onReceive(...)` methods.
Note: that the `srcAddr` must contain the link-layer source address.
### Abstract methods
There are a few more methods to take note of, these are not available as an already-implemented set of methods in
the `Link` class, and hence must be overriden.
#### Implementing your driver... _again_
Whilst the usage of the aforementioned `receive(byte[], string)` method had to do with processing _ingress_ traffic, these methods require
an implementation for handling _egress_ traffic.
| Method name | Description |
|------------------------------------------|-------------------------------------------------------------------------------------------|
| `void transmit(byte[] xmit, string addr)`| Link-implementation specific for driver to send data to a specific destination address |
| `void broadcast(byte[] xmit)` | Link-implementation specific for driver to broadcast to all hosts on its broadcast domain |
| `string getAddress()` | Link-implementation specific for driver to report its address |
Note: The last method, `getAddress()`, must return the `Link`'s link-layer address.

116
doc/router.md Normal file
View File

@ -0,0 +1,116 @@
Routing and forwarding
======================
Routing is the process by which one announces their routes to others, whilst forwarding is
the usage of those learnt routes in order to facilitate the transfer of packets from one
endpoint to another through a network of inter-connected routers.
## A route
Before we can get into the process of routing we must first have a conception of a _route_
itself.
A route consists of the following items:
1. A _destination_
* Describes to whom this route is for, i.e. a route to _who_
2. A _link_
* The `Link` object over which we can reach this host
3. A _gateway_
* This is who we would need to forward the packet _via_ in
order to get the packet either to the final destination (in
such a case the $gateway = destination$) _or_ the next-hop
gateway that we must forward via ($gateway \neq destination$)
4. A _distance_
* This is metric which doesn't affect _how_ packets are
forwarded but rather how routes that have the same matching
_destination_ are tie-broken.
* Given routes $r= \{r_1, r_2\}$ and a function $d(r_i)$
which returns the distance we shall install the route $r_i$
which has the lowest distance, hence $r_{installed} = r_i where d(r_i) = min(d(r))$ (TODO: fix this maths)
5. A _timer_ and _lifetime_
* We have _timer_ which ticks upwards and a _lifetime_
which allows us to check when the $timer > lifetime$ which
signifies that the route has expired, indicating that
we should remove it from the routing table.
And in code this can be found as the `Route` struct shown below:
```{.numberLines .d}
/**
* Represents a route
*/
public struct Route
{
private string dstKey; // destination
private Link ll; // link to use
private string viaKey; // gateway (can be empty)
private ubyte dst; // distance
private StopWatch lifetime; // timer
private Duration expirationTime; // maximum lifetime
...
}
```
### Methods
Some important methods that we have are the following (there are more but these are ones
that hold under certain conditions that are not so obvious, therefore I would like to
explicitly mention them):
| Method | Description |
|------------------------|------------------------------------------------------------------|
| `isDirect()` | Returns `true` when $gateway = destination$, otherwise `false` |
| `isSelfRoute()` | Returns `true` if the `Link` is `null`, otherwise `false` |
### Route equality
Lastly, route equality is something that is checked as part of the router's code, so we
should probably show how we have overrode the `opEquals(Route)` method. This is the method
that is called when two `Route` structs are compared for equality using the `==` operator.
Our implementation goes as follows:
```{.numberLines .d}
public struct Route
{
...
/**
* Compares two routes with one
* another
*
* Params:
* r1 = first route
* r2 = second route
* Returns: `true` if the routes
* match exactly, otherwise `false`
*/
public static bool isSameRoute(Route r1, Route r2)
{
return r1.destination() == r2.destination() &&
r1.gateway() == r2.gateway() &&
r1.distance() == r2.distance() &&
r1.link() == r2.link();
}
/**
* Compares this `Route` with
* another
*
* Params:
* rhs = the other route
* Returns: `true` if the routes
* are identical, `false` otherwise
*/
public bool opEquals(Route rhs)
{
return isSameRoute(this, rhs);
}
}
```
##

BIN
doc/texput.pdf Normal file

Binary file not shown.

View File

@ -20,7 +20,9 @@ void main(string[] args)
string identity = args[1];
string[] interfaces = args[2..$];
Router r = new Router([identity, "privKey1"]);
import twine.core.keys;
Identity ident = Identity.newIdentity();
Router r = new Router(ident);
import niknaks.debugging;
@ -42,6 +44,12 @@ void main(string[] args)
import core.thread;
while(true)
{
if(identity == "laptop")
{
import std.datetime.systime : Clock;
import std.conv : to;
r.sendData(cast(byte[])("The time currently is "~to!(string)(Clock.currTime())), "desktop");
}
r.dumpRoutes();
Thread.sleep(dur!("seconds")(5));
}

View File

@ -1,3 +1,6 @@
/**
* Address resolution protocol
*/
module twine.core.arp;
import core.sync.mutex : Mutex;
@ -7,29 +10,65 @@ import twine.links.link : Link, Receiver;
import twine.core.wire;
import twine.logging;
import niknaks.functional : Optional;
import std.datetime.stopwatch : StopWatch, AutoStart;
import std.datetime : Duration, dur;
/**
* Describes an ARP request with
* the networ-address and the `Link`
* on which the resolution is to
* be done
*/
private struct Target
{
private string networkAddr;
private Link onLink;
/**
* Constructs a new target
*
* Params:
* networkAddr = the network
* address
* link = the `Link` the
* request is to be resolved
* over
*/
this(string networkAddr, Link link)
{
this.networkAddr = networkAddr;
this.onLink = link;
}
/**
* Retrieve's this target's
* network address of which
* is to be used as part of
* the request
*
* Returns: the network address
*/
public string getAddr()
{
return this.networkAddr;
}
/**
* Retrieves this target's
* link upon which resolution
* is intended to take place
*
* Returns: the `Link`
*/
public Link getLink()
{
return this.onLink;
}
}
/**
* The arp management sub-system
*/
public class ArpManager : Receiver
{
private Mutex waitLock;
@ -37,8 +76,22 @@ public class ArpManager : Receiver
private Duration timeout = dur!("seconds")(5); // todo, configurabel
// map l3Addr -> llAddr
private string[string] addrIncome; // note also, these should be cleared after some time
// probably else it could leak if we receive ARP responses sent to us but of which were
// never requested
private CacheMap!(Target, ArpEntry) table;
/**
* Constructs a new `ArpManager` with
* the given sweep interval
*
* Params:
* sweepInterval = how often the arp
* table should be checked for expired
* entries
*/
this(Duration sweepInterval = dur!("seconds")(60))
{
this.table = new CacheMap!(Target, ArpEntry)(&regen, sweepInterval);
@ -128,7 +181,7 @@ public class ArpManager : Receiver
logger.dbg("attach done");
// todo, send request
// generate the message and send request
Arp arpReq = Arp.newRequest(addr);
Message msg;
if(toMessage(arpReq, msg))
@ -160,9 +213,23 @@ public class ArpManager : Receiver
}
}
// map l3Addr -> llAddr
private string[string] addrIncome;
/**
* Waits on a condition variable until we have
* retrieved the link-layer address which we
* requested. It also will time-out after
* the configured time if no matching reply
* is found.
*
* This wakes up periodically as well.
*
* Params:
* l3Addr = the network-layer address
* llAddrOut = the link-layer address
* Returns: `true` if we could resolve the
* link-layer address, `false` if we timed-out
* in waiting for it
*/
private bool waitForLLAddr(string l3Addr, ref string llAddrOut)
{
StopWatch timer = StopWatch(AutoStart.yes);
@ -193,6 +260,15 @@ public class ArpManager : Receiver
return false; // timed out
}
/**
* Called by the thread which has an ARP response
* it would like to pass off to the thread waiting
* on the condition variable
*
* Params:
* l3Addr = the network layer address
* llAddr = the link-layer address
*/
private void placeLLAddr(string l3Addr, string llAddr)
{
this.waitLock.lock();
@ -207,6 +283,15 @@ public class ArpManager : Receiver
this.addrIncome[l3Addr] = llAddr;
}
/**
* Called by the `Link` which received a packet which
* may be of interest to us
*
* Params:
* src = the `Link` from where the packet came from
* data = the packet's data
* srcAddr = the link-layer source address
*/
public override void onReceive(Link src, byte[] data, string srcAddr)
{
Message recvMesg;
@ -218,6 +303,7 @@ public class ArpManager : Receiver
Arp arpMesg;
if(recvMesg.decodeAs(arpMesg))
{
logger.dbg("arpMesg, received: ", arpMesg, "from: ", srcAddr);
ArpReply reply;
if(arpMesg.getReply(reply))
{
@ -247,6 +333,9 @@ public class ArpManager : Receiver
}
}
/**
* Shuts down the manager
*/
~this()
{
// todo, double check but yes this should be fine, I believe I added checks for this?
@ -254,44 +343,116 @@ public class ArpManager : Receiver
// the regeneration function treat it
destroy(this.table);
}
// todo, how to activate?
public void test_stop()
{
destroy(this.table);
}
}
import std.datetime.stopwatch : StopWatch, AutoStart;
import std.datetime : Duration, dur;
/**
* This tests the `ArpManager`'s ability
* to handle arp requests and responses
*
* We make use of a dummy `Link` which
* we provide with mappings of layer 3
* to layer 2 addresses such that when
* an ARP request comes in we can respond
* with the relevant details as such
*/
unittest
{
// Map some layer 3 -> layer 2 addresses
string[string] mappings;
mappings["hostA:l3"] = "hostA:l2";
mappings["hostB:l3"] = "hostB:l2";
// create a dummy link that responds with those mappings
ArpRespondingLink dummyLink = new ArpRespondingLink(mappings);
ArpManager man = new ArpManager();
// try resolve address `hostA:l3` over the `dummyLink` link (should PASS)
Optional!(ArpEntry) entry = man.resolve("hostA:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostA:l3"]);
// try resolve address `hostB:l3` over the `dummyLink` link (should PASS)
entry = man.resolve("hostB:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostB:l3"]);
// try top resolve `hostC:l3` over the `dummyLink` link (should FAIL)
entry = man.resolve("hostC:l3", dummyLink);
assert(entry.isPresent() == false);
// shutdown the dummy link to get the unittest to end
dummyLink.stop();
// shutdown the arp manager
destroy(man);
}
/**
* An ARP entry mapping a network-layer
* address to a link-layer address
*/
public struct ArpEntry
{
private string l3Addr;
private string l2Addr;
/**
* Constructs a new `ArpEntry`
* with the given network0layer
* address and link-layer address
*
* Params:
* networkAddr = the network-layer
* address
* llAddr = the link-layer address
*/
this(string networkAddr, string llAddr)
{
this.l3Addr = networkAddr;
this.l2Addr = llAddr;
}
/**
* Retrieves the network-layer
* address
*
* Returns: the address
*/
public string networkAddr()
{
return this.l3Addr;
}
/**
* Retrieves the link-layer
* address
*
* Returns: the address
*/
public string llAddr()
{
return this.l2Addr;
}
/**
* If this entry is empty
*
* Returns: `true` if both
* layer addresses are empty,
* `false` otherwise
*/
public bool isEmpty()
{
return this.l3Addr == "" && this.l2Addr == "";
}
/**
* Constructs a new `ArpEntry`
* with empty addresses
*
* Returns: the `ArpEntry`
*/
public static ArpEntry empty()
{
return ArpEntry("", "");
@ -306,7 +467,10 @@ version(unittest)
import core.sync.mutex : Mutex;
import core.sync.condition : Condition;
import std.conv : to;
}
version(unittest)
{
// a dummy link which will respond with
// arp replies using the provided map
// of l3Addr -> l2Addr
@ -431,49 +595,4 @@ version(unittest)
this.ingressSig.notify();
}
}
}
/**
* This tests the `ArpManager`'s ability
* to handle arp requests and responses
*
* We make use of a dummy `Link` which
* we provide with mappings of layer 3
* to layer 2 addresses such that when
* an ARP request comes in we can respond
* with the relevant details as such
*/
unittest
{
// Map some layer 3 -> layer 2 addresses
string[string] mappings;
mappings["hostA:l3"] = "hostA:l2";
mappings["hostB:l3"] = "hostB:l2";
// create a dummy link that responds with those mappings
ArpRespondingLink dummyLink = new ArpRespondingLink(mappings);
ArpManager man = new ArpManager();
// try resolve address `hostA:l3` over the `dummyLink` link (should PASS)
Optional!(ArpEntry) entry = man.resolve("hostA:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostA:l3"]);
// try resolve address `hostB:l3` over the `dummyLink` link (should PASS)
entry = man.resolve("hostB:l3", dummyLink);
assert(entry.isPresent());
assert(entry.get().llAddr() == mappings["hostB:l3"]);
// try top resolve `hostC:l3` over the `dummyLink` link (should FAIL)
entry = man.resolve("hostC:l3", dummyLink);
assert(entry.isPresent() == false);
// shutdown the dummy link to get the unittest to end
dummyLink.stop();
// shutdown the arp manager
// man.test_stop();
destroy(man);
}

View File

@ -1,8 +1,8 @@
module twine.core.keys;
import crypto.rsa;
import crypto.rsa : RSA, RSAKeyPair;
public struct Identity
public const struct Identity
{
private string publicKey;
private string privateKey;
@ -44,4 +44,18 @@ public struct Identity
RSAKeyPair kp = RSA.generateKeyPair();
return Identity(kp.publicKey, kp.privateKey);
}
}
private alias rsa_encrypt = RSA.encrypt;
public byte[] encrypt(byte[] raw, string publicKey)
{
return cast(byte[])rsa_encrypt(publicKey, cast(ubyte[])raw);
}
private alias rsa_decrypt = RSA.decrypt;
public byte[] decrypt(byte[] encrypted, string privateKey)
{
return cast(byte[])rsa_decrypt(privateKey, cast(ubyte[])encrypted);
}

View File

@ -4,23 +4,51 @@ import twine.links.link;
import std.datetime.stopwatch : StopWatch, AutoStart;
import std.datetime : Duration, dur;
/**
* Represents a route
*/
public struct Route
{
private string dstKey; // destination
private Link ll; // link to use
private string viaKey; // gateway (can be empty)
private ubyte dst;
private ubyte dst; // distance
private StopWatch lifetime;
private Duration expirationTime;
private StopWatch lifetime; // timer
private Duration expirationTime; // maximum lifetime
// direct route (reachable over the given link)
/**
* Constructs a route to a destination
* over a given link with a given metric.
*
* The destination of this route is
* directly reachable over the link.
*
* Params:
* dst = the destination network-layer
* address
* link = the `Link`
* distance = the distance
*/
this(string dst, Link link, ubyte distance)
{
this(dst, link, dst, distance);
}
// indirect route (reachable via the `via`)
/**
* Constructs a route to a destination
* over a link with a given metric.
*
* This also let's you set the next-hop
* gateway that should be used.
*
* Params:
* dst = the destination network-layer
* address
* link = the `Link`
* via = the next-hop gateway's address
* distance = the distance
*/
this(string dst, Link link, string via, ubyte distance, Duration expirationTime = dur!("seconds")(60))
{
this.dstKey = dst;
@ -32,43 +60,134 @@ public struct Route
this.expirationTime = expirationTime;
}
/**
* Is this route direct? As
* in the destination is
* directly reachable via
* the gateway (i.e. the
* destination matches the
* gateway)
*
* Returns: `true` if so,
* otherwise `false`
*/
public bool isDirect()
{
return this.dstKey == this.viaKey; // todo, should we ever use cmp?
}
/**
* Checks if this route
* has expired
*
* Returns: `true` if so,
* `false` otherwise
*/
public bool hasExpired()
{
return this.lifetime.peek() > this.expirationTime;
}
/**
* Resets the expiration
* timer for this route
*/
public void refresh()
{
this.lifetime.reset();
}
/**
* Retrieves this route's
* destination address
*
* Returns: the address
*/
public string destination()
{
return this.dstKey;
}
/**
* Retrieves this route's
* associated link
*
* Returns: a `Link`, or
* `null` if this is a
* self-route
*/
public Link link()
{
return this.ll;
}
/**
* Retirns whether or not
* this route is a self-route
* (i.e. the link set was
* `null`)
*
* Returns: `true` if so,
* otherwise `false`
*/
public bool isSelfRoute()
{
return this.ll is null;
}
/**
* Retrieves the gateway
* of this route
*
* Returns: the gateway's
* address
*/
public string gateway()
{
return this.viaKey;
}
/**
* Retrieves the distance
*
* Returns: the distance
* metric
*/
public ubyte distance()
{
return this.dst;
}
/**
* Compares two routes with one
* another
*
* Params:
* r1 = first route
* r2 = second route
* Returns: `true` if the routes
* match exactly, otherwise `false`
*/
public static bool isSameRoute(Route r1, Route r2)
{
return r1.destination() == r2.destination() &&
r1.gateway() == r2.gateway() &&
r1.distance() == r2.distance() &&
r1.link() == r2.link();
}
/**
* Compares this `Route` with
* another
*
* Params:
* rhs = the other route
* Returns: `true` if the routes
* are identical, `false` otherwise
*/
public bool opEquals(Route rhs)
{
return isSameRoute(this, rhs);
}
}

View File

@ -12,24 +12,51 @@ import core.sync.mutex : Mutex;
import core.sync.condition : Condition;
import niknaks.functional : Optional;
import twine.core.arp;
import twine.core.keys;
// for data destined to yourself (todo, in future maybe have dest?)
/**
* This represents data passed
* from the router to a user
* data handler when data destined
* to your node arrives
*/
public struct UserDataPkt
{
private string src;
private byte[] data;
/**
* Constructs a new `UserDataPkt`
* with the provided network-layer
* source address and payload
*
* Params:
* src = network-layer address
* data = packet payload
*/
this(string src, byte[] data)
{
this.src = src;
this.data = data;
}
/**
* Retrieve's the network-layer
* address of this packet
*
* Returns: the address
*/
public string getSrc()
{
return this.src;
}
/**
* Retrieve's the packet's
* payload
*
* Returns: the payload
*/
public byte[] getPayload()
{
return this.data;
@ -38,6 +65,7 @@ public struct UserDataPkt
import std.functional : toDelegate;
public alias DataCallbackDelegate = void delegate(UserDataPkt);
public alias DataCallbackFunction = void function(UserDataPkt);
private void nopHandler(UserDataPkt u)
{
@ -45,46 +73,28 @@ private void nopHandler(UserDataPkt u)
logger.dbg("NOP handler: ", cast(string)u.getPayload());
}
/**
* The router which is responsible for
* sending routing advertisements,
* receiving routes advertised by others,
* managing the routing table and
* providing a way to send, receive
* packets for the user.
*
* It also manages the forwarding of
* packets
*/
public class Router : Receiver
{
private bool running;
// todo, make use of this in the future with a message processing thread
// offload, from on-link processing
private struct ProcMesg
{
private Link link;
private byte[] data;
private string llSrcAddr;
this(Link from, byte[] recv, string llSrcAddr)
{
this.link = from;
this.data = recv;
this.llSrcAddr = llSrcAddr;
}
public Link getLink()
{
return this.link;
}
public byte[] getData()
{
return this.data;
}
public string getLLSource()
{
return this.llSrcAddr;
}
}
// link management
private const LinkManager linkMan; // const, never should be changed besides during construction
private Thread advThread;
private Duration advFreq;
private string[] keyPairs;
// crypto
private const Identity identity;
// routing tables
private Route[string] routes;
@ -96,15 +106,16 @@ public class Router : Receiver
// incoming message handler
private const DataCallbackDelegate messageHandler;
this(string[] keyPairs, DataCallbackDelegate messageHandler = toDelegate(&nopHandler))
// todo, set advFreq back to 5 seconds
this(const Identity identity, DataCallbackDelegate messageHandler = toDelegate(&nopHandler), Duration advFreq = dur!("seconds")(100))
{
this.linkMan = new LinkManager(this);
this.arp = new ArpManager();
this.advThread = new Thread(&advertiseLoop);
this.advFreq = dur!("seconds")(5);
this.advFreq = advFreq;
this.keyPairs = keyPairs;
this.identity = identity;
this.messageHandler = messageHandler;
this.routesLock = new Mutex();
@ -113,12 +124,24 @@ public class Router : Receiver
installSelfRoute();
}
// todo, set advFreq back to 5 seconds
this(Identity identity, DataCallbackFunction messageHandler, Duration advFreq = dur!("seconds")(100))
{
this(identity, toDelegate(messageHandler), advFreq);
}
/**
* Starts the router
*/
public void start()
{
this.running = true;
this.advThread.start();
}
/**
* Stops the router
*/
public void stop()
{
this.running = false;
@ -128,16 +151,44 @@ public class Router : Receiver
destroy(this.arp);
}
/**
* Returns the link manager
* instance of this router
*
* Returns: the `LinkManager`
*/
public final LinkManager getLinkMan()
{
return cast(LinkManager)this.linkMan;
}
/**
* Returns the public key associated
* with this router
*
* Returns: the public key
*/
private string getPublicKey()
{
return this.keyPairs[0];
return this.identity.getPublicKey();
}
private string getPrivateKey()
{
return this.identity.getPrivateKey();
}
/**
* Process a given payload from a given
* link and source link-layer address
*
* Params:
* link = the `Link` from which the
* packet was received
* data = the data itself
* srcAddr = the link-layer address
* which is the source of this packet
*/
private void process(Link link, byte[] data, string srcAddr)
{
logger.dbg("Received data from link '", link, "' with ", data.length, " many bytes (llSrc: "~srcAddr~")");
@ -175,6 +226,14 @@ public class Router : Receiver
private bool isForwarding = true; // todo, make togglable during runtime
/**
* Given a packet this will
* attempt to forward it
*
* Params:
* dataPkt = the packet as
* a `User`
*/
private void attemptForward(Data dataPkt)
{
// lookup route to host
@ -219,6 +278,21 @@ public class Router : Receiver
}
}
/**
* Handles a packet that contains user data.
*
* Depending on who it was destined to this
* will either call a user data packet handler
* or it will attempt to forward it (if forwarding
* is enabled)
*
* Params:
* link = the `Link` from which the packet
* was received
* srcAddr = the link-layer source address of
* the packet
* recvMesg = the received `Message`
*/
private void handle_DATA(Link link, string srcAddr, Message recvMesg)
{
Data dataPkt;
@ -233,6 +307,9 @@ public class Router : Receiver
{
logger.dbg("packet '", dataPkt, "' is destined to me");
// decode the data
payload = decrypt(payload, getPrivateKey());
// run handler
messageHandler(UserDataPkt(uSrc, payload));
}
@ -253,11 +330,30 @@ public class Router : Receiver
}
}
/**
* Handles a packet which contains
* ARP data in it. It detects
* firstly if it is an ARP request
* (as responses are ignored) and
* then, if so, it checks that the
* requested network-layer address
* matches our public key - and
* then proceeds to answer it.
*
* Params:
* link = the `Link` from which
* this packet was received
* srcAddr = the link-layer
* source address
* recvMesg = the received message
*/
private void handle_ARP(Link link, string srcAddr, Message recvMesg)
{
Arp arpMesg;
if(recvMesg.decodeAs(arpMesg))
{
logger.dbg("arpMesg: ", arpMesg);
if(arpMesg.isRequest())
{
string requestedL3Addr;
@ -274,6 +370,7 @@ public class Router : Receiver
Message mesgOut;
if(toMessage(arpRep, mesgOut))
{
logger.dbg("Sending out ARP response: ", arpRep);
link.transmit(mesgOut.encode(), srcAddr);
}
else
@ -304,11 +401,37 @@ public class Router : Receiver
}
}
/**
* Called whenever we receive a packet
* from one of the links associated
* with this router
*
* Params:
* link = the `Link` from which the
* packet came from
* data = the packet itself
* srcAddr = the link-layer source
* address of the packet
*/
public void onReceive(Link link, byte[] data, string srcAddr)
{
process(link, data, srcAddr);
}
// todo, add session-based send over here
import twine.core.keys;
/**
* Sends a piece of data to the given
* network-layer address
*
* Params:
* payload = the data to send
* to = the destination network-layer
* address
* Returns: `true` if sending succeeded
* but if not then `false`
*/
public bool sendData(byte[] payload, string to)
{
// lookup route to host
@ -317,6 +440,21 @@ public class Router : Receiver
// found route
if(route.isPresent())
{
Route r = route.get();
// is data to self
if(r.isSelfRoute())
{
// our link is null, we don't send to ourselves - rather
// we call the user handler right now
messageHandler(UserDataPkt(to, payload));
return true;
}
// encrypt the payload here to destination key
payload = encrypt(payload, to);
// construct data packet to send
Data dataPkt; // todo, if any crypto it would be with `to` NOT `via` (which is imply the next hop)
if(!Data.makeDataPacket(getPublicKey(), to, payload, dataPkt))
@ -333,35 +471,21 @@ public class Router : Receiver
return false;
}
Route r = route.get();
// resolve link-layer address of next hop
Optional!(ArpEntry) ae = this.arp.resolve(r.gateway(), r.link());
// is data to self
if(r.isSelfRoute())
if(ae.isPresent())
{
// our link is null, we don't send to ourselves - rather
// we call the user handler right now
messageHandler(UserDataPkt(to, payload));
// transmit over link to the destination ll-addr (as indiacted by arp)
r.link().transmit(mesgOut.encode(), ae.get().llAddr());
return true;
}
// to someone else
else
{
// resolve link-layer address of next hop
Optional!(ArpEntry) ae = this.arp.resolve(r.gateway(), r.link());
if(ae.isPresent())
{
// transmit over link to the destination ll-addr (as indiacted by arp)
r.link().transmit(mesgOut.encode(), ae.get().llAddr());
return true;
}
else
{
logger.error("ARP failed for next hop '", r.gateway(), "' when sending to dst '"~r.destination()~"'");
return false;
}
logger.error("ARP failed for next hop '", r.gateway(), "' when sending to dst '"~r.destination()~"'");
return false;
}
}
// route not found
else
@ -423,6 +547,11 @@ public class Router : Receiver
return false;
}
/**
* Prints out all the routes
* currently in the routing
* table
*/
public void dumpRoutes()
{
import std.stdio : writeln;
@ -443,6 +572,24 @@ public class Router : Receiver
}
}
/**
* Checks if the given route should be
* installed and, if so, installs it.
*
* If the incoming route is to a destination
* not yet present then it is installed,
* if to an already-present destination
* then metric is used to break the tie.
*
* If the route matches an existing one
* by everything then we don't install
* the new one (because it's identical)
* but rather reset the timer of the existing
* one.
*
* Params:
* route = the new route to install
*/
private void installRoute(Route route)
{
this.routesLock.lock();
@ -467,10 +614,27 @@ public class Router : Receiver
{
this.routes[route.destination()] = route;
}
else
{
// if matched route is the same as incoming route
// then simply refresh the current one
if(*cr == route)
{
cr.refresh();
}
}
}
}
// Handles all sort of advertisement messages
/**
* Handles incoming advertisement
* messages
*
* Params:
* link = the `Link` from which
* the message was received
* recvMesg = the message itself
*/
private void handle_ADV(Link link, Message recvMesg)
{
Advertisement advMesg;
@ -508,12 +672,25 @@ public class Router : Receiver
}
}
/**
* Installs a route to ourselves
* which has a distance of `0`,
* a destination of our public
* key and no link
*/
private void installSelfRoute()
{
Route selfR = Route(getPublicKey(), null, 0);
installRoute(selfR);
}
/**
* Returns a list of all
* the currently installled
* routes
*
* Returns: a `Route[]`
*/
private Route[] getRoutes()
{
this.routesLock.lock();
@ -526,6 +703,27 @@ public class Router : Receiver
return this.routes.values.dup;
}
private void routeSweep()
{
this.routesLock.lock();
scope(exit)
{
this.routesLock.unlock();
}
foreach(string destination; this.routes.keys())
{
Route cro = this.routes[destination];
if(cro.hasExpired())
{
this.routes.remove(destination);
logger.warn("Expired route '", cro, "'");
}
}
}
/**
* Sends out modified routes from the routing
* table (with us as the `via`) on an interval
@ -535,6 +733,9 @@ public class Router : Receiver
{
while(this.running)
{
// TODO: Add route expiration check here
routeSweep();
// advertise to all links
Link[] selected = getLinkMan().getLinks();
logger.info("Advertising to ", selected.length, " many links");
@ -692,11 +893,14 @@ unittest
p2.connect(p1, p1.getAddress());
Router r1 = new Router(["p1Pub", "p1Priv"]);
Identity r1_ident = Identity.newIdentity();
Identity r2_ident = Identity.newIdentity();
Router r1 = new Router(r1_ident, toDelegate(&nopHandler), dur!("seconds")(5));
r1.getLinkMan().addLink(p1);
r1.start();
Router r2 = new Router(["p2Pub", "p2Priv"]);
Router r2 = new Router(r2_ident, toDelegate(&nopHandler), dur!("seconds")(5));
r2.getLinkMan().addLink(p2);
r2.start();
@ -794,6 +998,12 @@ unittest
p1_to_p3.connect(p3_to_p1, p3_to_p1.getAddress());
p3_to_p1.connect(p1_to_p3, p1_to_p3.getAddress());
Identity r1_ident = Identity.newIdentity();
Identity r2_ident = Identity.newIdentity();
Identity r3_ident = Identity.newIdentity();
UserDataPkt r1_to_r1_reception;
void r1_msg_handler(UserDataPkt m)
{
@ -803,27 +1013,27 @@ unittest
UserDataPkt r1_to_r2_reception, r3_to_r2_reception;
void r2_msg_handler(UserDataPkt m)
{
if(m.getSrc() == "p1Pub")
if(m.getSrc() == r1_ident.getPublicKey())
{
r1_to_r2_reception = m;
}
else if(m.getSrc() == "p3Pub")
else if(m.getSrc() == r3_ident.getPublicKey())
{
r3_to_r2_reception = m;
}
}
Router r1 = new Router(["p1Pub", "p1Priv"], &r1_msg_handler);
Router r1 = new Router(r1_ident, &r1_msg_handler, dur!("seconds")(5));
r1.getLinkMan().addLink(p1_to_p2);
r1.getLinkMan().addLink(p1_to_p3);
r1.start();
Router r2 = new Router(["p2Pub", "p2Priv"], &r2_msg_handler);
Router r2 = new Router(r2_ident, &r2_msg_handler, dur!("seconds")(5));
r2.getLinkMan().addLink(p2_to_p1);
r2.start();
Router r3 = new Router(["p3Pub", "p3Priv"]);
Router r3 = new Router(r3_ident, toDelegate(&nopHandler), dur!("seconds")(5));
r3.getLinkMan().addLink(p3_to_p1);
r3.start();
@ -863,21 +1073,21 @@ unittest
writeln(dumpArray!(r3_routes));
// r1 -> r2 (on-link forwarding decision)
assert(r1.sendData(cast(byte[])"ABBA poespoes", "p2Pub"));
assert(r1.sendData(cast(byte[])"ABBA poespoes", r2_ident.getPublicKey()));
// todo, use condvar to wait aaasuredly
Thread.sleep(dur!("seconds")(2));
// check reception of message
assert(r1_to_r2_reception.getPayload() == "ABBA poespoes");
// r3 -> r2 (forwarded via r1)
assert(r3.sendData(cast(byte[])"ABBA naainaai", "p2Pub"));
assert(r3.sendData(cast(byte[])"ABBA naainaai", r2_ident.getPublicKey()));
// todo, use condvar to wait aaasuredly
Thread.sleep(dur!("seconds")(2));
// check reception of message
assert(r3_to_r2_reception.getPayload() == "ABBA naainaai");
// r1 -> r1 (self-route)
assert(r1.sendData(cast(byte[])"ABBA kakkak", "p1Pub"));
assert(r1.sendData(cast(byte[])"ABBA kakkak", r1_ident.getPublicKey()));
// todo, use condvar to wait aaasuredly
Thread.sleep(dur!("seconds")(2));
// check reception of message

View File

@ -12,14 +12,23 @@ public abstract class Link
private SList!(Receiver) receivers;
private Mutex receiverLock;
/**
* Constructs a new `Link`
*/
this()
{
this.receiverLock = new Mutex();
}
// A link driver must call this when it
// has new data
public final void receive(byte[] recv, string srcAddr) // todo, src addr?
/**
* A link driver must call this when it
* has new data
*
* Params:
* recv = the received bytes
* srcAddr = the link-layer source address
*/
public final void receive(byte[] recv, string srcAddr)
{
// To avoid potential design issues
// lock, copy, unlock and then emit
@ -53,6 +62,12 @@ public abstract class Link
}
}
/**
* Returns the number of attaches
* receivers
*
* Returns: the count
*/
public final auto getRecvCnt()
{
this.receiverLock.lock();
@ -65,6 +80,16 @@ public abstract class Link
return walkLength(this.receivers[]);
}
/**
* Attaches the given receiver.
*
* Will not attach it if
* already attached
*
* Params:
* receiver = the `Receiver`
* to attach
*/
public final void attachReceiver(Receiver receiver)
{
this.receiverLock.lock();
@ -86,6 +111,13 @@ public abstract class Link
this.receivers.insertAfter(this.receivers[], receiver);
}
/**
* Removes the given receiver.
*
* Params:
* receiver = the `Receiver`
* to remove
*/
public final void removeReceiver(Receiver receiver)
{
this.receiverLock.lock();
@ -98,18 +130,38 @@ public abstract class Link
this.receivers.linearRemoveElement(receiver);
}
// Link-implementation specific for driver to send data
// to a specific destination address
/**
* Link-implementation specific for driver to send data
* to a specific destination address
*
* Params:
* xmit = the data to transmit
* addr = the link-layer destination address
*/
public abstract void transmit(byte[] xmit, string addr);
// Link-implementation spefici for driver to broadcast
// to all hosts on its broadcast domain
/**
* Link-implementation spefici for driver to broadcast
* to all hosts on its broadcast domain
*
* Params:
* xmit = the data to broadcast
*/
public abstract void broadcast(byte[] xmit);
// Link-implementation specific for driver to report its address
/**
* Link-implementation specific for driver to report its address
*
* Returns: the link-layer address
*/
public abstract string getAddress();
// shows the memory address, type and the number of attached receivers
/**
* Shows the memory address, type and the number of
* attached receivers
*
* Returns: the string
*/
public override string toString()
{
// fixme, calling this has shown to cause deadlocks, and I know one case where
@ -124,11 +176,23 @@ public abstract class Link
}
}
// A subscriber could be a router that wants
// to subscribe to data coming in from this
// interface
/**
* A subscriber could be a router that wants
* to subscribe to data coming in from this
* interface
*/
public interface Receiver
{
/**
* On reception of the provided data from
* the given link-layer address over
* the given `Link`
*
* Params:
* source = the source `Link`
* recv = the received data
* srcAddr = the source link-layer address
*/
public void onReceive(Link source, byte[] recv, string srcAddr);
}

View File

@ -83,7 +83,19 @@ public class LLInterface : Link
public override string getAddress()
{
return if_.getAddress().toAddrString();
import std.stdio;
writeln("HHHA");
writeln("HHHA");
writeln("HHHA");
writeln("HHHA");
// we need the bound port (not the 0 from init)
string port = this.peerSock.localAddress().toPortString();
string ret = if_.getAddress().toAddrString()~":"~port;
writeln("ret: ", ret);
writeln("ret: ", ret);
writeln("ret: ", ret);
return ret;
}
public override string toString()
@ -91,15 +103,42 @@ public class LLInterface : Link
return "LLInterface [name: "~if_.getName()~", address: "~if_.getAddress().toAddrString()~", recvs: "~to!(string)(getRecvCnt())~"]";
}
private static Address getAddress_fromStringWithKak(string addr)
{
import std.string : split, lastIndexOf, strip;
string[] cmps = addr.split(":");
import std.conv : to;
writeln(cmps);
string host = strip(strip(addr[0..lastIndexOf(addr, ":")], "["), "]");
writeln("host: ", host);
ushort port = to!(ushort)(addr[lastIndexOf(addr, ":")+1..$]);
writeln("port: ", port);
return parseAddress(host, port);
}
public override void transmit(byte[] xmit, string addr)
{
// we could send via any socket probably, just destination address is iportant
this.peerSock.sendTo(xmit, parseAddress(addr));
import std.socket : SocketException;
try
{
// we could send via any socket probably, just destination address is iportant
writeln("transmit LLInterface to: "~addr);
writeln("transmit LLInterface to (Address object): ", getAddress_fromStringWithKak(addr));
auto i = this.peerSock.sendTo(xmit, getAddress_fromStringWithKak(addr));
writeln("transmit LLInterface to: "~addr~" with return-no: ", i);
}
catch(SocketException e)
{
writeln("transmit failure: ", e);
}
}
public override void broadcast(byte[] xmit)
{
this.peerSock.sendTo(xmit, cast(Address)this.mcastAddress);
writeln("heyo: broadcasting");
auto i = this.peerSock.sendTo(xmit, cast(Address)this.mcastAddress);
writeln("broadcast result: ", i);
}
private void mcastLoop()
@ -122,14 +161,14 @@ public class LLInterface : Link
// Now dequeue the correct number of bytes
else
{
Address fromAddr; // todo, do we need this?
Address fromAddr;
buffer.length = cnt;
this.mcastSock.receiveFrom(buffer, fromAddr);
writeln("from: ", fromAddr, "bytes: ", buffer);
// Pass received data on upwards
receive(buffer, fromAddr.toString()); // todo, pass in fromAddr
receive(buffer, fromAddr.toString());
}
}
}
@ -138,18 +177,37 @@ public class LLInterface : Link
{
while(this.running) // todo flag
{
byte[] buffer;
byte[] buffer = [1];
// this.socket.receiveFrom(buffer, SocketFlags.PEEK|)
Thread.sleep(dur!("seconds")(100));
// + Return the length of datagram, not successfully read bytes
// + Don't dequeue the datagram from the kernel's internal buffer
SocketFlags firstReadFlags = cast(SocketFlags)(MSG_TRUNC|MSG_PEEK);
ptrdiff_t cnt = this.peerSock.receiveFrom(buffer, firstReadFlags);
// todo, implement dis
if(cnt <= 0)
{
// todo handle errors
// 0 would not happen no dc
// anything less maybe?
}
// Now dequeue the correct number of bytes
else
{
Address fromAddr;
buffer.length = cnt;
this.peerSock.receiveFrom(buffer, fromAddr);
writeln("from: ", fromAddr, "bytes: ", buffer);
// Pass received data on upwards
receive(buffer, fromAddr.toString());
}
}
}
public void stop()
{
// todo, interrupt the thread here - I want to be able to do that
}
}