DHCPv6 oversimplified client for prefix delegation
Run a cron job or a startup program rather than a daemon
[Intro] [C program]

Intro

The BOOTP protocol was designed to supply basic boot parameters to diskless devices over a local network. It exchanged UDP datagrams containing IPv4 addresses for the server, the gateway, and the client itself, as well as hardware address, host name and boot filename. In addition BOOTP provided for a vendor-specific area. DHCP, that is DHCPv4, extended the vendor area and renamed it as options, otherwise maintaining compatibility with BOOTP. DHCPv6 removed all IPv4 fields, reducing the base record to just four bytes. All the relevant data is stuffed in the form of options. One of them is the IPv6 address lease, which is the semantic core of the protocol. The option for prefix delegation is derived by homology, adding just one byte for the prefix length. That way, boot parameters came to include router configurations.

Most ISPs install a router between a line terminal and the customer's PC or server. This is useless on a Linux box, since the OS includes support for whatever link layer it uses, such as PPPoE, 802.*, and similar. However, my ignorance of IPv6 amazed me when I found that simply connecting to the line didn't work. IPv4 was all right, IPv6 mute. I asked for help in Unix & Linux and eventually replied to myself there. The answer was DHCP. However, I was reluctant to run a DHCP client on the box which is already running a DHCP server. I had already configured fixed IP addresses and didn't want to add processes that would mess up with them. So I managed to hack a program to just send the two required packets.

C program

