Getting Familiar with CCN 1 Project Goal In this project you will experiment with simple client/server programs in CCN to familiarize yourselves with the CCN basics. You will compile, run, and answer the questions provided later in this document. Note that in order complete this project your will need a laptop with ccnd installed and a route to to the Netsec NDN hub (ndn.netsec.colostate.edu). Some questions, however, can be answered without being connected to the NDN hub. We will provide skeleton code for you, but you will need to make some simple modifications. You should study the code well enough to understand how things work before you make any changes. The topology you will deal with is shown in Fig 1. In that topology, the NDN hub is a separate machine (the NDN hub in the Netsec lab), but everything else will run on your laptop. CCND is the CCN daemon, which you should have installed at the beginning of the class. You will start the daemon with the command ccndstart. You will run two additional servers on your laptop that will register with CCND. Each server will publish its own namespace, as shown in the picture. After publishing the namespaces, CCND should forward all matching interests to these servers following the longest match rule. CCND should also have a route to the the Netsec NDN hub. The NDN hub, which is another CCN node, will also advertise a namespace (see figure). We will configure this for you, you don t have to mess with the NDN hub. Note that once you connect, you should be able to see all the namespaces published in the NDN testbed, from CSU and other universities. You may experiment (e.g., fetching files) with those if you like. The client and server code is attached at the end of this document. You can also download it from the class web page. Note that you will need to modify the code. For example, to observe the effects of caching in CCN you should add delay to the servers returning data in response to interests. This way, data retuerned by a server will be delayed a bit, but data relayed by CCN will not. Make the delay sufficiently different for each server so you can distinquish between them. Each client and server should print a log of all interests/data it receives/serves. After you complete your code changes, install and run the programs, and answer the following questions. 1. Print all routing information and published namespaces. 2. Verify that NDN caches content at each node. You can do this by doing two consecutive requests for the same content and recording the time for each request. 3. Devise a simple experiment to show that caching makes content distribution time almost constant, irrespective of number of clients. 4. Express interest for /local/video/test. Does anything happen for /local/video? Why? 1
Figure 1: Topology 5. Verify NDN routes on longest prefix match. This you can do by adding two similar routes, but one is longer that the other. 6. Show that CCN does not forward duplicate interests, but it provides a response to all such interests. 7. Show that all content in CCN must be verifiable to be valid. For this, try to send unsigned content from the servers and observe what happens. Explain what you observe. 2 What to Turn In By the deadline, email the instructor a tar file with the following: 1. Source files with your modified client and server programs and a Makefile 2. A README file with your notes, observations and results. Make sure you include the exact commands and parameters you used to run your experiments, the output and the answers to the questions. 3 Implementation Details This section will help you understand the client and server code we have provided to you. 2
3.1 The Client The client is a simple program that takes as argument the content name we are interested in. For example, we are interested in a video named /csu/video, we want invoke the client as: $./client /csu/video. The steps involved in the client are: Start by allocating memory for the interest name. The only data structure that is provided by CCN C api is ccn charbuf. struct ccn_charbuf *ccnb = ccn_charbuf_create(); CCNx does not understand plain text names, so you need to create an ccnb-encoded name from the string supplied by user. Also, you need not append ccnx : / at the beginning, the api does that for you. ccn_name_from_uri(ccnb, argv[1]); Create a client handle. We will use this handle for interacting with the CCN daemon. struct ccn *ccn = ccn_create(); Connect to the CCN daemon. ccn_connect(ccn, NULL); Allocate buffers for the result. Also, allocate another buffer for parsed content object. struct ccn_charbuf *resultbuf = ccn_charbuf_create(); struct ccn_parsed_contentobject pcobuf = 0 ; Send out the interest and wait for answer. ccn get by default sends out one interest and wait until it times out. You can pass the time to ccn get. For more advanced functions, you should use ccn express interest and handle the content on your own. int timeout_ms = 6000; ccn_get(ccn, ccnb, NULL, timeout_ms, resultbuf, &pcobuf, NULL, 0); You then wait until the reply comes back or it times out. Once it does, you can parse the answer using ccn content get value. ccn_content_get_value(ptr, length, &pcobuf, &ptr, &length); 3
3.2 The Server The server is more complicated. It runs on all the CCN nodes as a daemon. The server starts up and tells CCND that it is interested in all interests starting with some namespace. Once such an interest is received, ccnd forwards it to the server daemon. The server daemon does whatever it wants to do with it, that is for you to define. The steps involved in the server are: Connect to the CCN daemon. Define callback handlers for incoming data or content. Register a prefix with the CCN daemon. This is where we tell the CCN daemon what we are interested in. The CCN daemon will forward everything that matches what we are interested in. We declare a handle for upcalls that allow clients receive notifications of incoming interests and content using ccn closure. in interest is the handle in our case. We also point it to the callback function, incoming interest in our case. struct ccn_closure in_interest =.p = &incoming_interest; in_interest.data = &prefix; ccn_set_interest_filter(ccn, prefix, &in_interest); We then run the server in an infinite loop. ccn_run(ccn, -1); Once we have an interest matching our prefix, we want to handle it appropriately. We need to do this inside enum ccn_upcall_res incoming_interest(struct ccn_closure *selfp, enum ccn_upcall_kind kind, struct ccn_upcall_info *info) There can be various types of events. We will handle caseccn UP CALL INT EREST : for incoming interests. We then need to create a response content, sign it and send it back. ccn sign content. We sign it using ccn_sign_content(h, data, name, &sp, mymessage, size); ccn_put(info->h, data->buf, data->length) 4
3.3 Sample Code 3.3.1 Client #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <ccn/ccn.h> #include <ccn/uri.h> #include <ccn/keystore.h> #include <ccn/signing.h> #define DEBUG int main (int argc, char **argv) int res; //check if user supplied uri to trace if(argv[1] == NULL) printf("usage: trace URI\n"); //get the length of user provided URI int argv_length = strlen(argv[1]); //check first six chars for ccnx:/, if present, skip them int skip = 0; res = strncmp("ccnx:/", argv[1], 6); if(res == 0) skip = 5; //if URI does not begins with /, exit if (argv[1][skip]!= / ) printf("uri must begin with /\n"); 5
//check if uri ends with slash, append if missing char *slash = ""; if (argv[1][argv_length-1]!= / ) slash = "/"; //allocate memory for trace URI = /trace/user_input/random_number char *URI = (char *) malloc(sizeof(char)* argv_length+1); //find size of rand if(uri == NULL) fprintf(stderr, "Can not allocate memory for URI\n"); //put together the trace URI, add a random number to end of URI srand ((unsigned int)time (NULL)*getpid()); sprintf(uri, "%s%s", argv[1]+skip, slash); //allocate memory for interest struct ccn_charbuf *ccnb = ccn_charbuf_create(); if(ccnb == NULL) fprintf(stderr, "Can not allocate memory for interest\n"); //adding name to interest res = ccn_name_from_uri(ccnb, URI); if(res == -1) fprintf(stderr, "Failed to assign name to interest"); //create the ccn handle struct ccn *ccn = ccn_create(); if(ccn == NULL) fprintf(stderr, "Can not create ccn handle\n"); 6
//connect to ccnd res = ccn_connect(ccn, NULL); if (res == -1) fprintf(stderr, "Could not connect to ccnd... exiting\n"); #ifdef DEBUG printf("connected to CCND, return code: %d\n", res); #endif //allocate buffer for response struct ccn_charbuf *resultbuf = ccn_charbuf_create(); if(resultbuf == NULL) fprintf(stderr, "Can not allocate memory for URI\n"); //setting the parameters for ccn_get struct ccn_parsed_contentobject pcobuf = 0 ; int timeout_ms = 6000; //express interest res = ccn_get(ccn, ccnb, NULL, timeout_ms, resultbuf, &pcobuf, NULL, 0); if (res == -1) fprintf(stderr, "Did not receive answer for trace to %s\n", argv[1]); #ifdef DEBUG fprintf(stderr, "Did not receive answer for trace to URI: %s\n", URI); #endif //extract data from the response const unsigned char *ptr; size_t length; ptr = resultbuf->buf; length = resultbuf->length; ccn_content_get_value(ptr, length, &pcobuf, &ptr, &length); //check if received some data if(length == 0) 7
fprintf(stderr, "Received empty answer for trace to %s\n", argv[1]); #ifdef DEBUG fprintf(stderr, "Received empty answer for trace to URI: %s\n", URI); #endif //print the data printf("reply: %s\n", ptr); printf("length of data: %Zu\n", length); exit(0); 8
3.3.2 Server #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <ccn/ccn.h> #include <ccn/uri.h> #include <ccn/keystore.h> #include <ccn/signing.h> #include <ccn/charbuf.h> #include <ccn/reg_mgmt.h> #include <ccn/ccn_private.h> #include <ccn/ccnd.h> #define DEBUG int construct_trace_response(struct ccn *h, struct ccn_charbuf *data, const unsigned char *interest_msg, const struct ccn_parsed_interest *pi, char *myme //printf("path:construct trace response"); //**this function takes the interest, signs the content and returns to //upcall for further handling //copy the incoming interest name in ccn charbuf struct ccn_charbuf *name = ccn_charbuf_create(); struct ccn_signing_params sp = CCN_SIGNING_PARAMS_INIT; int res; printf("received interest, name length: %d\n", size); res = ccn_charbuf_append(name, interest_msg + pi->offset[ccn_pi_b_name], pi->offset[ccn_pi_e_name] - pi->offset[ccn_pi_b_name]); // printf("%s\n", ccn_charbuf_as_string(name)); if(res == -1) fprintf(stderr, "Can not copy interest name to buffer\n"); //sign the content, check if keystore exsists res = ccn_sign_content(h, data, name, &sp, mymessage, size); 9
if(res == -1) fprintf(stderr, "Can not sign content\n"); //free memory and return ccn_charbuf_destroy(&sp.template_ccnb); ccn_charbuf_destroy(&name); return res; enum ccn_upcall_res incoming_interest(struct ccn_closure *selfp, enum ccn_upcall_kind kind, struct ccn_upcall_info *info) //this is the callback function, all interest matching ccnx:/trace //will come here, handle them as appropriate int res = 0; //store the incoming interest name struct ccn_charbuf *data = ccn_charbuf_create(); //check for null, length of incoming interest name //size_t name_length = info->pi->offset[ccn_pi_e_name] - info->pi->offset[ccn_pi_b_name //define answer char *MYMESSAGE="Hello World"; //switch on type of event switch (kind) case CCN_UPCALL_FINAL: return CCN_UPCALL_RESULT_OK; break; case CCN_UPCALL_CONTENT: printf("received content\n"); break; case CCN_UPCALL_INTEREST: 10
default: break; return(0); //received matching interest //get the interest name from incoming packet construct_trace_response(info->h, data, info->interest_ccnb, info->pi, MYMESSAGE, s printf("sending binary content of length: %Zu \n", data->length); res = ccn_put(info->h, data->buf, data->length); break; int main(int argc, char **argv) // printf("path:main"); //no argument necessary if(argc!= 1) printf("usage:./server\n"); // for now, look in flags for local vs remote int res; //create ccn handle struct ccn *ccn = NULL; //connect to CCN ccn = ccn_create(); //NOTE:check for null if (ccn_connect(ccn, NULL) == -1) fprintf(stderr, "Could not connect to ccnd"); //create prefix we are interested in, register in FIB struct ccn_charbuf *prefix = ccn_charbuf_create(); //We are interested in anythin starting with ccnx:/ res = ccn_name_from_uri(prefix, "ccnx:/"); 11
if (res < 0) fprintf(stderr, "Can not convert name to URI\n"); //handle for upcalls, receive notifications of incoming interests and content. //specify where the reply will go struct ccn_closure in_interest =.p = &incoming_interest; in_interest.data = &prefix; //set the interest filter for prefix we created res = ccn_set_interest_filter(ccn, prefix, &in_interest); if (res < 0) fprintf(stderr, "Failed to register interest (res == %d)\n", res); //listen infinitely res = ccn_run(ccn, -1); //cleanup ccn_destroy(&ccn); ccn_charbuf_destroy(&prefix); exit(0); 12