/* * iface.c * * Written by Archie Cobbs * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved. * See ``COPYRIGHT.whistle'' * * TCP MSSFIX code copyright (c) 2000 Ruslan Ermilov * TCP MSSFIX contributed by Sergey Korolew * */ #include "ppp.h" #include "iface.h" #include "ipcp.h" #include "auth.h" #include "custom.h" #include "ngfunc.h" #include "netgraph.h" #include "radius.h" #include "util.h" #include #include #include #include #include #include #include #include /* * DEFINITIONS */ #define MAX_INTERFACES 32 /* * We are in a liberal position about MSS * (RFC 879, section 7). */ #define MAXMSS(mtu) (mtu - sizeof(struct ip) - sizeof(struct tcphdr)) /* Set menu options */ enum { SET_IDLE, SET_SESSION, SET_ADDRS, SET_ROUTE, SET_MTU, SET_UP_SCRIPT, SET_DOWN_SCRIPT, SET_ENABLE, SET_DISABLE, }; /* Configuration options */ enum { IFACE_CONF_ONDEMAND, IFACE_CONF_PROXY, IFACE_CONF_RADIUSMTU, IFACE_CONF_RADIUSIDLE, IFACE_CONF_RADIUSSESSION, IFACE_CONF_RADIUSROUTE, IFACE_CONF_RADIUSACL, IFACE_CONF_TCPMSSFIX, }; /* * INTERNAL FUNCTIONS */ static int IfaceSetCommand(int ac, char *av[], void *arg); static void IfaceIpIfaceUp(int ready); static void IfaceIpIfaceDown(void); static void IfaceIpIfaceReady(int ready); static void IfaceSessionTimeout(void *arg); static void IfaceIdleTimeout(void *arg); static void IfaceIdleTimerExpired(void *arg); static void IfaceCacheSend(void); static void IfaceCachePkt(int proto, Mbuf pkt); static int IfaceIsDemand(int proto, Mbuf pkt); static int IfaceAllocACL (struct acl_pool **ap, int start, char * ifname, int number); static int IfaceFindACL (struct acl_pool *ap, char * ifname, int number); static char * IFaceParseACL (char * src, char * ifname); static void IfaceCorrectMSS(struct tcphdr *tc, ssize_t pktlen, u_int16_t maxmss); /* * GLOBAL VARIABLES */ const struct cmdtab IfaceSetCmds[] = { { "addrs self peer", "Set interface addresses", IfaceSetCommand, NULL, (void *) SET_ADDRS }, { "route dest[/width]", "Add IP route", IfaceSetCommand, NULL, (void *) SET_ROUTE }, { "mtu size", "Set max allowed interface MTU", IfaceSetCommand, NULL, (void *) SET_MTU }, { "up-script [progname]", "Interface up script", IfaceSetCommand, NULL, (void *) SET_UP_SCRIPT }, { "down-script [progname]", "Interface down script", IfaceSetCommand, NULL, (void *) SET_DOWN_SCRIPT }, { "idle seconds", "Idle timeout", IfaceSetCommand, NULL, (void *) SET_IDLE }, { "session seconds", "Session timeout", IfaceSetCommand, NULL, (void *) SET_SESSION }, { "enable [opt ...]", "Enable option", IfaceSetCommand, NULL, (void *) SET_ENABLE }, { "disable [opt ...]", "Disable option", IfaceSetCommand, NULL, (void *) SET_DISABLE }, { NULL }, }; /* * INTERNAL VARIABLES */ static const struct confinfo gConfList[] = { { 0, IFACE_CONF_ONDEMAND, "on-demand" }, { 0, IFACE_CONF_PROXY, "proxy-arp" }, { 0, IFACE_CONF_RADIUSMTU, "radius-mtu" }, { 0, IFACE_CONF_RADIUSIDLE, "radius-idle" }, { 0, IFACE_CONF_RADIUSSESSION, "radius-session"}, { 0, IFACE_CONF_RADIUSROUTE, "radius-route" }, { 0, IFACE_CONF_RADIUSACL, "radius-acl" }, { 0, IFACE_CONF_TCPMSSFIX, "tcpmssfix" }, { 0, 0, NULL }, }; struct acl_pool * rule_pool = NULL; /* Pointer to the first element in the list of rules */ struct acl_pool * pipe_pool = NULL; /* Pointer to the first element in the list of pipes */ struct acl_pool * queue_pool = NULL; /* Pointer to the first element in the list of queues */ int rule_pool_start = 10000; /* Initial number of ipfw rules pool */ int pipe_pool_start = 10000; /* Initial number of ipfw dummynet pipe pool */ int queue_pool_start = 10000; /* Initial number of ipfw dummynet queue pool */ /* * IfaceInit() */ void IfaceInit(void) { IfaceState const iface = &bund->iface; /* Default configuration */ iface->mtu = NG_IFACE_MTU_DEFAULT; iface->max_mtu = NG_IFACE_MTU_DEFAULT; Disable(&iface->options, IFACE_CONF_ONDEMAND); Disable(&iface->options, IFACE_CONF_PROXY); Disable(&iface->options, IFACE_CONF_TCPMSSFIX); Log(LG_BUND|LG_IFACE, ("[%s] using interface %s", bund->name, bund->iface.ifname)); } /* * IfaceOpen() * * Open the interface layer */ void IfaceOpen(void) { IfaceState const iface = &bund->iface; Log(LG_IFACE, ("[%s] IFACE: Open event", bund->name)); /* If interface is already open do nothing */ if (iface->open) return; iface->open = TRUE; /* If on-demand, bring up system interface immediately and start listening for outgoing packets. The next outgoing packet will cause us to open the lower layer(s) */ if (Enabled(&iface->options, IFACE_CONF_ONDEMAND)) { IfaceIpIfaceUp(0); NgFuncConfigBPF(bund, BPF_MODE_DEMAND); SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_READY_TO_DIAL); return; } /* Open lower layer(s) and wait for them to come up */ IfaceOpenNcps(); } /* * IfaceClose() * * Close the interface layer */ void IfaceClose(void) { IfaceState const iface = &bund->iface; Log(LG_IFACE, ("[%s] IFACE: Close event", bund->name)); /* If interface is already closed do nothing */ if (!iface->open) return; iface->open = FALSE; /* Take down system interface */ if (iface->ip_up) { NgFuncConfigBPF(bund, BPF_MODE_OFF); IfaceIpIfaceDown(); } /* Close lower layer(s) */ IfaceCloseNcps(); } /* * IfaceUp() * * Our underlying PPP bundle is ready for traffic. * Note, while this assumes we're talking about IP traffic * here, in general a parameter could specify which type * of traffic, IP vs. AppleTalk vs. whatever, along with * additional protocol specific information (in this case, * the IP addresses of each end of the point-to-point link). */ void IfaceUp(struct in_addr self, struct in_addr peer) { IfaceState const iface = &bund->iface; struct radius *rad = &bund->radius; Log(LG_IFACE, ("[%s] IFACE: Up event", bund->name)); SetStatus(ADLG_WAN_CONNECTED, STR_CONN_ESTAB); /* Open ourselves if necessary (we in effect slave off IPCP) */ if (!iface->open) { Log(LG_IFACE, ("[%s] IFACE: Opening", bund->name)); iface->open = TRUE; /* Would call IfaceOpen(); effect is same */ } /* Start Session timer */ TimerStop(&iface->sessionTimer); if (Enabled(&iface->options, IFACE_CONF_RADIUSSESSION) && rad->valid) { iface->session_timeout = rad->session_timeout; Log(LG_IFACE, ("[%s] IFACE: using RADIUS session-timeout: %d seconds", bund->name, iface->session_timeout)); } if (iface->session_timeout > 0) { TimerInit(&iface->sessionTimer, "IfaceSession", iface->session_timeout * SECONDS, IfaceSessionTimeout, NULL); TimerStart(&iface->sessionTimer); } /* Start idle timer */ TimerStop(&iface->idleTimer); if (Enabled(&iface->options, IFACE_CONF_RADIUSIDLE) && rad->valid) { iface->idle_timeout = rad->idle_timeout; Log(LG_IFACE, ("[%s] IFACE: using RADIUS idle-timeout: %d seconds", bund->name, iface->idle_timeout)); } if (iface->idle_timeout > 0) { char path[NG_PATHLEN + 1]; TimerInit(&iface->idleTimer, "IfaceIdle", iface->idle_timeout * SECONDS / IFACE_IDLE_SPLIT, IfaceIdleTimeout, NULL); TimerStart(&iface->idleTimer); iface->traffic[1] = TRUE; iface->traffic[0] = FALSE; /* Reset bpf node statistics */ memset(&iface->idleStats, 0, sizeof(iface->idleStats)); snprintf(path, sizeof(path), "%s:%s", iface->ifname, NG_IFACE_HOOK_INET); if (NgSendMsg(bund->csock, path, NGM_BPF_COOKIE, NGM_BPF_CLR_STATS, BPF_HOOK_IFACE, sizeof(BPF_HOOK_IFACE)) < 0) Log(LG_ERR, ("[%s] can't clear %s stats: %s", bund->name, NG_BPF_NODE_TYPE, strerror(errno))); } /* (Re)number interface as necessary */ if (!iface->ip_up || self.s_addr != iface->self_addr.s_addr || peer.s_addr != iface->peer_addr.s_addr) { /* Bring down interface if already up */ if (iface->ip_up) IfaceIpIfaceDown(); /* Bring up interface with new addresses */ iface->self_addr = self; iface->peer_addr = peer; IfaceIpIfaceUp(1); } else { if (!iface->ready) IfaceIpIfaceReady(1); } /* Customization */ #ifdef IA_CUSTOM CustomIpIfaceUp(iface->self_addr, iface->peer_addr); #endif /* Turn on interface traffic flow */ if (Enabled(&iface->options, IFACE_CONF_TCPMSSFIX)) NgFuncConfigBPF(bund, BPF_MODE_MSSFIX); else NgFuncConfigBPF(bund, BPF_MODE_ON); /* Send any cached packets */ IfaceCacheSend(); } /* * IfaceDown() * * Our packet transport mechanism is no longer ready for traffic. */ void IfaceDown(void) { IfaceState const iface = &bund->iface; Log(LG_IFACE, ("[%s] IFACE: Down event", bund->name)); /* Customization */ #ifdef IA_CUSTOM CustomIpIfaceDown(); #endif /* If we're not open, it doesn't matter to us anyway */ TimerStop(&iface->idleTimer); if (!iface->open) return; /* If dial-on-demand, this is OK; just listen for future demand */ if (Enabled(&iface->options, IFACE_CONF_ONDEMAND)) { SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_READY_TO_DIAL); NgFuncConfigBPF(bund, BPF_MODE_DEMAND); IfaceIpIfaceReady(0); IfaceCloseNcps(); return; } /* Take down system interface */ if (iface->ip_up) IfaceIpIfaceDown(); NgFuncConfigBPF(bund, BPF_MODE_OFF); } /* * IfaceListenInput() * * A packet was received on our demand snooping hook. Stimulate a connection. */ void IfaceListenInput(int proto, Mbuf pkt) { IfaceState const iface = &bund->iface; int const isDemand = IfaceIsDemand(proto, pkt); Fsm fsm; /* Does this count as demand traffic? */ if (isDemand) iface->traffic[0] = TRUE; /* Get FSM for protocol (for now, we know it's IP) */ assert(proto == PROTO_IP); fsm = &bund->ipcp.fsm; /* Maybe do dial-on-demand here */ if (OPEN_STATE(fsm->state)) { if (bund->bm.n_up > 0) { if (Enabled(&iface->options, IFACE_CONF_TCPMSSFIX)) { struct ip *iphdr; int plen, hlen; if (proto == PROTO_IP) { iphdr = (struct ip *)MBDATA(pkt); hlen = iphdr->ip_hl << 2; plen = plength(pkt); /* * Check for MSS option only for TCP packets with zero fragment offsets * and correct total and header lengths. */ if (iphdr->ip_p == IPPROTO_TCP && (ntohs(iphdr->ip_off) & IP_OFFMASK) == 0 && ntohs(iphdr->ip_len) == plen && hlen <= plen && plen - hlen >= sizeof(struct tcphdr)) IfaceCorrectMSS((struct tcphdr *)(MBDATA(pkt) + hlen), plen - hlen, MAXMSS(iface->mtu)); } } else Log(LG_IFACE, ("[%s] unexpected outgoing packet, len=%d", bund->name, MBLEN(pkt))); NgFuncWriteFrame(bund->name, MPD_HOOK_DEMAND_TAP, pkt); } else { IfaceCachePkt(proto, pkt); } } else if (iface->open && isDemand) { Log(LG_IFACE, ("[%s] outgoing packet is demand", bund->name)); RecordLinkUpDownReason(NULL, 1, STR_DEMAND, "%s", AsciifyPacket(pkt)); IfaceOpenNcps(); IfaceCachePkt(proto, pkt); } else { PFREE(pkt); } } /* * IfaceAllocACL () * * Allocates unique real number for new ACL and adds it to the list of used ones. */ static int IfaceAllocACL(struct acl_pool **ap, int start, char *ifname, int number) { int i; struct acl_pool **rp,*rp1; rp1 = Malloc(MB_UTIL, sizeof(struct acl_pool)); strncpy(rp1->ifname, ifname, IFNAMSIZ); rp1->acl_number = number; rp = ap; i = start; while (*rp != NULL && (*rp)->real_number <= i) { i = (*rp)->real_number+1; rp = &((*rp)->next); }; if (*rp == NULL) { rp1->next = NULL; } else { rp1->next = *rp; }; rp1->real_number = i; *rp = rp1; return(i); }; /* * IfaceFindACL () * * Finds ACL in the list and gets its real number. */ static int IfaceFindACL (struct acl_pool *ap, char * ifname, int number) { int i; struct acl_pool *rp; rp=ap; i=-1; while (rp != NULL) { if ((rp->acl_number == number) && (strncmp(rp->ifname,ifname,IFNAMSIZ) == 0)) { i = rp->real_number; break; }; rp = rp->next; }; return(i); }; /* * IFaceParseACL () * * Parces ACL and replaces %r, %p and %q macroses * by the real numbers of rules, queues and pipes. */ static char * IFaceParseACL (char * src, char * ifname) { char *buf,*buf1; char *begin,*param,*end; char t; int num,real_number; struct acl_pool *ap; buf = Malloc(MB_UTIL,ACL_LEN+1); buf1 = Malloc(MB_UTIL,ACL_LEN+1); strncpy(buf,src,ACL_LEN); do { end = buf; begin = strsep(&end,"%"); param = strsep(&end," "); if (param != NULL) { if (sscanf(param,"%c%d",&t,&num) == 2) { switch (t) { case 'r': ap = rule_pool; break; case 'p': ap = pipe_pool; break; case 'q': ap = queue_pool; break; default: ap = NULL; }; real_number = IfaceFindACL(ap,ifname,num); if (end != NULL) { snprintf(buf1,ACL_LEN,"%s%d %s",begin,real_number,end); } else { snprintf(buf1,ACL_LEN,"%s%d",begin,real_number); }; strncpy(buf,buf1,ACL_LEN); }; }; } while (end != NULL); Freee(buf1); return(buf); }; /* * IfaceIpIfaceUp() * * Bring up the IP interface. The "ready" flag means that * IPCP is also up and we can deliver packets immediately. We signal * that the interface is not "ready" with the IFF_LINK0 flag. */ static void IfaceIpIfaceUp(int ready) { IfaceState const iface = &bund->iface; int k,i; struct sockaddr_dl hwa; char hisaddr[20]; u_char *ether; struct radius_acl *acls; char *buf; /* Sanity */ assert(!iface->ip_up); /* For good measure */ BundUpdateParams(); /* Set addresses and bring interface up */ snprintf(hisaddr, sizeof(hisaddr), "%s", inet_ntoa(iface->peer_addr)); ExecCmd(LG_IFACE, "%s %s %s %s netmask 0xffffffff %slink0", PATH_IFCONFIG, iface->ifname, inet_ntoa(iface->self_addr), hisaddr, ready ? "-" : ""); iface->ready = ready; /* Proxy ARP for peer if desired and peer's address is known */ iface->proxy_addr.s_addr = 0; if (Enabled(&iface->options, IFACE_CONF_PROXY)) { if (iface->peer_addr.s_addr == 0) { Log(LG_IFACE, ("[%s] can't proxy arp for %s", bund->name, inet_ntoa(iface->peer_addr))); } else if (IfaceGetEther(&iface->peer_addr, &hwa) < 0) { Log(LG_IFACE, ("[%s] no interface to proxy arp on for %s", bund->name, inet_ntoa(iface->peer_addr))); } else { ether = (u_char *) LLADDR(&hwa); if (ExecCmd(LG_IFACE, "%s -s %s %x:%x:%x:%x:%x:%x pub", PATH_ARP, inet_ntoa(iface->peer_addr), ether[0], ether[1], ether[2], ether[3], ether[4], ether[5]) == 0) iface->proxy_addr = iface->peer_addr; } } /* Add loopback route */ ExecCmd(LG_IFACE, "%s add %s -iface lo0", PATH_ROUTE, inet_ntoa(iface->self_addr)); if (Enabled(&iface->options, IFACE_CONF_RADIUSROUTE)) { for (i=0; (i < bund->radius.n_routes) && (bund->iface.n_routes < IFACE_MAX_ROUTES); i++) { memcpy(&(iface->routes[iface->n_routes++]), &(bund->radius.routes[i]), sizeof(struct ifaceroute)); }; Log(LG_IFACE, ("[%s] IFACE: using %d RADIUS routes", bund->name, bund->radius.n_routes)); } /* Add routes */ for (k = 0; k < iface->n_routes; k++) { IfaceRoute const r = &iface->routes[k]; char nmbuf[40], peerbuf[40]; if (r->netmask.s_addr) { snprintf(nmbuf, sizeof(nmbuf), " -netmask 0x%08lx", (u_long)ntohl(r->netmask.s_addr)); } else *nmbuf = 0; snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr)); r->ok = (ExecCmd(LG_IFACE, "%s add %s %s%s", PATH_ROUTE, inet_ntoa(r->dest), peerbuf, nmbuf) == 0); } /* Add ACLs */ if (Enabled(&iface->options, IFACE_CONF_RADIUSACL)) { Log(LG_IFACE, ("[%s] IFACE: using RADIUS ACLs", bund->name)); /* Allocate ACLs */ acls = bund->radius.acl_pipe; while (acls != NULL) { IfaceAllocACL(&pipe_pool,pipe_pool_start,iface->ifname,acls->number); acls = acls->next; }; acls = bund->radius.acl_queue; while (acls != NULL) { IfaceAllocACL(&queue_pool,queue_pool_start,iface->ifname,acls->number); acls = acls->next; }; acls = bund->radius.acl_rule; while (acls != NULL) { IfaceAllocACL(&rule_pool,rule_pool_start,iface->ifname,acls->number); acls = acls->next; }; /* Set ACLs */ acls = bund->radius.acl_pipe; while (acls != NULL) { i=IfaceFindACL(pipe_pool,iface->ifname,acls->number); buf=IFaceParseACL(acls->rule,iface->ifname); ExecCmd(LG_IFACE, "%s pipe %d config %s", PATH_IPFW, i, acls->rule); Freee(buf); acls = acls->next; }; acls = bund->radius.acl_queue; while (acls != NULL) { i = IfaceFindACL(queue_pool,iface->ifname,acls->number); buf = IFaceParseACL(acls->rule,iface->ifname); ExecCmd(LG_IFACE, "%s queue %d config %s", PATH_IPFW, i, buf); Freee(buf); acls = acls->next; }; acls = bund->radius.acl_rule; while (acls != NULL) { i = IfaceFindACL(rule_pool,iface->ifname,acls->number); buf = IFaceParseACL(acls->rule,iface->ifname); ExecCmd(LG_IFACE, "%s add %d %s via %s", PATH_IPFW, i, buf, iface->ifname); Freee(buf); acls = acls->next; }; } /* Call "up" script */ if (*iface->up_script) { char peerbuf[40]; char ns1buf[21], ns2buf[21]; if(bund->ipcp.want_dns[0].s_addr != 0) snprintf(ns1buf, sizeof(ns1buf), "dns1 %s", inet_ntoa(bund->ipcp.want_dns[0])); else ns1buf[0] = '\0'; if(bund->ipcp.want_dns[1].s_addr != 0) snprintf(ns2buf, sizeof(ns2buf), "dns2 %s", inet_ntoa(bund->ipcp.want_dns[1])); else ns2buf[0] = '\0'; snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr)); ExecCmd(LG_IFACE, "%s %s inet %s %s %s %s %s", iface->up_script, iface->ifname, inet_ntoa(iface->self_addr), peerbuf, *bund->peer_authname ? bund->peer_authname : bund->conf.authname, ns1buf, ns2buf); } /* Done */ iface->ip_up = TRUE; } /* * IfaceIpIfaceReady() * * (Un)set the interface IFF_LINK0 flag because IPCP is now up or down. * Call this when the addressing is already set correctly and you * just want to change the flag. */ static void IfaceIpIfaceReady(int ready) { IfaceState const iface = &bund->iface; ExecCmd(LG_IFACE, "%s %s %slink0", PATH_IFCONFIG, iface->ifname, ready ? "-" : ""); iface->ready = ready; } /* * IfaceIpIfaceDown() * * Bring down the IP interface. This implies we're no longer ready. */ static void IfaceIpIfaceDown(void) { IfaceState const iface = &bund->iface; int k; struct acl_pool **rp,*rp1; /* Sanity */ assert(iface->ip_up); /* Call "down" script */ if (*iface->down_script) { ExecCmd(LG_IFACE, "%s %s inet %s", iface->down_script, iface->ifname, *bund->peer_authname ? bund->peer_authname : bund->conf.authname); } /* Remove ACLs */ if (Enabled(&iface->options, IFACE_CONF_RADIUSACL)) { /* Remove rule ACLs */ rp = &rule_pool; while (*rp != NULL) { if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) { ExecCmd(LG_IFACE, "%s delete %d", PATH_IPFW, (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(rp1); } else { rp = &((*rp)->next); }; }; /* Remove queue ACLs */ rp = &queue_pool; while (*rp != NULL) { if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) { ExecCmd(LG_IFACE, "%s queue %d delete", PATH_IPFW, (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(rp1); } else { rp = &((*rp)->next); }; }; /* Remove pipe ACLs */ rp = &pipe_pool; while (*rp != NULL) { if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) { ExecCmd(LG_IFACE, "%s pipe %d delete", PATH_IPFW, (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(rp1); } else { rp = &((*rp)->next); }; }; }; /* Delete routes */ for (k = 0; k < iface->n_routes; k++) { IfaceRoute const r = &iface->routes[k]; char nmbuf[40], peerbuf[40]; if (!r->ok) continue; if (r->netmask.s_addr) { snprintf(nmbuf, sizeof(nmbuf), " -netmask 0x%08lx", (u_long)ntohl(r->netmask.s_addr)); } else *nmbuf = 0; snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr)); ExecCmd(LG_IFACE, "%s delete %s %s%s", PATH_ROUTE, inet_ntoa(r->dest), peerbuf, nmbuf); r->ok = 0; } if (Enabled(&iface->options, IFACE_CONF_RADIUSROUTE)) { iface->n_routes=0; }; /* Delete loopback route */ ExecCmd(LG_IFACE, "%s delete %s -iface lo0", PATH_ROUTE, inet_ntoa(iface->self_addr)); /* Delete any proxy arp entry */ if (iface->proxy_addr.s_addr) ExecCmd(LG_IFACE, "%s -d %s", PATH_ARP, inet_ntoa(iface->proxy_addr)); iface->proxy_addr.s_addr = 0; /* Bring down system interface */ ExecCmd(LG_IFACE, "%s %s down delete -link0", PATH_IFCONFIG, iface->ifname); iface->ready = 0; /* Done */ iface->ip_up = FALSE; } /* * IfaceIdleTimeout() */ static void IfaceIdleTimeout(void *arg) { IfaceState const iface = &bund->iface; char path[NG_PATHLEN + 1]; struct ng_bpf_hookstat oldStats; union { u_char buf[sizeof(struct ng_mesg) + sizeof(oldStats)]; struct ng_mesg reply; } u; int k; /* Get updated bpf node traffic statistics */ oldStats = iface->idleStats; snprintf(path, sizeof(path), "%s:%s", iface->ifname, NG_IFACE_HOOK_INET); if (NgSendMsg(bund->csock, path, NGM_BPF_COOKIE, NGM_BPF_GET_STATS, BPF_HOOK_IFACE, sizeof(BPF_HOOK_IFACE)) < 0) { Log(LG_ERR, ("[%s] can't get %s stats: %s", bund->name, NG_BPF_NODE_TYPE, strerror(errno))); return; } if (NgRecvMsg(bund->csock, &u.reply, sizeof(u), NULL) < 0) { Log(LG_ERR, ("[%s] node \"%s\" reply: %s", bund->name, path, strerror(errno))); return; } memcpy(&iface->idleStats, u.reply.data, sizeof(iface->idleStats)); /* Mark current traffic period if there was traffic */ if (iface->idleStats.recvMatchFrames > oldStats.recvMatchFrames) iface->traffic[0] = TRUE; else { /* no demand traffic for a whole idle timeout period? */ for (k = 0; k < IFACE_IDLE_SPLIT && !iface->traffic[k]; k++); if (k == IFACE_IDLE_SPLIT) { IfaceIdleTimerExpired(NULL); return; } } /* Shift traffic history */ memmove(iface->traffic + 1, iface->traffic, (IFACE_IDLE_SPLIT - 1) * sizeof(*iface->traffic)); iface->traffic[0] = FALSE; /* Restart timer */ TimerStart(&iface->idleTimer); } /* * IfaceIdleTimerExpired() * * The idle timeout expired with no demand traffic. Shutdown the * link gracefully. Give custom code a chance to do any last minute * things before shutting down though. At this point, the shutdown * is going to happen, even if there is subsequent demand. */ static void IfaceIdleTimerExpired(void *arg) { IfaceState const iface = &bund->iface; #ifdef IA_CUSTOM int delay; #endif /* We already did the final short delay, really shut down now */ if (arg != NULL) { RecordLinkUpDownReason(NULL, 0, STR_IDLE_TIMEOUT, NULL); IfaceCloseNcps(); return; } /* Idle timeout first detected */ Log(LG_BUND, ("[%s] idle timeout after %d seconds", bund->name, iface->idleTimer.load * IFACE_IDLE_SPLIT / SECONDS)); /* Get delay and do it */ #ifdef IA_CUSTOM if ((delay = CustomIdleTimeoutAction()) > 0) { TimerInit(&iface->idleTimer, "IfaceIdle", delay * SECONDS, IfaceIdleTimerExpired, (void *)1); TimerStart(&iface->idleTimer); } else #endif IfaceIdleTimerExpired((void *)1); } /* * IfaceSessionTimeout() */ static void IfaceSessionTimeout(void *arg) { Log(LG_BUND, ("[%s] session timeout ", bund->name)); RecordLinkUpDownReason(NULL, 0, STR_SESSION_TIMEOUT, NULL); if (Enabled(&bund->conf.options, BUND_CONF_NORETRY)) { IfaceCloseNcps(); } else { BundCloseLinks(); } } /* * IfaceOpenNcps() */ void IfaceOpenNcps(void) { IpcpOpen(); } /* * IfaceCloseNcps() */ void IfaceCloseNcps(void) { IfaceState const iface = &bund->iface; TimerStop(&iface->idleTimer); TimerStop(&iface->sessionTimer); IpcpClose(); } /* * IfaceCachePkt() * * A packet caused dial-on-demand; save it for later if possible. * Consumes the mbuf in any case. */ static void IfaceCachePkt(int proto, Mbuf pkt) { IfaceState const iface = &bund->iface; /* Only cache network layer data */ if (!PROT_NETWORK_DATA(proto)) { PFREE(pkt); return; } /* Release previously cached packet, if any, and save this one */ if (iface->dodCache.pkt) PFREE(iface->dodCache.pkt); iface->dodCache.pkt = pkt; iface->dodCache.proto = proto; iface->dodCache.ts = time(NULL); } /* * IfaceCacheSend() * * Send cached packet */ static void IfaceCacheSend(void) { IfaceState const iface = &bund->iface; if (iface->dodCache.pkt) { if (iface->dodCache.ts + MAX_DOD_CACHE_DELAY < time(NULL)) PFREE(iface->dodCache.pkt); else { assert(iface->dodCache.proto == PROTO_IP); if (NgFuncWriteFrame(bund->name, MPD_HOOK_DEMAND_TAP, iface->dodCache.pkt) < 0) { Log(LG_ERR, ("[%s] can't write cached pkt: %s", bund->name, strerror(errno))); } } iface->dodCache.pkt = NULL; } } /* * IfaceIsDemand() * * Determine if this outgoing packet qualifies for dial-on-demand * Packet must be contiguous */ static int IfaceIsDemand(int proto, Mbuf pkt) { switch (proto) { case PROTO_IP: { struct ip iphdr; struct ip *const ip = &iphdr; memcpy(&iphdr, MBDATA(pkt), sizeof(iphdr)); switch (ip->ip_p) { case IPPROTO_IGMP: /* No multicast stuff */ return(0); case IPPROTO_ICMP: { struct icmp *const icmp = (struct icmp *) ((u_int32_t *) ip + ip->ip_hl); switch (icmp->icmp_type) /* No ICMP replies */ { case ICMP_ECHOREPLY: case ICMP_UNREACH: case ICMP_REDIRECT: return(0); default: break; } } break; case IPPROTO_UDP: { struct udphdr *const udp = (struct udphdr *) ((u_int32_t *) ip + ip->ip_hl); #define NTP_PORT 123 if (ntohs(udp->uh_dport) == NTP_PORT) /* No NTP packets */ return(0); } break; case IPPROTO_TCP: { struct tcphdr *const tcp = (struct tcphdr *) ((u_int32_t *) ip + ip->ip_hl); if (tcp->th_flags & TH_RST) /* No TCP reset packets */ return(0); } break; default: break; } break; } default: break; } return(1); } /* * IfaceSetCommand() */ static int IfaceSetCommand(int ac, char *av[], void *arg) { IfaceState const iface = &bund->iface; if (ac == 0) return(-1); switch ((intptr_t)arg) { case SET_IDLE: iface->idle_timeout = atoi(*av); break; case SET_SESSION: iface->session_timeout = atoi(*av); break; case SET_ADDRS: { struct in_addr self_addr; struct in_addr peer_addr; /* Parse */ if (ac != 2) return(-1); if (!inet_aton(av[0], &self_addr)) { Log(LG_ERR, ("mpd: bad IP address \"%s\"", av[0])); break; } if (!inet_aton(av[1], &peer_addr)) { Log(LG_ERR, ("mpd: bad IP address \"%s\"", av[1])); break; } /* OK */ iface->self_addr = self_addr; iface->peer_addr = peer_addr; } break; case SET_ROUTE: { struct ifaceroute r; struct in_range range; /* Check */ if (ac != 1) return(-1); if (iface->n_routes >= IFACE_MAX_ROUTES) { Log(LG_ERR, ("iface: too many routes")); break; } /* Get dest address */ if (!strcasecmp(av[0], "default")) memset(&range, 0, sizeof(range)); else if (!ParseAddr(av[0], &range)) { Log(LG_ERR, ("route: bad dest address \"%s\"", av[0])); break; } r.netmask.s_addr = range.width ? htonl(~0 << (32 - range.width)) : 0; r.dest.s_addr = (range.ipaddr.s_addr & r.netmask.s_addr); iface->routes[iface->n_routes++] = r; } break; case SET_MTU: { int max_mtu; max_mtu = atoi(av[0]); if (max_mtu < IFACE_MIN_MTU || max_mtu > IFACE_MAX_MTU) { Log(LG_ERR, ("invalid interface mtu %d", max_mtu)); break; } iface->max_mtu = max_mtu; } break; case SET_UP_SCRIPT: switch (ac) { case 0: *iface->up_script = 0; break; case 1: snprintf(iface->up_script, sizeof(iface->up_script), "%s", av[0]); break; default: return(-1); } break; case SET_DOWN_SCRIPT: switch (ac) { case 0: *iface->down_script = 0; break; case 1: snprintf(iface->down_script, sizeof(iface->down_script), "%s", av[0]); break; default: return(-1); } break; case SET_ENABLE: EnableCommand(ac, av, &iface->options, gConfList); break; case SET_DISABLE: DisableCommand(ac, av, &iface->options, gConfList); break; default: assert(0); } return(0); } /* * IfaceStat() */ int IfaceStat(int ac, char *av[], void *arg) { IfaceState const iface = &bund->iface; int k; printf("Interface %s:\n", iface->ifname); printf("\tStatus : %s\n", iface->open ? "OPEN" : "CLOSED"); printf("\tIP Addresses : %s -> ", inet_ntoa(iface->self_addr)); printf("%s\n", inet_ntoa(iface->peer_addr)); printf("\tMaximum MTU : %d bytes\n", iface->max_mtu); printf("\tCurrent MTU : %d bytes\n", iface->mtu); printf("\tIdle timeout : %d seconds\n", iface->idle_timeout); printf("\tSession timeout : %d seconds\n", iface->session_timeout); printf("\tEvent scripts: UP: \"%s\" DOWN: \"%s\"\n", *iface->up_script ? iface->up_script : "", *iface->down_script ? iface->down_script : ""); printf("Static routes via peer:\n"); for (k = 0; k < iface->n_routes; k++) { printf("\t%s ", iface->routes[k].dest.s_addr ? inet_ntoa(iface->routes[k].dest) : "default"); if (iface->routes[k].netmask.s_addr) printf("\tnetmask %s", inet_ntoa(iface->routes[k].netmask)); printf("\n"); } printf("Interface level options:\n"); OptStat(&iface->options, gConfList); return(0); } /* * IfaceSetMTU() * * Set MTU and bandwidth on bundle's interface */ void IfaceSetMTU(int mtu, int speed) { IfaceState const iface = &bund->iface; struct ifreq ifr; int s; struct radius *rad = &bund->radius; /* Get socket */ if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Perror("socket"); DoExit(EX_ERRDEAD); } if (Enabled(&iface->options, IFACE_CONF_RADIUSMTU) && rad->valid && (rad->mtu > 0)) { iface->max_mtu = rad->mtu; Log(LG_IFACE, ("[%s] IFACE: using RADIUS max. mtu: %d", bund->name, iface->max_mtu)); } /* Limit MTU to configured maximum */ if (mtu > iface->max_mtu) { mtu = iface->max_mtu; } /* Set MTU on interface */ memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, bund->iface.ifname, sizeof(ifr.ifr_name)); ifr.ifr_mtu = mtu; Log(LG_BUND|LG_IFACE, ("[%s] setting interface %s MTU to %d bytes", bund->name, bund->iface.ifname, mtu)); if (ioctl(s, SIOCSIFMTU, (char *)&ifr) < 0) Perror("ioctl(%s, %s)", bund->iface.ifname, "SIOCSIFMTU"); close(s); /* Save MTU */ iface->mtu = mtu; } /* * IfaceGetAnyIpAddress() * * Get any non-loopback IP address owned by this machine * Prefer addresses from non-point-to-point interfaces. */ int IfaceGetAnyIpAddress(struct in_addr *ipaddr) { int s, p2p = 0; struct in_addr ipa = { 0 }; struct ifreq *ifr, *ifend; struct ifreq ifreq; struct ifconf ifc; struct ifreq ifs[MAX_INTERFACES]; /* Get interface list */ if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Perror("socket"); DoExit(EX_ERRDEAD); } ifc.ifc_len = sizeof(ifs); ifc.ifc_req = ifs; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { Perror("ioctl(SIOCGIFCONF)"); close(s); return(-1); } for (ifend = (struct ifreq *)(void *)(ifc.ifc_buf + ifc.ifc_len), ifr = ifc.ifc_req; ifr < ifend; ifr = (struct ifreq *)(void *)((char *) &ifr->ifr_addr + MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)))) { if (ifr->ifr_addr.sa_family == AF_INET) { /* Check that the interface is up, and not loopback; prefer non-p2p */ strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name)); if (ioctl(s, SIOCGIFFLAGS, &ifreq) < 0) continue; if ((ifreq.ifr_flags & (IFF_UP|IFF_LOOPBACK)) != IFF_UP) continue; if ((ifreq.ifr_flags & IFF_POINTOPOINT) && ipa.s_addr && !p2p) continue; /* Save IP address and interface name */ ipa = ((struct sockaddr_in *)(void *)&ifr->ifr_addr)->sin_addr; p2p = (ifreq.ifr_flags & IFF_POINTOPOINT) != 0; } } close(s); /* Found? */ if (ipa.s_addr == 0) return(-1); *ipaddr = ipa; return(0); } /* * IfaceGetEther() * * Get the hardware address of an interface on the the same subnet as addr. * If addr == NULL, finds the address of any local ethernet interface. */ int IfaceGetEther(struct in_addr *addr, struct sockaddr_dl *hwaddr) { int s; struct ifreq *ifr, *ifend, *ifp; u_long ina, mask; struct ifreq ifreq; struct ifconf ifc; struct ifreq ifs[MAX_INTERFACES]; /* Get interface list */ if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Perror("socket"); DoExit(EX_ERRDEAD); } ifc.ifc_len = sizeof(ifs); ifc.ifc_req = ifs; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { Perror("ioctl(SIOCGIFCONF)"); close(s); return(-1); } /* * Scan through looking for an interface with an IP * address on same subnet as `addr'. */ for (ifend = (struct ifreq *)(void *)(ifc.ifc_buf + ifc.ifc_len), ifr = ifc.ifc_req; ifr < ifend; ifr = (struct ifreq *)(void *)((char *) &ifr->ifr_addr + MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)))) { if (ifr->ifr_addr.sa_family == AF_INET) { /* Save IP address and interface name */ ina = ((struct sockaddr_in *)(void *)&ifr->ifr_addr)->sin_addr.s_addr; strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name)); /* Check that the interface is up, and not point-to-point or loopback */ if (ioctl(s, SIOCGIFFLAGS, &ifreq) < 0) continue; if ((ifreq.ifr_flags & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|IFF_LOOPBACK|IFF_NOARP)) != (IFF_UP|IFF_BROADCAST)) continue; /* Get its netmask and check that it's on the right subnet */ if (ioctl(s, SIOCGIFNETMASK, &ifreq) < 0) continue; mask = ((struct sockaddr_in *)(void *)&ifreq.ifr_addr)->sin_addr.s_addr; if (addr && (addr->s_addr & mask) != (ina & mask)) continue; /* OK */ break; } } close(s); /* Found? */ if (ifr >= ifend) return(-1); /* Now scan again looking for a link-level address for this interface */ for (ifp = ifr, ifr = ifc.ifc_req; ifr < ifend; ) { if (strcmp(ifp->ifr_name, ifr->ifr_name) == 0 && ifr->ifr_addr.sa_family == AF_LINK) { memcpy(hwaddr, (struct sockaddr_dl *)(void *)&ifr->ifr_addr, sizeof(*hwaddr)); return(0); } ifr = (struct ifreq *)(void *)((char *)&ifr->ifr_addr + MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr))); } /* Not found! */ return(-1); } static void IfaceCorrectMSS(struct tcphdr *tc, ssize_t pktlen, u_int16_t maxmss) { int hlen, olen, optlen; u_char *opt; u_int16_t *mss; int accumulate; hlen = tc->th_off << 2; /* Invalid header length or header without options. */ if (hlen <= sizeof(struct tcphdr) || hlen > pktlen) return; /* MSS option only allowed within SYN packets. */ if (!(tc->th_flags & TH_SYN)) return; for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1); olen > 0; olen -= optlen, opt += optlen) { if (*opt == TCPOPT_EOL) break; else if (*opt == TCPOPT_NOP) optlen = 1; else { optlen = *(opt + 1); if (optlen <= 0 || optlen > olen) break; if (*opt == TCPOPT_MAXSEG) { if (optlen != TCPOLEN_MAXSEG) continue; mss = (u_int16_t *)(opt + 2); if (ntohs(*mss) > maxmss) { #if 0 Log(LG_IFACE, ("[%s] MSS: %u -> %u", bund->name, ntohs(*mss), maxmss)); #endif accumulate = *mss; *mss = htons(maxmss); accumulate -= *mss; ADJUST_CHECKSUM(accumulate, tc->th_sum); } } } } }