1/* Copyright (c) 2007 Simon Kelley
2
3   This program is free software; you can redistribute it and/or modify
4   it under the terms of the GNU General Public License as published by
5   the Free Software Foundation; version 2 dated June, 1991.
6
7   This program is distributed in the hope that it will be useful,
8   but WITHOUT ANY WARRANTY; without even the implied warranty of
9   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10   GNU General Public License for more details.
11*/
12
13/* dhcp_lease_time <address> */
14
15/* Send a DHCPINFORM message to a dnsmasq server running on the local host
16   and print (to stdout) the time remaining in any lease for the given
17   address. The time is given as string printed to stdout.
18
19   If an error occurs or no lease exists for the given address,
20   nothing is sent to stdout a message is sent to stderr and a
21   non-zero error code is returned.
22
23   Requires dnsmasq 2.40 or later.
24*/
25
26#include <sys/types.h>
27#include <netinet/in.h>
28#include <net/if.h>
29#include <arpa/inet.h>
30#include <sys/socket.h>
31#include <unistd.h>
32#include <stdio.h>
33#include <string.h>
34#include <stdlib.h>
35#include <net/if_arp.h>
36#include <sys/ioctl.h>
37#include <linux/types.h>
38#include <linux/netlink.h>
39#include <linux/rtnetlink.h>
40#include <errno.h>
41
42#define DHCP_CHADDR_MAX          16
43#define BOOTREQUEST              1
44#define DHCP_COOKIE              0x63825363
45#define OPTION_PAD               0
46#define OPTION_LEASE_TIME        51
47#define OPTION_OVERLOAD          52
48#define OPTION_MESSAGE_TYPE      53
49#define OPTION_END               255
50#define DHCPINFORM               8
51#define DHCP_SERVER_PORT         67
52
53#define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
54#define option_ptr(opt) ((void *)&(((unsigned char *)(opt))[2]))
55
56
57typedef unsigned char u8;
58typedef unsigned short u16;
59typedef unsigned int u32;
60
61struct dhcp_packet {
62  u8 op, htype, hlen, hops;
63  u32 xid;
64  u16 secs, flags;
65  struct in_addr ciaddr, yiaddr, siaddr, giaddr;
66  u8 chaddr[DHCP_CHADDR_MAX], sname[64], file[128];
67  u32 cookie;
68  unsigned char options[308];
69};
70
71static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize)
72{
73  while (*p != OPTION_END)
74    {
75      if (p >= end)
76        return NULL; /* malformed packet */
77      else if (*p == OPTION_PAD)
78        p++;
79      else
80        {
81          int opt_len;
82          if (p >= end - 2)
83            return NULL; /* malformed packet */
84          opt_len = option_len(p);
85          if (p >= end - (2 + opt_len))
86            return NULL; /* malformed packet */
87          if (*p == opt && opt_len >= minsize)
88            return p;
89          p += opt_len + 2;
90        }
91    }
92
93  return opt == OPTION_END ? p : NULL;
94}
95
96static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize)
97{
98  unsigned char *ret, *overload;
99
100  /* skip over DHCP cookie; */
101  if ((ret = option_find1(&mess->options[0], ((unsigned char *)mess) + size, opt_type, minsize)))
102    return ret;
103
104  /* look for overload option. */
105  if (!(overload = option_find1(&mess->options[0], ((unsigned char *)mess) + size, OPTION_OVERLOAD, 1)))
106    return NULL;
107
108  /* Can we look in filename area ? */
109  if ((overload[2] & 1) &&
110      (ret = option_find1(&mess->file[0], &mess->file[128], opt_type, minsize)))
111    return ret;
112
113  /* finally try sname area */
114  if ((overload[2] & 2) &&
115      (ret = option_find1(&mess->sname[0], &mess->sname[64], opt_type, minsize)))
116    return ret;
117
118  return NULL;
119}
120
121static unsigned int option_uint(unsigned char *opt, int size)
122{
123  /* this worries about unaligned data and byte order */
124  unsigned int ret = 0;
125  int i;
126  unsigned char *p = option_ptr(opt);
127
128  for (i = 0; i < size; i++)
129    ret = (ret << 8) | *p++;
130
131  return ret;
132}
133
134int main(int argc, char **argv)
135{
136  struct in_addr lease;
137  struct dhcp_packet packet;
138  unsigned char *p = packet.options;
139  struct sockaddr_in dest;
140  int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
141  ssize_t rc;
142
143  if (argc < 2)
144    {
145      fprintf(stderr, "usage: dhcp_lease_time <address>\n");
146      exit(1);
147    }
148
149  if (fd == -1)
150    {
151      perror("cannot create socket");
152      exit(1);
153    }
154
155  lease.s_addr = inet_addr(argv[1]);
156
157  memset(&packet, 0, sizeof(packet));
158
159  packet.hlen = 0;
160  packet.htype = 0;
161
162  packet.op = BOOTREQUEST;
163  packet.ciaddr = lease;
164  packet.cookie = htonl(DHCP_COOKIE);
165
166  *(p++) = OPTION_MESSAGE_TYPE;
167  *(p++) = 1;
168  *(p++) = DHCPINFORM;
169
170  *(p++) = OPTION_END;
171
172  dest.sin_family = AF_INET;
173  dest.sin_addr.s_addr = inet_addr("127.0.0.1");
174  dest.sin_port = ntohs(DHCP_SERVER_PORT);
175
176  if (sendto(fd, &packet, sizeof(packet), 0,
177	     (struct sockaddr *)&dest, sizeof(dest)) == -1)
178    {
179      perror("sendto failed");
180      exit(1);
181    }
182
183  alarm(3); /* noddy timeout. */
184
185  rc = recv(fd, &packet, sizeof(packet), 0);
186
187  if (rc < (ssize_t)(sizeof(packet) - sizeof(packet.options)))
188    {
189      perror("recv failed");
190      exit(1);
191    }
192
193  if ((p = option_find(&packet, (size_t)rc, OPTION_LEASE_TIME, 4)))
194    {
195      unsigned int t = option_uint(p, 4);
196      if (t == 0xffffffff)
197	printf("infinite");
198      else
199	{
200	  unsigned int x;
201	  if ((x = t/86400))
202	    printf("%dd", x);
203	  if ((x = (t/3600)%24))
204	    printf("%dh", x);
205	  if ((x = (t/60)%60))
206	    printf("%dm", x);
207	  if ((x = t%60))
208	    printf("%ds", x);
209	}
210      return 0;
211    }
212
213  return 1; /* no lease */
214}
215