/* $Id: ucast.c,v 1.17.2.4 2005/01/12 02:25:47 horms Exp $ */ /* * Adapted from alanr's UDP broadcast heartbeat bcast.c by Stéphane Billiart * * * (c) 2002 Stéphane Billiart * (c) 2002 Alan Robertson * * Brian Tinsley * - allow use of hostname in ha.cf * - set IP type of service of write socket * - many MALLOC calls were not checked for failure * - code janitor * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include #ifndef HAVE_INET_ATON extern int inet_aton(const char *, struct in_addr *); #endif #include #include #include #if defined(SO_BINDTODEVICE) #include #endif #include #include /* * Plugin information */ #define PIL_PLUGINTYPE HB_COMM_TYPE #define PIL_PLUGINTYPE_S HB_COMM_TYPE_S #define PIL_PLUGIN ucast #define PIL_PLUGIN_S "ucast" #define PIL_PLUGINLICENSE LICENSE_LGPL #define PIL_PLUGINLICENSEURL URL_LGPL #include /* * Macros/Defines */ #define ISUCASTOBJECT(mp) ((mp) && ((mp)->vf == (void*)&ucastOps)) #define UCASTASSERT(mp) g_assert(ISUCASTOBJECT(mp)) #define LOG PluginImports->log #define MALLOC PluginImports->alloc #define STRDUP PluginImports->mstrdup #define FREE PluginImports->mfree #define MAXBINDTRIES 10 /* * Structure Declarations */ struct ip_private { char* interface; /* Interface name */ struct in_addr heartaddr; /* Peer node address */ struct sockaddr_in addr; /* Local address */ int port; /* UDP port */ int rsocket; /* Read-socket */ int wsocket; /* Write-socket */ }; /* * Function Prototypes */ PIL_rc PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports); static void ucastclosepi(PILPlugin *pi); static PIL_rc ucastcloseintf(PILInterface *pi, void *pd); static int ucast_parse(const char *line); static struct hb_media* ucast_new(const char *intf, const char *addr); static int ucast_open(struct hb_media *mp); static int ucast_close(struct hb_media *mp); static void* ucast_read(struct hb_media *mp, int* lenp); static int ucast_write(struct hb_media *mp, void *msg, int len); static int HB_make_receive_sock(struct hb_media *ei); static int HB_make_send_sock(struct hb_media *mp); static struct ip_private* new_ip_interface(const char *ifn, const char *hbaddr, int port); static int ucast_descr(char **buffer); static int ucast_mtype(char **buffer); static int ucast_isping(void); /* * External Data */ extern struct hb_media *sysmedia[]; extern int nummedia; /* * Module Public Data */ const char hb_media_name[] = "UDP/IP unicast"; static struct hb_media_fns ucastOps = { NULL, ucast_parse, ucast_open, ucast_close, ucast_read, ucast_write, ucast_mtype, ucast_descr, ucast_isping }; PIL_PLUGIN_BOILERPLATE("1.0", Debug, ucastclosepi); static const PILPluginImports* PluginImports; static PILPlugin* OurPlugin; static PILInterface* OurInterface; static struct hb_media_imports* OurImports; static void* interfprivate; static int localudpport; /* * Implmentation */ PIL_rc PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports) { /* Force the compiler to do a little type checking */ (void)(PILPluginInitFun)PIL_PLUGIN_INIT; PluginImports = imports; OurPlugin = us; /* Register ourself as a plugin */ imports->register_plugin(us, &OurPIExports); /* Register our interface implementation */ return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, &ucastOps, ucastcloseintf, &OurInterface, (void*)&OurImports, interfprivate); } static void ucastclosepi(PILPlugin *pi) { return; } static PIL_rc ucastcloseintf(PILInterface *pi, void *pd) { return PIL_OK; } static int ucast_parse(const char *line) { const char *bp = line; int toklen; struct hb_media *mp; char dev[MAXLINE]; char ucast[MAXLINE]; /* Skip over white space, then grab the device */ bp += strspn(bp, WHITESPACE); toklen = strcspn(bp, WHITESPACE); strncpy(dev, bp, toklen); bp += toklen; dev[toklen] = EOS; if (*dev != EOS) { #ifdef NOTYET if (!is_valid_dev(dev)) { PILCallLog(LOG, PIL_CRIT, "ucast: bad device [%s]", dev); return HA_FAIL; } #endif bp += strspn(bp, WHITESPACE); toklen = strcspn(bp, WHITESPACE); strncpy(ucast, bp, toklen); bp += toklen; ucast[toklen] = EOS; if (*ucast == EOS) { PILCallLog(LOG, PIL_CRIT, "ucast: [%s] missing target IP address/hostname", dev); return HA_FAIL; } if (!(mp = ucast_new(dev, ucast))) return HA_FAIL; sysmedia[nummedia++] = mp; } return HA_OK; } static int ucast_mtype(char **buffer) { *buffer = STRDUP(PIL_PLUGIN_S); if (!*buffer) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 2) ); return 0; } return strlen(*buffer); } static int ucast_descr(char **buffer) { *buffer = strdup(hb_media_name); if (!*buffer) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 2) ); return 0; } return strlen(*buffer); } static int ucast_isping(void) { return 0; } static int ucast_init(void) { struct servent *service; g_assert(OurImports != NULL); if (localudpport <= 0) { const char *chport; if ((chport = OurImports->ParamValue("udpport")) != NULL) { sscanf(chport, "%d", &localudpport); if (localudpport <= 0) { PILCallLog(LOG, PIL_CRIT, "ucast: bad port number %s", chport); return HA_FAIL; } } } /* No port specified in the configuration... */ if (localudpport <= 0) { /* If our service name is in /etc/services, then use it */ if ((service=getservbyname(HA_SERVICENAME, "udp")) != NULL) localudpport = ntohs(service->s_port); else localudpport = UDPPORT; } return HA_OK; } /* * Create new UDP/IP unicast heartbeat object * Name of interface and address are passed as parameters */ static struct hb_media* ucast_new(const char *intf, const char *addr) { struct ip_private *ipi; struct hb_media *ret; char *name; ucast_init(); if (!(ipi = new_ip_interface(intf, addr, localudpport))) { PILCallLog(LOG, PIL_CRIT, "ucast: interface [%s] does not exist", intf); return NULL; } if (!(ret = (struct hb_media*)MALLOC(sizeof(struct hb_media)))) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 2) ); FREE(ipi->interface); FREE(ipi); } else { ret->pd = (void*)ipi; if (!(name = STRDUP(intf))) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 3) ); FREE(ipi->interface); FREE(ipi); FREE(ret); ret = NULL; } else { ret->name = name; } } return ret; } /* * Open UDP/IP unicast heartbeat interface */ static int ucast_open(struct hb_media* mp) { struct ip_private * ei; UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; if ((ei->wsocket = HB_make_send_sock(mp)) < 0) return HA_FAIL; if ((ei->rsocket = HB_make_receive_sock(mp)) < 0) { ucast_close(mp); return HA_FAIL; } PILCallLog(LOG, PIL_INFO, "ucast: started on port %d interface %s to %s", localudpport, ei->interface, inet_ntoa(ei->addr.sin_addr)); return HA_OK; } /* * Close UDP/IP unicast heartbeat interface */ static int ucast_close(struct hb_media* mp) { struct ip_private *ei; int rc = HA_OK; UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; if (ei->rsocket >= 0) { if (close(ei->rsocket) < 0) rc = HA_FAIL; } if (ei->wsocket >= 0) { if (close(ei->wsocket) < 0) rc = HA_FAIL; } return rc; } /* * Receive a heartbeat unicast packet from UDP interface */ static void * ucast_read(struct hb_media* mp, int *lenp) { struct ip_private *ei; socklen_t addr_len; struct sockaddr_in their_addr; int numbytes; char buf[MAXLINE]; void *pkt; UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; addr_len = sizeof(struct sockaddr); if ((numbytes = recvfrom(ei->rsocket, buf, MAXLINE-1, 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) { if (errno != EINTR) { PILCallLog(LOG, PIL_CRIT, "ucast: error receiving from socket: %s", strerror(errno)); } return NULL; } if (numbytes == 0) { PILCallLog(LOG, PIL_CRIT, "ucast: received zero bytes"); return NULL; } buf[numbytes] = EOS; if (DEBUGPKT) { PILCallLog(LOG, PIL_DEBUG, "ucast: received %d byte packet from %s", numbytes, inet_ntoa(their_addr.sin_addr)); } if (DEBUGPKTCONT) { PILCallLog(LOG, PIL_DEBUG, "%s", buf); } pkt = ha_malloc(numbytes + 1); if(!pkt){ PILCallLog(LOG, PIL_CRIT , "Error in allocating memory"); return(NULL); } memcpy(pkt, buf, numbytes + 1); *lenp = numbytes +1; return(pkt); } /* * Send a heartbeat packet over unicast UDP/IP interface */ static int ucast_write(struct hb_media* mp, void *pkt, int len) { struct ip_private *ei; int rc; UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; if ((rc = sendto(ei->wsocket, pkt, len, 0 , (struct sockaddr *)&ei->addr , sizeof(struct sockaddr))) != len) { PILCallLog(LOG, PIL_CRIT, "Unable to send [%d] ucast packet: %s" , rc, strerror(errno)); return HA_FAIL; } if (DEBUGPKT) { PILCallLog(LOG, PIL_DEBUG, "ucast: sent %d bytes to %s", rc, inet_ntoa(ei->addr.sin_addr)); } if (DEBUGPKTCONT) { PILCallLog(LOG, PIL_DEBUG, "%s", (const char*)pkt); } return HA_OK; } /* * Set up socket for sending unicast UDP heartbeats */ static int HB_make_send_sock(struct hb_media *mp) { int sockfd; struct ip_private *ei; int tos; #if defined(SO_BINDTODEVICE) struct ifreq i; #endif UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { PILCallLog(LOG, PIL_CRIT, "ucast: Error creating write socket: %s", strerror(errno)); } /* * 21 December 2002 * Added by Brian TInsley */ tos = IPTOS_LOWDELAY; if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) { PILCallLog(LOG, PIL_CRIT, "ucast: error setting socket option IP_TOS: %s", strerror(errno)); } else { PILCallLog(LOG, PIL_INFO, "ucast: write socket priority set to IPTOS_LOWDELAY on %s", ei->interface); } #if defined(SO_BINDTODEVICE) { /* * We want to send out this particular interface * * This is so we can have redundant NICs, and heartbeat on both */ strcpy(i.ifr_name, ei->interface); if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &i, sizeof(i)) == -1) { PILCallLog(LOG, PIL_CRIT, "ucast: error setting option SO_BINDTODEVICE(w) on %s: %s", i.ifr_name, strerror(errno)); close(sockfd); return -1; } PILCallLog(LOG, PIL_INFO, "ucast: bound send socket to device: %s", i.ifr_name); } #endif if (fcntl(sockfd,F_SETFD, FD_CLOEXEC) < 0) { PILCallLog(LOG, PIL_CRIT, "ucast: error setting close-on-exec flag: %s", strerror(errno)); } return sockfd; } /* * Set up socket for listening to heartbeats (UDP unicast) */ static int HB_make_receive_sock(struct hb_media *mp) { struct ip_private *ei; struct sockaddr_in my_addr; int sockfd; int bindtries; int boundyet = 0; int j; UCASTASSERT(mp); ei = (struct ip_private*)mp->pd; memset(&(my_addr), 0, sizeof(my_addr)); /* zero my address struct */ my_addr.sin_family = AF_INET; /* host byte order */ my_addr.sin_port = htons(ei->port); /* short, network byte order */ my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { PILCallLog(LOG, PIL_CRIT, "ucast: error creating read socket: %s", strerror(errno)); return -1; } /* * Set SO_REUSEADDR on the server socket s. Variable j is used * as a scratch varable. * * 16th February 2000 * Added by Horms * with thanks to Clinton Work */ j = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&j, sizeof j) < 0) { /* Ignore it. It will almost always be OK anyway. */ PILCallLog(LOG, PIL_CRIT, "ucast: error setting socket option SO_REUSEADDR: %s", strerror(errno)); } #if defined(SO_BINDTODEVICE) { /* * We want to receive packets only from this interface... */ struct ifreq i; strcpy(i.ifr_name, ei->interface); if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &i, sizeof(i)) == -1) { PILCallLog(LOG, PIL_CRIT, "ucast: error setting option SO_BINDTODEVICE(r) on %s: %s", i.ifr_name, strerror(errno)); close(sockfd); return -1; } PILCallLog(LOG, PIL_INFO, "ucast: bound receive socket to device: %s", i.ifr_name); } #endif /* Try binding a few times before giving up */ /* Sometimes a process with it open is exiting right now */ for (bindtries=0; !boundyet && bindtries < MAXBINDTRIES; ++bindtries) { if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) < 0) { PILCallLog(LOG, PIL_CRIT, "ucast: error binding socket. Retrying: %s", strerror(errno)); sleep(1); } else{ boundyet = 1; } } if (!boundyet) { #if !defined(SO_BINDTODEVICE) if (errno == EADDRINUSE) { /* This happens with multiple udp or ppp interfaces */ PILCallLog(LOG, PIL_INFO, "ucast: someone already listening on port %d [%s]", ei->port, ei->interface); PILCallLog(LOG, PIL_INFO, "ucast: UDP read process exiting"); close(sockfd); cleanexit(0); } #else PILCallLog(LOG, PIL_CRIT, "ucast: unable to bind socket. Giving up: %s", strerror(errno)); close(sockfd); return -1; #endif } if (fcntl(sockfd,F_SETFD, FD_CLOEXEC) < 0) { PILCallLog(LOG, PIL_CRIT, "ucast: error setting close-on-exec flag: %s", strerror(errno)); } return sockfd; } static struct ip_private* new_ip_interface(const char *ifn, const char *hbaddr, int port) { struct ip_private *ep; struct hostent *h; /* * 21 December 2002 * Added by Brian TInsley */ if (!(h = gethostbyname(hbaddr))) { PILCallLog(LOG, PIL_CRIT, "ucast: cannot resolve hostname"); return NULL; } if (!(ep = (struct ip_private*) MALLOC(sizeof(struct ip_private)))) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 2) ); return NULL; } /* * use address from gethostbyname */ memcpy(&ep->heartaddr, h->h_addr_list[0], sizeof(ep->heartaddr)); if (!(ep->interface = STRDUP(ifn))) { PILCallLog(LOG, PIL_CRIT, "ucast: memory allocation error (line %d)", (__LINE__ - 2) ); FREE(ep); return NULL; } bzero(&ep->addr, sizeof(ep->addr)); /* zero the struct */ ep->addr.sin_family = AF_INET; /* host byte order */ ep->addr.sin_port = htons(port); /* short, network byte order */ ep->port = port; ep->wsocket = -1; ep->rsocket = -1; ep->addr.sin_addr = ep->heartaddr; return ep; }