miércoles, 16 de noviembre de 2011

3rd Theoretical Assignment - NachOS Networking

For our NachOS 3rd assignment we were asked to implement some network, and security stuff, and for that NachOS already has some code from to start implementing our own userprogs.

First of all, in the nachos/code/network directory, there are two interesting files, post.h-post.cc, which simulates a fixed-size message delivery, which I think they can be modified for other uses, for example, creating a chat.

The code from post.h defines some important classes to simulate this matter (implemented in post.cc), but for understanding purposes I'll show post.h code(which is also smaller because there are just definitions of methods).
  • 'MailHeader', which contains some needed information to send the message, like destination address, the sender's address and the bytes of data from the message:
  
class MailHeader {
  public:
    MailBoxAddress to;  // Destination mail box
    MailBoxAddress from; // Mail box to reply to
    unsigned length;  // Bytes of message data (excluding the
    // mail header)
};
As we see, there is nothing really complicated in that, every mail in real life include the first two, the other one, is the size of the message (apparently in a single message data can only be size : MaxMailSize = MaxPacketSize - sizeof(MailHeader) )
  • 'Mail' which defines the mail itselfs, and it's format. It contains information like and array of characters(length = MaxMailSize) and some other information appended by the 'PostOffice'(explained later on) or the Network.
  
class Mail {
  public:
     Mail(PacketHeader pktH, MailHeader mailH, const char *msgData);
    // Initialize a mail message by
    // concatenating the headers to the data

     PacketHeader pktHdr; // Header appended by Network
     MailHeader mailHdr; // Header appended by PostOffice
     char data[MaxMailSize]; // Payload -- message data
};
Here is a little more complicated to imagine how things work, the MailHeader part is the same that the one explained before, the mail will contain in it's header: the destination address, and the sender's address and the length of the message, but the third one is a little more complicated because the PacketHeader is defined in another class, but we'll see about later.

  • 'MailBox' defines a single and temporary storage, to store the received messages. The incoming messages are stored in the appropriate 'MailBox' by the 'PostOffice', these messages can be retrieved from threads on the running machine.
  
class MailBox {
  public: 
    MailBox();   // Allocate and initialize mail box
    ~MailBox();   // De-allocate mail box

    void Put(PacketHeader pktHdr, MailHeader mailHdr, const char *data);
       // Atomically put a message into the mailbox
    void Get(PacketHeader *pktHdr, MailHeader *mailHdr, char *data); 
       // Atomically get a message out of the 
    // mailbox (and wait if there is no message 
    // to get!)
  private:
    SynchList *messages; // A mailbox is just a list of arrived messages
};
So this 'MailBox' is the one in charge of putting messages in a temporary storage(volatile), and retrieving them for further use. Here I think(theoretically speaking) we could use both methods to retrieve messages and write them, to program a chat.

  • Last but not least, the class 'PostOffice', which defines a collection of several mailboxes. So this class is a synchronization object with the operations Send and Receive. With synchronization it came to mind the 1st NachOS assignment(Locks, Condition Variables, Semaphores), and I remember that a while ago I read in some webpage something about Locks, Semaphores and Condition Variables needed to implement networking stuff. And here, we see that was true.
  
class PostOffice {
  public:
    PostOffice(NetworkAddress addr, double reliability, int nBoxes);
     // Allocate and initialize Post Office
    //   "reliability" is how many packets
    //   get dropped by the underlying network
    ~PostOffice();  // De-allocate Post Office data
    
    void Send(PacketHeader pktHdr, MailHeader mailHdr, const char *data);
        // Send a message to a mailbox on a remote 
    // machine.  The fromBox in the MailHeader is 
    // the return box for ack's.
    
    void Receive(int box, PacketHeader *pktHdr, 
  MailHeader *mailHdr, char *data);
        // Retrieve a message from "box".  Wait if
    // there is no message in the box.

    void PostalDelivery(); // Wait for incoming messages, 
    // and then put them in the correct mailbox

    void PacketSent();  // Interrupt handler, called when outgoing 
    // packet has been put on network; next 
    // packet can now be sent
    void IncomingPacket(); // Interrupt handler, called when incoming
       // packet has arrived and can be pulled
    // off of network (i.e., time to call 
    // PostalDelivery)

  private:
    Network *network;  // Physical network connection
    NetworkAddress netAddr; // Network address of this machine
    MailBox *boxes;  // Table of mail boxes to hold incoming mail
    int numBoxes;  // Number of mail boxes
    Semaphore *messageAvailable;// V'ed when message has arrived from network
    Semaphore *messageSent; // V'ed when next message can be sent to network
    Lock *sendLock;  // Only one outgoing message at a time
};

And thats the post.h code, which defines the message delivery. 

And now continuing, in the nachos/code/machine directory, there are some other classes used in the NachOS network. The first one is network.h-network.cc which manages the simulation of the physical network connection . In the network.h file we can see some interesting stuff as the:
  
typedef int NetworkAddress;

This network address is the machine's ID, which is specified when we execute nachos with the command ./nachos -m 1 -o 0 (command used to test the message delivery, first number is the machine's ID, second is the destination machine's ID).

