/* ** isc_udp.c Routines to handle UDP ISC sessions ** ** Copyright (C) 1992, 1998-1999 by Peter Eriksson and ** Per Cederqvist of the Lysator Academic Computer Association. ** ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Library General Public ** License as published by the Free Software Foundation; either ** version 2 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 ** Library General Public License for more details. ** ** You should have received a copy of the GNU Library General Public ** License along with this library; if not, write to the Free ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ** ** history: ** 920209 pen code extracted from isc_session.c ** (See ChangeLog for recent history) */ #include #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STDDEF_H # include #endif #ifdef HAVE_STDARG_H # include #endif #include #include #include #include #include #include #include #ifdef HAVE_STRING_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifndef NULL # include #endif #include #include "isc.h" #include "intern.h" #include "unused.h" /* ** Receive an UDP message and put it into an IscMessage */ static IscMessage * isc_udp_read_fn(IscHandlerList *UNUSED(hl), IscSession *scb) { IscMessage *msg; struct sockaddr addr; int len; int status; msg = isc_allocmsg(scb->cfg->max.msgsize); len = sizeof(addr); status = recvfrom(scb->fd, msg->buffer, msg->size, 0, &addr, &len); if (status < 0) { isc_freemsg(msg); return NULL; } msg->length = status; msg->buffer[msg->length] = '\0'; msg->address = isc_mkipaddress(&addr); return msg; } /* ** Transmit an UDP message to a remote service */ static int isc_udp_write_fn(IscHandlerList *hl, IscSession *scb, IscMessage *msg) { /* No target and no default target? Then fail. */ if (!msg->address && !scb->info.udp.raddr) { errno = ENOTCONN; return -1; } /* Not target specified? Use default target */ if (!msg->address) return ISC_HCALLFUN2(hl, write, scb, msg); return sendto(scb->fd, msg->buffer, msg->length, 0, &msg->address->ip.saddr, sizeof(msg->address->ip.saddr)); } static void isc_udp_destroy_fn(IscHandlerList *UNUSED(hl), IscSession *scb) { if (scb->info.udp.raddr) { isc_freeaddress(scb->info.udp.raddr); scb->info.udp.raddr = NULL; } if (scb->info.udp.laddr) { isc_freeaddress(scb->info.udp.laddr); scb->info.udp.laddr = NULL; } } /* ** Create an UDP Session Address */ static IscAddress * isc_mkudpaddress(const char *address, const char *service) { struct sockaddr_in addr; struct hostent *hp; struct servent *sp; memset(&addr, 0, sizeof(addr)); /* Any local address? */ if (address == NULL) { addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); } else if (strcmp(address, "*") == 0) { /* Broadcast address */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); } else if (isdigit((int)(unsigned char)address[0])) { addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(address); if (addr.sin_addr.s_addr == INADDR_NONE) return NULL; } else if ((hp = gethostbyname(address)) == NULL) return NULL; else { addr.sin_family = hp->h_addrtype; memcpy(&addr.sin_addr, hp->h_addr, sizeof(addr.sin_addr)); } if (isdigit((int)(unsigned char)service[0])) addr.sin_port = htons(atoi(service)); else if ((sp = getservbyname(service, "udp")) == NULL) return NULL; else addr.sin_port = sp->s_port; return isc_mkipaddress((struct sockaddr *) &addr); } static IscHandler isc_udp_funs = { &isc_udp_read_fn, &isc_udp_write_fn, &isc_default_close_fn, NULL, NULL, &isc_udp_destroy_fn, NULL }; /* ** Create an UDP session */ IscSession * isc_createudp(IscSessionConfig *cfg) { IscSession *scb; int fd, res; int flag; struct linger ling; if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return NULL; fd = isc_relocate_fd(fd, cfg->fd_relocate); /* Set non blocking write mode */ if ((res = fcntl(fd, F_GETFL, 0)) == -1) { close(fd); return NULL; } /* If compilation fails on the next line, please report it as a bug to ceder@lysator.liu.se. I'd like to talk to you so that you can test an autoconf solution to this problem. As a workaround, you can change "O_NONBLOCK" to "FNDELAY". */ if (fcntl(fd, F_SETFL, res | O_NONBLOCK) == -1) { close(fd); return NULL; } #if 0 /* The setsockopt calls below used to look like this, but this style is apparently now obsolete. It doesn't work on some Linux boxes. */ setsockopt(fd, SOL_SOCKET, SO_DONTLINGER, 0, 0); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 0, 0); #else /* This is the modern way to turn off linger and turn on reuseaddr. */ ling.l_onoff = 0; ling.l_linger = 0; setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); flag = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); #endif scb = isc_create(cfg, &isc_udp_funs); if (!scb) return NULL; scb->type = ISC_TYPE_UDP; scb->fd = fd; scb->info.udp.raddr = NULL; scb->info.udp.laddr = NULL; return scb; } /* ** Bind an UDP session to a local port and address */ int isc_bindudp(IscSession *scb, const char *address, const char *service) { IscAddress *ia; if (scb->type != ISC_TYPE_UDP) return -1; ia = isc_mkudpaddress(address, service); if (!ia) return -1; if (bind(scb->fd, &ia->ip.saddr, sizeof(ia->ip.saddr)) < 0) return -1; scb->state = ISC_STATE_RUNNING; scb->info.udp.laddr = ia; return 0; } /* ** Connect an UDP session to a remove service. */ int isc_connectudp(IscSession *scb, const char *address, const char *service) { IscAddress *ia; int res; if (scb->type != ISC_TYPE_UDP) return -1; if (address == NULL) address = "localhost"; ia = isc_mkudpaddress(address, service); if (!ia) return -1; res = connect(scb->fd, &ia->ip.saddr, sizeof(ia->ip.saddr)); if (res < 0 && errno != EINPROGRESS) return -1; scb->state = (res < 0 ? ISC_STATE_CONNECTING : ISC_STATE_RUNNING); scb->info.udp.raddr = ia; if (scb->info.udp.laddr == NULL) scb->info.udp.laddr = isc_getladdress(scb); return 0; } /* ** Establish a session with a remote UDP service, then insert it ** into a master control structure- */ IscSession * isc_openudp(IscMaster *mcb, const char *address, const char *service) { IscSession *scb; scb = isc_createudp(&mcb->scfg); if (!scb) return NULL; if (isc_connectudp(scb, address, service) < 0) { isc_destroy(NULL, scb); return NULL; } (void) isc_insert(mcb, scb); return scb; } /* ** Create an UDP session, prepare it for incoming messages ** and insert it into the master structure */ IscSession * isc_listenudp(IscMaster *mcb, const char *address, const char *service) { IscSession *scb; scb = isc_createudp(&mcb->scfg); if (!scb) return NULL; if (isc_bindudp(scb, address, service) < 0) { isc_destroy(NULL, scb); return NULL; } (void) isc_insert(mcb, scb); return scb; }