- Initial work done on documentation
This commit is contained in:
Tristan B. Velloza Kildaire 2024-01-17 16:04:45 +02:00
parent 6aafe0c08f
commit e060ad4325
4 changed files with 246 additions and 1 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

240
doc/arp.md Normal file
View File

@ -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

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

BIN
doc/texput.pdf Normal file

Binary file not shown.