Nut/OS  4.10.3
API Reference
upnp.c
Go to the documentation of this file.
00001 /****************************************************************************
00002 *  Copyright (c) 2011 by Michael Fischer. All rights reserved.
00003 *
00004 *  This work based on source from proconX Pty Ltd. Therefore
00005 *  partial copyright by: Copyright (c) 2010 proconX Pty Ltd. 
00006 *
00007 *  Redistribution and use in source and binary forms, with or without 
00008 *  modification, are permitted provided that the following conditions 
00009 *  are met:
00010 *  
00011 *  1. Redistributions of source code must retain the above copyright 
00012 *     notice, this list of conditions and the following disclaimer.
00013 *  2. Redistributions in binary form must reproduce the above copyright
00014 *     notice, this list of conditions and the following disclaimer in the 
00015 *     documentation and/or other materials provided with the distribution.
00016 *  3. Neither the name of the author nor the names of its contributors may 
00017 *     be used to endorse or promote products derived from this software 
00018 *     without specific prior written permission.
00019 *
00020 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
00021 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
00022 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
00023 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
00024 *  THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
00025 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
00026 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
00027 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
00028 *  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
00029 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
00030 *  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
00031 *  SUCH DAMAGE.
00032 *
00033 *****************************************************************************
00034 *  History:
00035 *
00036 *  27.05.2011  mifi  First Version based on source from proconX Pty Ltd. 
00037 *                    Information about UPnP can be found at "www.upnp.org".
00038 *                    Note: I had no success with the UPnP Device Validator
00039 *                    from the "Open Software Projects". The old original
00040 *                    Intel tools from the book "UPnP Design by Example"
00041 *                    works a little bit better, but has problems too.
00042 ****************************************************************************/
00043 #define __MAIN_C__
00044 
00045 #include <sys/version.h>
00046 #include <sys/thread.h>
00047 #include <sys/socket.h>
00048 #include <sys/device.h>
00049 #include <sys/event.h>
00050 #include <sys/timer.h>
00051 #include <net/if_var.h>
00052 #include <arpa/inet.h>
00053 #include <pro/httpd.h>
00054 
00055 #include <stdlib.h>
00056 #include <stdio.h>
00057 #include <stdint.h>
00058 #include <string.h>
00059 
00060 /*=========================================================================*/
00061 /*  DEFINE: All Structures and Common Constants                            */
00062 /*=========================================================================*/
00063 
00064 /* 
00065  * Enable to fulfil the UPnP specification for notify messages.
00066  * I think this is not needed to advertise only the device on
00067  * windows networks.
00068  */
00069 #define UPNP_FULFIL_SPEC      0
00070 
00071 #ifndef UPNP_SERVICE_STACK
00072 #define UPNP_SERVICE_STACK    ((1024 * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD)
00073 #endif
00074 
00075 #define UPNP_CGI_NAME         "upnp.cgi"
00076 #define UPNP_UUID_PART1       "56F9C1D5-5083-4ee5-A6B3-"
00077 
00078 #define SSDP_IP               0xfaffffefU       /* 239.255.255.250 */
00079 #define SSDP_PORT             1900
00080 #define SSDP_BUFFER_SIZE      512
00081 #define SSDP_NOTIFY_TIMEOUT   300000U   /* 5 minutes */
00082 
00083 
00084 /* *INDENT-OFF* */
00085 
00086 static char HTTP_TEXT_XML[] = "text/xml; charset=\"utf-8\"";
00087 
00088 static char NOTIFY_ALIVE[] =
00089    "NOTIFY * HTTP/1.1\r\n"
00090    "Host: 239.255.255.250:1900\r\n"
00091    "Cache-Control: max-age=900\r\n"
00092    "Location: http://%s/cgi-bin/" UPNP_CGI_NAME "\r\n"
00093    "NT: %s\r\n"
00094    "NTS: ssdp:alive\r\n"
00095    "USN: %s%s\r\n"
00096    "Server: %s\r\n"
00097    "Content-Length: 0\r\n"
00098    "\r\n";
00099 
00100 static char NOTIFY_BYE[] =
00101    "NOTIFY * HTTP/1.1\r\n"
00102    "Host: 239.255.255.250:1900\r\n"
00103    "NT: upnp:rootdevice\r\n"
00104    "NTS: ssdp:byebye\r\n"
00105    "USN: %s::upnp:rootdevice\r\n"
00106    "Content-Length: 0\r\n"
00107    "\r\n";
00108 
00109 static char MSEARCH_RESPONSE[] =
00110    "HTTP/1.1 200 OK\r\n"
00111    "Cache-Control: max-age=900\r\n"
00112    "Ext:\r\n"
00113    "Location: http://%s/cgi-bin/" UPNP_CGI_NAME "\r\n"
00114    "Server: %s\r\n"
00115    "ST: upnp:rootdevice\r\n"
00116    "USN: %s::upnp:rootdevice\r\n"
00117    "Content-Length: 0\r\n"
00118    "\r\n";
00119    
00120 /*
00121  * All listed elements which are not tagged are "REQUIRED" as per
00122  * UPnP Device Architecture 1.0. Some other are "OPTIONAL" or "RECOMMENDED".
00123  */
00124 static char HTML_UPNP[] =
00125 "<?xml version=\"1.0\"?>\r\n"
00126 "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\r\n"
00127    "<specVersion>\r\n"
00128       "<major>1</major>\r\n"
00129       "<minor>0</minor>\r\n"
00130    "</specVersion>\r\n"
00131    "<URLBase>http://%s</URLBase>\r\n"                                                  /* OPTIONAL */
00132    "<device>\r\n"
00133       "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\r\n"
00134       "<friendlyName>NutOS Server (%02X%02X%02X%02X%02X%02X)</friendlyName>\r\n"
00135       "<manufacturer>egnite</manufacturer>\r\n"
00136       "<manufacturerURL>http://www.egnite.de</manufacturerURL>\r\n"                    /* OPTIONAL */
00137       "<modelDescription>NutOS HTTP Daemon</modelDescription>\r\n"                     /* RECOMMENDED */
00138       "<modelName>Ethernut</modelName>\r\n"
00139       "<modelNumber>%02X%02X%02X%02X%02X%02X</modelNumber>\r\n"                        /* RECOMMENDED */
00140       "<modelURL>http://www.ethernut.de</modelURL>\r\n"                                /* OPTIONAL */
00141       "<serialNumber>%02X%02X%02X%02X%02X%02X</serialNumber>\r\n"                      /* RECOMMENDED */
00142       "<UDN>uuid:" UPNP_UUID_PART1 "%02X%02X%02X%02X%02X%02X</UDN>\r\n"
00143       
00144       /*
00145        * Despite being not required by the UPnP standard, we must
00146        * define at leat one service to make the device show up on Windows XP.
00147        */
00148       "<serviceList>\r\n"
00149          "<service>\r\n"
00150             "<serviceType>urn:schemas-upnp-org:service:Dummy:1</serviceType>\r\n"
00151             "<serviceId>urn:upnp-org:serviceId:Dummy</serviceId>\r\n"
00152             "<SCPDURL>/upnp/dummy.xml</SCPDURL>\r\n"
00153             "<controlURL>/upnp</controlURL>\r\n"
00154             "<eventSubURL></eventSubURL>\r\n"
00155          "</service>\r\n"
00156       "</serviceList>\r\n"
00157       "<presentationURL>http://%s/index.html</presentationURL>\r\n"                    /* RECOMMENDED */
00158    "</device>\r\n"
00159 "</root>\r\n";
00160 
00161 /* *INDENT-ON* */
00162 
00163 /*=========================================================================*/
00164 /*  DEFINE: Prototypes                                                     */
00165 /*=========================================================================*/
00166 
00167 /*=========================================================================*/
00168 /*  DEFINE: Definition of all local Data                                   */
00169 /*=========================================================================*/
00170 
00171 static NUTDEVICE *dev;
00172 static IFNET *nif;
00173 static char MyUSN[48];
00174 static char ServerInfo[32];
00175 
00176 /*=========================================================================*/
00177 /*  DEFINE: Definition of all local Procedures                             */
00178 /*=========================================================================*/
00179 
00180 /***************************************************************************/
00181 /*  GetRand                                                                */
00182 /***************************************************************************/
00183 static uint32_t GetRand(uint32_t MaxValue)
00184 {
00185     uint32_t Value;
00186 
00187     Value = rand() & MaxValue;
00188 
00189     return (Value);
00190 } /* GetRand */
00191 
00192 /***************************************************************************/
00193 /*  LocationCGIHandler                                                     */
00194 /***************************************************************************/
00195 static int LocationCGIHandler(FILE * stream, REQUEST * req)
00196 {
00197     /*
00198      * Generate HTTP header
00199      */
00200     NutHttpSendHeaderTop(stream, req, 200, "OK");
00201     NutHttpSendHeaderBottom(stream, 0, HTTP_TEXT_XML, -1L);
00202 
00203     /*
00204      * Output XML data
00205      */
00206     fprintf(stream, HTML_UPNP, 
00207             inet_ntoa(nif->if_local_ip),      /* URLBase */
00208             
00209             nif->if_mac[0], nif->if_mac[1],   /* friendlyName */
00210             nif->if_mac[2], nif->if_mac[3], 
00211             nif->if_mac[4], nif->if_mac[5], 
00212             
00213             nif->if_mac[0], nif->if_mac[1],   /* modelNumber */
00214             nif->if_mac[2], nif->if_mac[3], 
00215             nif->if_mac[4], nif->if_mac[5], 
00216             
00217             nif->if_mac[0], nif->if_mac[1],   /* serialNumber */
00218             nif->if_mac[2], nif->if_mac[3], 
00219             nif->if_mac[4], nif->if_mac[5], 
00220             
00221             nif->if_mac[0], nif->if_mac[1],   /* UDN */
00222             nif->if_mac[2], nif->if_mac[3], 
00223             nif->if_mac[4], nif->if_mac[5], 
00224             
00225             inet_ntoa(nif->if_local_ip));     /* presentationURL */
00226 
00227     return 0;
00228 } /* LocationCGIHandler */
00229 
00230 /***************************************************************************/
00231 /*  SendNotifyAliveChunk                                                   */
00232 /***************************************************************************/
00233 static void SendNotifyAliveChunk(char *Buffer, UDPSOCKET * TxSock)
00234 {
00235     int Size;
00236 
00237     /*
00238      * Send device discovery messages.
00239      * The first message is really needed.
00240      * All other to fulfil the UPnP specification.
00241      */
00242 
00243     /* Send first alive message */
00244     Size = sprintf(Buffer, NOTIFY_ALIVE, 
00245                    inet_ntoa(nif->if_local_ip), 
00246                    "upnp:rootdevice",           /* NT */
00247                    MyUSN, "::upnp:rootdevice",  /* USN */
00248                    ServerInfo);
00249     NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size);
00250     NutSleep(25);
00251 
00252 #if (UPNP_FULFIL_SPEC >= 1)
00253 
00254     /* Send second alive message */
00255     Size = sprintf(Buffer, NOTIFY_ALIVE, 
00256                    inet_ntoa(nif->if_local_ip), 
00257                    MyUSN,     /* NT */
00258                    MyUSN, "", /* USN */
00259                    ServerInfo);
00260     NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size);
00261     NutSleep(25);
00262 
00263     /* Send third alive message */
00264     Size = sprintf(Buffer, NOTIFY_ALIVE, 
00265                    inet_ntoa(nif->if_local_ip), 
00266                    "urn:schemas-upnp-org:device:Basic:1",            /* NT */
00267                    MyUSN, "::urn:schemas-upnp-org:device:Basic:1",   /* USN */
00268                    ServerInfo);
00269     NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size);
00270     NutSleep(25);
00271 
00272 
00273     /*
00274      * Send embedded device discovery messages
00275      */
00276 
00277     /* No embedded device available */
00278 
00279 
00280     /*
00281      * Send service discovery messages
00282      */
00283     Size = sprintf(Buffer, NOTIFY_ALIVE, 
00284                    inet_ntoa(nif->if_local_ip), 
00285                    "urn:schemas-upnp-org:service:Dummy:1",           /* NT */
00286                    MyUSN, "::urn:schemas-upnp-org:service:Dummy:1",  /* USN */
00287                    ServerInfo);
00288     NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size);
00289 #endif
00290 } /* SendNotifyAliveChunk */
00291 
00292 /***************************************************************************/
00293 /*  SendNotifyAlive                                                        */
00294 /***************************************************************************/
00295 static void SendNotifyAlive(char *Buffer, UDPSOCKET * TxSock)
00296 {
00297     uint32_t RandomDelay;
00298 
00299     /*
00300      * Send device discovery messages. Here two chunks will be send
00301      * with a random delay between each chunk.
00302      */
00303 
00304     SendNotifyAliveChunk(Buffer, TxSock);
00305 
00306     /* Create random value between 500...2000ms */
00307     RandomDelay = 500 + GetRand(1500);
00308 
00309     NutSleep(RandomDelay);
00310 
00311     SendNotifyAliveChunk(Buffer, TxSock);
00312 
00313 } /* SendNotifyAlive */
00314 
00315 /***************************************************************************/
00316 /*  NotifyTask                                                             */
00317 /***************************************************************************/
00318 THREAD(NotifyTask, arg)
00319 {
00320     char *Buffer;
00321     UDPSOCKET *TxSock;
00322 
00323     /*
00324      * Alloc buffer and create sockets
00325      */
00326     Buffer = malloc(SSDP_BUFFER_SIZE + 1);      /* Add one for the string termination 0 */
00327     TxSock = NutUdpCreateSocket(0);
00328 
00329     for (;;) {
00330         NutSleep(SSDP_NOTIFY_TIMEOUT);
00331 
00332         /* Send alive message. */
00333         SendNotifyAlive(Buffer, TxSock);
00334 
00335         NutThreadYield();
00336     }
00337 } /* NotifyTask */
00338 
00339 /***************************************************************************/
00340 /*  SSDPTask                                                               */
00341 /***************************************************************************/
00342 THREAD(SSDPTask, arg)
00343 {
00344     int Size;
00345     char *Buffer;
00346     UDPSOCKET *RxSock;
00347     UDPSOCKET *TxSock;
00348     uint32_t RemoteIP;
00349     uint16_t Port;
00350     char *Start;
00351     char *End;
00352     uint32_t MaxDelay;
00353     uint32_t Delay;
00354 
00355     sprintf(MyUSN, "uuid:%s%02X%02X%02X%02X%02X%02X",
00356             UPNP_UUID_PART1, 
00357             nif->if_mac[0], nif->if_mac[1], 
00358             nif->if_mac[2], nif->if_mac[3], 
00359             nif->if_mac[4], nif->if_mac[5]);
00360 
00361     sprintf(ServerInfo, "NutOS/%d.%d UPnP/1.0", NUT_VERSION_MAJOR, NUT_VERSION_MINOR);
00362 
00363     /*
00364      * Alloc buffer and create sockets
00365      */
00366     Buffer = malloc(SSDP_BUFFER_SIZE + 1);      /* Add one for the string termination 0 */
00367     RxSock = NutUdpCreateSocket(SSDP_PORT);
00368     TxSock = NutUdpCreateSocket(0);
00369 
00370     /*
00371      * First send ByeBye to the network.
00372      */
00373     Size = sprintf(Buffer, NOTIFY_BYE, MyUSN);
00374     NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size);
00375 
00376     NutSleep(500);
00377 
00378     /*
00379      * Now we can send a "hello" to the network, we are back.
00380      */
00381     SendNotifyAlive(Buffer, TxSock);
00382 
00383     for (;;) {
00384         Size = NutUdpReceiveFrom(RxSock, &RemoteIP, &Port, Buffer, SSDP_BUFFER_SIZE, NUT_WAIT_INFINITE);
00385         if (Size > 0) {
00386             Buffer[Size] = 0;   /* Terminate string */
00387 
00388             /*
00389              * Check if a control point serach for a network device.
00390              */
00391             if ((strstr(Buffer, "M-SEARCH") != NULL) &&        /* <= REQUIRED */
00392                 (strstr(Buffer, "ssdp:discover") != NULL)) {   /* <= REQUIRED */
00393                 /*
00394                  * Check if the control point search for "all", "root devices"
00395                  * or for a special device.
00396                  */
00397                 if ((strstr(Buffer, "ssdp:all") != NULL) ||
00398                     (strstr(Buffer, "upnp:rootdevice") != NULL) || (strstr(Buffer, MyUSN) != NULL)) {
00399                     Start = strstr(Buffer, "MX:");
00400                     if (Start != NULL) {
00401                         /* Jump over the "MX:" */
00402                         Start += 3;
00403 
00404                         /* Get delay in seconds */
00405                         MaxDelay = strtol(Start, &End, 10);
00406                         if (MaxDelay == 0) {
00407                             MaxDelay = 1;
00408                         }
00409 
00410                         /* Change to millisecond */
00411                         MaxDelay *= 1000;
00412 
00413                         /* Get random value */
00414                         Delay = GetRand(MaxDelay);
00415                     } else {
00416                         /* Ups, error */
00417                         Delay = 1234;
00418                     }
00419 
00420                     NutSleep(Delay);
00421 
00422                     /*
00423                      * We must send a M-SEARCH response
00424                      */
00425                     Size = sprintf(Buffer, MSEARCH_RESPONSE, 
00426                                    inet_ntoa(nif->if_local_ip), 
00427                                    ServerInfo, MyUSN);
00428                     NutUdpSendTo(TxSock, RemoteIP, Port, Buffer, Size);
00429                     NutSleep(25);
00430                     NutUdpSendTo(TxSock, RemoteIP, Port, Buffer, Size);
00431                 }
00432             }
00433         }
00434 
00435         NutThreadYield();
00436     }
00437 } /* SSDPTask */
00438 
00439 /*=========================================================================*/
00440 /*  DEFINE: All code exported                                              */
00441 /*=========================================================================*/
00442 /***************************************************************************/
00443 /*  upnp_Init                                                              */
00444 /***************************************************************************/
00445 void upnp_Init(void)
00446 {
00447 
00448     /* Set UPnP multicast address */
00449     printf("Add UPnP multicast address...");
00450     if (NutNetIfAddMcastAddr("eth0", SSDP_IP) != 0) {
00451         puts("failed");
00452     } else {
00453         puts("OK");
00454 
00455         /*
00456          * Multicast will be supported, 
00457          * get device and network interface info for eth0.
00458          */
00459         dev = NutDeviceLookup("eth0");
00460         if (dev != NULL) {
00461             /* Get network interface pointer */
00462             nif = dev->dev_icb;
00463 
00464             if (NutRegisterCgi(UPNP_CGI_NAME, LocationCGIHandler) == 0) {
00465                 /* Start the UPnP threads */
00466                 NutThreadCreate("ssdp", SSDPTask, NULL, UPNP_SERVICE_STACK);
00467                 NutThreadCreate("ssdp-notify", NotifyTask, NULL, UPNP_SERVICE_STACK);
00468             }
00469         }
00470     }
00471 } /* upnp_Init */
00472 
00473 /*** EOF ***/