And the previously mentioned 'PacketHeader' is also defined in this file, which is similar to the 'MailHeader' but instead of containing a 'MailBox' address, it contains a 'NetworkAddress' like the previously explained, for the source and destination machines. 

And finally, the 'Network' class, which simulates a physical network device which sends fixed-size packages to machines connected in this simulated network. Also there's is a reliability number to determine the chances of a packet to lose. A random number is used to determine which packet will be dropped.
  
class Network {
  public:
    Network(NetworkAddress addr, double reliability,
     VoidFunctionPtr readAvail, VoidFunctionPtr writeDone, void* callArg);
    // Allocate and initialize network driver
    ~Network();   // De-allocate the network driver data
    
    void Send(PacketHeader hdr, const char* data);
        // Send the packet data to a remote machine,
    // specified by "hdr".  Returns immediately.
        // "writeHandler" is invoked once the next 
    // packet can be sent.  Note that writeHandler 
    // is called whether or not the packet is 
    // dropped, and note that the "from" field of 
    // the PacketHeader is filled in automatically 
    // by Send().

    PacketHeader Receive(char* data);
        // Poll the network for incoming messages.  
    // If there is a packet waiting, copy the 
    // packet into "data" and return the header.
    // If no packet is waiting, return a header 
    // with length 0.

    void SendDone();  // Interrupt handler, called when message is 
    // sent
    void CheckPktAvail(); // Check if there is an incoming packet

  private:
    NetworkAddress ident; // This machine's network address
    double chanceToWork; // Likelihood packet will be dropped
    int sock;   // UNIX socket number for incoming packets
    char sockName[32];  // File name corresponding to UNIX socket
    VoidFunctionPtr writeHandler; // Interrupt handler, signalling next packet 
    //      can be sent.  
    VoidFunctionPtr readHandler;  // Interrupt handler, signalling packet has 
    //  arrived.
    void* handlerArg;  // Argument to be passed to interrupt handler
    //   (pointer to post office)
    bool sendBusy;  // Packet is being sent.
    bool packetAvail;  // Packet has arrived, can be pulled off of
    //   network
    PacketHeader inHdr;  // Information about arrived packet
    char inbox[MaxPacketSize];  // Data for arrived packet
};

The socket management is defined in the sysdep.h-sysdep.cc files, here we can see some functions used to open a socket where other NachOS machines can send messages, close a socket, initialize a socket, send a packet to another NachOS port, etc.
  
int
OpenSocket()
{
    int sockID;
    
    sockID = socket(AF_UNIX, SOCK_DGRAM, 0);
    ASSERT(sockID >= 0);

    return sockID;
}

void
CloseSocket(int sockID)
{
    (void) close(sockID);
}
static void 
InitSocketName(struct sockaddr_un *uname, const char *name)
{
    uname->sun_family = AF_UNIX;
    strcpy(uname->sun_path, name);
}
void
ReadFromSocket(int sockID, char *buffer, int packetSize)
{
    int retVal;
//    Comentado para evitar error de compilacion Red Hat 9 (2004)    
 // extern int errno;
    struct sockaddr_un uName;
    int size = sizeof(uName);
   
    retVal = recvfrom(sockID, buffer, packetSize, 0,
       (struct sockaddr *) &uName,(socklen_t*) &size);

    if (retVal != packetSize) {
        perror("in recvfrom");
//        Comentado para evitar error de compilacion Red Hat 9 (2004) 
    //   printf("called: %x, got back %d, %d\n", buffer, retVal, errno);
    }
    ASSERT(retVal == packetSize);
}

void
SendToSocket(int sockID, const char *buffer, int packetSize, const char *toName)
{
    struct sockaddr_un uName;
    int retVal;

    InitSocketName(&uName, toName);
#ifdef HOST_LINUX
    retVal = sendto(sockID, buffer, packetSize, 0,
     (const struct sockaddr *) &uName, sizeof(uName));
#else
    retVal = sendto(sockID, buffer, packetSize, 0,
     (char *) &uName, sizeof(uName));
#endif
perror("ERROR HERE");
    ASSERT(retVal == packetSize)

}

So here we could implement some functions to block a certain socket, or something like that.

Testing the network:

To test the message delivery in NachOS, we can as easy as(provided NachOS is already compiled):
  • Go to the nachos/code/network directory.
  • Open two command lines.
  • In one run:
    • ./nachos -m 0 -o 1 &
  • In the other:
    • ./nachos -m 1 -o 0 &
This should produce some output like this:

Output from: ./nachos -m 0 -o 1 &:


Output from: ./nachos -m 1 -o 0 &:



Note 1: For this, you need to already have the condition variables implemented correctly or else, it won't work.

Note 2: For this test, we had problems with Segmentation Faults, and Asserts, we couldn't tell what was the problem until we used GDB, to track where our NachOS was failing. You can check a post we did on how to use GDB, here.

And that's it. All of this(and some more) is already in our NachOS distribution, and we're going to work with this, to try and implement our own networking userprog.

Any doubts or suggestions feel free to comment.

1 comentario: