core: Changed WS from being a flag on a TCP/TLS connection to a protocol in its own...
[sip-router] / udp_server.c
index 898c62f..3889a65 100644 (file)
@@ -1,5 +1,54 @@
 /*
  * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * --------
+ *  2003-01-28  packet zero-termination moved to receive_msg (jiri)
+ *  2003-02-10  undoed the above changes (andrei)
+ *  2003-03-19  replaced all the mallocs/frees w/ pkg_malloc/pkg_free (andrei)
+ *  2003-04-14  set sockopts to TOS low delay (andrei)
+ *  2004-05-03  applied multicast support patch from janakj
+ *              added set multicast ttl support (andrei)
+ *  2004-07-05  udp_rcv_loop: drop packets with 0 src port + error msg.
+ *              cleanups (andrei)
+ *  2005-03-10  multicast options are now set for all the udp sockets (andrei)
+ *  2005-06-26  failure to set mcast options is not an error anymore (andrei)
+ *  2006-04-12  udp_send() switched to struct dest_info (andrei)
+ *  2006-10-13  added STUN support (vlada)
+ *  2007-08-28  disable/set MTU discover option for the udp sockets
+ *               (in linux it's enabled by default which produces udp packets
+ *                with the DF flag ser) (patch from hscholz)
+ *  2010-06-15  support for using raw sockets for sending (andrei)
+ */
+
+
+/** udp send and loop-receive functions.
+ * @file udp_server.c
+ * @ingroup core
+ * Module: @ref core
  */
 
 #include <stdlib.h>
@@ -7,6 +56,8 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
 #include <errno.h>
 #include <arpa/inet.h>
 #ifdef __linux__
 
 
 #include "udp_server.h"
+#include "compiler_opt.h"
 #include "globals.h"
 #include "config.h"
 #include "dprint.h"
 #include "receive.h"
 #include "mem/mem.h"
 #include "ip_addr.h"
+#include "cfg/cfg_struct.h"
+#include "events.h"
+#ifdef USE_RAW_SOCKS
+#include "raw_sock.h"
+#endif /* USE_RAW_SOCKS */
+
 
-#ifdef DEBUG_DMALLOC
-#include <mem/dmalloc.h>
+#ifdef USE_STUN
+  #include "ser_stun.h"
 #endif
 
 #ifdef DBG_MSG_QA
@@ -44,9 +102,9 @@ static int dbg_msg_qa(char *buf, int len)
        enum { QA_ANY, QA_SPACE, QA_EOL1 } state;
 
 
-       /* is there a zero character inthere ? */       
+       /* is there a zero character in there ? */      
        if (memchr(buf, 0, len)) {
-               LOG(L_CRIT, "BUG: message being sent with 0 in it\n");
+               LOG(L_CRIT, "BUG: message with 0 in it\n");
                return 0;
        }
 
