/* * Copyright: 2005 Axis Communications AB * * This file is part of Mini DHCP6. * * mdhcp6 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * mdhcp6 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mdhcp6. If not, see . * * * mdhcp6 - a thin dhcpv6 client for embedded unix-like systems. * * Authors: Edgar E. Iglesias */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msgbuf.h" #include "dhcpv6.h" #include "optionmap.h" #include "lease.h" #include "notify.h" #include "if.h" #include "ptime.h" #define D(x) /* maximum number of request options */ #define MAX_REQ_DHCP_OPTS 32 #define IRT_DEFAULT 86400 #define IRT_MINIMUM 600 #define IRT_MDHCP6_DEFAULT (2*60*60) /* stateless refresh every 2 hours */ struct arguments { char *interface; char *execute; char *pidfile; char *leasefile; int force; int force_statefull; int updateresolv; int no_daemon; int roptions[MAX_REQ_DHCP_OPTS]; int nr_roptions; /* Only one enterprise number */ uint32_t vclass_enterprise_nr; const unsigned char *vclass_data[MAX_VC_DATA]; int vclass_data_count; }; static struct arguments args = { NULL, NULL, NULL, NULL, 0, 0, 0, 0, /* * Default list of options to request */ { DH6OPT_SIP_DOMAINS, DH6OPT_SIP_SERVERS, DH6OPT_DNS_SERVERS, DH6OPT_DOMAIN_LIST, DH6OPT_NTP_SERVERS, DH6OPT_TIME_ZONE }, 6, /* Vclass options */ 0, { 0 }, 0 }; enum dhcpv6_policy_t { DHCPV6_DO_NADA, DHCPV6_DO_RACHECK, DHCPV6_DO_MANAGED, DHCPV6_DO_OTHER, DHCPV6_DO_RENEW, DHCPV6_DO_REBIND }; static struct lease_t lease; static sig_atomic_t running = 1; static const char mdhcp6_usagestr[] = \ "mdhcp6 " PACKAGE_VERSION "\n" "-e exec-script\n" "-fs force statefull dhcp transaction\n" "-fo force stateless dhcp transaction\n" "-l leasefile\n" "-p pidfile\n" "-r comma separated list of options to request\n" "-i iface\n" "-n no-daemon\n" "-E enterprise number\n" "-V vendor-class-data string"; static void usage(void) { puts(mdhcp6_usagestr); } /* * parses the string of cli provided DHCP options to request for. * The string is a comma separated list of numbers. */ static int parse_dhcp_options(char *s, int *opts, size_t maxlen) { char *next = s; int i = 0; while (i < maxlen && next) { opts[i++] = strtoul(s, &next, 0); if (next == s) break; else s = next + 1; } return --i; } static void parse_arguments(int argc, char **argv) { int c; while (1) { c = getopt(argc, argv, "E:e:f:hi:l:np:r:V:v"); if (c == -1) break; switch (c) { case 'e': args.execute = optarg; break; case 'E': args.vclass_enterprise_nr = strtoul(optarg, (char **)NULL, 0); break; case 'f': args.force = 1; if (optarg && *optarg == 's') args.force_statefull = 1; break; case 'i': args.interface = optarg; break; case 'l': args.leasefile = optarg; break; case 'p': args.pidfile = optarg; break; case 'r': args.nr_roptions = parse_dhcp_options(optarg, args.roptions, sizeof args.roptions / sizeof args.roptions[0]); break; case 'n': args.no_daemon = 1; break; case 'V': args.vclass_data[args.vclass_data_count++] = (const unsigned char *) optarg; break; case 'v': case 'h': default: usage(); exit(EXIT_FAILURE); break; } } } /* * Decide what to do based on the interfaces ra flags */ static enum dhcpv6_policy_t dhcpv6_policy(int flags) { int received; enum dhcpv6_policy_t p = DHCPV6_DO_NADA; received = flags & IF_RA_RCVD; if (args.force) { if (args.force_statefull) p = DHCPV6_DO_MANAGED; else p = DHCPV6_DO_OTHER; } else { if (received) { if (received && (flags & (IF_RA_OTHERCONF))) p = DHCPV6_DO_OTHER; if (received && (flags & (IF_RA_MANAGED))) p = DHCPV6_DO_MANAGED; } else p = DHCPV6_DO_MANAGED; } /* If we already got a lease and we continue with managed, try to renew it. */ if (p == DHCPV6_DO_MANAGED && lease.ia && lease.server) p = DHCPV6_DO_RENEW; return p; } static void create_pidfile(char *file) { FILE *fp; fp = fopen(file, "w+"); if (fp == NULL) return; fprintf(fp, "%d", getpid()); fclose(fp); } /* * fabricate an ia to put on our initial request, we aim for infinte lifetimes. */ static struct dhcpv6_option_ia_t * fabricate_initial_ia(struct in6_addr *addr, char *interface) { struct dhcpv6_option_ia_t *ia; ia = calloc(sizeof *ia, 1); if (ia) { memcpy(&ia->iaddr.addr, addr, sizeof *addr); ia->iaddr.prefered_lft = 0xffffffff; ia->iaddr.valid_lft = 0xffffffff; ia->iaid = if_nametoindex(interface); } return ia; } /* * Callback registered with the optionmap to update a string option value. * Only update if the value differs from the one we hold. */ static void update_string(struct optionmap_t *om, void *newvalue) { char *str = newvalue; size_t len; /* empty new value ? */ if (!str || (len = strlen(str)) == 0) { om->needsreconfig = 1; free(om->value); om->value = NULL; return; } if (!om->value || memcmp(om->value, newvalue, len) != 0) { free(om->value); om->value = malloc(len + 1); if (om->value) { memcpy(om->value, newvalue, len + 1); } om->needsreconfig = 1; } } /* * Callback registered with the optionmap to update an array of ipv6 addresses. * Only update if the value differs from the one we hold. */ static void update_addrlist(struct optionmap_t *om, void *newvalue) { struct dhcpv6_option_addrlist_t *opt = newvalue; char *nvalue, *str; int a; size_t size; assert(opt); /* * space for a space separated list with traling nul * character. */ size = (INET6_ADDRSTRLEN * opt->nr_addr) + opt->nr_addr + 1; str = nvalue = malloc(size); if (!str) return; for (a = 0; a < opt->nr_addr; a++) { inet_ntop(AF_INET6, opt->addr + a, str, INET6_ADDRSTRLEN); str += strlen(str); *str++ = ' '; } *(str - 1) = 0; if (!om->value || memcmp(om->value, nvalue, str - nvalue) != 0) { free(om->value); om->value = nvalue; om->needsreconfig = 1; } else free(nvalue); } /* * Dont forget to cleanup after us before we leave. On most unix systmes * this doesnt matter (the kernel will clean up after us) but it is useful * in the verification process while running valgrind and seeing zero leaks. */ static int graceful_exit(void) { lease_cleanup(&lease); lease_set_ia(&lease, NULL); memset(&lease.addr, 0, 16); notify_exec_script(args.execute, &lease); return EXIT_SUCCESS; } static void sigint(int signum) { running = 0; lease_cleanup(&lease); exit(EXIT_SUCCESS); } static void sigterm(int signum) { running = 0; } /* Wrapper around sleep to handle integer overflows and avoid negative * interpretations by sleep(). We continously poll for RA reconfig. * TODO: Find a nice way to let netlink notify us when there are new netif * flags to be read. */ static inline void dh_sleep(uint32_t timeout, int raflags) { time_t start, now, diff; int64_t tleft; int new_raflags = raflags; int max_sleep; /* If we already got an RA we poll for net reconfig of the managed and other flags through RA. A 120s poll interval should be ok. If we haven't got an RA yet, we might have incorrectly setup DHCP addresses so we poll more agressively. If the user has forced a specific mode, we saturate the sleep to avoid integer overflows. */ if (args.force) max_sleep = 60 * 60 * 24 * 7; /* avoid overflow. */ else if (raflags & IF_RA_RCVD) max_sleep = 60; /* poll for RA reconfig. */ else max_sleep = 16; /* Waiting for RA. */ D(printf("%s(%d, %x)\n", __func__, timeout, raflags)); tleft = timeout; start = ptime(); do { unsigned int sleep_time; /* * Staturate sleep time to avoid negative interpretations by * sleep(). */ sleep_time = tleft; /* Avoid 64bit cmp. */ if (sleep_time > max_sleep) sleep_time = max_sleep; sleep(sleep_time); /* Now, compute the actual time slept and decrease timeout. */ now = ptime(); diff = now - start; /* signed arithmetics. */ start = now; tleft -= diff; if (!args.force) new_raflags = if_get_raflags(args.interface); } while (running && tleft > 0 && new_raflags == raflags); } int run_mdhcp6(int argc, char **argv) { unsigned int force_notify = 1; uint32_t timeout; enum dhcpv6_policy_t action = DHCPV6_DO_RACHECK; int ifindex, if_raflags = 0; int r = EXIT_FAILURE; int leaseisvalid; int statuscode; int needsreconfig; struct dhcpv6_option_t *opt; struct sigaction saction; struct dhcpv6_message_t reply = {0}; struct dhcpv6_option_vendorclass_t *vclass = NULL; int i; lease_init(&lease); memset(&saction, 0, sizeof saction); sigfillset (&saction.sa_mask); saction.sa_handler = sigint; saction.sa_flags = 0; sigaction(SIGINT, &saction, NULL); sigfillset (&saction.sa_mask); saction.sa_handler = sigterm; saction.sa_flags = 0; sigaction(SIGTERM, &saction, NULL); parse_arguments(argc, argv); if (args.interface == NULL) { usage(); return EXIT_FAILURE; } ifindex = if_nametoindex(args.interface); if (!args.no_daemon) { if (daemon(0, 0) == -1) { perror("daemon\n"); return EXIT_FAILURE; } } openlog("mdhcp6", LOG_PID, LOG_DAEMON); syslog(LOG_INFO, "mdhcp6 starting\n"); optionmap_add(lease.omap, DH6OPT_DNS_SERVERS, "dh6_dnssrv", update_addrlist); optionmap_add(lease.omap, DH6OPT_NTP_SERVERS, "dh6_ntpsrv", update_addrlist); optionmap_add(lease.omap, DH6OPT_SIP_SERVERS, "dh6_sipsrv", update_addrlist); optionmap_add(lease.omap, DH6OPT_SIP_DOMAINS, "dh6_sipdomains", update_string); optionmap_add(lease.omap, DH6OPT_DOMAIN_LIST, "dh6_dnslist", update_string); optionmap_add(lease.omap, DH6OPT_TIME_ZONE, "dh6_timezone", update_string); setenv("dh6_iface", args.interface, 1); if (args.pidfile) create_pidfile(args.pidfile); lease_readfile(args.leasefile, &lease); if (args.vclass_enterprise_nr) { vclass = calloc(1, sizeof *vclass); if (vclass) { vclass->enterprise_nr = args.vclass_enterprise_nr; if (args.vclass_data_count > 0) { vclass->nr_vcdata = args.vclass_data_count; for (i = 0; i < vclass->nr_vcdata; i++) { vclass->vcdata[i].len = strlen((char*) args.vclass_data[i]); vclass->vcdata[i].data = args.vclass_data[i]; } } } } /* fabricate the initial ia if we have an old lease */ if (memcmp(&in6addr_any, &lease.addr, sizeof lease.addr) != 0) lease.ia = fabricate_initial_ia(&lease.addr, args.interface); /* Decide what to start with ? */ action = dhcpv6_policy(if_raflags); /* * the main loop */ do { int new_if_raflags; r = EXIT_FAILURE; timeout = 60; new_if_raflags = if_get_raflags(args.interface); if (new_if_raflags != if_raflags) { D(printf ("ra change! Update policy! %x %x\n", new_if_raflags, if_raflags)); if_raflags = new_if_raflags; lease.raflags = if_raflags; action = dhcpv6_policy(if_raflags); } D(printf("action=%d\n", action)); lease.state = action; switch (action) { case DHCPV6_DO_RACHECK: { r = EXIT_SUCCESS; action = dhcpv6_policy(if_raflags); continue; } break; case DHCPV6_DO_MANAGED: if (lease.ia && lease.ia->iaddr.valid_lft) action = DHCPV6_DO_REBIND; lease_set_server(&lease, NULL); /* do_statefull will overwrite the server */ r = dhcpv6_do_statefull(&reply, &lease.server, lease.ia, vclass, args.interface, args.roptions, args.nr_roptions); break; case DHCPV6_DO_OTHER: /* invalidate ia and server */ lease_set_ia(&lease, NULL); lease_set_server(&lease, NULL); r = dhcpv6_do_stateless(&reply, vclass, args.interface, args.roptions, args.nr_roptions); timeout = IRT_MDHCP6_DEFAULT; break; case DHCPV6_DO_NADA: /* RA says we shoudnt do DHCP. If we've got any previous leases we drop them here. */ if (lease_drop(&lease)) { D(printf("NO DHCP, Cleanup leases.\n")); notify_exec_script(args.execute, &lease); } timeout = 60 * 60 * 24; r = EXIT_SUCCESS; break; case DHCPV6_DO_RENEW: /* renew will read the server */ r = dhcpv6_do_reclaim(DH6_RENEW, &reply, &lease.server, lease.ia, vclass, args.interface, args.roptions, args.nr_roptions, lease.aquired ? 60 : 5); break; case DHCPV6_DO_REBIND: /* renew will read the server */ r = dhcpv6_do_reclaim(DH6_REBIND, &reply, &lease.server, lease.ia, vclass, args.interface, args.roptions, args.nr_roptions, 60); break; default: assert(0); break; } if (r == EXIT_FAILURE) action = DHCPV6_DO_RACHECK; /* back to init */ statuscode = -1; leaseisvalid = 0; opt = reply.options; while (opt) { switch (opt->code) { case DH6OPT_NTP_SERVERS: case DH6OPT_DNS_SERVERS: case DH6OPT_SIP_SERVERS: case DH6OPT_SIP_DOMAINS: case DH6OPT_DOMAIN_LIST: case DH6OPT_TIME_ZONE: optionmap_up(lease.omap, opt->code, opt->interp); break; case DH6OPT_IA_NA: { struct dhcpv6_option_ia_t *na = opt->interp; int iaddrisvalid = 0; /* * did we get a valid IADDR? */ if (na->iaddr.valid_lft != 0) { D(printf("IA-NA: got lease\n")); /* record the relevant timing */ lease.aquired = ptime(); timeout = na->t1; /* save it from destruction */ lease_set_ia(&lease, NULL); lease.ia = na; opt->interp_delete = NULL; memcpy(&lease.addr, &na->iaddr.addr, 16); /* sync the lease file */ lease_sync(args.leasefile, &lease); action = DHCPV6_DO_RENEW; iaddrisvalid = 1; } else { D(printf("IA-NA: invalid lifetime\n")); } if (!na->status) { if(iaddrisvalid != 0) { /* no status means success */ statuscode = 0; leaseisvalid = 1; } break; } statuscode = na->status->code; if(na->status->code != 0) { D(printf("IA-NA: error %d\n", na->status->code)); /* * Something is wrong, go back * to initial state. */ lease_set_server(&lease, NULL); if (action == DHCPV6_DO_RENEW) { /* * keep lease address, we might * be able to get the same * after a complete solicit, * request and reply sequence. */ action = DHCPV6_DO_RACHECK; /* fast-retry */ timeout = 1; } else lease_set_ia(&lease, NULL); } else leaseisvalid = 1; D(printf("status: %s\n", na->status->str)); } break; default: /* unsupported, silent ignore */ break; } opt = opt->next; } if (!leaseisvalid) lease_set_server(&lease, NULL); dhcpv6_free_options(reply.options); reply.options = NULL; /* * dont configure anything until we give the solicit/request * a chance to get the same address. This is to avoid multiple * reconfigurations (once without address and once with). */ if (statuscode == DH6OPT_STCODE_NOBINDING) continue; needsreconfig = (leaseisvalid || statuscode == -1) && lease_test_and_clear_reconfig(&lease); if (needsreconfig) { notify_exec_script(args.execute, &lease); } else if (force_notify && leaseisvalid && !statuscode) { force_notify = 0; notify_exec_script(args.execute, &lease); } dh_sleep(timeout, if_raflags); } while(running); free(vclass); return graceful_exit(); }