mirror of https://github.com/deavmi/twine
parent
6aafe0c08f
commit
e060ad4325
|
@ -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,240 @@
|
|||
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 hsot 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 mechansim because several links may be
|
||||
backed by a different link-layer protocols in their `Link` impelemntation
|
||||
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 mematheical 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 theresolution 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, afterwhich 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 therad 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 singaled 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()` wakeup 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 retyurning 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
|
|
@ -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
|
Binary file not shown.
Loading…
Reference in New Issue