@@ -60,7 +118,7 @@ static int dbg_msg_qa(char *buf, int len)
                        case ' ':       if (state==QA_SPACE) {
                                                        space_cnt++;
                                                        if (space_cnt==4) {
-                                                               LOG(L_CRIT, "BUG(propably): DBG_MSG_QA: "
+                                                               LOG(L_CRIT, "BUG(probably): DBG_MSG_QA: "
                                                                        "too many spaces\n");
                                                                return 0;
                                                        }
@@ -115,7 +173,7 @@ int probe_max_receive_buffer( int udp_sock )
        }
        if ( ioptval==0 ) 
        {
-               LOG(L_DBG, "DEBUG: udp_init: SO_RCVBUF initialy set to 0; resetting to %d\n",
+               LOG(L_DBG, "DEBUG: udp_init: SO_RCVBUF initially set to 0; resetting to %d\n",
                        BUFFER_INCREMENT );
                ioptval=BUFFER_INCREMENT;
        } else LOG(L_INFO, "INFO: udp_init: SO_RCVBUF is initially %d\n", ioptval );
@@ -133,7 +191,7 @@ int probe_max_receive_buffer( int udp_sock )
                        LOG(L_DBG, "DEBUG: udp_init: SOL_SOCKET failed"
                                        " for %d, phase %d: %s\n", optval, phase, strerror(errno));
                        /* if setting buffer size failed and still in the aggressive
-                          phase, try less agressively; otherwise give up 
+                          phase, try less aggressively; otherwise give up 
                        */
                        if (phase==0) { phase=1; optval >>=1 ; continue; } 
                        else break;
@@ -154,7 +212,7 @@ int probe_max_receive_buffer( int udp_sock )
                        if (voptval<optval) {
                                LOG(L_DBG, "DEBUG: setting SO_RCVBUF has no effect\n");
                                /* if setting buffer size failed and still in the aggressive
-                               phase, try less agressively; otherwise give up 
+                               phase, try less aggressively; otherwise give up 
                                */
                                if (phase==0) { phase=1; optval >>=1 ; continue; } 
                                else break;
@@ -176,20 +234,75 @@ int probe_max_receive_buffer( int udp_sock )
        /* EoJKU */
 }
 
+
+#ifdef USE_MCAST
+
+/*
+ * Setup multicast receiver
+ */
+static int setup_mcast_rcvr(int sock, union sockaddr_union* addr)
+{
+       struct ip_mreq mreq;
+#ifdef USE_IPV6
+       struct ipv6_mreq mreq6;
+#endif /* USE_IPV6 */
+       
+       if (addr->s.sa_family==AF_INET){
+               memcpy(&mreq.imr_multiaddr, &addr->sin.sin_addr, 
+                      sizeof(struct in_addr));
+               mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+               
+               if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq,
+                              sizeof(mreq))==-1){
+                       LOG(L_ERR, "ERROR: setup_mcast_rcvr: setsockopt: %s\n",
+                           strerror(errno));
+                       return -1;
+               }
+               
+#ifdef USE_IPV6
+       } else if (addr->s.sa_family==AF_INET6){
+               memcpy(&mreq6.ipv6mr_multiaddr, &addr->sin6.sin6_addr, 
+                      sizeof(struct in6_addr));
+               mreq6.ipv6mr_interface = 0;
+#ifdef __OS_linux
+               if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6,
+#else
+               if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
+#endif
+                              sizeof(mreq6))==-1){
+                       LOG(L_ERR, "ERROR: setup_mcast_rcvr: setsockopt:%s\n",
+                           strerror(errno));
+                       return -1;
+               }
+               
+#endif /* USE_IPV6 */
+       } else {
+               LOG(L_ERR, "ERROR: setup_mcast_rcvr: Unsupported protocol family\n");
+               return -1;
+       }
+       return 0;
+}
+
+#endif /* USE_MCAST */
+
+
 int udp_init(struct socket_info* sock_info)
 {
        union sockaddr_union* addr;
        int optval;
-
+#ifdef USE_MCAST
+       unsigned char m_ttl, m_loop;
+#endif
        addr=&sock_info->su;
 /*
-       addr=(union sockaddr_union*)malloc(sizeof(union sockaddr_union));
+       addr=(union sockaddr_union*)pkg_malloc(sizeof(union sockaddr_union));
        if (addr==0){
                LOG(L_ERR, "ERROR: udp_init: out of memory\n");
                goto error;
        }
 */
-       if (init_su(addr, &sock_info->address, htons(sock_info->port_no))<0){
+       sock_info->proto=PROTO_UDP;
+       if (init_su(addr, &sock_info->address, sock_info->port_no)<0){
                LOG(L_ERR, "ERROR: udp_init: could not init sockaddr_union\n");
                goto error;
        }
@@ -206,7 +319,14 @@ int udp_init(struct socket_info* sock_info)
                LOG(L_ERR, "ERROR: udp_init: setsockopt: %s\n", strerror(errno));
                goto error;
        }
-#if defined (__linux__) && defined(UDP_ERRORS)
+       /* tos */
+       optval = tos;
+       if (setsockopt(sock_info->socket, IPPROTO_IP, IP_TOS, (void*)&optval, 
+                       sizeof(optval)) ==-1){
+               LOG(L_WARN, "WARNING: udp_init: setsockopt tos: %s\n", strerror(errno));
+               /* continue since this is not critical */
+       }
+#if defined (__OS_linux) && defined(UDP_ERRORS)
        optval=1;
        /* enable error receiving on unconnected sockets */
        if(setsockopt(sock_info->socket, SOL_IP, IP_RECVERR,
@@ -215,14 +335,68 @@ int udp_init(struct socket_info* sock_info)
                goto error;
        }
 #endif
+#if defined (__OS_linux)
+       /* if pmtu_discovery=1 then set DF bit and do Path MTU discovery
+        * disabled by default */
+       optval= (pmtu_discovery) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
+       if(setsockopt(sock_info->socket, IPPROTO_IP, IP_MTU_DISCOVER,
+                       (void*)&optval, sizeof(optval)) ==-1){
+               LOG(L_ERR, "ERROR: udp_init: setsockopt: %s\n", strerror(errno));
+               goto error;
+       }
+#endif
 
+#ifdef USE_MCAST
+       if ((sock_info->flags & SI_IS_MCAST) 
+           && (setup_mcast_rcvr(sock_info->socket, addr)<0)){
+                       goto error;
+       }
+       /* set the multicast options */
+       if (addr->s.sa_family==AF_INET){
+               m_loop=mcast_loopback;
+               if (setsockopt(sock_info->socket, IPPROTO_IP, IP_MULTICAST_LOOP, 
+                                               &m_loop, sizeof(m_loop))==-1){
+                       LOG(L_WARN, "WARNING: udp_init: setsockopt(IP_MULTICAST_LOOP):"
+                                               " %s\n", strerror(errno));
+                       /* it's only a warning because we might get this error if the
+                         network interface doesn't support multicasting -- andrei */
+               }
+               if (mcast_ttl>=0){
+                       m_ttl=mcast_ttl;
+                       if (setsockopt(sock_info->socket, IPPROTO_IP, IP_MULTICAST_TTL,
+                                               &m_ttl, sizeof(m_ttl))==-1){
+                               LOG(L_WARN, "WARNING: udp_init: setsockopt (IP_MULTICAST_TTL):"
+                                               " %s\n", strerror(errno));
+                       }
+               }
+#ifdef USE_IPV6
+       } else if (addr->s.sa_family==AF_INET6){
+               if (setsockopt(sock_info->socket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 
+                                               &mcast_loopback, sizeof(mcast_loopback))==-1){
+                       LOG(L_WARN, "WARNING: udp_init: setsockopt (IPV6_MULTICAST_LOOP):"
+                                       " %s\n", strerror(errno));
+               }
+               if (mcast_ttl>=0){
+                       if (setsockopt(sock_info->socket, IPPROTO_IP, IPV6_MULTICAST_HOPS,
+                                                       &mcast_ttl, sizeof(mcast_ttl))==-1){
+                               LOG(L_WARN, "WARNING: udp_init: setssckopt "
+                                               "(IPV6_MULTICAST_HOPS): %s\n", strerror(errno));
+                       }
+               }
+#endif /* USE_IPV6*/
+       } else {
+               LOG(L_ERR, "ERROR: udp_init: Unsupported protocol family %d\n",
+                                       addr->s.sa_family);
+               goto error;
+       }
+#endif /* USE_MCAST */
 
        if ( probe_max_receive_buffer(sock_info->socket)==-1) goto error;
        
        if (bind(sock_info->socket,  &addr->s, sockaddru_len(*addr))==-1){
                LOG(L_ERR, "ERROR: udp_init: bind(%x, %p, %d) on %s: %s\n",
                                sock_info->socket, &addr->s, 
-                               sockaddru_len(*addr),
+                               (unsigned)sockaddru_len(*addr),
                                sock_info->address_str.s,
                                strerror(errno));
        #ifdef USE_IPV6
@@ -233,11 +407,11 @@ int udp_init(struct socket_info* sock_info)
                goto error;
        }
 
-/*     free(addr);*/
+/*     pkg_free(addr);*/
        return 0;
 
 error:
-/*     if (addr) free(addr);*/
+/*     if (addr) pkg_free(addr);*/
        return -1;
 }
 
@@ -251,17 +425,26 @@ int udp_rcv_loop()
 #else
        static char buf [BUF_SIZE+1];
 #endif
-
+       char *tmp;
        union sockaddr_union* from;
        unsigned int fromlen;
+       struct receive_info ri;
 
 
-       from=(union sockaddr_union*) malloc(sizeof(union sockaddr_union));
+       from=(union sockaddr_union*) pkg_malloc(sizeof(union sockaddr_union));
        if (from==0){
                LOG(L_ERR, "ERROR: udp_rcv_loop: out of memory\n");
                goto error;
        }
        memset(from, 0 , sizeof(union sockaddr_union));
+       ri.bind_address=bind_address; /* this will not change, we do it only once*/
+       ri.dst_port=bind_address->port_no;
+       ri.dst_ip=bind_address->address;
+       ri.proto=PROTO_UDP;
+       ri.proto_reserved1=ri.proto_reserved2=0;
+
+       /* initialize the config framework */
+       if (cfg_child_init()) goto error;
 
        for(;;){
 #ifdef DYN_BUF
@@ -276,59 +459,118 @@ int udp_rcv_loop()
                len=recvfrom(bind_address->socket, buf, BUF_SIZE, 0, &from->s,
                                                                                        &fromlen);
                if (len==-1){
+                       if (errno==EAGAIN){
+                               DBG("udp_rcv_loop: packet with bad checksum received\n");
+                               continue;
+                       }
                        LOG(L_ERR, "ERROR: udp_rcv_loop:recvfrom:[%d] %s\n",
                                                errno, strerror(errno));
-                       if ((errno==EINTR)||(errno==EAGAIN)||(errno==EWOULDBLOCK)||
-                                       (errno==ECONNREFUSED))
+                       if ((errno==EINTR)||(errno==EWOULDBLOCK)|| (errno==ECONNREFUSED))
                                continue; /* goto skip;*/
                        else goto error;
                }
-               /*debugging, make print* msg work */
-               buf[len+1]=0;
+               /* we must 0-term the messages, receive_msg expects it */
+               buf[len]=0; /* no need to save the previous char */
 
+               ri.src_su=*from;
+               su2ip_addr(&ri.src_ip, from);
+               ri.src_port=su_getport(from);
+
+               if(unlikely(sr_event_enabled(SREV_NET_DGRAM_IN)))
+               {
+                       void *sredp[3];
+                       sredp[0] = (void*)buf;
+                       sredp[1] = (void*)(&len);
+                       sredp[2] = (void*)(&ri);
+                       if(sr_event_exec(SREV_NET_DGRAM_IN, (void*)sredp)<0) {
+                               /* data handled by callback - continue to next packet */
+                               continue;
+                       }
+               }
 #ifndef NO_ZERO_CHECKS
-               if (len==0) {
-                       LOG(L_WARN, "WARNING: empty packet received\n");
-                       continue;
+#ifdef USE_STUN
+               /* STUN support can be switched off even if it's compiled */
+               if (stun_allow_stun == 0 || (unsigned char)*buf != 0x00) {
+#endif
+                 if (len<MIN_UDP_PACKET) {
+                         tmp=ip_addr2a(&ri.src_ip);
+                         DBG("udp_rcv_loop: probing packet received from %s %d\n",
+                                       tmp, htons(ri.src_port));
+                         continue;
+                 }
+#ifdef USE_STUN
                }
+#endif
+/* historically, zero-terminated packets indicated a bug in clients
+ * that calculated wrongly packet length and included string-terminating
+ * zero; today clients exist with legitimate binary payloads and we
+ * shall not check for zero-terminated payloads
+ */
+#ifdef TRASH_ZEROTERMINATED_PACKETS
                if (buf[len-1]==0) {
-                       LOG(L_WARN, "WARNING: upstream bug - 0-terminated packet\n");
+                       tmp=ip_addr2a(&ri.src_ip);
+                       LOG(L_WARN, "WARNING: udp_rcv_loop: "
+                                       "upstream bug - 0-terminated packet from %s %d\n",
+                                       tmp, htons(ri.src_port));
                        len--;
                }
 #endif
+#endif
 #ifdef DBG_MSG_QA
                if (!dbg_msg_qa(buf, len)) {
-                       LOG(L_WARN, "WARNING: an incoming message didn't pass test, drop it: %.*s\n",
-                               len, buf );
+                       LOG(L_WARN, "WARNING: an incoming message didn't pass test,"
+                                               "  drop it: %.*s\n", len, buf );
                        continue;
                }
 #endif
+               if (ri.src_port==0){
+                       tmp=ip_addr2a(&ri.src_ip);
+                       LOG(L_INFO, "udp_rcv_loop: dropping 0 port packet from %s\n", tmp);
+                       continue;
+               }
                
+               /* update the local config */
+               cfg_update();
+#ifdef USE_STUN
+                       /* STUN support can be switched off even if it's compiled */
+                       if (stun_allow_stun && (unsigned char)*buf == 0x00) {
+                           /* stun_process_msg releases buf memory if necessary */
+                               if ((stun_process_msg(buf, len, &ri)) != 0) {
+                                       continue; /* some error occurred */
+                               }
+                       } else
+#endif
                /* receive_msg must free buf too!*/
-               receive_msg(buf, len, from);
+               receive_msg(buf, len, &ri);
                
        /* skip: do other stuff */
                
        }
        /*
-       if (from) free(from);
+       if (from) pkg_free(from);
        return 0;
        */
        
 error:
-       if (from) free(from);
+       if (from) pkg_free(from);
        return -1;
 }
 
 
 
 
-/* which socket to use? main socket or new one? */
-int udp_send(struct socket_info *source, char *buf, unsigned len, union sockaddr_union*  to)
+/* send buf:len over udp to dst (uses only the to and send_sock dst members)
+ * returns the numbers of bytes sent on success (>=0) and -1 on error
+ */
+int udp_send(struct dest_info* dst, char *buf, unsigned len)
 {
 
        int n;
        int tolen;
+       struct ip_addr ip; /* used only on error, for debugging */
+#ifdef USE_RAW_SOCKS
+       int mtu;
+#endif /* USE_RAW_SOCKS */
 
 #ifdef DBG_MSG_QA
        /* aborts on error, does nothing otherwise */
@@ -337,23 +579,47 @@ int udp_send(struct socket_info *source, char *buf, unsigned len, union sockaddr
                abort();
        }
 #endif
-
-       tolen=sockaddru_len(*to);
+#ifdef USE_RAW_SOCKS
+       if (likely( ! (raw_udp4_send_sock >= 0 &&
+                                       cfg_get(core, core_cfg, udp4_raw) &&
+                                       dst->send_sock->address.af == AF_INET) )) {
+#endif /* USE_RAW_SOCKS */
+               /* normal send over udp socket */
+               tolen=sockaddru_len(dst->to);
 again:
-       n=sendto(source->socket, buf, len, 0, &to->s, tolen);
+               n=sendto(dst->send_sock->socket, buf, len, 0, &dst->to.s, tolen);
 #ifdef XL_DEBUG
-       LOG(L_INFO, "INFO: send status: %d\n", n);
+               LOG(L_INFO, "INFO: send status: %d\n", n);
 #endif
-       if (n==-1){
-               LOG(L_ERR, "ERROR: udp_send: sendto(sock,%p,%d,0,%p,%d): %s(%d)\n",
-                               buf,len,to,tolen,
-                               strerror(errno),errno);
-               if (errno==EINTR) goto again;
-               if (errno==EINVAL) {
-                       LOG(L_CRIT,"CRITICAL: invalid sendtoparameters\n"
-                       "one possible reason is the server is bound to localhost and\n"
-                       "attempts to send to the net\n");
+               if (unlikely(n==-1)){
+                       su2ip_addr(&ip, &dst->to);
+                       LOG(L_ERR, "ERROR: udp_send: sendto(sock,%p,%u,0,%s:%d,%d):"
+                                       " %s(%d)\n", buf,len, ip_addr2a(&ip),
+                                       su_getport(&dst->to), tolen, strerror(errno), errno);
+                       if (errno==EINTR) goto again;
+                       if (errno==EINVAL) {
+                               LOG(L_CRIT,"CRITICAL: invalid sendtoparameters\n"
+                               "one possible reason is the server is bound to localhost and\n"
+                               "attempts to send to the net\n");
+                       }
+               }
+#ifdef USE_RAW_SOCKS
+       } else {
+               /* send over a raw socket */
+               mtu = cfg_get(core, core_cfg, udp4_raw_mtu);
+raw_again:
+               n=raw_iphdr_udp4_send(raw_udp4_send_sock, buf, len,
+                                                               &dst->send_sock->su,
+                                                               &dst->to,
+                                                               mtu);
+               if (unlikely(n==-1)){
+                       su2ip_addr(&ip, &dst->to);
+                       LOG(L_ERR, "ERROR: raw_iphdr_udp4_send(%d,%p,%u,...,%s:%d,%d):"
+                                       " %s(%d)\n", raw_udp4_send_sock, buf,len, ip_addr2a(&ip),
+                                       su_getport(&dst->to), mtu, strerror(errno), errno);
+                       if (errno==EINTR) goto raw_again;
                }
        }
+#endif /* USE_RAW_SOCKS */
        return n;
 }