/* This file is in the public domain. */ /* * LEP implementation for `ip', `ip4', and `ip6' endpoint lines. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "util.h" #include "netfd.h" #include "config.h" #include "structs.h" #include "pollloop.h" /* * Because a single line can lead to listening on multiple addresses, * we have to keep some kind of list of multiple addresses. A * LISTEN_IP_PRIV is our per-LEP private data blob; a LIPADDR is the * per-address structure. */ typedef struct listen_ip_priv LISTEN_IP_PRIV; typedef struct lipaddr LIPADDR; /* * All we have here is a backpointer to the LISTEN, the text given in * the config file (for error reporting), and the chain of LIPADDRs. */ struct listen_ip_priv { LISTEN *l; char *text; LIPADDR *addrs; } ; /* * Per-address data. link is the list link. lip is a backpointer to * the LISTEN_IP_PRIV. sa is the sockaddr (and salen its size, which * we must keep separately because Linux couldn't be arsed to pick up * sa_len - so much for API compatability!); sa is mallocked. af, * socktype, and proto are the socket() arguments, as obtained from * getaddrinfo(). fd is the socket itself, or -1 if it's not yet set * up. ioid is the poll ID as returned by add_poll_fd, or PL_NOID if * it's not yet set up. text is a text form specific to this * particular LIPADDR, constructed from getnameinfo() output. xfer is * used when switching configs; see the *_transfer methods. */ struct lipaddr { LIPADDR *link; LISTEN_IP_PRIV *lip; struct sockaddr *sa; socklen_t salen; /* thank you linux...not! */ int af; int socktype; int proto; int fd; int ioid; char *text; LIPADDR *xfer; } ; /* * Forward declaration of our LEP_OPS. */ extern const LEP_OPS lep_ops_ip; /* * Allow functions (see our add method). */ /* For `ip' lines. Accept INET and INET6. */ static int allow_ip(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: case AF_INET6: return(1); break; } return(0); } /* For `ip4' lines. Accept INET. */ static int allow_ip4(const struct sockaddr *sa) { return(sa->sa_family==AF_INET); } /* For `ip6' lines. Accept INET6. */ static int allow_ip6(const struct sockaddr *sa) { return(sa->sa_family==AF_INET6); } /* * Our add method. Depending on the keyword, set a filter function to * apply to getaddrinfo() output and an adjective for error messages. * Then crack the argument at the slash, if any, and call * getaddrinfo(). Walk the resulting list, testing each address and * saving those that pass. Finally, if we end up with any LIPADDRS * after all that, construct the LEP. */ static int lep_ip_add(CONFIG *cfg, LISTEN *l, const char *key, int keylen, const char *rest) { int (*test)(const struct sockaddr *); const char *adjective; const char *slash; char *addrstr; const char *portstr; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; LISTEN_IP_PRIV *lip; LIPADDR *la; LEP *lep; char ihost[NI_MAXHOST]; char iserv[NI_MAXSERV]; if ((keylen == 2) && !bcmp(key,"ip",2)) { test = &allow_ip; adjective = "IP"; } else if ((keylen == 3) && !bcmp(key,"ip4",3)) { test = &allow_ip4; adjective = "IPv4"; } else if ((keylen == 3) && !bcmp(key,"ip6",3)) { test = &allow_ip6; adjective = "IPv6"; } else { return(0); } slash = index(rest,'/'); if (slash) { addrstr = blk_to_nulterm(rest,slash-rest); portstr = slash + 1; } else { addrstr = 0; portstr = rest; } hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = 0; hints.ai_canonname = 0; hints.ai_next = 0; e = getaddrinfo(addrstr,portstr,&hints,&ai0); free(addrstr); switch (e) { case 0: if (ai0) break; e = EAI_NODATA; /* fall through */ default: config_err(cfg,"error looking up %s: %s",rest,gai_strerror(e)); break; } lip = malloc(sizeof(LISTEN_IP_PRIV)); lip->l = l; lip->text = strdup(rest); lip->addrs = 0; for (ai=ai0;ai;ai=ai->ai_next) { if (! (*test)(ai->ai_addr)) continue; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&ihost[0],sizeof(ihost),&iserv[0],sizeof(iserv),NI_NUMERICHOST|NI_NUMERICSERV)) { config_err(cfg,"can't get text form of AF %d address: %s",ai->ai_family,strerror(errno)); } la = malloc(sizeof(LIPADDR)); la->lip = lip; la->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,la->sa,ai->ai_addrlen); la->salen = ai->ai_addrlen; la->af = ai->ai_family; la->socktype = ai->ai_socktype; la->proto = ai->ai_protocol; la->fd = -1; la->ioid = PL_NOID; asprintf(&la->text,"%s/%s",&ihost[0],&iserv[0]); la->link = lip->addrs; lip->addrs = la; } freeaddrinfo(ai0); if (! lip->addrs) { free(lip->text); free(lip); config_err(cfg,"error looking up %s: no %s addresses",rest,adjective); } lep = malloc(sizeof(LEP)); lep->ops = &lep_ops_ip; lep->priv = lip; lep->link = l->endpoints; l->endpoints = lep; return(1); } /* * Our free method. Nothing unobvious here. */ static void lep_ip_free(void *pv) { LISTEN_IP_PRIV *lip; LIPADDR *a; lip = pv; while (lip->addrs) { a = lip->addrs; lip->addrs = a->link; free(a->sa); if (a->fd >= 0) close(a->fd); if (a->ioid != PL_NOID) remove_poll_id(a->ioid); free(a->text); free(a); } free(lip); } /* * Transfer functions. * * Our transfer paradigm basically amounts to pushing the double loops * down to the LIPADDRs themselves; when there's a match, we set each * one's xfer pointer to point to the other. (There's no ambiguity * here, because one is always old and the other new, and we know * which is which.) * * pre_transfer just clears the xfer pointers. * * setup_transfer loops through the cross-product, looking for matches * based on the socket parameters, sockaddr sa_familys, and then the * actual address and port values. When it finds a match, it sets the * xfer pointers (which cause those LIPADDRs to be skipped in any * future calls). * * do_transfer then just moves the fd if there's an xfer pointer set. * * no_transfer doesn't actually need to do anything, but it clears the * xfer pointers out of paranoia, to avoid having dangling pointers * even if they don't actually get followed. (The reason this is * unnecessary is that they'll get cleared by pre_transfer before the * next time they matter.) */ static void lep_ip_pre_transfer(void *pv) { LISTEN_IP_PRIV *p; LIPADDR *a; p = pv; for (a=p->addrs;a;a=a->link) a->xfer = 0; } static void lep_ip_setup_transfer(void *fv, void *tv) { LISTEN_IP_PRIV *flip; LIPADDR *fa; LISTEN_IP_PRIV *tlip; LIPADDR *ta; int match; flip = fv; tlip = tv; for <"f"> (fa=flip->addrs;fa;fa=fa->link) { if (fa->xfer) continue; for (ta=tlip->addrs;ta;ta=ta->link) { if (ta->xfer) continue; if ( (fa->af != ta->af) || (fa->socktype != ta->socktype) || (fa->proto != ta->proto) || (fa->sa->sa_family != ta->sa->sa_family) ) continue; switch (fa->sa->sa_family) { case AF_INET: #define fsin ((struct sockaddr_in *)fa->sa) #define tsin ((struct sockaddr_in *)ta->sa) match = (fsin->sin_addr.s_addr == tsin->sin_addr.s_addr) && (fsin->sin_port == tsin->sin_port); #undef fsin #undef tsin break; case AF_INET6: #define fsin6 ((struct sockaddr_in6 *)fa->sa) #define tsin6 ((struct sockaddr_in6 *)ta->sa) match = (fsin6->sin6_port == tsin6->sin6_port) && IN6_ARE_ADDR_EQUAL(&fsin6->sin6_addr,&tsin6->sin6_addr); #undef fsin6 #undef tsin6 break; default: abort(); break; } if (match) { fa->xfer = ta; ta->xfer = fa; continue <"f">; } } } } static void lep_ip_do_transfer(void *pv) { LIPADDR *a; for (a=((LISTEN_IP_PRIV *)pv)->addrs;a;a=a->link) { if (a->xfer) { if (a->xfer->xfer != a) abort(); a->fd = a->xfer->fd; a->xfer->fd = -1; a->xfer->xfer = 0; a->xfer = 0; } } } static void lep_ip_no_transfer(void *pv) { LIPADDR *a; for (a=((LISTEN_IP_PRIV *)pv)->addrs;a;a=a->link) a->xfer = 0; } /* * Our openfds method. Just do the socket-bind-listen dance for each * LIPADDR. */ static int lep_ip_openfds(void *pv) { LISTEN_IP_PRIV *p; LIPADDR *a; int err; int errs; p = pv; errs = 0; for <"addrs"> (a=p->addrs;a;a=a->link) { if (a->xfer) continue; if (a->fd >= 0) abort(); do <"err"> { a->fd = socket(a->af,a->socktype,a->proto); if (a->fd < 0) { fprintf(stderr,"%s: %s (%s): socket(%d,%d,%d): %s\n", __progname,p->text,a->text,a->af,a->socktype,a->proto,strerror(errno)); break <"err">; continue; } if (bind(a->fd,a->sa,a->salen) < 0) { fprintf(stderr,"%s: %s (%s): bind: %s\n",__progname,p->text,a->text,strerror(err)); break <"err">; } if (listen(a->fd,25) < 0) { fprintf(stderr,"%s: %s (%s): listen: %s\n",__progname,p->text,a->text,strerror(err)); break <"err">; } continue <"addrs">; } while (0); if (a->fd >= 0) { close(a->fd); a->fd = -1; } errs = 1; } return(errs); } /* * This is our read callback for add_poll_fd(). * * Do an accept() and set up the new socket as a comm point (see * netfd_new()). */ static void accept_ip(void *av) { LIPADDR *a; struct sockaddr_storage ss; socklen_t sl; int new; char *txt; char ihost[NI_MAXHOST]; char iserv[NI_MAXSERV]; a = av; sl = sizeof(ss); new = accept(a->fd,(void *)&ss,&sl); if (new < 0) { switch (errno) { case EINTR: case NONBLOCKING: return; break; } fprintf(stderr,"%s: %s (%s): accept: %s\n",__progname,a->lip->text,a->text,strerror(errno)); return; } if (getnameinfo((void *)&ss,sl,&ihost[0],sizeof(ihost),&iserv[0],sizeof(iserv),NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: %s (%s): can't get text form of AF %d address: %s",__progname,a->lip->text,a->text,ss.ss_family,strerror(errno)); close(new); return; } asprintf(&txt,"%s (%s) <-> %s/%s",a->lip->text,a->text,&ihost[0],&iserv[0]); set_nonblock(new); netfd_new(new,a->lip->l,txt); } /* * Our setup_poll method. Set the socket nonblocking and call * add_poll_fd() to call us back for incoming connections. */ static void lep_ip_setup_poll(void *pv) { LISTEN_IP_PRIV *p; LIPADDR *a; p = pv; for (a=p->addrs;a;a=a->link) { set_nonblock(a->fd); a->ioid = add_poll_fd(a->fd,&rwtest_always,&rwtest_never,&accept_ip,0,a); } } /* * Our LEP_OPS. */ const LEP_OPS lep_ops_ip = LEP_OPS_INIT(ip);