Add first lessons

This commit is contained in:
Wojciech Kozlowski 2019-04-07 22:30:53 +02:00
parent 735131c453
commit 8aa98e7460
9 changed files with 365 additions and 12 deletions

View File

@ -19,6 +19,12 @@ makes them difficult to learn from and experiment with. As somebody looking
for resources to learn about configuring networks to play with on my own
computer that seemed like a big gap.
Furthermore, there are lots of resources online for learning the theory of how
networks are connected to each other and how routing works on the Internet, but
very few of them come with any practical examples. It is one thing to learn
the concepts and a completely different thing to apply them in a practical real
world scenario. Route 0 was made to address this issue as well.
### Purpose
The primary purpose of Route 0 is to provide a framework for learning how
@ -101,7 +107,18 @@ The password for all daemons is `route0`.
### Lessons
TODO: WRITE UP LESSONS
The lessons in this repository are aimed to take somebody who knows nothing
about IP routing all the way to setting up networks with multiple autonomous
systems and VPN tunnels. The lessons are structured in such a way that the
reader must first manually setup and configure the network before moving on to
the next step. Each stage starts at a point which can be automatically
provisioned by Route 0 and the purpose of each lesson is to explain how this
automation is achieved through network configuration. This particular
structure also means that it is possible to dive in at any point making it
suitable for people with more experience as well.
The table of contents with links to all the lessons can be found in the
[lessons directory](lessons).
## Mininet Concepts

4
lessons/README.md Normal file
View File

@ -0,0 +1,4 @@
# Table of Contents
* [Introduction to Route 0](introduction-to-route-0.md)
* [IP Addresses and Subnets](ip-addresses-and-subnets.md)

View File

@ -0,0 +1,153 @@
# Introduction to Route 0
This lesson is an introduction to Route 0, some basic networking commands on
Linux, and Wireshark.
## Topology
First, let's look at the topology that we will be using for this lesson, the
`one_rtr` topology. You can view it in this its
[README](../topology/one_rtr/README.md). The network is very simple. It
consists of three nodes, but only one of them, `R1`, is a router, hence the
name of the topology. The other two are end-hosts. A host is not necessarily
a different device to a router, but it has a very different role in the
network. A host will only have one outgoing link and it will not forward IP
packets which means that it can only be the source or destination of IP
communication. The convention in Route 0 is to name routers with a name that
starts with the letter `R` and hosts with a name starting with `h`.
You can launch the network by running
```
sudo python route0.py --topology one_rtr --scenario basic
```
This command instructs the driver script `route0.py` to start a network with
the `one_rtr` topology running the `basic` scenario. The `basic` scenario is
special and simply means to run the network and set up all the interface
addresses and default routes. We will go over what this means later in this
lesson.
Once the CLI prompt appears let us inspect Mininet's representation of the
network by running
```
net
```
in the command prompt. The output tells us about all the nodes in the network
and the connections between them. We can see that `R1`'s `R1-eth1` interface
is connected to `h1_1`'s `h1_1-eth1` interface and `R1-eth2` is connected to
`h1_2`'s `h1_2-eth1` interface. You can visualise the network by copy pasting
the output into this [web
tool](https://achille.github.io/mininet-dump-visualizer/) though its usefulness
is limited for small networks such as this.
## Basic IP commands
Let us now inspect the network using some basic Linux commands. The three main
commands we will use to investigate the state on the nodes are `ip
address`, `ip route`, and `ping`. To run any of these commands on a particular node,
you need to prefix it with the node's name in the Mininet CLI. For example, to
see all the interfaces and their addresses on `R1` you would run
```
R1 ip address
```
There is also an older obsolete command `ifconfig` which is still commonly
used. However, all information available through `ifconfig` is available
through the `ip` commands.
### ip address
This command lists all addresses assigned to the interfaces on the given
device. This includes the Ethernet address as well as all IPv4 and IPv6
addresses. For the purposes of these lessons we are only interested in the
IPv4 addresses which are displayed as either `x.x.x.x` or `x.x.x.x/y`.
The first thing to notice when running this command (especially on `R1`) is
that there are multiple IP addresses assigned to a single device. This is
because IP addresses are bound to network interfaces not devices. Furthermore,
it is also possible to assign multiple IP addresses to a single interface. You
will notice that the `lo` interface on `R1` actually has two IP addresses.
### ip route
The `ip route` command is used to list all the routes installed on a particular
node. The basic format of a route is `x.x.x.x/y via z.z.z.z` which says that
to reach the IP network `x.x.x.x/y` you must go via the address `z.z.z.z` which
should resolve to a directly connected neighbour. Note that you won't see such
routes in this network setup, because the network is too simple.
The host nodes have a default route installed which looks like `default via
z.z.z.z` which means that the node should route all traffic it doesn't have a
more specific route for via `z.z.z.z`.
In the network we have running you will also see routes of the form `x.x.x.x/y
dev if-name` which means that in order to reach `x.x.x.x/y` you must go via the
network connected to the interface `if-name`.
### ping
The command `ping` sends a special IP packet to the specified destination to
verify connectivity with that end-host. Try sending a ping from `h1_1` to an
IP address on `h1_2` by running
```
h1_1 ping 10.2.0.1
```
The address `10.2.0.1` is the IPv4 address assigned to the interface
`h1_2-eth1` on `h1_2`. The command will keep pinging the specified destination
every second. To stop press `Ctrl+C`. Now try pinging the other way. The
intermediate node `R1` knows how to forward the traffic between the two hosts,
because it is directly connected to both of them.
## Wireshark
Before moving on to the next section it would be good to introduce a
particularly useful tool in studying networks, Wireshark, by using it to look
at pings from `h1_1` to `h1_2`. Wireshark is a tool that lets you capture and
inspect packets sent and received over all interfaces on a device.
Furthermore, it is able to present them in a human readable form rather than
simply dumping the binary representation directly from the wire.
Start by running the command to trigger `h1_1` to start sending pings to
`h1_2`. Now open a new terminal window and navigate to the `route0` directory.
We will use the `attach.py` helper script to run Wireshark on `R1` and `h1_2`.
Let's start with `R1` by running
```
sudo python attach.py --node R1 --cmd wireshark
```
When the Wireshark window opens you can dismiss all the Lua errors if you get
any. First, we need to select which interface we would like to inspect the
packets on. Let's start with `R1-eth1` as that's the interface that is
connected to `h1_2`, the source of the packets. You can either double-click on
the interface name or select the appropriate button on the menu bar in the
top-left corner.
Once the packet capture notice how the ping packets appear every second as a
request/reply pair. Look at the source and destination IP addresses as well.
Note how the originating node has filled out the source address with the
address of its interface `h1_2-eth1` and how the reply has the addresses
flipped around. Have a look around and inspect the contents if you wish, but
we won't go into any detail on the form of the ping packets.
Now let's look at the packet capture on the other interface on `R1`. You can
do this by stopping the current capture, finding the capture options button and
starting a capture on `R1-eth2`. The packets on this interface look identical
which is expected. The `R1` router has forwarded the request packet from
`R1-eth1` to `R1-eth2` and vice-versa for the reply packet.
You can also inspect the capture on `h1_2`, but since this is a different node
you will have to close the Wireshark window and run the `attach.py` command on
the host node.
## Leaving Route 0
To exit the Mininet CLI and return to the shell just run the `exit` command.
This will shut down all the nodes and protocols that are running.
## Conclusion
In this lesson you learned how to start up Route 0 experiments and learned how
to inspect your network using basic Linux commands and Wireshark. You will
find these tools will come in handy at all times whenever dealing with
networks.

View File

@ -0,0 +1,178 @@
# IP Addresses and Subnets
This lesson introduces and explains IP addresses, subnets, and routing between
directly connected devices.
This lesson is a continuation of the
[introduction](introduction-to-route-0.md). Here, we will manually setup all
the interfaces and routes to achieve the network connectivity we had there for
the `one_rtr` topology.
## Assembling the network
The best way to learn and understand what is going on with addresses and routes
is to actually manually setup the network and inspect the effects of the
individual pieces on the connectivity of the network.
Start the network just like in the introductory lesson, but this time with none
of the address and route configuration by running the `plain` scenario
```
sudo python route0.py --topology one_rtr --scenario plain
```
Start by having a look around using the commands you learned in the previous
lesson, `ip address` and `ip route`, and notice how none of the addresses or
routes are present on any of the nodes. Furthermore, if you try running the
pings between any of the nodes, you will find they do not work and fail with a
`Network is unreachable error`. In this lesson we will manually reconstruct
the `basic` network to illustrate all the different concepts involved.
### Assigning IP addresses
A good place to start would be to simply assign all the IP addresses as per the
`one_rtr` topology [README](../topology/one_rtr/README.md). The command to
assign an IP address to an interface in Linux has the form
```
ip address add [ip]/[mask-digits] dev [if-name]
```
This command assigns the address `ip` associated with the subnet defined by the
`mask-digits` to the interface `if-name`. This should be pretty
self-explanatory except for the subnet which may be a new concept for some of
you.
An IPv4 address is basically a 32-bit number. The common representation
`x.x.x.x` simply splits this number into four 8-bit numbers making it more
readable for a human. This is why none of the four numbers ever exceed 255 as
that is the largest number you can represent with 8 bits.
A subnet is a subdivision of an IP network and determines all the possible IP
addresses that can be connected directly to each other over a local network.
All IP addresses that belong to the same subnet are accessible through the same
local network. Therefore, having an interface that is part of a particular
subnet means that we can communicate with all the other addresses in that
subnet by using this interface.
The subnet of an IP address is determined by its prefix. The length in bits of
the prefix is determine by the `mask-digits.`. Thus, the IP address
`10.11.12.13/24` belongs to a subnet defined by its first 24 bits, that is
`10.11.12.0/24`. The router will now forward all traffic to any IP address on
this subnet, such as `10.11.12.1` or `10.11.12.165`, over this interface.
This is how in the example in the previous lesson `R1` was able to forward the
packet from `h1_1` to `h1_2`. `R1` received a packet addressed to `10.2.0.1`
on its interface with `h1_1`. It quickly determined that `10.2.0.1` belongs to
the subnet `10.2.0.0/24` which is connected to its `R1-eth2` interface which
had the address `10.2.0.254/24`. Therefore, it was able to forward the packet
over this interface.
Go ahead and assign all the IP addresses to the interfaces in the network.
Don't forget to prefix the commands with the name of the node on which you want
to run the command.
Once all addresses have been assigned try pinging `h1_1` and `h1_2` from the
middle node, `R1`, and verify that this works. You should also verify that
both `h1_1` and `h1_2` are able to ping the IP address at the other end of
their link. However, neither `h1_1` or `h1_2` should be able to ping the
interface on the other side of `R1` or each other.
Try also running the `ip route` command on the nodes and notice how they have
the routes associated with the interface subnets installed already without any
additional intervention.
## Address Resolution Protocol (ARP)
In the previous section we said that packets for any address on a given subnet
are forwarded through the interface that belongs to that subnet. What if the
destination IP address is not connected to that subnet? In our `one_rtr`
example we only have `10.1.0.1` and `10.1.0.254` on the network on the subnet
`10.1.0.0/24` which is effectively a local network of one point-to-point link.
Try pinging `10.100.0.5` and `10.1.0.5` from `h1_1`. Notice how both fail, but
only the first one returns the `Network is unreachable error`. Why does the
second one appear to be stuck? Since `10.1.0.5` belongs to the same subnet as
`h1_1-eth1` the host tries to send the ping over this interface, but as the
other end does not exist, the response never arrives.
Let's investigate this using Wireshark. Set up `h1_1` to ping the nonexistent
`10.1.0.5` and open Wireshark on its interface from a different terminal window
by running
```
sudo python attach.py --node h1_1 --cmd wireshark
```
and start a packet capture on the `h1_1-eth1` interface.
The first thing you will notice is how `h1_1` keeps send ARP protocol messages.
ARP stands for the Address Resolution Protocol and is the mechanism by which a
node finds the MAC address of the interface associated with the particular IP
address. In order to send a packet over a link it must be addressed to the
right MAC address as otherwise no interface on the local network will pick the
packet up. In this case we see packets constantly asking "Who has 10.1.0.5?
Tell 10.1.0.1", but nobody owns that IP address so nobody responds.
Let's now look at what happens when the IP address exists on the network. Set
`h1_1` to ping the other end of its link `10.1.0.254` (you don't have to close
wireshark). Most of the packets sent and received will be the already known
ping packets, but every now and then an ARP request is sent. However, this
time `h1_1` receives a response telling it the MAC address of the interface.
If you inspect the ping packets that originate at `h1_1` you will notice that
they do use that MAC address in the Ethernet header.
You may wonder why do the nodes need to do this? After all the IP address
already uniquely identifies the interface. This is because the IP protocol
doesn't actually know how to communicate over a local network using a physical
interface, it needs another protocol to do it instead. In this case it's the
Ethernet protocol, but it could be something entirely different such as Wi-Fi
or some older protocol. The ARP protocol is a tool for the IP protocol to find
out what address to give to the Ethernet protocol so that it can send its
packet to the next node.
## Default routes
At this point `h1_1` and `h1_2` still cannot ping each other. If you try to
ping `10.2.0.1` from `h1_1` you will be told that the network is unreachable.
If you look at the output of `ip route` on the host this error makes sense.
The routing table doesn't know how to reach any subnet other than
`10.1.0.0/24`. We could just add a route for the `10.2.0.0/24` subnet to go
via `R1` to `h1_1` which would work for `h1_2`, but would fail as soon as any
new host is added to `R1`.
Instead we will add a default route to our host. A default route is the route
used for IP addresses that do not match any other more specific route. To add
a default route we simply run
```
ip route add 0.0.0.0/0 via 10.1.0.254
```
which tells `h1_1` to send all packets via `10.1.0.254` which is the IP address
of the interface on `R1`. `h1_1` knows how to route to this address, because
it's on the same subnet as its own interface.
Why do we not just install a route to go via the interface directly instead of
specifying an IP address? In our topology we only have one node connected to
the local network, but in principle we could have more. In that case,
specifying an interface would not uniquely identify the next hop.
Try pinging `10.2.0.1` from `h1_1` now. You will notice that it no longer
fails with a "Network unreachable error", but it still doesn't work. Let's
investigate using Wireshark. If you inspect the traffic at `h1_1` you will
notice that the requests are being sent, but no responses are received. Let's
check if `R1` is forwarding the packets. If you launch Wireshark on `R1` you
will notice that the packets are received on one interface and are forwarded to
the other. If you also inspect `h1_2` you will find that the request packets
actually manage to make their way to the destination, but no response is sent.
Can you figure out what's going on? What happens if you try pinging `h1_1`'s
interface from `h1_2`?
The problem is that `h1_2` doesn't have a default route itself. It receives
the ping packets and it tries to send a response back to source IP address, but
then it finds out it doesn't know how which way to send a packet to that IP
address. The solution is to install a default route just like we did for
`h1_1`. Once installed you will notice that pings from `h1_1` now succeed.
## Conclusion
In this lesson you learned how to assign IP addresses to interfaces, what
subnet is and how it is used in routing, and you also learned how to install
default routs on hosts. With these foundations we can move on to more complex
routing where not all hosts are directly connected to the same router.

View File

@ -71,7 +71,8 @@ def run(experiment):
CLI(net)
net.stop()
os.system("killall -9 {}".format(' '.join(experiment.daemons)))
if experiment.daemons:
os.system("killall -9 {}".format(' '.join(experiment.daemons)))
def main():

View File

@ -3,7 +3,7 @@
```
------
| |
h1_1 ---|1 R1 2|--- h2_1
h1_1 ---|1 R1 2|--- h1_2
| |
------
```
@ -22,8 +22,8 @@ Interface | Name | Address/Subnet
----------|-----------|---------------
1 | h1_1-eth1 | 10.1.0.1/24
## h2_1
## h1_2
Interface | Name | Address/Subnet
----------|-----------|---------------
1 | h2_1-eth1 | 10.2.0.1/24
1 | h1_2-eth1 | 10.2.0.1/24

View File

@ -1,9 +1,9 @@
! -*- staticd -*-
hostname h2_1-staticd
hostname h1_2-staticd
password route0
enable password route0
ip route 0.0.0.0/0 10.2.0.254
log file /tmp/h2_1-staticd.log debugging
log file /tmp/h1_2-staticd.log debugging

View File

@ -16,8 +16,8 @@ class NetTopo(Topo):
# Add hosts
h_1_1 = self.addSwitch('h1_1')
h_2_1 = self.addSwitch('h2_1')
h_1_2 = self.addSwitch('h1_2')
# Setup links as shown in README.md
self.addLink(r_1, h_1_1)
self.addLink(r_1, h_2_1)
self.addLink(r_1, h_1_2)

View File

@ -1,10 +1,10 @@
! -*- zebra -*-
hostname h2_1-zebra
hostname h1_2-zebra
password route0
enable password route0
interface h2_1-eth1
interface h1_2-eth1
ip address 10.2.0.1/24
log file /tmp/h2_1-zebra.log debugging
log file /tmp/h1_2-zebra.log debugging