0001: // dhcpv6cli.c - written by Vesely on 09 May 2023
0002: /*
0003: * Oversimplified DHCPv6 client for Prefix Delegation only.  This is not
0004: * a daemon, does not renew delegations if they expire.
0005: *
0006: * Run:
0007: * dhcpv6cli -dv ppp0
0008: *
0009: * Compile:
0010: * sudo gcc -W -Wall -g -o /usr/sbin/dhcpv6cli dhcpv6cli.c -luuid -lm
0011: */
0012: 
0013: #include <stdio.h>
0014: #include <inttypes.h>
0015: #include <arpa/inet.h>
0016: #include <sys/socket.h>
0017: #include <ifaddrs.h>
0018: 
0019: #include <uuid/uuid.h>
0020: 
0021: #include <sys/types.h>
0022: #include <sys/stat.h>
0023: #include <sys/select.h>
0024: #include <sys/ioctl.h>
0025: #include <net/if.h>
0026: #include <unistd.h>
0027: #include <math.h>
0028: #include <errno.h>
0029: #include <string.h>
0030: #include <stdlib.h>
0031: #include <stddef.h>
0032: #include <ctype.h>
0033: #include <time.h>
0034: #include <assert.h>
0035: 
0036: 
0037: char *program_name;
0038: 
0039: /*
0040: *  From RFC 8415:
0041: *  C: Solicit from fe80 address to ff02::1:2, port 547, include Client ID
0042: *  S: Advertise to port 546, include Server ID, C choose a server,
0043: *  C: Request with Prefix Delegation, Client ID, Server ID
0044: *  S: Reply
0045: *
0046: *  If/when the lease expires, the client sends Renew and receives Reply.
0047: */
0048: 
0049: #define DHCP_CLI_PORT 546
0050: #define DHCP_SRV_PORT 547
0051: 
0052: #define SOLICIT 1
0053: #define ADVERTISE 2
0054: #define REQUEST 3
0055: #define RENEW 5
0056: #define REPLY 7
0057: 
0058: #define OPTION_CLIENTID 1
0059: #define OPTION_SERVERID 2
0060: #define OPTION_ORO 6
0061: #define OPTION_PREFERENCE 7
0062: #define OPTION_ELAPSED_TIME 8
0063: #define OPTION_STATUS_CODE 13
0064: #define OPTION_IA_PD 25
0065: #define OPTION_IAPREFIX 26
0066: #define OPTION_SOL_MAX_RT 82
0067: 
0068: #define SOL_MAX_RT 3600
0069: #define SOL_TIMEOUT 1
0070: #define REQ_TIMEOUT 1
0071: #define REQ_MAX_RT 30
0072: #define REQ_MAX_RC 10
0073: 
0074: #define Success       0 // Success
0075: #define UnspecFail    1 // Failure, reason unspecified
0076: #define NoAddrsAvail  2 // The server has no addresses available
0077: #define NoBinding     3 // Client record (binding) unavailable
0078: #define NotOnLink     4 // The prefix is not appropriate
0079: #define UseMulticast  5 // Use All_DHCP_Relay_Agents_and_Servers
0080: #define NoPrefixAvail 6 // The server has no prefixes available
0081: 
0082: #pragma pack(1)
0083: 
0084: static void
0085: dump(char const *descr, void const *buffer, unsigned length)
0086: {
0087:    unsigned char const *buf = (unsigned char const*)buffer;
0088:    unsigned const char *ebuf = buf + length;
0089: 
0090:    printf("\n%s\n", descr);
0091:    while (buf < ebuf)
0092:    {
0093:       int top = 16;
0094:       if (buf + 16 >= ebuf)
0095:          top = ebuf - buf;
0096:       for (int i = 0; i < top; ++i)
0097:       {
0098:          if (i == 8) putchar(' ');
0099:          printf(" %02x", buf[i]);
0100:       }
0101:       putchar(' ');
0102:       if (top < 8)
0103:          putchar(' ');
0104:       for (int i = top; i < 16; ++i)
0105:          putchar(' ');
0106:       for (int i = 0; i < top; ++i)
0107:          putchar(isprint(buf[i])? buf[i]: '.');
0108:       putchar('\n');
0109:       buf += top;
0110:    }
0111: }
0112: 
0113: static int fatal(char const *what)
0114: {
0115:    fprintf(stderr, "%s error: %s\n", what, strerror(errno));
0116:    return -1;
0117: }
0118: 
0119: static double ftime(void)
0120: {
0121:    struct timespec ts;
0122:    clock_gettime(CLOCK_MONOTONIC, &ts);
0123:    return ts.tv_sec + ts.tv_nsec/1000000000.0;
0124: }
0125: 
0126: static int verbose, debug;
0127: 
0128: typedef struct dhcpmsg
0129: {
0130:    unsigned char msg_type;
0131:    unsigned char transaction[3]; // random value to match replies
0132:    unsigned char options[];
0133: } dhcpmsg;
0134: 
0135: typedef struct dhcp_option_hdr
0136: {
0137:    uint16_t option_code;
0138:    uint16_t option_len;
0139: } dhcp_option_hdr;
0140: 
0141: typedef struct dhcp_option
0142: {
0143:    dhcp_option_hdr hdr;
0144:    unsigned char options[];
0145: } dhcp_option;
0146: 
0147: static dhcp_option *find_option(int option_code, dhcpmsg *msg, unsigned length)
0148: {
0149:    dhcp_option *op = (dhcp_option *) &msg->options;
0150:    unsigned char *end = (unsigned char*)msg + length;
0151:    while (&op->options[3] < end) // at least the header is in
0152:    {
0153:       if (ntohs(op->hdr.option_code) == option_code)
0154:          return op;
0155: 
0156:       op = (dhcp_option *)&op->options[ntohs(op->hdr.option_len)];
0157:    }
0158: 
0159:    return NULL;
0160: }
0161: 
0162: typedef struct send_params
0163: {
0164:    double start;
0165:    union
0166:    {
0167:       uint16_t *time_elapsed;
0168:       char *adjustable_time_elapsed;
0169:    } u;
0170:    struct sockaddr_in6 to;
0171:    unsigned length;
0172:    int sock;
0173:    time_t sol_max_rt;
0174: } send_params;
0175: 
0176: typedef struct received
0177: {
0178:    struct received *next;
0179:    unsigned size;
0180:    dhcpmsg msg;
0181: } received;
0182: 
0183: // amount that must be added to the received buffer.
0184: #define overhead offsetof(received, msg)
0185: 
0186: static void clear_received(received *rbuf)
0187: {
0188:    while (rbuf)
0189:    {
0190:       received *tmp = rbuf->next;
0191:       free(rbuf);
0192:       rbuf = tmp;
0193:    }
0194: }
0195: 
0196: static inline void invalid_pck(char const *what, double retr)
0197: {
0198:    if (verbose)
0199:       printf("Discard invalid packet: %s. Listen for %g seconds, then send again\n",
0200:          what, retr);
0201: }
0202: 
0203: static received* dhcpsend(send_params *sp, dhcpmsg *msg)
0204: /*
0205: * Send and receive replies according to Section 15 of RFC 8415
0206: */
0207: {
0208:    assert(sp);
0209:    assert(sp->sock >= 0);
0210:    assert(msg);
0211: 
0212:    union
0213:    {
0214:       uint32_t random;
0215:       uint8_t rb[4];
0216:    } ur;
0217:    ur.random = random();
0218:    msg->transaction[0] = ur.rb[0];
0219:    msg->transaction[1] = ur.rb[1];
0220:    msg->transaction[2] = ur.rb[2];
0221: 
0222:    dhcp_option *my_cli = find_option(OPTION_CLIENTID, msg, sp->length);
0223:    unsigned retr_count = 0, max_retr_count;
0224:    double max_retr_time, initial_retr_time;
0225:    int expected_response;
0226:    switch (msg->msg_type)
0227:    {
0228:       case SOLICIT:
0229:          sp->start = ftime();
0230:          max_retr_time = sp->sol_max_rt;
0231:          initial_retr_time = SOL_TIMEOUT;
0232:          max_retr_count = 10; // should be 0, autoimpose a limit
0233:          expected_response = ADVERTISE;
0234:          break;
0235: 
0236:       case REQUEST:
0237:          max_retr_time = REQ_MAX_RT;
0238:          initial_retr_time = REQ_TIMEOUT;
0239:          max_retr_count = REQ_MAX_RC;
0240:          expected_response = REPLY;
0241:          break;
0242:    }
0243: 
0244:    received *rbuf = NULL, *next = NULL;
0245:    unsigned alloced = 0;
0246: 
0247:    for (;;) /* send and receive loop (described by RFC) */
0248:    {
0249:       if (max_retr_count && retr_count > max_retr_count)
0250:          return NULL;
0251: 
0252:       fd_set rfds, wfds;
0253:       FD_ZERO(&rfds);
0254:       FD_SET(sp->sock, &rfds);
0255:       FD_ZERO(&wfds);
0256:       FD_SET(sp->sock, &wfds);
0257: 
0258:       int trysend = 0;
0259:       for (;;) /* send loop */
0260:       {
0261:          /*
0262:          * The amount of time since the client began its current DHCP
0263:          * transaction.  This time is expressed in hundredths of a
0264:          * second (10^-2 seconds).  A 2-octet field containing an
0265:          * unsigned integer.
0266:          */
0267:          if (msg->msg_type == SOLICIT && retr_count == 0)
0268:             *sp->u.time_elapsed = 0;
0269:          else
0270:          {
0271:             double diff = (ftime() - sp->start) * 100.0;
0272:             if (diff > UINT16_MAX)
0273:                *sp->u.time_elapsed = UINT16_MAX;
0274:             else
0275:                *sp->u.time_elapsed = htons((uint16_t)trunc(diff));
0276:          }
0277: 
0278:          if (debug)
0279:             dump("sending", msg, sp->length);
0280:          int n = sendto(sp->sock, msg, sp->length, 0, (struct sockaddr*)&sp->to, sizeof sp->to);
0281: 
0282:          if (n > 0) // success
0283:             break;
0284: 
0285:          fprintf(stderr, "send attempt %d failed: %s\n", ++trysend, strerror(errno));
0286:          if (trysend > 4)
0287:             return NULL;
0288: 
0289:          // possibly wait up to 2 secs before retrying
0290:          struct timeval tv;
0291:          tv.tv_sec = 2;
0292:          tv.tv_usec = 0;
0293:          select(sp->sock + 1, NULL, &wfds, NULL, &tv);
0294:       }
0295: 
0296:       /*
0297:       * Retransmission time must vary by a random value between -0.1
0298:       * and 0.1.  For advertise, wait for the whole duration or high
0299:       * priority message, otherwise first valid message.
0300:       *
0301:       * If the client is waiting for an Advertise message, the mechanism
0302:       * is modified as follows.  The client collects valid Advertise
0303:       * messages until the first RT has elapsed.  Also, the first RT
0304:       * MUST be selected to be strictly greater than initial retry time
0305:       * by choosing rnd to be strictly greater than 0.
0306:       */
0307:       double rnd = 0.2*drand48() - 0.1, retr;
0308:       if (++retr_count == 1)
0309:       {
0310:          if (msg->msg_type == SOLICIT && rnd < 0)
0311:             rnd = -rnd;
0312:          retr = initial_retr_time + rnd * initial_retr_time;
0313:       }
0314:       else
0315:          retr = 2*retr + rnd*retr;
0316: 
0317:       if (max_retr_time && retr > max_retr_time)
0318:          retr = max_retr_time + rnd*max_retr_time;
0319: 
0320:       double retr_elapsed = retr;
0321:       while (retr_elapsed > 0)
0322:       {
0323:          struct timeval retr_time;
0324:          retr_time.tv_sec = trunc(retr_elapsed);
0325:          retr_time.tv_usec = trunc(fmod(retr_elapsed, 1.0)*1000000.0);
0326:          double retr_start = ftime();
0327:          int rtc = select(sp->sock + 1, &rfds, NULL, NULL, &retr_time);
0328:          retr_elapsed -= ftime() - retr_start;
0329:          if (rtc == -1)
0330:          {
0331:             fatal("select");
0332:          }
0333:          else if (rtc) // got data
0334:          {
0335:             unsigned int avail = 0;
0336:             if (ioctl(sp->sock, FIONREAD, &avail))
0337:             {
0338:                fatal("ioctl");
0339:                avail = 1024;
0340:             }
0341: 
0342:             if (rbuf == NULL || alloced < avail)
0343:             {
0344:                received *re_rbuf = realloc(rbuf, avail + overhead);
0345:                if (re_rbuf == NULL)
0346:                {
0347:                   fatal("MEMORY");
0348:                   return NULL;
0349:                }
0350:                memset(re_rbuf, 0, overhead);
0351:                rbuf = re_rbuf;
0352:                alloced = avail;
0353:             }
0354: 
0355:             int n = recvfrom(sp->sock, &rbuf->msg, avail, 0, NULL, NULL);
0356:             if (n < 0)
0357:             {
0358:                fatal("recvfrom");
0359:                continue;
0360:             }
0361:             if (debug)
0362:                dump("received", &rbuf->msg, n);
0363: 
0364:             // expected reply?
0365:             if (rbuf->msg.msg_type != expected_response)
0366:             {
0367:                invalid_pck("wrong type", retr_elapsed);
0368:                continue;
0369:             }
0370: 
0371:             if (rbuf->msg.transaction[0] != msg->transaction[0] ||
0372:                rbuf->msg.transaction[1] != msg->transaction[1] ||
0373:                rbuf->msg.transaction[2] != msg->transaction[2])
0374:             {
0375:                invalid_pck("transaction mismatch", retr_elapsed);
0376:                continue;
0377:             }
0378: 
0379:             dhcp_option *op = find_option(OPTION_CLIENTID, &rbuf->msg, n);
0380:             if (op == NULL)
0381:             {
0382:                invalid_pck("missing Client ID", retr_elapsed);
0383:                continue;
0384:             }
0385: 
0386:             unsigned char *end = (unsigned char*)&rbuf->msg + n;
0387:             unsigned op_len = ntohs(op->hdr.option_len);
0388:             if (my_cli &&
0389:                (my_cli->hdr.option_len != op->hdr.option_len ||
0390:                &op->options[op_len] >= end ||
0391:                memcmp(op, my_cli, op_len) != 0))
0392:             {
0393:                invalid_pck("Client ID mismatch", retr_elapsed);
0394:                continue;
0395:             }
0396: 
0397:             if (find_option(OPTION_SERVERID, &rbuf->msg, n) == NULL)
0398:             {
0399:                invalid_pck("missing Server ID", retr_elapsed);
0400:                continue;
0401:             }
0402: 
0403:             // accept message
0404:             rbuf->size = (unsigned)n;
0405:             if (expected_response != ADVERTISE)
0406:                return rbuf;
0407: 
0408:             rbuf->next = next;
0409:             next = rbuf;
0410:             rbuf = NULL;
0411: 
0412:             op = find_option(OPTION_PREFERENCE, &next->msg, n);
0413:             if (op && op->options[0] == 255) // high priority
0414:                return next;
0415:          }
0416:       }
0417:       free(rbuf);
0418:       if (next) // Advertisements collected
0419:          return next;
0420:    }
0421: }
0422: 
0423: static inline int
0424: print_status(char const *descr, dhcp_option *op, unsigned char *end)
0425: {
0426:    assert(ntohs(op->hdr.option_code) == OPTION_STATUS_CODE);
0427: 
0428:    unsigned op_len = ntohs(op->hdr.option_len);
0429:    if (&op->options[op_len] <= end)
0430:    {
0431:       int status = op->options[0] * 256 + op->options[1];
0432:       if (verbose)
0433:          printf("%s status %d: %.*s\n", descr,
0434:             status,
0435:             op_len - 2,
0436:             &op->options[2]);
0437:       return status;
0438:    }
0439: 
0440:    return 1;
0441: }
0442: 
0443: static int
0444: check_ia_pd(char const *descr, dhcp_option **inout_op, unsigned char *end)
0445: /*
0446: * Return the highest prefix found, if any, or a negative number
0447: * corresponding to error status code.
0448: */
0449: {
0450:    assert(ntohs((*inout_op)->hdr.option_code) == OPTION_IA_PD);
0451: 
0452:    dhcp_option *op = *inout_op;
0453:    int prefix_len = 0; // has prefix?
0454: 
0455:    struct ia_pd_recv_op
0456:    {
0457:       dhcp_option_hdr  hdr;
0458:       uint32_t iaid, t1, t2;
0459:       unsigned char options[];
0460:    } *ia_pd_recv_op = (struct ia_pd_recv_op*) op;
0461: 
0462:    if (&ia_pd_recv_op->options[0] > end)
0463:       return 0;
0464: 
0465:    if (verbose)
0466:       printf("%s iaid=%d, t1=%d, t2=%d\n",
0467:          descr? descr: "",
0468:          ntohl(ia_pd_recv_op->iaid),
0469:          ntohl(ia_pd_recv_op->t1),
0470:          ntohl(ia_pd_recv_op->t2));
0471: 
0472:    unsigned op_len = ntohs(op->hdr.option_len);
0473:    unsigned char *end_option = &op->options[op_len];
0474:    if (end_option > end)
0475:       return 0;
0476: 
0477:    dhcp_option *inop = (dhcp_option*)&ia_pd_recv_op->options[0];
0478:    while (&inop->options[0] <= end_option)
0479:    {
0480:       unsigned inop_len = ntohs(inop->hdr.option_len);
0481:       if (ntohs(inop->hdr.option_code) == OPTION_IAPREFIX)
0482:       {
0483:          int err = 0;
0484:          struct ia_prefix_op
0485:          {
0486:             dhcp_option_hdr hdr;
0487:             uint32_t t1, t2;
0488:             uint8_t prefix_len;
0489:             struct in6_addr prefix;
0490:             unsigned char options[];
0491:          } *ia_prefix_op = (struct ia_prefix_op*) inop;
0492:          if (&ia_prefix_op->options[0] > end)
0493:          {
0494:             fatal("Corrupted iapr");
0495:             return 0;
0496:          }
0497: 
0498:          if (verbose)
0499:          {
0500:             char buf[INET6_ADDRSTRLEN];
0501:             char const *prefix = inet_ntop(AF_INET6, &ia_prefix_op->prefix, buf, sizeof buf);
0502:             if (prefix)
0503:                printf(" %s/%d valid for %d sec (preferred %d)\n",
0504:                   buf, ia_prefix_op->prefix_len,
0505:                   ntohl(ia_prefix_op->t2), ntohl(ia_prefix_op->t1));
0506:             else
0507:                fatal("inet_ntop");
0508:          }
0509:          unsigned prefix_op_len = inop_len - 25;
0510:          unsigned pop_offset = 0;
0511:          while (prefix_op_len > sizeof(dhcp_option_hdr))
0512:          {
0513:             dhcp_option *pop = (dhcp_option*)&ia_prefix_op->options[pop_offset];
0514:             if (&pop->options[0] > end)
0515:             {
0516:                fatal("Corrupted 4");
0517:                return 0;
0518:             }
0519: 
0520:             unsigned pop_len = ntohs(pop->hdr.option_len);
0521:             if (&pop->options[pop_len] > end)
0522:             {
0523:                fatal("Corrupted 5");
0524:                return 0;
0525:             }
0526: 
0527:             // status related to this prefix
0528:             uint16_t code = ntohs(pop->hdr.option_code);
0529:             if (code == OPTION_STATUS_CODE)
0530:                err |= print_status(" -->", pop, end);
0531:             else if (verbose)
0532:                printf("  prefix opt %d\n", code);
0533: 
0534:             pop_len += sizeof(dhcp_option_hdr);
0535:             if (pop_len < prefix_op_len)
0536:             {
0537:                prefix_op_len -= pop_len;
0538:                pop_offset += pop_len;
0539:             }
0540:             else
0541:             {
0542:                fatal("invalid opion length");
0543:                break;
0544:             }
0545:          }
0546:          if (err == 0 && ia_prefix_op->prefix_len > prefix_len)
0547:          {
0548:             prefix_len = ia_prefix_op->prefix_len;
0549:             *inout_op = inop;
0550:          }
0551:       }
0552:       // status related to this request
0553:       else if (ntohs(inop->hdr.option_code) == OPTION_STATUS_CODE)
0554:       {
0555:          int err = print_status("", inop, end);
0556:          if (err)
0557:             return -err;
0558:       }
0559:       else if (verbose)
0560:          printf(" option %d\n", ntohs(inop->hdr.option_code));
0561:       inop = (dhcp_option*) &inop->options[inop_len];
0562:    }
0563: 
0564:    return prefix_len;
0565: }
0566: 
0567: static int dhcpcli(char const *if_name,
0568:    struct sockaddr_in6 const *from_hint, uuid_t client_id,
0569:    uint32_t sol_max_rt)
0570: {
0571:    assert(if_name);
0572:    assert(from_hint);
0573:    assert(client_id);
0574: 
0575:    send_params sp;
0576:    memset(&sp, 0, sizeof sp);
0577:    sp.sol_max_rt = sol_max_rt;
0578:    sp.sock = socket(AF_INET6, SOCK_DGRAM, 0);
0579:    if (sp.sock < 0)
0580:    {
0581:       fprintf(stderr, "Cannot open socket: %s\n", strerror(errno));
0582:       return -1;
0583:    }
0584: 
0585:    int flag = 1;
0586:    if (setsockopt(sp.sock, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof flag))
0587:    {
0588:       fprintf(stderr, "Failed setsockopt: %s\n", strerror(errno));
0589:    }
0590: 
0591:    struct sockaddr_in6 from;
0592:    memset(&from, 0, sizeof from);
0593:    from.sin6_family = AF_INET6;
0594:    from.sin6_addr = from_hint->sin6_addr;
0595:    from.sin6_port = htons(DHCP_CLI_PORT);
0596:    from.sin6_scope_id = if_nametoindex(if_name);
0597:    if (bind(sp.sock, (struct sockaddr*)&from, sizeof from))
0598:    {
0599:       fprintf(stderr, "Cannot bind: %s\n", strerror(errno));
0600:       return -1;
0601:    }
0602: 
0603:    sp.to.sin6_family = AF_INET6;
0604:    // All_DHCP_Relay_Agents_and_Servers multicast address
0605:    inet_pton(AF_INET6, "ff02::1:2"/* "2a02:29e1:300:e922::1"*/, &sp.to.sin6_addr.s6_addr);
0606:    sp.to.sin6_port = htons(DHCP_SRV_PORT);
0607: 
0608:    union
0609:    {
0610:       dhcpmsg msg;
0611:       char options[512];
0612:    } u;
0613:    memset(&u, 0, sizeof u);
0614:    u.msg.msg_type = SOLICIT;
0615:    sp.length = offsetof(dhcpmsg, options);
0616: 
0617:    struct client_id_op
0618:    {
0619:       dhcp_option_hdr  hdr;
0620:       uint16_t duid_type;
0621:       uuid_t uuid;
0622:    } *client_id_op = (struct client_id_op*) &u.options[sp.length];
0623:    client_id_op->hdr.option_code = htons(OPTION_CLIENTID);
0624:    client_id_op->hdr.option_len = htons(2 + sizeof(uuid_t));
0625:    client_id_op->duid_type = htons(4);
0626:    // Useless to convert uuid to network byte order, methinks.
0627:    memcpy(client_id_op->uuid, client_id, sizeof(uuid_t));
0628:    sp.length += sizeof *client_id_op;
0629: 
0630:    struct elapsed_time_op
0631:    {
0632:       dhcp_option_hdr  hdr;
0633:       uint16_t elapsed;
0634:    } *elapsed_time_op = (struct elapsed_time_op*) &u.options[sp.length];
0635:    elapsed_time_op->hdr.option_code = htons(OPTION_ELAPSED_TIME);
0636:    elapsed_time_op->hdr.option_len = htons(2);
0637:    elapsed_time_op->elapsed = 0;
0638:    sp.u.time_elapsed = &elapsed_time_op->elapsed;
0639:    sp.length += sizeof *elapsed_time_op;
0640: 
0641:    struct oro_op
0642:    {
0643:       dhcp_option_hdr  hdr;
0644:       uint16_t req_code;
0645:    } *oro_op = (struct oro_op*) &u.options[sp.length];
0646:    oro_op->hdr.option_code = htons(OPTION_ORO);
0647:    oro_op->hdr.option_len = htons(2);
0648:    oro_op->req_code = htons(OPTION_SOL_MAX_RT);
0649:    sp.length += sizeof *oro_op;
0650: 
0651:    struct ia_pd_sent_op // trailing option, can be enlarged
0652:    {
0653:       dhcp_option_hdr  hdr;
0654:       uint32_t iaid, t1, t2;
0655:    } *ia_pd_sent_op = (struct ia_pd_sent_op*) &u.options[sp.length];
0656:    ia_pd_sent_op->hdr.option_code = htons(OPTION_IA_PD);
0657:    ia_pd_sent_op->hdr.option_len = htons(12);
0658:    ia_pd_sent_op->iaid = 0;
0659:    ia_pd_sent_op->t1 = 0;
0660:    ia_pd_sent_op->t2 = 0;
0661:    sp.length += sizeof *ia_pd_sent_op;
0662: 
0663:    received *rbuf = dhcpsend(&sp, &u.msg);
0664:    received *best = NULL;
0665:    int best_pref = 0, prefix_len = 0, choosen = 0, count = 0;
0666:    dhcp_option *choosen_prefix = NULL;
0667: 
0668:    for (received *r = rbuf; r; r = r->next)
0669:    {
0670:       unsigned char *end = (unsigned char*)&r->msg + r->size;
0671:       dhcp_option *op = find_option(OPTION_PREFERENCE, &r->msg, r->size);
0672:       unsigned op_len;
0673:       if (op)
0674:       {
0675:          op_len = ntohs(op->hdr.option_len);
0676:          if (&op->options[op_len] > end)
0677:          {
0678:             fatal("corrupted");
0679:             continue;
0680:          }
0681:       }
0682: 
0683:       int pref = op? op->options[0]: 0;
0684:       int plen = 0;
0685:       dhcp_option *prefix = NULL;
0686:       if (verbose)
0687:          printf("Advertise packet #%d, pref %d\n", ++count, pref);
0688: 
0689:       int err = 0;
0690:       op = find_option(OPTION_STATUS_CODE, &r->msg, r->size);
0691:       if (op)
0692:          err = print_status("", op, end);
0693: 
0694:       op = (dhcp_option *) &r->msg.options;
0695:       while (&op->options[3] < end) // at least the header is in
0696:       {
0697:          if (ntohs(op->hdr.option_code) == OPTION_IA_PD)
0698:          {
0699:             dhcp_option *pprefix = op;
0700:             int pplen = check_ia_pd(NULL, &pprefix, end);
0701:             if (pplen > plen)
0702:             {
0703:                plen = pplen;
0704:                prefix = pprefix;
0705:             }
0706:             else if (pplen < 0)
0707:                err |= -pplen;
0708:          }
0709:          op = (dhcp_option *)&op->options[ntohs(op->hdr.option_len)];
0710:       }
0711: 
0712:       if (verbose)
0713:          putchar('\n');
0714: 
0715:       /*
0716:       * TODO: If the client receives a valid Advertise message that
0717:       * includes a Preference option with a preference value of 255, the
0718:       * client immediately begins a client-initiated message exchange (as
0719:       * described in Section 18.2.2) by sending a Request message to the
0720:       * server from which the Advertise message was received.
0721:       */
0722:          if (err == 0 && (best == NULL || (plen > prefix_len) ||
0723:          (plen <= prefix_len && pref > best_pref)))
0724:       {
0725:          best = r;
0726:          prefix_len = plen;
0727:          best_pref = pref;
0728:          choosen = count;
0729:          choosen_prefix = prefix;
0730:       }
0731:    }
0732:    if (verbose && count > 1)
0733:       printf("Choose server #%d\n", choosen);
0734: 
0735:    /*
0736:    * Prepare request.  Insert a serverid option in front of the
0737:    * solicit message, shifting the rest upward.  Then clear rbuf.
0738:    */
0739:    int rtc = 0;
0740:    if (best)
0741:    {
0742:       if (choosen_prefix)
0743:       /*
0744:       * Add space for a ia_prefix option inside trailing ia_pd, then
0745:       * copy the selected ia_prefix.
0746:       */
0747:       {
0748:          ia_pd_sent_op->hdr.option_len = htons(12 + 29);
0749:          memcpy(&u.options[sp.length], choosen_prefix, 29);
0750:          u.options[sp.length + 3] = 25; // option length
0751:          sp.length += 29;
0752:       }
0753: 
0754:       dhcp_option *op = find_option(OPTION_SERVERID, &best->msg, best->size);
0755:       assert(op); // checked by dhcpsend()
0756:       unsigned op_len = sizeof(dhcp_option_hdr) + ntohs(op->hdr.option_len);
0757:       memmove(u.msg.options + op_len, u.msg.options, sp.length);
0758:       memcpy(u.msg.options, op, op_len);
0759:       clear_received(rbuf);
0760:       u.msg.msg_type = REQUEST;
0761:       sp.length += op_len;
0762:       sp.u.adjustable_time_elapsed += op_len;
0763:       rbuf = dhcpsend(&sp, &u.msg);
0764:       if (rbuf)
0765:       {
0766:          unsigned char *end = (unsigned char*)&rbuf->msg + rbuf->size;
0767:          if (verbose)
0768:          {
0769:             op = find_option(OPTION_STATUS_CODE, &rbuf->msg, rbuf->size);
0770:             if (op)
0771:                print_status("Reply", op, end);
0772:          }
0773: 
0774:          op = find_option(OPTION_IA_PD, &rbuf->msg, rbuf->size);
0775:          if (op)
0776:          {
0777:             rtc = check_ia_pd("Reply", &op, end);
0778:          }
0779:          free(rbuf);
0780:       }
0781:    }
0782: 
0783:    return rtc > 0? 0: -1;
0784: }
0785: 
0786: int if_address(struct ifaddrs *ifap, char const *if_name,
0787:    struct sockaddr_in6 **out)
0788: /*
0789: * Find link-local IP address on given interface and set out.
0790: * Return 0 if ok, -1 if not found and out untouched.
0791: */
0792: {
0793:    assert(ifap);
0794:    assert(if_name);
0795:    assert(out);
0796: 
0797:    for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next)
0798:    {
0799:       if (ifa->ifa_addr &&
0800:          strcmp(ifa->ifa_name, if_name) == 0 &&
0801:          ifa->ifa_addr->sa_family == AF_INET6)
0802:       {
0803:          unsigned char *addr = (unsigned char*)&((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr;
0804:          if (addr[0] == 0xfe && addr[1] == 0x80)
0805:          {
0806:             *out = (struct sockaddr_in6 *)ifa->ifa_addr;
0807:             return 0;
0808:          }
0809:       }
0810:    }
0811: 
0812:    return -1;
0813: }
0814: 
0815: /*
0816: * Config file management:
0817: *
0818: * Some constants, e.g. DUID and interface names have to be stored
0819: * in a config file.  It stays in /etc, as it shouldn't be updated
0820: * for permanent delegations.
0821: *
0822: * Have a buffer and a struct with pointers inside the buffer.
0823: * The file has sections identified by square brackets that indicate an
0824: * interface.  Each interface has name value pairs.  All items are
0825: * represented as strings terminated by either a space or a newline.
0826: * Such pointers are actually uintptr_t (long int) after buf.
0827: *
0828: * This should go into a separate file...
0829: */
0830: typedef struct c_nv_pair
0831: {
0832:    struct c_nv_pair *next_param;
0833:    uintptr_t name;
0834:    uintptr_t value;
0835:    uintptr_t length; // overall buffer space
0836:    unsigned name_length, value_length;
0837: } c_nv_pair;
0838: 
0839: typedef struct c_section
0840: {
0841:    struct c_section *next_section;
0842:    uintptr_t if_name; // including brackets -- start of section
0843:    uintptr_t length; // buffer occupied, excluding params
0844:    unsigned name_length; // excluding brackets
0845:    unsigned param_count;
0846:    c_nv_pair *first_param;
0847: } c_section;
0848: 
0849: typedef struct c_config
0850: {
0851:    size_t alloc; // allocated buf
0852:    uintptr_t length; // used part of alloc buffer
0853:    uintptr_t leading_comment; // pointer to first section name
0854:    char *fname;
0855:    char *buf;    // the config file
0856:    c_section *first_section, *current_section;
0857:    unsigned sect_count; // number of section
0858:    int dirty;
0859: } c_config;
0860: 
0861: static void assert_c_c(c_config *c_c)
0862: {
0863: #if !defined(NDEBUG)
0864:    assert(c_c);
0865:    uintptr_t sum = c_c->leading_comment;
0866:    unsigned sect_count = 0;
0867:    for (c_section *sect = c_c->first_section; sect; sect = sect->next_section)
0868:    {
0869:       unsigned param_count = 0;
0870:       assert(sect->if_name >= sum);
0871:       assert(sect->length >= sect->name_length + 3); // brackets + newline
0872:       sum += sect->length;
0873:       for (c_nv_pair *param = sect->first_param; param; param = param->next_param)
0874:       {
0875:          assert(param->name >= sum);
0876:          assert(param->value >= sum + param->name_length + 1);
0877:          assert(param->length >= param->name_length + param->value_length + 2);
0878:          sum += param->length;
0879:          ++param_count;
0880:       }
0881:       assert(param_count == sect->param_count);
0882:       ++sect_count;
0883:    }
0884:    assert(sect_count == c_c->sect_count);
0885:    assert(sum == c_c->length);
0886: #endif //!defined(NDEBUG)
0887: }
0888: 
0889: static void c_clear(c_config *c_c)
0890: {
0891:    assert(c_c);
0892: 
0893:    free(c_c->buf);
0894:    for (c_section *sect = c_c->first_section; sect; )
0895:    {
0896:       c_section *tmp = sect->next_section;
0897:       for (c_nv_pair *param = sect->first_param; param;)
0898:       {
0899:          c_nv_pair *tmp = param->next_param;
0900:          free(param);
0901:          param = tmp;
0902:       }
0903:       free(sect);
0904:       sect = tmp;
0905:    }
0906:    free(c_c);
0907: }
0908: 
0909: static c_config* c_start(char *fname)
0910: {
0911:    c_config *c_c = malloc(sizeof(c_config));
0912:    if (c_c == NULL)
0913:       return NULL;
0914: 
0915:    memset(c_c, 0, sizeof *c_c);
0916:    c_c->fname = fname;
0917: 
0918:    struct stat st;
0919:    if (stat(fname, &st) == 0)
0920:    {
0921:       if (!S_ISREG(st.st_mode))
0922:       {
0923:          fprintf(stderr, "Not a regular file %s\n", fname);
0924:          c_clear(c_c);
0925:          return NULL;
0926:       }
0927:       c_c->length = st.st_size;
0928:       c_c->alloc = c_c->length + 1024;
0929:       c_c->buf = malloc(c_c->alloc);
0930:       if (c_c->buf == NULL)
0931:       {
0932:          c_clear(c_c);
0933:          return NULL;
0934:       }
0935: 
0936:       FILE *fp = fopen(fname, "r");
0937:       if (fp == NULL || fread(c_c->buf, c_c->length, 1, fp) != 1)
0938:       {
0939:          fprintf(stderr, "Cannot read %s: %s\n", fname, strerror(errno));
0940:          c_clear(c_c);
0941:          if (fp)
0942:             fclose(fp);
0943:          return NULL;
0944:       }
0945:       fclose(fp);
0946: 
0947:       /*
0948:       * Parse the buffer and build structure
0949:       */
0950:       int ch;
0951:       char *p = c_c->buf;
0952:       char *end = p + c_c->length;
0953: 
0954:       c_section **next_section = &c_c->first_section;
0955:       c_nv_pair **next_param = NULL;
0956:       char* last_ptr = p;
0957:       uintptr_t *last_length = &c_c->leading_comment;
0958: 
0959:       if (end[-1] != '\n') // add newline if needed
0960:       {
0961:          *end++ = '\n';
0962:          ++c_c->length;
0963:       }
0964: 
0965:       *end = 0; // terminator
0966: 
0967:       int lineno = 0;
0968:       while (p < end)
0969:       {
0970:          enum {got_nothing, got_section, got_param} got_what = got_nothing;
0971:          char *b0 = NULL, *b1 = NULL, *b2 = NULL, *b3 = NULL;
0972:          while (p < end && got_what == got_nothing)
0973:          {
0974:             ++lineno;
0975:             char *eol = strchr(p, '\n');
0976:             if (eol == NULL)
0977:             {
0978:                fprintf(stderr, "Corrupted %s about line %d\n", fname, lineno);
0979:                c_clear(c_c);
0980:                return NULL;
0981:             }
0982: 
0983:             if (eol == p) // empty line
0984:             {
0985:                ++p;
0986:                continue;
0987:             }
0988: 
0989:             while (p < eol && isspace(ch = *(unsigned char*)p))
0990:                ++p;
0991:             if (ch == '[')
0992:             /*
0993:             * A section must begin with '[', no spaces, ']'.  The
0994:             * rest of line is ignored.  The name should be left than
0995:             * IF_NAMESIZE (16) —not checked.
0996:             */
0997:             {
0998:                b0 = p;
0999:                while (p < eol && !isspace(ch = *(unsigned char*)p))
1000:                   ++p;
1001:                --p;
1002:                if (p < eol && ch == ']' && p - b0 > 1)
1003:                {
1004:                   b1 = p;
1005:                   got_what = got_section;
1006:                }
1007:             }
1008:             else if (isalpha(ch) || (ch & 0xc0) == 0xc0) // UTF-8?
1009:             /*
1010:             * Very permissive pairs.  The first word is the name,
1011:             * the rest the value, until eol or comment.
1012:             */
1013:             {
1014:                b0 = p;
1015:                while (p < eol && !isspace(ch = *(unsigned char*)p))
1016:                   ++p;
1017:                b1 = p;
1018:                while (p < eol && isspace(ch = *(unsigned char*)p))
1019:                   ++p;
1020:                if (p < eol)
1021:                {
1022:                   b2 = p;
1023:                   do // parameter value until eol or comment
1024:                   {
1025:                      while (p < eol && !isspace(ch = *(unsigned char*)p))
1026:                         ++p;
1027:                      b3 = p++;
1028:                      while (p < eol && isspace(ch = *(unsigned char*)p))
1029:                         ++p;
1030:                   } while (p < eol && ch != '#');
1031:                   if (b3 - b2 > 0)
1032:                      got_what = got_param;
1033:                }
1034:             }
1035:             p = eol + 1;
1036:          }
1037:          switch (got_what)
1038:          {
1039:             case got_nothing:
1040:                break;
1041: 
1042:             case got_section:
1043:             {
1044:                c_section *sect = malloc(sizeof(c_section));
1045:                if (sect == NULL)
1046:                {
1047:                   free(c_c->buf);
1048:                   return NULL;
1049:                }
1050: 
1051:                memset(sect, 0, sizeof *sect);
1052:                sect->name_length = b1 - b0 - 1;
1053:                sect->if_name = b0 - c_c->buf;
1054:                c_c->current_section = *next_section = sect;
1055:                next_section = &sect->next_section;
1056:                ++c_c->sect_count;
1057:                *last_length = b0 - last_ptr;
1058:                last_length = &sect->length;
1059:                next_param = &sect->first_param;
1060:                last_ptr = b0;
1061:                break;
1062:             }
1063: 
1064:             case got_param:
1065:             {
1066:                if (next_param == NULL)
1067:                   break; // discard parameters out of any section
1068: 
1069:                assert(c_c->current_section); // set along with next_param
1070: 
1071:                c_nv_pair *param = malloc(sizeof(c_nv_pair));
1072:                if (param == NULL)
1073:                {
1074:                   free(c_c->buf);
1075:                   return NULL;
1076:                }
1077: 
1078:                memset(param, 0, sizeof *param);
1079:                param->name = b0 - c_c->buf;
1080:                param->name_length = b1 - b0;
1081:                param->value = b2 - c_c->buf;
1082:                param->value_length = b3 - b2;
1083:                ++c_c->current_section->param_count;
1084:                *last_length = b0 - last_ptr;
1085:                last_length = &param->length;
1086:                *next_param = param;
1087:                next_param = &param->next_param;
1088:                last_ptr = b0;
1089:                break;
1090:             }
1091:          }
1092:       }
1093:       *last_length = end - last_ptr;
1094:       assert_c_c(c_c);
1095:       return c_c;
1096:    }
1097:    else if (errno == ENOENT)  // create new config file
1098:    {
1099:       return c_c;
1100:    }
1101: 
1102:    fprintf(stderr, "Cannot stat %s: %s\n", fname, strerror(errno));
1103:    free(c_c);
1104:    return NULL;
1105: }
1106: 
1107: static void c_finish(c_config *c_c)
1108: {
1109:    assert_c_c(c_c);
1110: 
1111:    if (c_c->fname && c_c->buf && c_c->length && c_c->dirty)
1112:    {
1113:       FILE *fp = fopen(c_c->fname, "w");
1114:       if (fp == NULL || fwrite(c_c->buf, c_c->length, 1, fp) != 1)
1115:          fprintf(stderr, "Cannot save %s: %s\n", c_c->fname, strerror(errno));
1116:       if (fp)
1117:          fclose(fp);
1118:    }
1119: 
1120:    c_clear(c_c);
1121: }
1122: 
1123: inline static char* c_ptr(c_config *c_c, uintptr_t offset)
1124: {
1125:    assert_c_c(c_c);
1126: 
1127:    return c_c->buf? c_c->buf + offset: NULL;
1128: }
1129: 
1130: static int c_grow_buf(c_config *c_c, size_t add_size)
1131: {
1132:    assert_c_c(c_c);
1133: 
1134:    c_c->dirty = 1;
1135:    if (c_c->length + add_size < c_c->alloc)
1136:       return 0;
1137: 
1138:    size_t new_alloc = c_c->alloc + 4096;
1139:    if (new_alloc <= c_c->length + add_size)
1140:       new_alloc += c_c->length + add_size;
1141: 
1142:    char *new_buf = realloc(c_c->buf, new_alloc);
1143:    if (new_buf == NULL)
1144:    {
1145:       free(c_c->buf);
1146:       c_c->buf = NULL;
1147:       return -1;
1148:    }
1149: 
1150:    if (c_c->buf == NULL)
1151:    {
1152:       c_c->leading_comment = sprintf(new_buf,
1153:          "#\n# Parameter file for %s\n",
1154:          program_name);
1155:       new_buf[c_c->leading_comment] = '\n';
1156:       assert(c_c->length == 0);
1157:       c_c->length = c_c->leading_comment;
1158:    }
1159:    c_c->buf = new_buf;
1160:    c_c->alloc = new_alloc;
1161: 
1162:    assert_c_c(c_c);
1163:    return 0;
1164: }
1165: 
1166: static c_section *c_add_section(c_config *c_c, char const *if_name)
1167: /*
1168: * Create new section and append it to buffer
1169: */
1170: {
1171:    assert_c_c(c_c);
1172: 
1173:    c_section *sect = malloc(sizeof(c_section));
1174:    if (sect)
1175:    {
1176:       memset(sect, 0, sizeof *sect);
1177:       unsigned name_length = strlen(if_name);
1178:       unsigned sect_length = name_length + 3;
1179:       if (c_grow_buf(c_c, sect_length))  // brackets + newline
1180:       {
1181:          free(sect);
1182:          return NULL;
1183:       }
1184:       sect->if_name = c_c->length;
1185:       uintptr_t section_end = sect->if_name + sect_length;
1186:       char save_term = c_c->buf[section_end];
1187:       sprintf(c_c->buf + sect->if_name, "[%s]\n", if_name);
1188:       c_c->buf[section_end] = save_term;
1189:       sect->name_length = name_length;
1190:       sect->length = sect_length;
1191:       c_c->length += sect_length;
1192: 
1193:       // link
1194:       c_section **last = &c_c->first_section;
1195:       while (*last)
1196:          last = &(*last)->next_section;
1197:       *last = c_c->current_section = sect;
1198:       ++c_c->sect_count;
1199:    }
1200: 
1201:    assert_c_c(c_c);
1202:    return sect;
1203: }
1204: 
1205: static c_section *c_find_section(c_config *c_c, char const *if_name)
1206: /*
1207: * Find section if exists
1208: */
1209: {
1210:    assert_c_c(c_c);
1211: 
1212:    unsigned name_length = strlen(if_name);
1213:    c_section *sect = c_c->first_section;
1214:    while (sect)
1215:    {
1216:       if (sect->name_length == name_length &&
1217:          strncmp(if_name, c_ptr(c_c, sect->if_name + 1), name_length) == 0)
1218:             return c_c->current_section = sect;
1219:       sect = sect->next_section;
1220:    }
1221: 
1222:    return NULL;
1223: }
1224: 
1225: static c_nv_pair *c_add_param(c_config *c_c, char const *name, char const *value)
1226: /*
1227: * Create new param in the current section and append to buffer
1228: */
1229: {
1230:    assert_c_c(c_c);
1231:    assert(name);
1232:    assert(value);
1233:    assert(c_c->current_section);
1234: 
1235: 
1236:    c_nv_pair *param = malloc(sizeof(c_nv_pair));
1237:    if (param)
1238:    {
1239:       memset(param, 0, sizeof *param);
1240:       param->name_length = strlen(name);
1241:       param->value_length = strlen(value);
1242: 
1243:       param->length = param->name_length + param->value_length + 2;
1244:       if (c_grow_buf(c_c, param->length))
1245:       {
1246:          free(param);
1247:          return NULL;
1248:       }
1249: 
1250:       c_section *sect = c_c->current_section;
1251:       c_nv_pair **last = &sect->first_param;
1252:       uintptr_t length = sect->length;
1253:       while (*last)
1254:       {
1255:          length += (*last)->length;
1256:          last = &(*last)->next_param;
1257:       }
1258:       *last = param;
1259:       param->name = sect->if_name + length;
1260:       param->value = param->name + param->name_length + 1;
1261:       ++sect->param_count;
1262:       c_c->length += param->length;
1263: 
1264:       if (sect->next_section) // shift the rest by the added length
1265:       {
1266:          sect = sect->next_section;
1267:          length = param->length;
1268:          memmove(c_c->buf + sect->if_name + length,
1269:             c_c->buf + sect->if_name, /* don't call c_ptr */
1270:             c_c->length - length - sect->if_name);
1271:          do
1272:          {
1273:             sect->if_name += length;
1274:             for (c_nv_pair *p = sect->first_param; p; p = p->next_param)
1275:             {
1276:                p->name += length;
1277:                p->value += length;
1278:             }
1279:             sect = sect->next_section;
1280:          } while (sect);
1281:       }
1282:       uintptr_t param_end = param->value + param->value_length + 1;
1283:       char save_term = c_c->buf[param_end];
1284:       sprintf(c_ptr(c_c, param->name), "%s %s\n", name, value);
1285:       c_c->buf[param_end] = save_term;
1286:    }
1287: 
1288:    assert_c_c(c_c);
1289:    return param;
1290: }
1291: 
1292: static c_nv_pair *c_find_param(c_config *c_c, char const *name)
1293: /*
1294: * Find param in current section if it exiists
1295: */
1296: {
1297:    assert_c_c(c_c);
1298:    assert(c_c->current_section);
1299: 
1300:    unsigned name_length = strlen(name);
1301:    c_section *sect = c_c->current_section;
1302:    for (c_nv_pair *param = sect->first_param; param; param = param->next_param)
1303:       if (param->name_length == name_length &&
1304:          strncmp(name, c_ptr(c_c, param->name), name_length) == 0)
1305:             return param;
1306: 
1307:    return NULL;
1308: }
1309: ////////////////// end of config utilities ///////////////
1310: 
1311: static inline char *my_basename(char const *name) // neither GNU nor POSIX...
1312: {
1313:     char *b = strrchr(name, '/');
1314:     if (b)
1315:         return b + 1;
1316:     return (char*)name;
1317: }
1318: 
1319: int main(int argc, char *argv[])
1320: {
1321:    program_name = my_basename(argv[0]);
1322:    union
1323:    {
1324:       unsigned short int seed16v[3];
1325:       time_t tt[2];
1326:    } seed;
1327:    srandom(time(&seed.tt[0]));
1328:    seed.tt[1] = random();
1329:    seed48(seed.seed16v);
1330: 
1331:    int no_opt = 0;
1332:    char *config_file = "/etc/dhcpv6cli.conf";
1333:    enum expect {expect_nothing, expect_config} expect_opt = expect_nothing;
1334:    int expect_arg = 0;
1335:    int errs = 0;
1336:    int first_if = argc;
1337:    for (int i = 1; i < argc; ++i)
1338:    {
1339:       char *a = argv[i];
1340:       if (a[0] == '-' && !no_opt)
1341:       {
1342:          int ch = *(unsigned char *)++a;
1343:          while (ch)
1344:          {
1345:             switch (ch)
1346:             {
1347:                case '-':
1348:                   no_opt = 1;
1349:                   break;
1350:                case 'd':
1351:                   ++debug;
1352:                   break;
1353:                case 'f':
1354:                   expect_opt = expect_config;
1355:                   expect_arg = i;
1356:                   break;
1357:                case 'v':
1358:                   ++verbose;
1359:                   break;
1360:                default:
1361:                   fprintf(stderr, "invalid opt %c in %s\n",
1362:                      ch, argv[i]);
1363:                   ++errs;
1364:                   break;
1365:             }
1366:             ch = *(unsigned char *)++a;
1367:          }
1368:          continue;
1369:       }
1370:       else if (expect_opt)
1371:       {
1372:          switch(expect_opt)
1373:          {
1374:             case expect_config:
1375:                config_file = a;
1376:                expect_opt = expect_nothing;
1377:                break;
1378:             default:
1379:                fprintf(stderr, "invalid option %s\n", a);
1380:                ++errs;
1381:                break;
1382:          }
1383:       }
1384:       else
1385:       {
1386:          first_if = i;
1387:          break;
1388:       }
1389:    }
1390:    if (expect_opt)
1391:    {
1392:       fprintf(stderr, "option %s requires an argument\n", argv[expect_arg]);
1393:       ++errs;
1394:    }
1395:    if (errs)
1396:       return 1;
1397: 
1398:    c_config *c_c = c_start(config_file);
1399:    if (c_c == NULL)
1400:    {
1401:       return 1;
1402:    }
1403: 
1404: #if defined(TEST_C_CONFIG)
1405:    /*
1406:    * Exercise c_config functions.
1407:    */
1408: 
1409:    if (c_find_section(c_c, "foo") == NULL &&
1410:       c_add_section(c_c, "foo") == NULL)
1411:    {
1412:       fprintf(stderr, "Cannot add section foo\n");
1413:       return 1;
1414:    }
1415: 
1416:    c_nv_pair *param = c_find_param(c_c, "parameter1");
1417:    if (param == NULL)
1418:       c_add_param(c_c, "parameter1", "initial value of parameter 1");
1419:    param = c_find_param(c_c, "parameter2");
1420:    if (param == NULL)
1421:       c_add_param(c_c, "parameter2", "initial value of parameter 2");
1422: 
1423:    if (c_find_section(c_c, "bar") == NULL &&
1424:       c_add_section(c_c, "bar") == NULL)
1425:    {
1426:       fprintf(stderr, "Cannot add section foo\n");
1427:       return 1;
1428:    }
1429: 
1430:    param = c_find_param(c_c, "param");
1431:    if (param == NULL)
1432:       c_add_param(c_c, "param", "initial value of param in [bar]");
1433:    param = c_find_param(c_c, "παραμ");
1434:    if (param == NULL)
1435:       c_add_param(c_c, "παραμ", "αρχική τιμή της παραμ σε [bar]");
1436: 
1437:    if (c_find_section(c_c, "foo") == NULL)
1438:       assert(0);
1439: 
1440:    param = c_find_param(c_c, "parameter3");
1441:    if (param == NULL)
1442:       c_add_param(c_c, "parameter3", "initial value of parameter 3");
1443: 
1444: 
1445:    // print all params
1446:    printf("%s has %d sections:\n", config_file, c_c->sect_count);
1447:    for (c_section *sect = c_c->first_section; sect; sect = sect->next_section)
1448:    {
1449:       printf("[%.*s] # %d parameters\n",
1450:          sect->name_length, c_ptr(c_c, sect->if_name + 1), sect->param_count);
1451:       for (param = sect->first_param; param; param = param->next_param)
1452:          printf("%.*s = \"%.*s\"\n",
1453:             param->name_length, c_ptr(c_c, param->name),
1454:             param->value_length, c_ptr(c_c, param->value));
1455:    }
1456: #endif // defined(TEST_C_CONFIG)
1457: 
1458:    static const char client_id_name[] = "Client-ID";
1459:    static const char sol_max_rt_name[] = "SOL_MAX_RT";
1460: 
1461:    struct ifaddrs *ifap;
1462:    if (getifaddrs (&ifap))
1463:    {
1464:       fprintf(stderr, "Cannot get interface addresses: %s\n", strerror(errno));
1465:       c_clear(c_c);
1466:       return 1;
1467:    }
1468: 
1469:    for (int i = first_if; i < argc; ++i)
1470:    {
1471:       struct sockaddr_in6 *from = NULL;
1472:       if (if_address(ifap, argv[i], &from))
1473:       {
1474:          fprintf(stderr, "Interface %s not found\n", argv[i]);
1475:          ++errs;
1476:          continue;
1477:       }
1478: 
1479:       uuid_t client_id;
1480:       uint32_t sol_max_rt;
1481:       c_nv_pair *param = NULL;
1482:       c_section *if_sect = c_find_section(c_c, argv[i]);
1483: 
1484:       if (if_sect)
1485:       {
1486:          c_nv_pair *sol_param = c_find_param(c_c, sol_max_rt_name);
1487:          if (sol_param)
1488:          {
1489:             char *value =  c_ptr(c_c, sol_param->value);
1490:             char *term = value + sol_param->value_length,
1491:                save_term = *term;
1492:             *term = 0;
1493:             sol_max_rt = atoi(value);
1494:             *term = save_term;
1495:          }
1496:          else
1497:             sol_max_rt = SOL_MAX_RT;
1498:          param = c_find_param(c_c, client_id_name);
1499:       }
1500: 
1501:       if (param) // use stored DUID
1502:       {
1503:          char *p = c_ptr(c_c, param->value);
1504:          char *term = p + param->value_length, save_term = *term;
1505:          *term = 0;
1506:          int rtc = uuid_parse(p, client_id);
1507:          *term = save_term;
1508:          // if (uuid_parse_range(p, p + param->value_length, client_id))
1509:          if (rtc)
1510:          {
1511:             fprintf(stderr, "Corrupted value %.*s in %s\n",
1512:                param->value_length, p, config_file);
1513:             ++errs;
1514:             continue;
1515:          }
1516:       }
1517:       else // generate DUID and save it
1518:       {
1519:          char p[40];
1520:          uuid_generate(client_id);
1521:          if (!if_sect && c_add_section(c_c, argv[i]) == NULL)
1522:          {
1523:             fprintf(stderr, "Cannot add section %s to %s\n",
1524:                argv[i], config_file);
1525:             ++errs;
1526:             continue;
1527:          }
1528:          uuid_unparse(client_id, p);
1529:          c_add_param(c_c, client_id_name, p);
1530:       }
1531: 
1532:       if (dhcpcli(argv[i], from, client_id, sol_max_rt))
1533:          ++errs;
1534:    }
1535: 
1536:    freeifaddrs(ifap);
1537:    c_finish(c_c);
1538:    return errs > 0;
1539: }
1540: 
1541: 
1542: 
1543: 
1544: 
1545: 
1546: 
1547: 
1548: 
1549: 
1550: 
1551: 
1552: 
1553: 
1554: 
1555: 
1556: 
1557: 
1558: 
1559: 
zero rights

The program doesn't (yet) check that the delegation went as expected. Copy and paste the script to your editor of choice. Note that it uses \n line endings. Add \r if needed. Save it as dhcpv6cli.c. Last update was today, Mon 22 May 2023.