--- /dev/null
+.\" $OpenBSD: dhcp6leasectl.8,v 1.1 2024/06/06 15:16:57 florian Exp $
+.\"
+.\" Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+.\" Copyright (c) 2016 Kenneth R Westerback <kwesterback@gmail.com>
+.\" Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: June 6 2024 $
+.Dt DHCP6LEASECTL 8
+.Os
+.Sh NAME
+.Nm dhcp6leasectl
+.Nd control the dhcp6leased client
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl s Ar socket
+.Op Fl w Ar maxwait
+.Ar interface
+.Sh DESCRIPTION
+The
+.Nm
+program instructs the
+.Xr dhcp6leased 8
+daemon to request a new lease.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl l
+List the configured lease on
+.Ar interface
+instead of requesting a new lease.
+.It Fl s Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /dev/dhcp6leased.sock
+to communicate with
+.Xr dhcp6leased 8 .
+.It Fl w Ar maxwait
+Specify the maximum number of seconds to wait for
+.Ar interface
+to be configured.
+The default is to wait 10 seconds unless
+.Fl l
+is specified.
+.El
+.Sh FILES
+.Bl -tag -width "/dev/dhcp6leased.sockXX" -compact
+.It Pa /dev/dhcp6leased.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr dhcp6leased 8 .
+.El
+.Sh SEE ALSO
+.Xr dhcp6leased.conf 5 ,
+.Xr dhcp6leased 8
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 7.6 .
--- /dev/null
+/* $OpenBSD: dhcp6leasectl.c,v 1.1 2024/06/06 15:16:57 florian Exp $ */
+
+/*
+ * Copyright (c) 2021, 2024 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dhcp6leased.h"
+
+__dead void usage(void);
+void show_interface_msg(struct ctl_engine_info *);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-l] [-s socket] [-w maxwait] interface\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un sun;
+ struct imsg imsg;
+ struct ctl_engine_info *cei;
+ int ctl_sock;
+ int n, lFlag = 0, maxwait_set = 0, didot = 0;
+ int ch, if_index = 0, maxwait = 10, bound = 0;
+ char *sockname;
+ const char *errstr;
+
+ sockname = _PATH_CTRL_SOCKET;
+ while ((ch = getopt(argc, argv, "ls:w:")) != -1) {
+ switch (ch) {
+ case 'l':
+ lFlag = 1;
+ break;
+ case 's':
+ sockname = optarg;
+ break;
+ case 'w':
+ maxwait_set = 1;
+ maxwait = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "maxwait value is %s: %s",
+ errstr, optarg);
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if ((if_index = if_nametoindex(argv[0])) == 0)
+ errx(1, "unknown interface");
+
+ if (lFlag && !maxwait_set)
+ maxwait = 0;
+
+ /* Connect to control socket. */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
+
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", sockname);
+
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+
+ if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+ err(1, NULL);
+ imsg_init(ibuf, ctl_sock);
+
+ if (!lFlag) {
+ imsg_compose(ibuf, IMSG_CTL_SEND_REQUEST, 0, 0, -1,
+ &if_index, sizeof(if_index));
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+ err(1, "write error");
+
+ }
+
+ for(;;) {
+ imsg_compose(ibuf, IMSG_CTL_SHOW_INTERFACE_INFO, 0, 0, -1,
+ &if_index, sizeof(if_index));
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+ err(1, "write error");
+
+
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+
+ if (imsg.hdr.type == IMSG_CTL_END) {
+ if (lFlag)
+ errx(1, "non-autoconf interface %s", argv[0]);
+ else if (--maxwait < 0)
+ break;
+ else
+ continue;
+ }
+
+ cei = imsg.data;
+ if (strcmp(cei->state, "Bound") == 0)
+ bound = 1;
+
+ if (bound || --maxwait < 0) {
+ if (didot)
+ putchar('\n');
+ show_interface_msg(cei);
+ break;
+ } else {
+ didot = 1;
+ putchar('.');
+ fflush(stdout);
+ }
+ imsg_free(&imsg);
+ sleep(1);
+ }
+ close(ctl_sock);
+ free(ibuf);
+
+ return (0);
+}
+
+void
+show_interface_msg(struct ctl_engine_info *cei)
+{
+ struct timespec now, diff;
+ time_t d, h, m, s;
+ int i, has_pd = 0;
+ char buf[IF_NAMESIZE], *bufp;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ bufp = if_indextoname(cei->if_index, buf);
+ printf("%s [%s]\n", bufp != NULL ? bufp : "unknown", cei->state);
+
+ for (i = 0; i < MAX_IA; i++) {
+ if (cei->pds[i].prefix_len == 0)
+ continue;
+ has_pd = 1;
+ printf ("\tIA_PD %d: %s/%d\n", i, inet_ntop(AF_INET6,
+ &cei->pds[i], ntopbuf, INET6_ADDRSTRLEN),
+ cei->pds[i].prefix_len);
+ }
+
+ if (has_pd) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &cei->request_time, &diff);
+ s = cei->lease_time - diff.tv_sec;
+ if (s < 0)
+ s = 0;
+
+ if ( s > 86400 ) {
+ d = s / 86400;
+
+ /* round up */
+ if (s - d * 86400 > 43200)
+ d++;
+ printf("\tlease %lld day%s\n", d, d > 1 ? "s" : "");
+ } else if (s > 3600) {
+ h = s / 3600;
+
+ /* round up */
+ if (s - h * 3600 > 1800)
+ h++;
+ printf("\tlease %lld hour%s\n", h, h > 1 ? "s" : "");
+ } else if (s > 60) {
+ m = s / 60;
+
+ /* round up */
+ if (s - m * 60 > 30)
+ m++;
+ printf("\tlease %lld minute%s\n", m, m > 1 ? "s" : "");
+ } else
+ printf("\tlease %lld second%s\n", s, s > 1 ? "s" : "");
+
+ }
+}