TDTS06 The labs and x-kernel Christian Vestlund, christian.vestlund@liu.se IDA/ADIT, Linköping University 2008-09-03 Me christian.vestlund@liu.se PhD Student in the security engineering and networks group Office hours: Monday 13 - 15 Thursday 13 - 15 Friday 9 - 10 2 Outline The lab series What are you supposed to do? Lab environment x-kernel Message flow protocols sessions libraries Also, ask questions! 3 Just a short outline of what to expect of this session. Iʼm going to mention the labs and what the labs are about. Then we are going to dive into the lab environment and framework and talk about what it does and how it works. What are the labs about? Understanding concepts in networking Application programmer’s view: !"#$%#&'())*%+(,%-# ."+"%/%#&'())*%+(,%-# 2343 2343 ."*%(0*"'+1(##"* 4 What we want you to achieve during the labs and the course is to understand how the protocols and the protocol stack works in relation to the real-world characteristics of transmission channels. Usually a programmer of an application imagines the network to be like this. The developer uses some library to create connections between hosts and the packets arrive at the receiver using black magic. This view has some disadvantages even though application programming usually comes down to this. Juha has already mentioned some of the application characteristics with regards to network properties so you should already be aware that applications can be sensitive to network attributes such as loss and delay. An application programmer utilizing networks needs to have some understanding on how a network can behave and what can affect an application. What are the labs about? Understanding concepts in networking Characteristics of unreliable channel determines the complexity of reliable data transfer protocols. !"#$%#&'())*%+(,%-# ."+"%/%#&'())*%+(,%-# 2343 2343 5$,96"#$:; 2343 ."*%(0*"'+1(##"* ."*%(0*"'$(,(' ,5(#67"5')5-,-+-* Your work %)96"#$:; )(+8", 5$,9$"*%/"5:; 2343 ."*%(0*"'$(,( ,5(#67"5')5-,-+-* 5$,95+/:; )(+8", <#5"*%(0*"'''+1(##"* 5 This is however the real situation. You have an unreliable physical channel which is used to transfer the data but it drops, delays and distorts packets. On top of that you often have some sort of reliable transfer protocol which takes care of problems due to the nature of the unreliable channel and the higher layers doesn’t have to know about stuff that goes wrong beneath them. And depending on application requirements and the characteristics of the transmission channel the complexity of the reliable transfer protocol varies. In the labs it is your task to ensure the reliability of the transfer protocol by implementing such functionality! The lab series Lab 1: Handling corrupt packets Packet distortion Lab 2: NAK-free protocol Only ACKs Lab 3: Handling lost packets Retransmission Lab 4: MTU discovery and fragmentation 6 The lab series consist of four labs. Each lab is an incrementation of the previous lab, i.e. you will add functionality to handle unreliable behavior in the lower levels of transmission. In the end you will end up with a working and reliable transfer protocol. *In the first lab you will have to take care of possible packet distortion by using ACKs, NAKs and checksums to ensure that a message has arrived at the receiver. *In the second lab we will remove the NAKs and the server will only respond with ACKs, this will require the use of some sort of sequence number *In the third lab we will add a virtual protocol which drops packets every now and then, thus you will have to implement retransmission at the sender side. *And in the last lab we you will have to deal with fragmentation and different unit sizes. The first three labs all correspond to a state machine in the textbook which more or less gives you the solution in terms of what concepts that need to be implemented. In the rest of this session we will cover how you can implement them and what you will have to use in terms of x-kernel primitives. Lab environment Solaris Refresh your Unix commands Setting up the environment 1. $ module initadd /home/TDTS06/modules/tdts06 2. log out 3. log in again 4. $ initTDTS06 You will get a new directory (TDTS06) with several directories and files 7 The labs will be done using x-kernel on the solaris systems in the computer rooms in the B-building. We don’t have any support for running x-kernel on your own system but you can try if you feel that you have time to spare. If you haven’t used any unix system before you should read up a little on basic commands for moving files etc. But you don’t have to worry, if you don’t know how, just come to the lab sessions or my office hours and i’ll help you out. To install x-kernel you need to follow these four steps on the slide. The procedure is on the course web page so you don’t have to remember it now. When you’ve completed the four steps you will get a TDTS06 directory that contains several more files and directories in your home directory. Lab environment Important files in /TDTS06 Makefile (better left alone) graph.comp (we will look closer on this one later on) Protocol subdirectories asp, lab1, lab2, etc. Testing subdirectories Server (start server here) rom file for server Client (start client here) rom file for client Always run make first when you log in to do the labs, this updates the real ip addresses in the rom files. 8 There are some files that are of more interest than others. We have the Makefile, which you don’t need to do anything with. The graph.comp file describes the network stack in use, we will look closer on that one later on. The protocol directories asp, lab1, lab2, etc contain the files you will be working with to implement your solutions. Exactly what you will be changing where will be in the lab descriptions, and I will show you an example of some code later. In the labs you will have to copy your files from the previous lab to the next lab in order to increment on the code. Lastly we have the testing directories where you will start the client and server, the rom files in those directories describes the network addresses used and simulated. rom files # The SIMETH line identifies the UDP port that this simulated host # should use to simulate an ethernet device. simeth 1234 ... # One of the real port numbers should correspond to the port number in # the preceding SIMETH line. # # simulated # IP address Real IP address Real Port number arp 128.1.2.3 arp 128.1.2.4 130.236.177.13 130.236.177.13 1234 9876 9 The rom files are important to configure correctly, otherwise you will get into trouble like many students before you. The simeth line identifies the port number for *this* host, which means the host in which directory you’re in. The arp lines then identifies the ports and ip numbers for every other host simulated. In this case the first line corresponds to the server and the second to the client. Thus you can see that the real port number and the simeth number are identical. All this is in the setup guide so you don’t have to memorize it, I just want to highlight the importance of it. The x-kernel framework Object-based framework for implementing, testing and simulate protocols. Lots of code (you don’t have to learn all of it) Written in C Real-life language for implementing e.g. protocols. Fairly high-level language with access to internal hardware Learn your pointers 10 So further on to the actual framework. The framework is object-based, not to be confused with object-oriented. The actual difference is that you always send along session objects to the other layers instead of calling different layers for a session object. The framework consists of a lot of code for the different protocol layers, and you don’t have to learn all of it. But it is a good idea to look on other example protocols for implementation hints. x-kernel and the protocols are all written in C. This makes for a quite good exercise since C is a “quick and dirty” programming language used in real life for implementing for example, protocols and other fast data handling functionality. The most important part for these lab, however, is that you need to know how to handle pointers. C pointers y int y = 5; int* yPtr; 5 #42 yPtr = &y; yPtr 42 #1337 printf(“*yPtr = %d, y = %d”, *yPtr, y); > *yPtr = 5, y = 5 printf(“yPtr = %d, &y = %d”, yPtr, &y); > yPtr = 42, &y = 42 11 This is just a short description of C pointers. Suppose we have to variable, one is an integer y with the value 5 and the other one is an unassigned integer pointer. Each variable resides on their own address in memory, in this case 42 and 1337. When we assign the yPtr variable the address of the y variable the yPtr will get the address of y as its value, i.e. it points to that address in memory. So if you dereference the yPtr variable and print it out you will print out the value of the variable it’s pointing to, in this case 5. And the other way around if you print out the yPtr variable’s value you will get the address to the variable it’s pointing to. x-kernel Main objects Collection of libraries Protocols Message library Sessions Event library Uniform interface for protocol interaction xPush, xPop, etc. Map library Trace library Utility routines 12 Now we will dig deeper into x-kernel and describe the objects and functions you will be working with. The main objects in x-kernel are the protocols and the sessions. You will be implementing the functionality in one of the protocols in the protocol stack. The sessions can be regarded as an established connection between a pair of hosts. Further on we have a uniform interface for interacting with higher and lower protocols which have caused confusion in the past. What it means is that you don’t call specific Push and Pop functions to interact with each layer, you call the xPush function with a session object which will identify which protocol’s xPush function you want to call. I will show you some examples of the flow of function calls in a short while. Lastly we have the libraries with all the primitives you need to manipulate messages, schedule events and debug your solutions. We will look closer into each of these as well. Flow of messages xPush is linked to the lower layer’s push function. i.e. if asptest.c calls xPush(...) the aspPush(...) function in asp.c will be called. xDemux is linked to the higher layer’s xDemux function. xPop in aspDemux will call aspPop aspPop decides whether to call xDemux in the higher layer or reply by calling xPush. 13 This is just a textual description of the protocol interaction functions. The three functions you really have to care about are xPush, xPop and xDemux. xPush is linked to the lower layer’s push function. xDemux is linked to the higher layer’s demux functions. And lastly, a layer’s demux function will call the pop function for the same layer and the pop function decides whether to call xDemux for the next layer to deliver the message or to call xPush to reply to the message. Flow of messages !"#$%"$&' !"#$%"$&' ()*+%"$ ,-)". "%(4%(5%6), !"#&' !"#&' !"#5%6), !"#-)". !"#-7# ,-)". /#&' /#&' /#-)". /#5%6), /#-7# ,-)". ,5%6), ,5%6), 0%1/!21%3'.!**%1 14 Graphically it can look like this. The runTest function in the test protocol calls xPush, which invokes the aspPush function in the asp protocol. aspPush calls xPush to forward the message to a lower layer, which in this case is the ip layer. The ipPush function will call xPush to push the message further down the stack. When the message then arrives at the receiving host the lowest layer will call xDemux to pop it upwards in the protocol stack. The ipDemux will be called, which calls the ipPop function which in turn calls xDemux to deliver it up to aspDemux, and so on. Example - aspPush static XkHandle aspPush(Sessn self, Msg *msg){ SessnState *sstate = (SessnState *)self->state; ASPhdr hdr; char *buf; /* create a header by inserting length into header template */ hdr = sstate->hdr; hdr.ulen = msgLength(msg) + HLEN; /* attach header to message and pass it on down the stack */ buf = msgPush(msg, HLEN); aspHdrStore(&hdr, buf, HLEN); xTraceS3(self, TR_DETAILED, "PUSH header(sport=%d, dport=%d, ulen=%d)", hdr.sport, hdr.dport, hdr.ulen); return xPush(xGetSessnDown(self, 0), msg); } 15 Lastly I have some code excerpts from the asp protocol’s push and pop function. The asp protocol does not really implement anything other than just the absolute basics such as header manipulation. As you can see in the push function here we get a message from the layer above, we create a header, prepends the header to the message and the push it further down the protocol stack. You can see that there are nothing whatsoever added to the header except for the length of the message, no checksum, data type or anything. Here is also an example of the trace library’s trace functions. Example - aspPop static XkReturn aspPop(Sessn self, Sessn lls, Msg *msg, void *hdr){ ASPhdr *h = (ASPhdr *)hdr; /* truncate message to length shown in header */ if (h->ulen - HLEN < msgLength(msg)) msgTruncate(msg, (int)h->ulen); xTraceS1(self, TR_DETAILED, "Datagram now has size: %d", msgLength(msg)); if (DEBUG) { print_msg(msg); printf("\n"); } /* pass the message to the next protocol up the stack */ return xDemux(xGetUp(self), self, msg); } 16 The pop function is even simpler than the push function. We get a message and the corresponding header from aspDemux, the message gets truncated to remove any trailer still left, and then it gets demuxed to the higher layer. Here we can see that there is no checking if the message is in order, is correct or anything like that. It’s just a simple transfer protocol. Protocols and sessions The protocols are statically loaded and initialized Sessions are “instances” of protocols which are linked to lower and/or higher protocol layers. Sessions are either opened actively or passively, depending on the hosts mode (client/server). *'++)%&+ ".%(%$%/# +(,$0 ,+-('+( ,+)- !"#$%&&'$()%& 17 Each protocol in x-kernel is statically loaded and initialized. Each protocol keeps a map of the different sessions it currently has. This means that there can be several connections between different hosts in x-kernel, you do not have to care about this since we will only be using two hosts and one connection between them. The sessions can be seen as “instances” of protocols, each session has a state which keeps track of its connection to the other host. Session state Each session has a session state struct (asp_internal.h) Useful to keep session related attributes in this struct typedef struct sstate { ASPhdr hdr; } SessnState; 18 A session state structure is meant to keep tabs on variables necessary to handle that session, in here it might be useful to add for example sequence numbers or session events. The example here is for the asp protocol which doesn’t really keep track of anything except for the header template. Protocols and sessions Control functions are used to get or set variables in other protocol layers. rdttest.c: sessnRes = xControlSessn(ps->lls, RDT_SETPKTSIZE, (char *)&pkt_size, sizeof(pkt_size)); rdt.c: case RDT_SETPKTSIZE: checkLen(len, sizeof(short)); pktsize = *(short *)buf; if (pktsize > RDT_MAX_PKT || pktsize <= HLEN) {return -1;} return sizeof(pktsize); 19 There are also control functions that you can use to set or get variables for sessions in lower/higher layers. You are going to need this in at least the last lab to get the MTU from the lower layer. This can look like this code example. In rdttest.c you will call the control function for the lower layer with the argument RDT_SETPKTSIZE. This will invoke the control function in the lower layer for the case RDT_SETPKTSIZE for session ps->lls which will write the MTU to the variable value sent down with the function call. Protocol headers Each protocol has its own protocol header (asp_internal.h) typedef struct header { ASPport sport; /* source port */ ASPport dport; /* destination port */ u_short ulen; /* ASP length */ } ASPhdr; 20 Each protocol also has its own protocol header which needs to be added when the message is passed down the stack. In the header you will need to add necessary information to ensure the transfer reliability for the transmission channel in the lab. This might be, for example, the packet’s sequence number and checksum. The protocol graph $cat graph.comp @; name=simeth; name=eth protocols=simeth; name=arp protocols=eth; name=vnet protocols=eth,arp; name=ip protocols=vnet; name=icmp protocols=ip; name=vdelay protocols=ip; name=vdrop protocols=vdelay; name=vdistort protocols=vdrop; name=rdt files=rdt/rdt protocols=vdistort; name=rdttest files=rdt/rdttest protocols=rdt; @; 21 The graph.comp file describes the current protocol stack in use in xkernel. The two lowest protocols are the ones you will be working with, rdttest and rdt. You can see that each protocol is linked to another protocol, this linkage defines the protocol stack. You will need to modify this file depending on what the lab requires in terms of channel characteristics. You can also see three virtual protocols, which are protocols which you will need to add to the graph.comp file in order to simulate the channel characteristics. In other words, they are there to make the labs a bit more interesting. Virtual protocols vdrop, vdistort, vdelay Does not add/remove headers or information in a message Simulates real-world communication channel characteristics ',-.$,./* ',-.$,./* #0"1$,. 230,+ ,$#4$#5$602 ',-/* ',-/* ',-5$602 ',-30,+ ',-37- 230,+ 25$602 859:3 &-/* &-/* &-30,+ &-5$602 &-37- 230,+ 25$602 !"#$%&'(%$)))*+'""$% 22 The virtual protocols are just to simulate the real-world issues in the transmission channel. We have three virtual protocols which will be used in the labs; vdrop, vdistort and vdelay. The protocols does not add/remove headers or information in a message but for example the vdrop protocol can remove entire messages from reaching the receiver. In lab 1 and 2 you will only have to use the vdistort protocol which affects the order of the bytes in a packet and in lab 3 and 4 you are going to use all virtual protocols. Message library Creating and manipulating messages Optimized to avoid excessive copying Examples of message objects; IP datagram, Ethernet frame, TCP segment, etc. 23 Now we will go into the libraries used to implement your reliable transfer protocol. The message library will be the one of most interest since this is mostly about creating and manipulating messages. The message library is optimized with regards to copying overheads, however we will not go any deeper into that since it’s quite a complex structure. Typical examples of real-life messages are IP datagrams, ethernet frames and TCP segments. Message library - creating messages Creating messages Msg *myMsg; Msg myMsg; char *bug; char *bug; ... ... myMsg = X_NEW(Msg); MsgConstructBuffer(&myMs g, buf, strlen(buf)); MsgConstructBuffer(myMsg, buf, strlen(buf)); ... ... msgDestroy(&myMsg); msgDestroy(myMsg); 24 This slide shows two different ways of creating a message. You can probably spot that the main difference is in the line with X_NEW, which is a memory allocation macro. Otherwise they are quite alike even though the difference in declaration, so remember to be wary of allocation and pointers. Message library - headers Adding a header m DATA buf Stripping a header hdr m msgPush(m, HLEN); aspHdrStore(&buf, m, HLEN); m hdr+DATA hdr+DATA buf = msgPop(m, HLEN); m DATA buf hdr RDThdr h; aspHdrLoad(&h, buf, HLEN); 25 Other operations are to add and remove headers from a message. The removal of headers is usually done in the xDemux function which you probably don’t have to worry about. Adding a header is more important for you. There are two functions that need to be used for this action; msgPush and aspHdrStore. The msgPush function allocates memory of size HLEN in front of the message and returns a pointer to that location. Then you use the HdrStore function to store the header of length HLEN at location m. Message library - fragmentation Fragmenting m DATA Reassembling m1 DA + m2 TA msgBreak(m, new, 2); msgJoin(new, m1, m2); new DA + m TA new DATA 26 Fragmentation will be an important part for the last lab. The actual use of the fragmentation function is quite easy, you call the msgBreak function with the message you want to fragment, the empty message new in which you want to store the fragment and then the size of the fragment. in this example we have message m which we want to split into chunks of 2 bytes length. Thus we call msgBreak with m, our empty new message and 2 as arguments and we end up with two smaller messages. Reassembling a message is not any more difficult. Call msgJoin with your two fragments and a new message which will consist of the assembled fragments and voila, you have a new message with all the fragments. Also note that the functions takes message pointers as arguments. Message library Fragmentation When fragmenting you want to create empty fragments to assign message fragment to. Msg* frag_msg; frag_msg = X_NEW(Msg); msgConstructEmpty(frag_msg); -(.% !&& !"#$%"$&' )%""!*% ... msgBreak(msg, frag_msg, MTU); !"#&' +!',%$ (#&' 27 Lastly, when dealing with fragmentation you will have to create empty messages in which to put your fragments. The only difference here is the usage of msgConstructEmpty which initializes the empty message. The fragmentation is important since each layer might have its own predefined MTU. In the x-kernel framework the test file reads a file and sends messages down to your protocol. The protocol in turn might have its own MTU and must divide the message into several smaller packets, then append a header to each of those and send it down to the next layer. Event library Scheduling protocol timeouts evSchedule evSchedule(retransmit, (void*)arg, timeout_length) The first argument is the function you want to be executed when the timeout occurs. Definition of retransmit function: static void retransmit(Event ev, void *arg){ //Cast arg to non-void type ... } evCancel evCancel(Event ev) 28 Next we have the event library which is a critical part in any reliable protocol. Since you don’t know whether a message has arrived in some cases (lost ACKs or lost messages) you might have to resend a message. Thus you have to schedule your retransmissions using events. The scheduling of events is done by calling the function evSchedule with the arguments; the retransmit function that should be called when the timeout occurs, the argument for the retransmit function and in how long time the event shall occur. evSchedule returns an event which you want to keep track of in case you need to cancel the event. The retransmit function has to be defined with the event as first argument and then the retransmit arguments as second argument. Also not that the arguments to the retransmit function must be casted to (void *) and then cast to a non-void type in the retransmit function. Cancelling an event is done by calling evCancel with the event as argument. To be able to cancel an event you must remember to keep track of it using for example the session state. Trace library Useful when debugging and understanding x-kernel flow 29 The trace library is what you want to use if you need to follow the execution of the protocols for testing and debugging. There are several different levels of traces which you can see on the slide. the most useful ones in my opinion are TR_FULL_TRACE and TR_DETAILED. Trace library Used in graph.comp @; name=simeth; name=eth protocols=simeth; name=arp protocols=eth; name=vnet protocols=eth,arp; name=ip protocols=vnet; name=icmp protocols=ip; name=vdelay protocols=ip; name=vdrop protocols=vdelay; name=vdistort protocols=vdrop; name=rdt files=rdt/rdt protocols=vdistort trace=TR_DETAILED; name=rdttest files=rdt/rdttest protocols=rdt; @; 30 You activate the traces by adding an argument in the graph.comp file. just add trace followed by the trace level and run make compose which update x-kernel with the graph.comp file. This will affect the output when you run the server and client since it will print out all predefined (and own defined) traces. Semaphores Each session will need a semaphore to wait for ACKs. Semaphore mySem; Initialized once when the session is created semInit(&sstate->mySem, 0); A call to semWait will block others from using the resource (e.g. when you have done xPush and wait for an ACK). semWait(&sstate->mySem); A call to semSignal will increment the semaphore’s value and allow the use of resources again. semSignal(&sstate->mySem); semInit(init) semWait() //init - 1 semSignal() //init + 1 31 Semaphores are what you are going to use to be able to wait for acknowledgements on a sent message. What’s important when you use semaphores is the initiation value. In your labs you will only have to initiate the semaphore to 0 because you are only going to have one message inthe-air at any one time. Then, for each time you push a message, you will call semWait which decrements the value of the semaphore and causes it to block all other use of this session. When for example an ACK arrives you will have to increment the semaphore using semSignal in order to allow continued execution. Checksum Checksum variable is initialized to 0. Calculation of checksum is done over the whole message (message + header). chk = inCkSum(msg, &hdr, sizeof(ASPhdr)); Expects an even number of bytes in msg and hdr. 32 In the first lab you will need to implement the use of checksums in order to handle corrupt packets. To calculate the checksum for a message you need to call the inCkSum function with the message, header and header size as arguments. Be careful about the sizes of your messages and headers here. The checksum function expects both the message and the header to be an even number of bytes, so you should be smart when choosing your message and header sizes. Protocol examples Look in /home/TDTS06/xkernel/protocols for examples of other implemented protocols. Very useful to see how different mechanisms are implemented. E.g. BLAST for fragmentation 33 For examples of already implemented protocols you can go to the TDTS06 xkernel directory. There are a lot of example protocols which all implements various mechanisms. I know that I used to look at the CHAN and BLAST protocols when I took this course. Debugging printf is your friend Use the Trace library by adding trace lines in graph.comp Plan your development Small increments are often better 34 Now we are at the end of this tutorial, just some concluding remarks left. When you implement your solution you can never print too much to see how your protocol executes, either by using the trace library or your own prints. And also, try to plan your development so you keep track of which parts interacts with what.