pete > courses > CS 431 Spring 25 > Lecture 06: Network Layer I – IP intuition


Lecture 06: Network Layer I – IP intuition

Goals


first, a couple technical things that will come in handy for the implementation work you’ll be doing


starting with: structs!

we’re going to look at a program line by line

starting with this definition:

struct widget {
    uint32_t the_answer;
    uint16_t a;
    uint16_t b;
};

what does it do?

my guess is that the primary purpose behind your use of structs in the past has been to gather together a bunch of different values that all pertain to a single thing

the implication of the above definition is that there are three pieces of information relevant to a particular widget: a 4-byte unsigned integer and two 2-byte unsigned integers

structs are very useful to gather this stuff together in one thing that can be passed around to functions and such

but there is another important aspect to it that you may not have interacted with directly: a struct definition also establishes a memory layout

the above definition comprises 8 bytes (ie, the sum of the sizes of the constituent fields), so a value of type struct widget needs 8 bytes of memory

indeed, if we declare a local variable of type struct widget, which the compiler will make room for in the current stack frame, this is how the compiler know how much memory to allocate

furthermore, the definition says that the field the_answer is 4 bytes long and comes first, followed by a, followed by b

this means that if I’ve got a value of type struct widget in memory, its the_answer field starts at the beginning of that chunk of memory; its a field starts 4 bytes later; and its b field starts 2 bytes after that

hold onto that thought for a moment


consider this line:

    uint8_t *mem;

what does it do?

it declares a pointer variable—that is, a variable whose value is a memory address

and if we go to that memory address, we’re supposed to interpret the bits we find as unsigned 8-bit integers


now this line:

    mem = malloc(8);

asks for 8 bytes of memory in the heap and stores the address of those bytes in the variable mem

and this line:

    memcpy(mem, "\x2a\x00\x00\x00\x88\x0e\x01\x00", 8);

copies 8 bytes of data into the memory pointed to by mem

(the "\x2a" notation is a way to specify an arbitrary byte within an otherwise-ASCII string: the two digits following the "\x" are the pair of hex digits that represent the desired byte)

so now we’ve filled this chunk of memory with 8 bytes of data


now the tricky bit:

    struct widget *foo = (struct widget *) mem;

we’re assigning the value of mem to foo

mem points to the 8 bytes of memory we just allocated and filled with raw bytes

mem is declared to have type uint8_t *, which means that it’s a pointer to unsigned 8-bit integers

but we’re assigning that value (ie, the memory address) to foo, which has type struct widget *, which means that it’s a pointer to a struct widget

this suggests that the value of foo tells us where in memory to find a value of type struct widget

but we never put a struct widget there, we just put arbitrary bytes

how can this possibly work?


recall that the struct definition also specifies the memory layout, and that the first four bytes of this struct comprise the the_answer field

so if we use the foo->the_answer notation, this will access the first four bytes of memory pointed to by foo

which happens to also be the first four bytes pointed to by mem

which we set to be 0x2a, 0x00,0x00, and0x00` in the memcpy above

like so:

    printf("foo->the_answer is %d\n", foo->the_answer);
    printf("foo->a is %d\n", foo->a);
    printf("foo->b is %d\n", foo->b);

full program here: struct-overlay.c & Makefile

compile and run:

$ gmake
cc -g -Wall -pedantic -c -o struct-overlay.o struct-overlay.c
cc -o struct-overlay struct-overlay.o
$ ./struct-overlay
foo->the_answer is 42
foo->a is 3720
foo->b is 1

(it prints 42 because my machine is big-endian)


how is this useful in networking?

well, you’re going to be writing code that receives chunks of bytes (eg, using read(2)) and those bytes will not have a type associated with them: they’ll be like a uint8_t *

but stuff like the Ethernet header is structured data

and it’s far easier to manipulate (both read and write) fields within the Ethernet header using structs than direct bit operations


second technical topic: the network setup we’ll be working with

in the upcoming assignment, you’ll write code that does Ethernet-y stuff

and in the following assignment, you’ll write code that does IP-ish stuff

it’s worth talking about how all of this is going to fit together


you’re going to write a program that behaves like a host: it will send frames and receive frames

we’re going to use software called "Virtual Distributed Ethernet" to create a virtual switch

and we’re going to run your program and plug the resulting process into it

we can plug many different processes into the same switch, acting like a bunch of machines on the same network


an important development tool is Wireshark, which will allow us to observe frames that are sent and received

we will first run a program that creates an imaginary interface (the name of the interface will always begin with the word tap) and connects it to the virtual switch

so any packets the switch sees, it will send to the interface

then, we will run Wireshark and pass it any packets that the interface sees

the real power of Wireshark is that it will deconstruct each frame and tell us how the various bits are being interpreted

(Wireshark is THE tool for this job. everybody in networking knows it and uses it. the old-school, command-line-only equivalent, is tcpdump, whose existence is worth knowing about because it was previously just as pervasive as Wireshark is now.)

in the upcoming assignment, I will give explicit directions and code to do all of the above; I just want to cover the concepts here


when Wireshark is running, it has three main panes

(the default layout has all three panes stacked vertically; to change it, go to Edit -> Preferences -> Appearance -> Layout; I like the second choice)

the top pane is the list of frames, one per line

if you click a frame in that list, it will show details about the frame in the other two frames

one pane (bottom in the default layout, bottom-right in my layout) shows the raw bytes of the frame, in the same format as you produce with binary_to_hex from assignment 1

the other pane (middle in the default layout, bottom-left in mine) shows the deconstructed frame

you can click on the little triangles to expand the information about each header

and within you can click on a field and it will highlight the bytes in the frame that correspond to that information

Wireshark will be very useful when you’re writing code that produces frames and it’s not clear why the frames are not being interpreted the way you want


back to where we left off last time

we defined network, which is the set of hosts that can be reached by a broadcast frame

we defined bridge, which is a computer that has multiple interfaces, on different networks and combines them into a single one, by passing frames back and forth between them

we observed that this doesn’t scale: as the number of hosts grows, eventually the task of passing frames back and forth (not to mention sending broadcast frames to all destinations) gets intractible

we are now going to develop a solution


let us imagine an internetwork that consists of four networks: One, Two, Three, and Four

Host A resides on Network One

Host B resides on Network Two

Host C resides on Network Three

Host D resides on Network Four

Host R1 resides on both Network One and Network Two

Host R2 resides on both Network One and Network Three

Host R3 resides on both Network Two and Network Four

because these are separate networks, broadcast frames will not be passed through hosts R1, R2, and R3

we will refer to machines that connect together networks as routers

so R1, R2, and R3 are routers


so how can Host A get a frame to Host B?

it has to go through Host R1

likewise, for host A to get a frame to Host D, it has to first go through R1 and then through R3

but how can A know that the frame destined for D has to go through R1?

how can it know that R2 won’t get the job done?

one possible solution is that A sends the frame down all possible paths that might lead to D

this is potentially extraordinarily wasteful: that frame might get sent to a ton of different networks that serve no purpose in its path to D

another possible solution is that A knows the path to every other machine (MAC address) in the internetwork

as the number of machines grows, this quickly becomes intractible: it’s too much data and it’s too difficult to keep it up to date, especially when machines might move among networks (in the distant past, not a huge problem; but these days it happens all the time)

a slight modification of the second possible solution is that routers have knowledge of what MAC addresses can be reached through them

but this just kicks the problem one step over: it still doesn’t scale


it could scale, however, if there was an efficient way of describing which addresses are accessible through a particular router

for example, instead of enumerating 16,384 different MAC addresses, we could much more efficiently say "all MAC addresses beginning with 11:22:33:44"

this won’t work because MAC addresses are assigned when the NIC is manufactured and there is no telling where (geographically) a particular NIC will end up

so we can’t depend on MAC addresses being organized in a way that will permit such an efficient way to describing their location

nor can we coerce it into happening ourselves

the conclusion is that MAC addresses are not suitable for the purpose of identifying a machine on a different (but, ultimately, connected) network

we need another kind of address, one that does reflect the network structure, and thus will allow us to say things like "all machines with addresses in this range are over there"


enter: the IP address

the identifying information used in the Internet Protocol

ie, the protocol that enables sending and receiving packets across an internetwork

(in the world of IP, we will use the word "packet" to refer to the unit of data transmission, just as we used the word "frame" to refer to the same idea at the link layer)

the idea behind IP addresses is that a given network will all have IP addresses in the same range


an IP address is a 32-bit (4-byte) value and is usually written as a "dotted quad", meaning four 8-bit (ie, in the range 0-255, inclusive) numeric values separated by dots

for example: 140.233.20.7 and 192.168.1.19 are both IP addresses

in the example above, all the machines on Network 1 could have IP addresses that begin with 140.233.20

furthermore, the implication is that every machine with an IP address in that range will reside in that single network: it cannot live anywhere else in the internetwork

likewise, machines on Network 2 could have IP addresses that begin with 187.54

machines on Network 3 could have IP addresses that begin with 97.146.83

and machines on Network 4 could have IP addreses that begin with 1.2.3


with this arrangement, then, host A needs to know four things:

important things to note:


the IP address 140.233.20.6 is an example of an IP address in Network 1

we can divide this address into two parts:

the "140.233.20" part identifies the network

and the ".6" part identifies the host within the network

likewise, the IP address 187.54.19.201 is an example of an IP address in Network 2

the 187.54 part identifies the network

and the ".19.201" part identifies the host within the network

so for all machines in the 140.233.20 network, it is those first 24 bits that identifies the network and the last 8 bits that identifies the host

likewise, for all machines in the 187.54 network, the first 16 bits identifies the network and the last 16 bits identifies the host


taking this one step further, we can describe these networks using two pieces of information: the leading bits that are common to all IP addresses in the network and the number of bits

so the two networks from the previous examples could also be described as 140.233.20/24 and 187.54/16, where the number after the slash refers to the number of bits used to idenfify the network in those IP addresses


to bring this back to the four things that host A needs to know, each of them can be described by three values:

so "machines with addresses that begin with 187.54 are accessible through R1" can be remembered as "187.54/16 -> R1’s IP address on Network 1"

and "machines with addresses that begin with 97.146.83 are accessible through R2" can be remembered as "97.146.83/16 -> R2’s IP address on Network 2"

all of this information will be remembered in a data structure within Host A called a routing table


this is the big high-level concept underlying IP; we’ll look at concrete specifics next time

Last modified: