mirror of https://github.com/deavmi/twine
Compare commits
57 Commits
cebf44562a
...
1419e99f29
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
```
|
|
@ -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
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##
|
Binary file not shown.
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)(®en, 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue