/* * 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 "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __DragonFly__ #include #include #include #include #else #include #include #include #include #endif #include #include #include #ifdef USE_NG_NAT #include #endif #ifdef USE_NG_TCPMSS #include #endif #ifdef USE_NG_IPACCT #include #undef r_ip_p /* XXX:DIRTY CONFLICT! */ #endif #ifdef USE_NG_NETFLOW #include #endif #ifdef USE_NG_CAR #include #endif #include /* * DEFINITIONS */ #define TEMPHOOK "temphook" /* Set menu options */ enum { SET_IDLE, SET_SESSION, SET_ADDRS, SET_ROUTE, SET_MTU, SET_UP_SCRIPT, SET_DOWN_SCRIPT, SET_ENABLE, SET_DISABLE, }; /* * INTERNAL FUNCTIONS */ static int IfaceNgIpInit(Bund b, int ready); static void IfaceNgIpShutdown(Bund b); static int IfaceNgIpv6Init(Bund b, int ready); static void IfaceNgIpv6Shutdown(Bund b); #ifdef USE_NG_NETFLOW static int IfaceInitNetflow(Bund b, char *path, char *hook, char out); static int IfaceSetupNetflow(Bund b, char out); static void IfaceShutdownNetflow(Bund b, char out); #endif #ifdef USE_NG_IPACCT static int IfaceInitIpacct(Bund b, char *path, char *hook); static void IfaceShutdownIpacct(Bund b); #endif #ifdef USE_NG_NAT static int IfaceInitNAT(Bund b, char *path, char *hook); static int IfaceSetupNAT(Bund b); static void IfaceShutdownNAT(Bund b); #endif static int IfaceInitTee(Bund b, char *path, char *hook); static void IfaceShutdownTee(Bund b); static int IfaceInitMSS(Bund b, char *path, char *hook); static void IfaceSetupMSS(Bund b, uint16_t maxMSS); static void IfaceShutdownMSS(Bund b); static int IfaceInitLimits(Bund b, char *path, char *hook); static void IfaceSetupLimits(Bund b); static void IfaceShutdownLimits(Bund b); static int IfaceSetCommand(Context ctx, int ac, char *av[], void *arg); static void IfaceSessionTimeout(void *arg); static void IfaceIdleTimeout(void *arg); static void IfaceCacheSend(Bund b); static void IfaceCachePkt(Bund b, 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); /* * 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_TCPMSSFIX, "tcpmssfix" }, { 0, IFACE_CONF_TEE, "tee" }, { 0, IFACE_CONF_NAT, "nat" }, { 0, IFACE_CONF_NETFLOW_IN, "netflow-in" }, { 0, IFACE_CONF_NETFLOW_OUT, "netflow-out" }, { 0, IFACE_CONF_IPACCT, "ipacct" }, { 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 */ struct acl_pool * table_pool = NULL; /* Pointer to the first element in the list of tables */ 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 */ int table_pool_start = 32; /* Initial number of ipfw tables pool */ /* A BPF filter that matches TCP SYN packets */ static const struct bpf_insn gTCPSYNProg[] = { /*00*/ BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 9), /* A <- IP protocol */ /*01*/ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 0, 6), /* !TCP => 8 */ /*02*/ BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 6), /* A <- fragmentation offset */ /*03*/ BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, 0x1fff, 4, 0), /* fragment => 8 */ /*04*/ BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 0), /* X <- header len */ /*05*/ BPF_STMT(BPF_LD+BPF_B+BPF_IND, 13), /* A <- TCP flags */ /*06*/ BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, TH_SYN, 0, 1), /* !TH_SYN => 8 */ /*07*/ BPF_STMT(BPF_RET+BPF_K, (u_int)-1), /* accept packet */ /*08*/ BPF_STMT(BPF_RET+BPF_K, 0), /* reject packet */ }; #define TCPSYN_PROG_LEN (sizeof(gTCPSYNProg) / sizeof(*gTCPSYNProg)) /* A BPF filter that matches nothing */ static const struct bpf_insn gNoMatchProg[] = { BPF_STMT(BPF_RET+BPF_K, 0) }; #define NOMATCH_PROG_LEN (sizeof(gNoMatchProg) / sizeof(*gNoMatchProg)) /* A BPF filter that matches everything */ static const struct bpf_insn gMatchProg[] = { BPF_STMT(BPF_RET+BPF_K, (u_int)-1) }; #define MATCH_PROG_LEN (sizeof(gMatchProg) / sizeof(*gMatchProg)) #define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}} static const struct in6_addr in6mask128 = IN6MASK128; /* * IfaceInit() */ void IfaceInit(Bund b) { IfaceState const iface = &b->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); NatInit(b); Log(LG_BUND|LG_IFACE, ("[%s] using interface %s", b->name, b->iface.ifname)); } /* * IfaceOpen() * * Open the interface layer */ void IfaceOpen(Bund b) { IfaceState const iface = &b->iface; Log(LG_IFACE, ("[%s] IFACE: Open event", b->name)); /* Open is useless without on-demand. */ if (!Enabled(&iface->options, IFACE_CONF_ONDEMAND)) { Log(LG_ERR, ("[%s] 'open iface' is useless without on-demand enabled", b->name)); return; } /* 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) */ BundNcpsJoin(b, NCP_NONE); } /* * IfaceClose() * * Close the interface layer */ void IfaceClose(Bund b) { IfaceState const iface = &b->iface; Log(LG_IFACE, ("[%s] IFACE: Close event", b->name)); /* If interface is already closed do nothing */ if (!iface->open) return; iface->open = FALSE; /* If there was on-demand, tell that it is not needed anymore */ BundNcpsLeave(b, NCP_NONE); } /* * IfaceOpenCmd() * * Open the interface layer */ void IfaceOpenCmd(Context ctx) { IfaceOpen(ctx->bund); } /* * IfaceCloseCmd() * * Close the interface layer */ void IfaceCloseCmd(Context ctx) { IfaceClose(ctx->bund); } /* * 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(Bund b, int ready) { IfaceState const iface = &b->iface; int session_timeout = 0, idle_timeout = 0; struct acl *acls, *acl; char *buf; struct acl_pool **poollast; int poollaststart; int prev_number; int prev_real_number; Log(LG_IFACE, ("[%s] IFACE: Up event", b->name)); if (ready) { /* Start Session timer */ if (b->params.session_timeout > 0) { session_timeout = b->params.session_timeout; } else if (iface->session_timeout > 0) { session_timeout = iface->session_timeout; } if (session_timeout > 0) { Log(LG_IFACE2, ("[%s] IFACE: session-timeout: %d seconds", b->name, session_timeout)); TimerInit(&iface->sessionTimer, "IfaceSession", session_timeout * SECONDS, IfaceSessionTimeout, b); TimerStart(&iface->sessionTimer); } /* Start idle timer */ if (b->params.idle_timeout > 0) { idle_timeout = b->params.idle_timeout; } else if (iface->idle_timeout > 0) { idle_timeout = iface->idle_timeout; } if (idle_timeout > 0) { Log(LG_IFACE2, ("[%s] IFACE: idle-timeout: %d seconds", b->name, idle_timeout)); TimerInit(&iface->idleTimer, "IfaceIdle", idle_timeout * SECONDS / IFACE_IDLE_SPLIT, IfaceIdleTimeout, b); TimerStart(&iface->idleTimer); iface->traffic[1] = TRUE; iface->traffic[0] = FALSE; /* Reset statistics */ memset(&iface->idleStats, 0, sizeof(iface->idleStats)); } /* Allocate ACLs */ acls = b->params.acl_pipe; poollast = &pipe_pool; poollaststart = pipe_pool_start; while (acls != NULL) { acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number); poollaststart = acls->real_number; acls = acls->next; }; acls = b->params.acl_queue; poollast = &queue_pool; poollaststart = queue_pool_start; while (acls != NULL) { acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number); poollaststart = acls->real_number; acls = acls->next; }; prev_number = -1; prev_real_number = -1; acls = b->params.acl_table; poollast = &table_pool; poollaststart = table_pool_start; while (acls != NULL) { if (acls->real_number == 0) { if (acls->number == prev_number) { /* ACL list is presorted so we need not allocate if equal */ acls->real_number = prev_real_number; } else { acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number); poollaststart = acls->real_number; prev_number = acls->number; prev_real_number = acls->real_number; } } acls = acls->next; }; acls = b->params.acl_rule; poollast = &rule_pool; poollaststart = rule_pool_start; while (acls != NULL) { acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number); poollaststart = acls->real_number; acls = acls->next; }; /* Set ACLs */ acls = b->params.acl_pipe; while (acls != NULL) { buf = IFaceParseACL(acls->rule, iface->ifname); ExecCmd(LG_IFACE2, b->name, "%s pipe %d config %s", PATH_IPFW, acls->real_number, acls->rule); Freee(MB_IFACE, buf); acls = acls->next; } acls = b->params.acl_queue; while (acls != NULL) { buf = IFaceParseACL(acls->rule,iface->ifname); ExecCmd(LG_IFACE2, b->name, "%s queue %d config %s", PATH_IPFW, acls->real_number, buf); Freee(MB_IFACE, buf); acls = acls->next; } acls = b->params.acl_table; while (acls != NULL) { acl = Malloc(MB_IFACE, sizeof(struct acl)); memcpy(acl, acls, sizeof(struct acl)); acl->next = iface->tables; iface->tables = acl; ExecCmd(LG_IFACE2, b->name, "%s table %d add %s", PATH_IPFW, acls->real_number, acls->rule); acls = acls->next; }; acls = b->params.acl_rule; while (acls != NULL) { buf = IFaceParseACL(acls->rule, iface->ifname); ExecCmd(LG_IFACE2, b->name, "%s add %d %s via %s", PATH_IPFW, acls->real_number, buf, iface->ifname); Freee(MB_IFACE, buf); acls = acls->next; }; }; /* Bring up system interface */ IfaceChangeFlags(b, (ready?IFF_LINK0:0), IFF_UP | (ready?0:IFF_LINK0)); /* Send any cached packets */ IfaceCacheSend(b); } /* * IfaceDown() * * Our packet transport mechanism is no longer ready for traffic. */ void IfaceDown(Bund b) { IfaceState const iface = &b->iface; struct acl_pool **rp, *rp1; char cb[32768]; struct acl *acl, *aclnext; Log(LG_IFACE, ("[%s] IFACE: Down event", b->name)); /* Bring down system interface */ IfaceChangeFlags(b, IFF_UP | IFF_LINK0, 0); TimerStop(&iface->idleTimer); TimerStop(&iface->sessionTimer); /* Remove rule ACLs */ rp = &rule_pool; cb[0]=0; while (*rp != NULL) { if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) { sprintf(cb+strlen(cb), " %d", (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(MB_IFACE, rp1); } else { rp = &((*rp)->next); }; }; if (cb[0]!=0) ExecCmdNosh(LG_IFACE2, b->name, "%s delete%s", PATH_IPFW, cb); /* Remove table ACLs */ rp = &table_pool; while (*rp != NULL) { if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) { rp1 = *rp; *rp = (*rp)->next; Freee(MB_IFACE, rp1); } else { rp = &((*rp)->next); }; }; acl = iface->tables; while (acl != NULL) { ExecCmd(LG_IFACE2, b->name, "%s table %d delete %s", PATH_IPFW, acl->real_number, acl->rule); aclnext = acl->next; Freee(MB_IFACE, acl); acl = aclnext; }; iface->tables = NULL; /* Remove queue ACLs */ rp = &queue_pool; cb[0]=0; while (*rp != NULL) { if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) { sprintf(cb+strlen(cb), " %d", (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(MB_IFACE, rp1); } else { rp = &((*rp)->next); }; }; if (cb[0]!=0) ExecCmdNosh(LG_IFACE2, b->name, "%s queue delete%s", PATH_IPFW, cb); /* Remove pipe ACLs */ rp = &pipe_pool; cb[0]=0; while (*rp != NULL) { if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) { sprintf(cb+strlen(cb), " %d", (*rp)->real_number); rp1 = *rp; *rp = (*rp)->next; Freee(MB_IFACE, rp1); } else { rp = &((*rp)->next); }; }; if (cb[0]!=0) ExecCmdNosh(LG_IFACE2, b->name, "%s pipe delete%s", PATH_IPFW, cb); } /* * IfaceListenInput() * * A packet was received on our demand snooping hook. Stimulate a connection. */ void IfaceListenInput(Bund b, int proto, Mbuf pkt) { IfaceState const iface = &b->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 = &b->ipcp.fsm; if (OPEN_STATE(fsm->state)) { if (b->bm.n_up > 0) { #ifndef USE_NG_TCPMSS if (Enabled(&iface->options, IFACE_CONF_TCPMSSFIX)) { if (proto == PROTO_IP) IfaceCorrectMSS(pkt, MAXMSS(iface->mtu)); } else Log(LG_IFACE, ("[%s] unexpected outgoing packet, len=%d", b->name, MBLEN(pkt))); #endif NgFuncWriteFrame(b, MPD_HOOK_DEMAND_TAP, pkt); } else { IfaceCachePkt(b, proto, pkt); } /* Maybe do dial-on-demand here */ } else if (iface->open && isDemand) { Log(LG_IFACE, ("[%s] outgoing packet is demand", b->name)); RecordLinkUpDownReason(b, NULL, 1, STR_DEMAND, NULL); BundOpenLinks(b); IfaceCachePkt(b, 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_IFACE, 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; *ap = rp; 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_IFACE, ACL_LEN+1); buf1 = Malloc(MB_IFACE, 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; case 't': ap = table_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(MB_IFACE, buf1); return(buf); }; /* * IfaceIpIfaceUp() * * Bring up the IP interface. The "ready" flag means that * IPCP is also up and we can deliver packets immediately. */ void IfaceIpIfaceUp(Bund b, int ready) { IfaceState const iface = &b->iface; struct sockaddr_dl hwa; char hisaddr[20]; IfaceRoute r; u_char *ether; if (ready) { in_addrtou_range(&b->ipcp.want_addr, 32, &iface->self_addr); in_addrtou_addr(&b->ipcp.peer_addr, &iface->peer_addr); } if (IfaceNgIpInit(b, ready)) { Log(LG_ERR, ("[%s] IfaceNgIpInit() error, closing IPCP", b->name)); FsmFailure(&b->ipcp.fsm, FAIL_NEGOT_FAILURE); return; }; /* Set addresses */ IfaceChangeAddr(b, 1, &iface->self_addr, &iface->peer_addr); /* Proxy ARP for peer if desired and peer's address is known */ u_addrclear(&iface->proxy_addr); if (Enabled(&iface->options, IFACE_CONF_PROXY)) { if (u_addrempty(&iface->peer_addr)) { Log(LG_IFACE, ("[%s] can't proxy arp for %s", b->name, u_addrtoa(&iface->peer_addr,hisaddr,sizeof(hisaddr)))); } else if (GetEther(&iface->peer_addr, &hwa) < 0) { Log(LG_IFACE, ("[%s] no interface to proxy arp on for %s", b->name, u_addrtoa(&iface->peer_addr,hisaddr,sizeof(hisaddr)))); } else { ether = (u_char *) LLADDR(&hwa); if (ExecCmdNosh(LG_IFACE2, b->name, "%s -S %s %x:%x:%x:%x:%x:%x pub", PATH_ARP, u_addrtoa(&iface->peer_addr,hisaddr,sizeof(hisaddr)), ether[0], ether[1], ether[2], ether[3], ether[4], ether[5]) == 0) iface->proxy_addr = iface->peer_addr; } } /* Add static routes */ SLIST_FOREACH(r, &iface->routes, next) { if (u_rangefamily(&r->dest)==AF_INET) { r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_addr) == 0); } } /* Add dynamic routes */ SLIST_FOREACH(r, &b->params.routes, next) { if (u_rangefamily(&r->dest)==AF_INET) { r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_addr) == 0); } } #ifdef USE_NG_NAT /* Set NAT IP */ if (iface->nat_up) { IfaceSetupNAT(b); } #endif /* Call "up" script */ if (*iface->up_script) { char selfbuf[40],peerbuf[40]; char ns1buf[21], ns2buf[21]; if(b->ipcp.want_dns[0].s_addr != 0) snprintf(ns1buf, sizeof(ns1buf), "dns1 %s", inet_ntoa(b->ipcp.want_dns[0])); else ns1buf[0] = '\0'; if(b->ipcp.want_dns[1].s_addr != 0) snprintf(ns2buf, sizeof(ns2buf), "dns2 %s", inet_ntoa(b->ipcp.want_dns[1])); else ns2buf[0] = '\0'; ExecCmd(LG_IFACE2, b->name, "%s %s inet %s %s '%s' %s %s", iface->up_script, iface->ifname, u_rangetoa(&iface->self_addr,selfbuf, sizeof(selfbuf)), u_addrtoa(&iface->peer_addr, peerbuf, sizeof(peerbuf)), *b->params.authname ? b->params.authname : "-", ns1buf, ns2buf); } } /* * IfaceIpIfaceDown() * * Bring down the IP interface. This implies we're no longer ready. */ void IfaceIpIfaceDown(Bund b) { IfaceState const iface = &b->iface; IfaceRoute r; char buf[64]; /* Call "down" script */ if (*iface->down_script) { ExecCmd(LG_IFACE2, b->name, "%s %s inet '%s'", iface->down_script, iface->ifname, *b->params.authname ? b->params.authname : "-"); } /* Delete dynamic routes */ SLIST_FOREACH(r, &b->params.routes, next) { if (u_rangefamily(&r->dest)==AF_INET) { if (!r->ok) continue; IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_addr); r->ok = 0; } } /* Delete static routes */ SLIST_FOREACH(r, &iface->routes, next) { if (u_rangefamily(&r->dest)==AF_INET) { if (!r->ok) continue; IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_addr); r->ok = 0; } } /* Delete any proxy arp entry */ if (!u_addrempty(&iface->proxy_addr)) ExecCmdNosh(LG_IFACE2, b->name, "%s -d %s", PATH_ARP, u_addrtoa(&iface->proxy_addr, buf, sizeof(buf))); u_addrclear(&iface->proxy_addr); /* Remove address from interface */ IfaceChangeAddr(b, 0, &iface->self_addr, &iface->peer_addr); IfaceNgIpShutdown(b); } /* * IfaceIpv6IfaceUp() * * Bring up the IPv6 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. */ void IfaceIpv6IfaceUp(Bund b, int ready) { IfaceState const iface = &b->iface; IfaceRoute r; struct u_range rng; if (ready) { iface->self_ipv6_addr.family = AF_INET6; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[0] = 0x80fe; /* Network byte order */ iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[1] = 0x0000; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[2] = 0x0000; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[3] = 0x0000; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[4] = ((u_short*)b->ipv6cp.myintid)[0]; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[5] = ((u_short*)b->ipv6cp.myintid)[1]; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[6] = ((u_short*)b->ipv6cp.myintid)[2]; iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[7] = ((u_short*)b->ipv6cp.myintid)[3]; iface->peer_ipv6_addr.family = AF_INET6; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[0] = 0x80fe; /* Network byte order */ iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[1] = 0x0000; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[2] = 0x0000; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[3] = 0x0000; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[4] = ((u_short*)b->ipv6cp.hisintid)[0]; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[5] = ((u_short*)b->ipv6cp.hisintid)[1]; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[6] = ((u_short*)b->ipv6cp.hisintid)[2]; iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[7] = ((u_short*)b->ipv6cp.hisintid)[3]; } if (IfaceNgIpv6Init(b, ready)) { Log(LG_ERR, ("[%s] IfaceNgIpv6Init() failed, closing IPv6CP", b->name)); FsmFailure(&b->ipv6cp.fsm, FAIL_NEGOT_FAILURE); return; }; /* Set addresses */ rng.addr = iface->self_ipv6_addr; rng.width = 64; IfaceChangeAddr(b, 1, &rng, &iface->peer_ipv6_addr); /* Add static routes */ SLIST_FOREACH(r, &iface->routes, next) { if (u_rangefamily(&r->dest)==AF_INET6) { r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_ipv6_addr) == 0); } } /* Add dynamic routes */ SLIST_FOREACH(r, &b->params.routes, next) { if (u_rangefamily(&r->dest)==AF_INET6) { r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_ipv6_addr) == 0); } } /* Call "up" script */ if (*iface->up_script) { char selfbuf[64],peerbuf[64]; ExecCmd(LG_IFACE2, b->name, "%s %s inet6 %s%%%s %s%%%s '%s'", iface->up_script, iface->ifname, u_addrtoa(&iface->self_ipv6_addr, selfbuf, sizeof(selfbuf)), iface->ifname, u_addrtoa(&iface->peer_ipv6_addr, peerbuf, sizeof(peerbuf)), iface->ifname, *b->params.authname ? b->params.authname : "-"); } } /* * IfaceIpv6IfaceDown() * * Bring down the IPv6 interface. This implies we're no longer ready. */ void IfaceIpv6IfaceDown(Bund b) { IfaceState const iface = &b->iface; IfaceRoute r; struct u_range rng; /* Call "down" script */ if (*iface->down_script) { ExecCmd(LG_IFACE2, b->name, "%s %s inet6 '%s'", iface->down_script, iface->ifname, *b->params.authname ? b->params.authname : "-"); } /* Delete dynamic routes */ SLIST_FOREACH(r, &b->params.routes, next) { if (u_rangefamily(&r->dest)==AF_INET6) { if (!r->ok) continue; IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_ipv6_addr); r->ok = 0; } } /* Delete static routes */ SLIST_FOREACH(r, &iface->routes, next) { if (u_rangefamily(&r->dest)==AF_INET6) { if (!r->ok) continue; IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_ipv6_addr); r->ok = 0; } } if (!u_addrempty(&iface->self_ipv6_addr)) { /* Remove address from interface */ rng.addr = iface->self_ipv6_addr; rng.width = 64; IfaceChangeAddr(b, 0, &rng, &iface->peer_ipv6_addr); } IfaceNgIpv6Shutdown(b); } /* * IfaceIdleTimeout() */ static void IfaceIdleTimeout(void *arg) { Bund b = (Bund)arg; IfaceState const iface = &b->iface; int k; /* Get updated bpf node traffic statistics */ BundUpdateStats(b); /* Mark current traffic period if there was traffic */ if (iface->idleStats.recvFrames + iface->idleStats.xmitFrames < b->stats.recvFrames + b->stats.xmitFrames) { 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) { Log(LG_BUND, ("[%s] idle timeout", b->name)); RecordLinkUpDownReason(b, NULL, 0, STR_IDLE_TIMEOUT, NULL); BundClose(b); return; } } iface->idleStats = b->stats; /* 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); } /* * IfaceSessionTimeout() */ static void IfaceSessionTimeout(void *arg) { Bund b = (Bund)arg; Log(LG_BUND, ("[%s] session timeout ", b->name)); RecordLinkUpDownReason(b, NULL, 0, STR_SESSION_TIMEOUT, NULL); BundClose(b); } /* * IfaceCachePkt() * * A packet caused dial-on-demand; save it for later if possible. * Consumes the mbuf in any case. */ static void IfaceCachePkt(Bund b, int proto, Mbuf pkt) { IfaceState const iface = &b->iface; Mbuf new; int len; /* 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); /* Make an own permanent pkt copy */ new = mballoc(pkt->type, len = plength(pkt)); assert(mbread(pkt, MBDATA(new), len, NULL) == NULL); iface->dodCache.pkt = new; iface->dodCache.proto = proto; iface->dodCache.ts = time(NULL); } /* * IfaceCacheSend() * * Send cached packet */ static void IfaceCacheSend(Bund b) { IfaceState const iface = &b->iface; if (iface->dodCache.pkt) { if (iface->dodCache.ts + MAX_DOD_CACHE_DELAY < time(NULL)) PFREE(iface->dodCache.pkt); else { if (NgFuncWritePppFrame(b, NG_PPP_BUNDLE_LINKNUM, iface->dodCache.proto, iface->dodCache.pkt) < 0) { Log(LG_ERR, ("[%s] can't write cached pkt: %s", b->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: { u_char buf[256]; struct ip *const ip = (struct ip *)(&buf); mbcopy(pkt, buf, sizeof(buf)); 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(Context ctx, int ac, char *av[], void *arg) { IfaceState const iface = &ctx->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 u_range self_addr; struct u_addr peer_addr; /* Parse */ if (ac != 2) return(-1); if (!ParseRange(av[0], &self_addr, ALLOW_IPV4)) { Log(LG_ERR, ("[%s] IFACE: Bad IP address \"%s\"", ctx->bund->name, av[0])); return(-1); } if (!ParseAddr(av[1], &peer_addr, ALLOW_IPV4)) { Log(LG_ERR, ("[%s] IFACE: Bad IP address \"%s\"", ctx->bund->name, av[1])); return(-1); } /* OK */ iface->self_addr = self_addr; iface->peer_addr = peer_addr; } break; case SET_ROUTE: { struct u_range range; IfaceRoute r; /* Check */ if (ac != 1) return(-1); /* Get dest address */ if (!strcasecmp(av[0], "default")) { u_rangeclear(&range); range.addr.family=AF_INET; } else if (!ParseRange(av[0], &range, ALLOW_IPV4|ALLOW_IPV6)) { Log(LG_ERR, ("[%s] IFACE: Bad route dest address \"%s\"", ctx->bund->name, av[0])); return(-1); } r = Malloc(MB_IFACE, sizeof(struct ifaceroute)); r->dest = range; r->ok = 0; SLIST_INSERT_HEAD(&iface->routes, r, next); } 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, ("[%s] IFACE: Invalid interface mtu %d", ctx->bund->name, max_mtu)); return(-1); } 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(Context ctx, int ac, char *av[], void *arg) { IfaceState const iface = &ctx->bund->iface; IfaceRoute r; int k; char buf[64]; Printf("Interface configuration:\r\n"); Printf("\tName : %s\r\n", iface->ifname); Printf("\tMaximum MTU : %d bytes\r\n", iface->max_mtu); Printf("\tIdle timeout : %d seconds\r\n", iface->idle_timeout); Printf("\tSession timeout : %d seconds\r\n", iface->session_timeout); Printf("\tEvent scripts\r\n"); Printf("\t up-script : \"%s\"\r\n", *iface->up_script ? iface->up_script : ""); Printf("\t down-script : \"%s\"\r\n", *iface->down_script ? iface->down_script : ""); Printf("Interface options:\r\n"); OptStat(ctx, &iface->options, gConfList); if (!SLIST_EMPTY(&iface->routes)) { Printf("Static routes via peer:\r\n"); SLIST_FOREACH(r, &iface->routes, next) { Printf("\t%s\r\n", u_rangetoa(&r->dest,buf,sizeof(buf))); } } Printf("Interface status:\r\n"); Printf("\tAdmin status : %s\r\n", iface->open ? "OPEN" : "CLOSED"); Printf("\tStatus : %s\r\n", iface->up ? "UP" : "DOWN"); if (iface->up) Printf("\tMTU : %d bytes\r\n", iface->mtu); if (iface->ip_up && !u_rangeempty(&iface->self_addr)) { Printf("\tIP Addresses : %s -> ", u_rangetoa(&iface->self_addr,buf,sizeof(buf))); Printf("%s\r\n", u_addrtoa(&iface->peer_addr,buf,sizeof(buf))); } if (iface->ipv6_up && !u_addrempty(&iface->self_ipv6_addr)) { Printf("\tIPv6 Addresses : %s%%%s -> ", u_addrtoa(&iface->self_ipv6_addr,buf,sizeof(buf)), iface->ifname); Printf("%s%%%s\r\n", u_addrtoa(&iface->peer_ipv6_addr,buf,sizeof(buf)), iface->ifname); } if (iface->up && !SLIST_EMPTY(&ctx->bund->params.routes)) { Printf("Dynamic routes via peer:\r\n"); SLIST_FOREACH(r, &ctx->bund->params.routes, next) { Printf("\t%s\r\n", u_rangetoa(&r->dest,buf,sizeof(buf))); } } if (iface->up && (ctx->bund->params.acl_limits[0] || ctx->bund->params.acl_limits[1])) { struct acl *a; Printf("Traffic filters:\r\n"); for (k = 0; k < ACL_FILTERS; k++) { a = ctx->bund->params.acl_filters[k]; while (a) { Printf("\t%d#%d\t: '%s'\r\n", (k + 1), a->number, a->rule); a = a->next; } } Printf("Traffic limits:\r\n"); for (k = 0; k < 2; k++) { a = ctx->bund->params.acl_limits[k]; while (a) { Printf("\t%s#%d\t: '%s'\r\n", (k?"out":"in"), a->number, a->rule); a = a->next; } } } return(0); } /* * IfaceSetMTU() * * Set MTU and bandwidth on bundle's interface */ void IfaceSetMTU(Bund b, int mtu) { IfaceState const iface = &b->iface; struct ifreq ifr; int s; /* Get socket */ if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Perror("[%s] IFACE: Can't get socket to set MTU!", b->name); return; } if ((b->params.mtu > 0) && (mtu > b->params.mtu)) { mtu = b->params.mtu; Log(LG_IFACE2, ("[%s] IFACE: forcing MTU of auth backend: %d bytes", b->name, 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, b->iface.ifname, sizeof(ifr.ifr_name)); ifr.ifr_mtu = mtu; Log(LG_IFACE2, ("[%s] IFACE: setting %s MTU to %d bytes", b->name, b->iface.ifname, mtu)); if (ioctl(s, SIOCSIFMTU, (char *)&ifr) < 0) Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCSIFMTU"); close(s); /* Save MTU */ iface->mtu = mtu; } void IfaceChangeFlags(Bund b, int clear, int set) { struct ifreq ifrq; int s, new_flags; Log(LG_IFACE2, ("[%s] IFACE: Change interface flags: -%d +%d", b->name, clear, set)); if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Perror("[%s] IFACE: Can't get socket to change interface flags!", b->name); return; } memset(&ifrq, '\0', sizeof(ifrq)); strncpy(ifrq.ifr_name, b->iface.ifname, sizeof(ifrq.ifr_name) - 1); ifrq.ifr_name[sizeof(ifrq.ifr_name) - 1] = '\0'; if (ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) { Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCGIFFLAGS"); close(s); return; } new_flags = (ifrq.ifr_flags & 0xffff) | (ifrq.ifr_flagshigh << 16); new_flags &= ~clear; new_flags |= set; ifrq.ifr_flags = new_flags & 0xffff; ifrq.ifr_flagshigh = new_flags >> 16; if (ioctl(s, SIOCSIFFLAGS, &ifrq) < 0) { Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCSIFFLAGS"); close(s); return; } close(s); } #if defined(__KAME__) && !defined(NOINET6) static void add_scope(struct sockaddr *sa, int ifindex) { struct sockaddr_in6 *sa6; if (sa->sa_family != AF_INET6) return; sa6 = (struct sockaddr_in6 *)sa; if (!IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) && !IN6_IS_ADDR_MC_LINKLOCAL(&sa6->sin6_addr)) return; if (*(u_int16_t *)&sa6->sin6_addr.s6_addr[2] != 0) return; *(u_int16_t *)&sa6->sin6_addr.s6_addr[2] = htons(ifindex); } #endif void IfaceChangeAddr(Bund b, int add, struct u_range *self, struct u_addr *peer) { struct ifaliasreq ifra; struct in6_aliasreq ifra6; struct sockaddr_in *me4, *msk4, *peer4; struct sockaddr_storage ssself, sspeer, ssmsk; int res = 0; int s; char buf[64], buf1[64]; Log(LG_IFACE2, ("[%s] IFACE: %s address %s->%s %s %s", b->name, add?"Add":"Remove", u_rangetoa(self, buf, sizeof(buf)), ((peer != NULL)?u_addrtoa(peer, buf1, sizeof(buf1)):""), add?"to":"from", b->iface.ifname)); u_rangetosockaddrs(self, &ssself, &ssmsk); if (peer) u_addrtosockaddr(peer, 0, &sspeer); if ((s = socket(self->addr.family, SOCK_DGRAM, 0)) < 0) { Perror("[%s] IFACE: Can't get socket to change interface address!", b->name); return; } switch (self->addr.family) { case AF_INET: memset(&ifra, '\0', sizeof(ifra)); strncpy(ifra.ifra_name, b->iface.ifname, sizeof(ifra.ifra_name) - 1); me4 = (struct sockaddr_in *)&ifra.ifra_addr; memcpy(me4, &ssself, sizeof(*me4)); msk4 = (struct sockaddr_in *)&ifra.ifra_mask; memcpy(msk4, &ssmsk, sizeof(*msk4)); peer4 = (struct sockaddr_in *)&ifra.ifra_broadaddr; if (peer == NULL || peer->family == AF_UNSPEC) { peer4->sin_family = AF_INET; peer4->sin_len = sizeof(*peer4); peer4->sin_addr.s_addr = INADDR_NONE; } else memcpy(peer4, &sspeer, sizeof(*peer4)); res = ioctl(s, add?SIOCAIFADDR:SIOCDIFADDR, &ifra); if (res == -1) { Perror("[%s] IFACE: %s IPv4 address %s %s failed", b->name, add?"Adding":"Removing", add?"to":"from", b->iface.ifname); } break; case AF_INET6: memset(&ifra6, '\0', sizeof(ifra6)); strncpy(ifra6.ifra_name, b->iface.ifname, sizeof(ifra6.ifra_name) - 1); memcpy(&ifra6.ifra_addr, &ssself, sizeof(ifra6.ifra_addr)); memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof(ifra6.ifra_prefixmask)); if (peer == NULL || peer->family == AF_UNSPEC) ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC; else if (memcmp(&((struct sockaddr_in6 *)&ssmsk)->sin6_addr, &in6mask128, sizeof(in6mask128)) == 0) memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof(ifra6.ifra_dstaddr)); ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; res = ioctl(s, add?SIOCAIFADDR_IN6:SIOCDIFADDR_IN6, &ifra6); if (res == -1) { Perror("[%s] IFACE: %s IPv6 address %s %s failed", b->name, add?"Adding":"Removing", add?"to":"from", b->iface.ifname); } break; } close(s); } struct rtmsg { struct rt_msghdr m_rtm; char m_space[256]; }; static size_t memcpy_roundup(char *cp, const void *data, size_t len) { size_t padlen; #define ROUNDUP(x) ((x) ? (1 + (((x) - 1) | (sizeof(long) - 1))) : sizeof(long)) padlen = ROUNDUP(len); memcpy(cp, data, len); if (padlen > len) memset(cp + len, '\0', padlen - len); return padlen; } int IfaceSetRoute(Bund b, int cmd, struct u_range *dst, struct u_addr *gw) { struct rtmsg rtmes; int s, nb, wb; char *cp; const char *cmdstr = (cmd == RTM_ADD ? "Add" : "Delete"); struct sockaddr_storage sadst, samask, sagw; char buf[64], buf1[64]; s = socket(PF_ROUTE, SOCK_RAW, 0); if (s < 0) { Perror("[%s] IFACE: Can't get route socket!", b->name); return (-1); } memset(&rtmes, '\0', sizeof(rtmes)); rtmes.m_rtm.rtm_version = RTM_VERSION; rtmes.m_rtm.rtm_type = cmd; rtmes.m_rtm.rtm_addrs = RTA_DST; rtmes.m_rtm.rtm_seq = ++gRouteSeq; rtmes.m_rtm.rtm_pid = gPid; rtmes.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC; u_rangetosockaddrs(dst, &sadst, &samask); #if defined(__KAME__) && !defined(NOINET6) add_scope((struct sockaddr *)&sadst, b->iface.ifindex); #endif cp = rtmes.m_space; cp += memcpy_roundup(cp, &sadst, sadst.ss_len); if (gw != NULL) { u_addrtosockaddr(gw, 0, &sagw); #if defined(__KAME__) && !defined(NOINET6) add_scope((struct sockaddr *)&sagw, b->iface.ifindex); #endif cp += memcpy_roundup(cp, &sagw, sagw.ss_len); rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY; } else if (cmd == RTM_ADD) { Log(LG_ERR, ("[%s] IfaceSetRoute: gw is not set\n", b->name)); close(s); return (-1); } if (!u_rangehost(dst)) { cp += memcpy_roundup(cp, &samask, samask.ss_len); rtmes.m_rtm.rtm_addrs |= RTA_NETMASK; } nb = cp - (char *)&rtmes; rtmes.m_rtm.rtm_msglen = nb; wb = write(s, &rtmes, nb); if (wb < 0) { Log(LG_ERR, ("[%s] IFACE: %s route %s %s failed: %s", b->name, cmdstr, u_rangetoa(dst, buf, sizeof(buf)), ((gw != NULL)?u_addrtoa(gw, buf1, sizeof(buf1)):""), (rtmes.m_rtm.rtm_errno != 0)?strerror(rtmes.m_rtm.rtm_errno):strerror(errno))); close(s); return (-1); } close(s); Log(LG_IFACE2, ("[%s] IFACE: %s route %s %s", b->name, cmdstr, u_rangetoa(dst, buf, sizeof(buf)), ((gw != NULL)?u_addrtoa(gw, buf1, sizeof(buf1)):""))); return (0); } #ifndef USE_NG_TCPMSS void IfaceCorrectMSS(Mbuf pkt, uint16_t maxmss) { struct ip *iphdr; struct tcphdr *tc; int pktlen, hlen, olen, optlen, accumulate; uint16_t *mss; u_char *opt; if (pkt == NULL) return; iphdr = (struct ip *)MBDATAU(pkt); hlen = iphdr->ip_hl << 2; pktlen = plength(pkt) - hlen; tc = (struct tcphdr *)(MBDATAU(pkt) + hlen); 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) { accumulate = *mss; *mss = htons(maxmss); accumulate -= *mss; ADJUST_CHECKSUM(accumulate, tc->th_sum); } } } } } #endif static int IfaceNgIpInit(Bund b, int ready) { struct ngm_connect cn; char path[NG_PATHLEN + 1]; char hook[NG_HOOKLEN + 1]; if (!ready) { /* Dial-on-Demand mode */ /* Use demand hook of the socket node */ snprintf(path, sizeof(path), ".:"); strcpy(hook, MPD_HOOK_DEMAND_TAP); } else { snprintf(path, sizeof(path), "%s", MPD_HOOK_PPP); strcpy(hook, NG_PPP_HOOK_INET); #ifdef USE_NG_NAT /* Add a nat node if configured */ if (Enabled(&b->iface.options, IFACE_CONF_NAT)) { if (IfaceInitNAT(b, path, hook)) goto fail; b->iface.nat_up = 1; } #endif /* Add a tee node if configured */ if (Enabled(&b->iface.options, IFACE_CONF_TEE)) { if (IfaceInitTee(b, path, hook)) goto fail; b->iface.tee_up = 1; } #ifdef USE_NG_IPACCT /* Connect a ipacct node if configured */ if (Enabled(&b->iface.options, IFACE_CONF_IPACCT)) { if (IfaceInitIpacct(b, path, hook)) goto fail; b->iface.ipacct_up = 1; } #endif /* USE_NG_IPACCT */ #ifdef USE_NG_NETFLOW /* Connect a netflow node if configured */ if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN)) { if (IfaceInitNetflow(b, path, hook, 0)) goto fail; b->iface.nfin_up = 1; } if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)) { if (IfaceInitNetflow(b, path, hook, 1)) goto fail; b->iface.nfout_up = 1; } #endif /* USE_NG_NETFLOW */ } if (Enabled(&b->iface.options, IFACE_CONF_TCPMSSFIX)) { if (IfaceInitMSS(b, path, hook)) goto fail; b->iface.mss_up = 1; } if (IfaceInitLimits(b, path, hook)) goto fail; /* Connect graph to the iface node. */ strcpy(cn.ourhook, hook); snprintf(cn.path, sizeof(cn.path), "%s:", b->iface.ifname); snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_IFACE_HOOK_INET); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); goto fail; } if (ready) { #ifdef USE_NG_NETFLOW if (b->iface.nfin_up) IfaceSetupNetflow(b, 0); if (b->iface.nfout_up) IfaceSetupNetflow(b, 1); #endif /* USE_NG_NETFLOW */ if (b->iface.mss_up) IfaceSetupMSS(b, MAXMSS(b->iface.mtu)); } IfaceSetupLimits(b); /* OK */ return(0); fail: return(-1); } /* * IfaceNgIpShutdown() */ static void IfaceNgIpShutdown(Bund b) { char path[NG_PATHLEN + 1]; #ifdef USE_NG_NAT if (b->iface.nat_up) IfaceShutdownNAT(b); b->iface.nat_up = 0; #endif if (b->iface.tee_up) IfaceShutdownTee(b); b->iface.tee_up = 0; #ifdef USE_NG_NETFLOW if (b->iface.nfin_up) IfaceShutdownNetflow(b, 0); b->iface.nfin_up = 0; if (b->iface.nfout_up) IfaceShutdownNetflow(b, 1); b->iface.nfout_up = 0; #endif #ifdef USE_NG_IPACCT if (b->iface.ipacct_up) IfaceShutdownIpacct(b); b->iface.ipacct_up = 0; #endif if (b->iface.mss_up) IfaceShutdownMSS(b); b->iface.mss_up = 0; IfaceShutdownLimits(b); NgFuncDisconnect(b->csock, b->name, MPD_HOOK_PPP, NG_PPP_HOOK_INET); snprintf(path, sizeof(path), "%s:", b->iface.ifname); NgFuncDisconnect(b->csock, b->name, path, NG_IFACE_HOOK_INET); } static int IfaceNgIpv6Init(Bund b, int ready) { struct ngm_connect cn; char path[NG_PATHLEN + 1]; if (!ready) { } else { /* Connect ipv6 hook of ng_ppp(4) node to the ng_iface(4) node. */ snprintf(path, sizeof(path), "%s", MPD_HOOK_PPP); snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", NG_PPP_HOOK_IPV6); snprintf(cn.path, sizeof(cn.path), "%s:", b->iface.ifname); snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_IFACE_HOOK_INET6); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); goto fail; } } /* OK */ return(0); fail: return(-1); } /* * IfaceNgIpv6Shutdown() */ static void IfaceNgIpv6Shutdown(Bund b) { char path[NG_PATHLEN + 1]; NgFuncDisconnect(b->csock, b->name, MPD_HOOK_PPP, NG_PPP_HOOK_IPV6); snprintf(path, sizeof(path), "%s:", b->iface.ifname); NgFuncDisconnect(b->csock, b->name, path, NG_IFACE_HOOK_INET); } #ifdef USE_NG_NAT static int IfaceInitNAT(Bund b, char *path, char *hook) { NatState const nat = &b->iface.nat; struct ngm_mkpeer mp; struct ngm_name nm; struct in_addr ip; #ifdef NG_NAT_LOG struct ng_nat_mode mode; #endif Log(LG_IFACE2, ("[%s] IFACE: Connecting NAT", b->name)); snprintf(mp.type, sizeof(mp.type), "%s", NG_NAT_NODE_TYPE); strcpy(mp.ourhook, hook); strcpy(mp.peerhook, NG_NAT_HOOK_IN); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node at \"%s\"->\"%s\": %s", b->name, NG_NAT_NODE_TYPE, path, mp.ourhook, strerror(errno))); return(-1); } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-nat", gPid, b->name); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("[%s] can't name %s node: %s", b->name, NG_NAT_NODE_TYPE, strerror(errno))); return(-1); } strcpy(hook, NG_NAT_HOOK_OUT); /* Set NAT IP */ if (u_addrempty(&nat->alias_addr)) { ip.s_addr = 1; // Set something just to make it ready } else { ip = nat->alias_addr.u.ip4; } if (NgSendMsg(b->csock, path, NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, &ip, sizeof(ip)) < 0) { Log(LG_ERR, ("[%s] can't set NAT ip: %s", b->name, strerror(errno))); } #ifdef NG_NAT_LOG /* Set NAT mode */ mode.flags = 0; if (Enabled(&nat->options, NAT_CONF_LOG)) mode.flags |= NG_NAT_LOG; if (!Enabled(&nat->options, NAT_CONF_INCOMING)) mode.flags |= NG_NAT_DENY_INCOMING; if (Enabled(&nat->options, NAT_CONF_SAME_PORTS)) mode.flags |= NG_NAT_SAME_PORTS; if (Enabled(&nat->options, NAT_CONF_UNREG_ONLY)) mode.flags |= NG_NAT_UNREGISTERED_ONLY; mode.mask = NG_NAT_LOG | NG_NAT_DENY_INCOMING | NG_NAT_SAME_PORTS | NG_NAT_UNREGISTERED_ONLY; if (NgSendMsg(b->csock, path, NGM_NAT_COOKIE, NGM_NAT_SET_MODE, &mode, sizeof(mode)) < 0) { Log(LG_ERR, ("[%s] can't set NAT mode: %s", b->name, strerror(errno))); } /* Set NAT target IP */ if (!u_addrempty(&nat->target_addr)) { ip = nat->target_addr.u.ip4; if (NgSendMsg(b->csock, path, NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, &ip, sizeof(ip)) < 0) { Log(LG_ERR, ("[%s] can't set NAT target IP: %s", b->name, strerror(errno))); } } #endif return(0); } static int IfaceSetupNAT(Bund b) { NatState const nat = &b->iface.nat; char path[NG_PATHLEN+1]; if (u_addrempty(&nat->alias_addr)) { snprintf(path, sizeof(path), "mpd%d-%s-nat:", gPid, b->name); if (NgSendMsg(b->csock, path, NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, &b->iface.self_addr.addr.u.ip4, sizeof(b->iface.self_addr.addr.u.ip4)) < 0) { Log(LG_ERR, ("[%s] can't set NAT ip: %s", b->name, strerror(errno))); return (-1); } } return (0); } static void IfaceShutdownNAT(Bund b) { char path[NG_PATHLEN+1]; snprintf(path, sizeof(path), "mpd%d-%s-nat:", gPid, b->name); NgFuncShutdownNode(b->csock, b->name, path); } #endif static int IfaceInitTee(Bund b, char *path, char *hook) { struct ngm_mkpeer mp; struct ngm_name nm; Log(LG_IFACE2, ("[%s] IFACE: Connecting tee", b->name)); snprintf(mp.type, sizeof(mp.type), "%s", NG_TEE_NODE_TYPE); strcpy(mp.ourhook, hook); strcpy(mp.peerhook, NG_TEE_HOOK_RIGHT); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node at \"%s\"->\"%s\": %s", b->name, NG_TEE_NODE_TYPE, path, mp.ourhook, strerror(errno))); return(-1); } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); snprintf(nm.name, sizeof(nm.name), "%s-tee", b->iface.ifname); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("[%s] can't name %s node: %s", b->name, NG_TEE_NODE_TYPE, strerror(errno))); return(-1); } strcpy(hook, NG_TEE_HOOK_LEFT); return(0); } static void IfaceShutdownTee(Bund b) { char path[NG_PATHLEN+1]; snprintf(path, sizeof(path), "%s-tee:", b->iface.ifname); NgFuncShutdownNode(b->csock, b->name, path); } #ifdef USE_NG_IPACCT static int IfaceInitIpacct(Bund b, char *path, char *hook) { struct ngm_mkpeer mp; struct ngm_name nm; struct ngm_connect cn; char path1[NG_PATHLEN+1]; struct { struct ng_ipacct_mesg m; int data; } ipam; Log(LG_IFACE2, ("[%s] IFACE: Connecting ipacct", b->name)); snprintf(mp.type, sizeof(mp.type), "%s", NG_TEE_NODE_TYPE); strcpy(mp.ourhook, hook); strcpy(mp.peerhook, NG_TEE_HOOK_RIGHT); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node at \"%s\"->\"%s\": %s", b->name, NG_TEE_NODE_TYPE, path, mp.ourhook, strerror(errno))); return(-1); } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); snprintf(nm.name, sizeof(nm.name), "%s_acct_tee", b->iface.ifname); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("[%s] can't name %s node: %s", b->name, NG_TEE_NODE_TYPE, strerror(errno))); return(-1); } strcpy(hook, NG_TEE_HOOK_LEFT); snprintf(mp.type, sizeof(mp.type), "%s", NG_IPACCT_NODE_TYPE); strcpy(mp.ourhook, NG_TEE_HOOK_RIGHT2LEFT); snprintf(mp.peerhook, sizeof(mp.peerhook), "%s_in", b->iface.ifname); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node at \"%s\"->\"%s\": %s", b->name, NG_IPACCT_NODE_TYPE, path, mp.ourhook, strerror(errno))); return(-1); } snprintf(path1, sizeof(path1), "%s.%s", path, NG_TEE_HOOK_RIGHT2LEFT); snprintf(nm.name, sizeof(nm.name), "%s_ip_acct", b->iface.ifname); if (NgSendMsg(b->csock, path1, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("[%s] can't name %s node: %s", b->name, NG_IPACCT_NODE_TYPE, strerror(errno))); return(-1); } strcpy(cn.ourhook, NG_TEE_HOOK_LEFT2RIGHT); strcpy(cn.path, NG_TEE_HOOK_RIGHT2LEFT); snprintf(cn.peerhook, sizeof(cn.peerhook), "%s_out", b->iface.ifname); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); return (-1); } snprintf(ipam.m.hname, sizeof(ipam.m.hname), "%s_in", b->iface.ifname); ipam.data = DLT_RAW; if (NgSendMsg(b->csock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_SETDLT, &ipam, sizeof(ipam)) < 0) { Log(LG_ERR, ("[%s] can't set DLT \"%s\"->\"%s\": %s", b->name, path, ipam.m.hname, strerror(errno))); return (-1); } ipam.data = 10000; if (NgSendMsg(b->csock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_STHRS, &ipam, sizeof(ipam)) < 0) { Log(LG_ERR, ("[%s] can't set DLT \"%s\"->\"%s\": %s", b->name, path, ipam.m.hname, strerror(errno))); return (-1); } snprintf(ipam.m.hname, sizeof(ipam.m.hname), "%s_out", b->iface.ifname); ipam.data = DLT_RAW; if (NgSendMsg(b->csock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_SETDLT, &ipam, sizeof(ipam)) < 0) { Log(LG_ERR, ("[%s] can't set DLT \"%s\"->\"%s\": %s", b->name, path, ipam.m.hname, strerror(errno))); return (-1); } ipam.data = 10000; if (NgSendMsg(b->csock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_STHRS, &ipam, sizeof(ipam)) < 0) { Log(LG_ERR, ("[%s] can't set DLT \"%s\"->\"%s\": %s", b->name, path, ipam.m.hname, strerror(errno))); return (-1); } return(0); } static void IfaceShutdownIpacct(Bund b) { char path[NG_PATHLEN+1]; snprintf(path, sizeof(path), "%s_acct_tee:", b->iface.ifname); NgFuncShutdownNode(b->csock, b->name, path); } #endif #ifdef USE_NG_NETFLOW static int IfaceInitNetflow(Bund b, char *path, char *hook, char out) { struct ngm_connect cn; Log(LG_IFACE2, ("[%s] IFACE: Connecting netflow (%s)", b->name, out?"out":"in")); /* Create global ng_netflow(4) node if not yet. */ if (gNetflowNode == FALSE) { if (NgFuncInitGlobalNetflow(b)) return(-1); } /* Connect ng_netflow(4) node to the ng_bpf(4)/ng_tee(4) node. */ strcpy(cn.ourhook, hook); snprintf(cn.path, sizeof(cn.path), "%s:", gNetflowNodeName); if (out) { snprintf(cn.peerhook, sizeof(cn.peerhook), "%s%d", NG_NETFLOW_HOOK_OUT, gNetflowIface + b->id*2 + out); } else { snprintf(cn.peerhook, sizeof(cn.peerhook), "%s%d", NG_NETFLOW_HOOK_DATA, gNetflowIface + b->id*2 + out); } if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); return (-1); } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); if (out) { snprintf(hook, NG_HOOKLEN, "%s%d", NG_NETFLOW_HOOK_DATA, gNetflowIface + b->id*2 + out); } else { snprintf(hook, NG_HOOKLEN, "%s%d", NG_NETFLOW_HOOK_OUT, gNetflowIface + b->id*2 + out); } return (0); } static int IfaceSetupNetflow(Bund b, char out) { char path[NG_PATHLEN + 1]; struct ng_netflow_setdlt nf_setdlt; struct ng_netflow_setifindex nf_setidx; /* Configure data link type and interface index. */ snprintf(path, sizeof(path), "%s:", gNetflowNodeName); nf_setdlt.iface = gNetflowIface + b->id*2 + out; nf_setdlt.dlt = DLT_RAW; if (NgSendMsg(b->csock, path, NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETDLT, &nf_setdlt, sizeof(nf_setdlt)) < 0) { Log(LG_ERR, ("[%s] can't configure data link type on %s: %s", b->name, path, strerror(errno))); goto fail; } if (!out) { nf_setidx.iface = gNetflowIface + b->id*2 + out; nf_setidx.index = if_nametoindex(b->iface.ifname); if (NgSendMsg(b->csock, path, NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETIFINDEX, &nf_setidx, sizeof(nf_setidx)) < 0) { Log(LG_ERR, ("[%s] can't configure interface index on %s: %s", b->name, path, strerror(errno))); goto fail; } } return 0; fail: return -1; } static void IfaceShutdownNetflow(Bund b, char out) { char path[NG_PATHLEN+1]; char hook[NG_HOOKLEN+1]; snprintf(path, NG_PATHLEN, "%s:", gNetflowNodeName); snprintf(hook, NG_HOOKLEN, "%s%d", NG_NETFLOW_HOOK_DATA, gNetflowIface + b->id*2 + out); NgFuncDisconnect(b->csock, b->name, path, hook); snprintf(hook, NG_HOOKLEN, "%s%d", NG_NETFLOW_HOOK_OUT, gNetflowIface + b->id*2 + out); NgFuncDisconnect(b->csock, b->name, path, hook); } #endif static int IfaceInitMSS(Bund b, char *path, char *hook) { struct ngm_mkpeer mp; #if NG_NODESIZ>=32 struct ngm_name nm; #endif #ifndef USE_NG_TCPMSS struct ngm_connect cn; #endif Log(LG_IFACE2, ("[%s] IFACE: Connecting tcpmssfix", b->name)); #ifdef USE_NG_TCPMSS /* Create ng_tcpmss(4) node. */ snprintf(mp.type, sizeof(mp.type), "%s", NG_TCPMSS_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", hook); snprintf(mp.peerhook, sizeof(mp.peerhook), "in"); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("can't create %s node at \"%s\"->\"%s\": %s", NG_TCPMSS_NODE_TYPE, path, mp.ourhook, strerror(errno))); goto fail; } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); snprintf(hook, NG_HOOKLEN, "out"); /* Set the new node's name. */ snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-mss", gPid, b->name); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("can't name %s node: %s", NG_TCPMSS_NODE_TYPE, strerror(errno))); goto fail; } #else /* Create a bpf node for SYN detection. */ snprintf(mp.type, sizeof(mp.type), "%s", NG_BPF_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", hook); snprintf(mp.peerhook, sizeof(mp.peerhook), "ppp"); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("can't create %s node at \"%s\"->\"%s\": %s", NG_BPF_NODE_TYPE, path, mp.ourhook, strerror(errno))); goto fail; } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); strcpy(hook, "iface"); #if NG_NODESIZ>=32 /* Set the new node's name. */ snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-mss", gPid, b->name); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("can't name tcpmssfix %s node: %s", NG_BPF_NODE_TYPE, strerror(errno))); goto fail; } #endif /* Connect to the bundle socket node. */ snprintf(cn.path, sizeof(cn.path), "%s", path); snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", MPD_HOOK_TCPMSS_IN); snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", MPD_HOOK_TCPMSS_IN); if (NgSendMsg(b->csock, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); goto fail; } snprintf(cn.path, sizeof(cn.path), "%s", path); snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", MPD_HOOK_TCPMSS_OUT); snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", MPD_HOOK_TCPMSS_OUT); if (NgSendMsg(b->csock, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); goto fail; } #endif return (0); fail: return (-1); } /* * BundConfigMSS() * * Configure the tcpmss node to reduce MSS to given value. */ static void IfaceSetupMSS(Bund b, uint16_t maxMSS) { #ifdef USE_NG_TCPMSS struct ng_tcpmss_config tcpmsscfg; char path[NG_PATHLEN]; snprintf(path, sizeof(path), "mpd%d-%s-mss:", gPid, b->name); /* Send configure message. */ memset(&tcpmsscfg, 0, sizeof(tcpmsscfg)); tcpmsscfg.maxMSS = maxMSS; snprintf(tcpmsscfg.inHook, sizeof(tcpmsscfg.inHook), "in"); snprintf(tcpmsscfg.outHook, sizeof(tcpmsscfg.outHook), "out"); if (NgSendMsg(b->csock, path, NGM_TCPMSS_COOKIE, NGM_TCPMSS_CONFIG, &tcpmsscfg, sizeof(tcpmsscfg)) < 0) { Log(LG_ERR, ("[%s] can't configure %s node program: %s", b->name, NG_TCPMSS_NODE_TYPE, strerror(errno))); } snprintf(tcpmsscfg.inHook, sizeof(tcpmsscfg.inHook), "out"); snprintf(tcpmsscfg.outHook, sizeof(tcpmsscfg.outHook), "in"); if (NgSendMsg(b->csock, path, NGM_TCPMSS_COOKIE, NGM_TCPMSS_CONFIG, &tcpmsscfg, sizeof(tcpmsscfg)) < 0) { Log(LG_ERR, ("[%s] can't configure %s node program: %s", b->name, NG_TCPMSS_NODE_TYPE, strerror(errno))); } #else union { u_char buf[NG_BPF_HOOKPROG_SIZE(TCPSYN_PROG_LEN)]; struct ng_bpf_hookprog hprog; } u; struct ng_bpf_hookprog *const hp = &u.hprog; /* Setup programs for ng_bpf hooks */ memset(&u, 0, sizeof(u)); strcpy(hp->thisHook, "ppp"); hp->bpf_prog_len = TCPSYN_PROG_LEN; memcpy(&hp->bpf_prog, &gTCPSYNProg, TCPSYN_PROG_LEN * sizeof(*gTCPSYNProg)); strcpy(hp->ifMatch, MPD_HOOK_TCPMSS_IN); strcpy(hp->ifNotMatch, "iface"); if (NgSendMsg(b->csock, MPD_HOOK_TCPMSS_IN, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } memset(&u, 0, sizeof(u)); strcpy(hp->thisHook, "iface"); hp->bpf_prog_len = TCPSYN_PROG_LEN; memcpy(&hp->bpf_prog, &gTCPSYNProg, TCPSYN_PROG_LEN * sizeof(*gTCPSYNProg)); strcpy(hp->ifMatch, MPD_HOOK_TCPMSS_OUT); strcpy(hp->ifNotMatch, "ppp"); if (NgSendMsg(b->csock, MPD_HOOK_TCPMSS_OUT, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } memset(&u, 0, sizeof(u)); strcpy(hp->thisHook, MPD_HOOK_TCPMSS_IN); hp->bpf_prog_len = NOMATCH_PROG_LEN; memcpy(&hp->bpf_prog, &gNoMatchProg, NOMATCH_PROG_LEN * sizeof(*gNoMatchProg)); strcpy(hp->ifMatch, "ppp"); strcpy(hp->ifNotMatch, "ppp"); if (NgSendMsg(b->csock, MPD_HOOK_TCPMSS_IN, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } memset(&u, 0, sizeof(u)); strcpy(hp->thisHook, MPD_HOOK_TCPMSS_OUT); hp->bpf_prog_len = NOMATCH_PROG_LEN; memcpy(&hp->bpf_prog, &gNoMatchProg, NOMATCH_PROG_LEN * sizeof(*gNoMatchProg)); strcpy(hp->ifMatch, "iface"); strcpy(hp->ifNotMatch, "iface"); if (NgSendMsg(b->csock, MPD_HOOK_TCPMSS_OUT, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } #endif /* USE_NG_TCPMSS */ } static void IfaceShutdownMSS(Bund b) { #ifdef USE_NG_TCPMSS char path[NG_PATHLEN+1]; snprintf(path, sizeof(path), "mpd%d-%s-mss:", gPid, b->name); NgFuncShutdownNode(b->csock, b->name, path); #else NgFuncShutdownNode(b->csock, b->name, MPD_HOOK_TCPMSS_IN); #endif } static int IfaceInitLimits(Bund b, char *path, char *hook) { struct ngm_mkpeer mp; #if NG_NODESIZ>=32 struct ngm_name nm; #endif if (b->params.acl_limits[0] || b->params.acl_limits[1]) { Log(LG_IFACE2, ("[%s] IFACE: Connecting limits", b->name)); /* Create a bpf node for traffic filtering. */ snprintf(mp.type, sizeof(mp.type), "%s", NG_BPF_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", hook); snprintf(mp.peerhook, sizeof(mp.peerhook), "ppp"); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("can't create %s node at \"%s\"->\"%s\": %s", NG_BPF_NODE_TYPE, path, mp.ourhook, strerror(errno))); goto fail; } strlcat(path, ".", NG_PATHLEN); strlcat(path, hook, NG_PATHLEN); strcpy(hook, "iface"); #if NG_NODESIZ>=32 /* Set the new node's name. */ snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-lim", gPid, b->name); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { Log(LG_ERR, ("can't name limits %s node: %s", NG_BPF_NODE_TYPE, strerror(errno))); goto fail; } #endif } return (0); fail: return (-1); } /* * BundConfigLimits() * * Configure the bpf & car nodes. */ static void IfaceSetupLimits(Bund b) { #define ACL_MAX_PROGLEN 4096 union { u_char buf[NG_BPF_HOOKPROG_SIZE(ACL_MAX_PROGLEN)]; struct ng_bpf_hookprog hprog; } u; struct ng_bpf_hookprog *const hp = &u.hprog; struct ngm_connect cn; char path[NG_PATHLEN + 1]; char inhook[2][NG_HOOKLEN+1]; char inhookn[2][NG_HOOKLEN+1]; char outhook[NG_HOOKLEN+1]; struct acl *l; char str[ACL_LEN]; #define ACL_MAX_PARAMS 5 int ac; char *av[ACL_MAX_PARAMS]; int num, dir; int i, p; if (b->params.acl_limits[0] || b->params.acl_limits[1]) { snprintf(path, sizeof(path), "mpd%d-%s-lim:", gPid, b->name); for (dir = 0; dir < 2; dir++) { if (dir == 0) { strcpy(inhook[0], "ppp"); strcpy(inhook[1], ""); strcpy(outhook, "iface"); } else { strcpy(inhook[0], "iface"); strcpy(inhook[1], ""); strcpy(outhook, "ppp"); } num = 0; l = b->params.acl_limits[dir]; while (l) { Log(LG_IFACE2, ("[%s] IFACE: limit %s#%d: '%s'", b->name, (dir?"out":"in"), l->number, l->rule)); strncpy(str, l->rule, sizeof(str)); ac = ParseLine(str, av, ACL_MAX_PARAMS, 0); if (ac >= 2) { memset(&u, 0, sizeof(u)); if (l->next) { sprintf(hp->ifNotMatch, "%d-%d-n", dir, num); sprintf(inhookn[1], "%d-%d-ni", dir, num); /* Connect bpf to itself. */ strcpy(cn.ourhook, hp->ifNotMatch); strcpy(cn.path, path); strcpy(cn.peerhook, inhookn[1]); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); } } else { strcpy(hp->ifNotMatch, outhook); strcpy(inhookn[1], ""); } if (strcasecmp(av[0], "all") == 0) { hp->bpf_prog_len = MATCH_PROG_LEN; memcpy(&hp->bpf_prog, &gMatchProg, MATCH_PROG_LEN * sizeof(*gMatchProg)); } else if (strncasecmp(av[0], "flt", 3) == 0) { int flt; flt = atoi(av[0] + 3); if (flt <= 0 || flt > ACL_FILTERS || b->params.acl_filters[flt - 1] == NULL) { Log(LG_ERR, ("[%s] IFACE: incorrect filter: '%s'", b->name, av[0])); } else { struct bpf_program pr; char buf[16384], sbuf[256]; int bufbraces; struct acl *f; buf[0] = 0; bufbraces = 0; f = b->params.acl_filters[flt - 1]; while (f) { char *b1, *b2; strlcpy(sbuf, f->rule, sizeof(sbuf)); b2 = sbuf; b1 = strsep(&b2, " "); if (b2 != NULL) { if (strcasecmp(b1, "match") == 0) { strncat(buf, "( ", sizeof(buf)); strncat(buf, b2, sizeof(buf)); strncat(buf, " ) ", sizeof(buf)); if (f->next) { strncat(buf, "|| ( ", sizeof(buf)); bufbraces++; } } else if (strcasecmp(b1, "nomatch") == 0) { strncat(buf, "( not ( ", sizeof(buf)); strncat(buf, b2, sizeof(buf)); strncat(buf, " ) ) ", sizeof(buf)); if (f->next) { strncat(buf, "&& ( ", sizeof(buf)); bufbraces++; } } else { Log(LG_ERR, ("[%s] IFACE: filter action '%s' is unknown", b->name, b1)); } }; f = f->next; } for (i = 0; i < bufbraces; i++) strncat(buf, ") ", sizeof(buf)); Log(LG_IFACE2, ("[%s] IFACE: flt%d: '%s'", b->name, flt, buf)); if (pcap_compile_nopcap((u_int)-1, DLT_RAW, &pr, buf, 1, 0xffffff00)) { Log(LG_ERR, ("[%s] IFACE: filter '%s' compilation error", b->name, av[0])); } else if (pr.bf_len > ACL_MAX_PROGLEN) { Log(LG_ERR, ("[%s] IFACE: filter '%s' is too long", b->name, av[0])); pcap_freecode(&pr); } else { hp->bpf_prog_len = pr.bf_len; memcpy(&hp->bpf_prog, pr.bf_insns, pr.bf_len * sizeof(struct bpf_insn)); pcap_freecode(&pr); } } } else { Log(LG_ERR, ("[%s] IFACE: incorrect filter: '%s'", b->name, av[0])); hp->bpf_prog_len = NOMATCH_PROG_LEN; memcpy(&hp->bpf_prog, &gNoMatchProg, NOMATCH_PROG_LEN * sizeof(*gNoMatchProg)); } p = 1; if (strcasecmp(av[p], "pass") == 0) { strcpy(hp->ifMatch, outhook); strcpy(inhookn[0], ""); } else if (strcasecmp(av[p], "deny") == 0) { strcpy(hp->ifMatch, "deny"); strcpy(inhookn[0], ""); #ifdef USE_NG_CAR } else if ((strcasecmp(av[p], "shape") == 0) || (strcasecmp(av[p], "rate-limit") == 0)) { struct ngm_mkpeer mp; struct ng_car_bulkconf car; char tmppath[NG_PATHLEN + 1]; union { u_char buf[NG_BPF_HOOKPROG_SIZE(ACL_MAX_PROGLEN)]; struct ng_bpf_hookprog hprog; } u1; struct ng_bpf_hookprog *const hp1 = &u1.hprog; sprintf(hp->ifMatch, "%d-%d-m", dir, num); snprintf(tmppath, sizeof(tmppath), "%s%d-%d-m", path, dir, num); /* Create a car node for traffic shaping. */ snprintf(mp.type, sizeof(mp.type), "%s", NG_CAR_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%d-%d-m", dir, num); strcpy(mp.peerhook, ((dir == 0)?NG_CAR_HOOK_LOWER:NG_CAR_HOOK_UPPER)); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't create %s node at \"%s\"->\"%s\": %s", b->name, NG_CAR_NODE_TYPE, path, mp.ourhook, strerror(errno))); } /* Connect car to bpf. */ snprintf(cn.ourhook, sizeof(cn.ourhook), "%d-%d-mi", dir, num); snprintf(cn.path, sizeof(cn.path), "%s", tmppath); strcpy(cn.peerhook, ((dir == 0)?NG_CAR_HOOK_UPPER:NG_CAR_HOOK_LOWER)); if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s", b->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno))); } bzero(&car, sizeof(car)); if (strcasecmp(av[p], "shape") == 0) { car.upstream.mode = NG_CAR_SHAPE; } else { car.upstream.mode = NG_CAR_RED; } p++; if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) { car.upstream.cir = atol(av[p]); p++; if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) { car.upstream.cbs = atol(av[p]); p++; if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) { car.upstream.ebs = atol(av[p]); p++; } else { car.upstream.ebs = car.upstream.cbs * 2; } } else { car.upstream.cbs = car.upstream.cir / 8; car.upstream.ebs = car.upstream.cbs * 2; } } else { car.upstream.cir = 8000; car.upstream.cbs = car.upstream.cir / 8; car.upstream.ebs = car.upstream.cbs * 2; } car.upstream.green_action = NG_CAR_ACTION_FORWARD; car.upstream.yellow_action = NG_CAR_ACTION_FORWARD; car.upstream.red_action = NG_CAR_ACTION_DROP; car.downstream = car.upstream; if (NgSendMsg(b->csock, tmppath, NGM_CAR_COOKIE, NGM_CAR_SET_CONF, &car, sizeof(car)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't set %s configuration: %s", b->name, NG_CAR_NODE_TYPE, strerror(errno))); } if (ac > p) { if (strcasecmp(av[p], "pass") == 0) { memset(&u1, 0, sizeof(u1)); strcpy(hp1->ifMatch, outhook); strcpy(hp1->ifNotMatch, outhook); hp1->bpf_prog_len = MATCH_PROG_LEN; memcpy(&hp1->bpf_prog, &gMatchProg, MATCH_PROG_LEN * sizeof(*gMatchProg)); sprintf(hp1->thisHook, "%d-%d-mi", dir, num); if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp1, NG_BPF_HOOKPROG_SIZE(hp1->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } strcpy(inhookn[0], ""); } else { Log(LG_ERR, ("[%s] IFACE: unknown action: '%s'", b->name, av[p])); strcpy(inhookn[0], ""); } } else { sprintf(inhookn[0], "%d-%d-mi", dir, num); } #endif /* USE_NG_CAR */ } else { Log(LG_ERR, ("[%s] IFACE: unknown action: '%s'", b->name, av[1])); strcpy(inhookn[0], ""); } for (i = 0; i < 2; i++) { if (inhook[i][0] != 0) { strcpy(hp->thisHook, inhook[i]); if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't set %s node program: %s", b->name, NG_BPF_NODE_TYPE, strerror(errno))); } } strcpy(inhook[i], inhookn[i]); } num++; } else { Log(LG_ERR, ("[%s] IFACE: incorrect limit: '%s'", b->name, l->rule)); } l = l->next; } for (i = 0; i < 2; i++) { if (inhook[i][0] != 0) { memset(&u, 0, sizeof(u)); strcpy(hp->thisHook, inhook[i]); hp->bpf_prog_len = MATCH_PROG_LEN; memcpy(&hp->bpf_prog, &gMatchProg, MATCH_PROG_LEN * sizeof(*gMatchProg)); strcpy(hp->ifMatch, outhook); strcpy(hp->ifNotMatch, outhook); if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) { Log(LG_ERR, ("[%s] IFACE: can't set %s node %s %s program (2): %s", b->name, NG_BPF_NODE_TYPE, path, hp->thisHook, strerror(errno))); } } } } } } static void IfaceShutdownLimits(Bund b) { char path[NG_PATHLEN + 1]; if (b->params.acl_limits[0] || b->params.acl_limits[1]) { snprintf(path, sizeof(path), "mpd%d-%s-lim:", gPid, b->name); NgFuncShutdownNode(b->csock, b->name, path); } }