bpf.c revision ffd68729961f7383f2e35494a03ccdef20f86c98
1/* dnsmasq is Copyright (c) 2000-2009 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, or
6   (at your option) version 3 dated 29 June, 2007.
7
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   GNU General Public License for more details.
12
13   You should have received a copy of the GNU General Public License
14   along with this program.  If not, see <http://www.gnu.org/licenses/>.
15*/
16
17#include "dnsmasq.h"
18
19#if defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK)
20
21static struct iovec ifconf = {
22  .iov_base = NULL,
23  .iov_len = 0
24};
25
26static struct iovec ifreq = {
27  .iov_base = NULL,
28  .iov_len = 0
29};
30
31int iface_enumerate(void *parm, int (*ipv4_callback)(), int (*ipv6_callback)())
32{
33  char *ptr;
34  struct ifreq *ifr;
35  struct ifconf ifc;
36  int fd, errsav, ret = 0;
37  int lastlen = 0;
38  size_t len = 0;
39
40  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
41    return 0;
42
43  while(1)
44    {
45      len += 10*sizeof(struct ifreq);
46
47      if (!expand_buf(&ifconf, len))
48	goto err;
49
50      ifc.ifc_len = len;
51      ifc.ifc_buf = ifconf.iov_base;
52
53      if (ioctl(fd, SIOCGIFCONF, &ifc) == -1)
54	{
55	  if (errno != EINVAL || lastlen != 0)
56	    goto err;
57	}
58      else
59	{
60	  if (ifc.ifc_len == lastlen)
61	    break; /* got a big enough buffer now */
62	  lastlen = ifc.ifc_len;
63	}
64    }
65
66  for (ptr = ifc.ifc_buf; ptr < (char *)(ifc.ifc_buf + ifc.ifc_len); ptr += len)
67    {
68      /* subsequent entries may not be aligned, so copy into
69	 an aligned buffer to avoid nasty complaints about
70	 unaligned accesses. */
71
72      len = sizeof(struct ifreq);
73
74#ifdef HAVE_SOCKADDR_SA_LEN
75      ifr = (struct ifreq *)ptr;
76      if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru))
77	len = ifr->ifr_addr.sa_len + offsetof(struct ifreq, ifr_ifru);
78#endif
79
80      if (!expand_buf(&ifreq, len))
81	goto err;
82
83      ifr = (struct ifreq *)ifreq.iov_base;
84      memcpy(ifr, ptr, len);
85
86      if (ifr->ifr_addr.sa_family == AF_INET && ipv4_callback)
87	{
88	  struct in_addr addr, netmask, broadcast;
89	  broadcast.s_addr = 0;
90	  addr = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr;
91	  if (ioctl(fd, SIOCGIFNETMASK, ifr) == -1)
92	    continue;
93	  netmask = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr;
94	  if (ioctl(fd, SIOCGIFBRDADDR, ifr) != -1)
95	    broadcast = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr;
96	  if (!((*ipv4_callback)(addr,
97				 (int)if_nametoindex(ifr->ifr_name),
98				 netmask, broadcast,
99				 parm)))
100	    goto err;
101	}
102#ifdef HAVE_IPV6
103      else if (ifr->ifr_addr.sa_family == AF_INET6 && ipv6_callback)
104	{
105	  struct in6_addr *addr = &((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_addr;
106	  /* voodoo to clear interface field in address */
107	  if (!(daemon->options & OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
108	    {
109	      addr->s6_addr[2] = 0;
110	      addr->s6_addr[3] = 0;
111	    }
112	  if (!((*ipv6_callback)(addr,
113				 (int)((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_scope_id,
114				 (int)if_nametoindex(ifr->ifr_name),
115				 parm)))
116	    goto err;
117	}
118#endif
119    }
120
121  ret = 1;
122
123 err:
124  errsav = errno;
125  close(fd);
126  errno = errsav;
127
128  return ret;
129}
130#endif
131
132
133#if defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP)
134#include <net/bpf.h>
135
136void init_bpf(void)
137{
138  int i = 0;
139
140  while (1)
141    {
142      /* useful size which happens to be sufficient */
143      if (expand_buf(&ifreq, sizeof(struct ifreq)))
144	{
145	  sprintf(ifreq.iov_base, "/dev/bpf%d", i++);
146	  if ((daemon->dhcp_raw_fd = open(ifreq.iov_base, O_RDWR, 0)) != -1)
147	    return;
148	}
149      if (errno != EBUSY)
150	die(_("cannot create DHCP BPF socket: %s"), NULL, EC_BADNET);
151    }
152}
153
154void send_via_bpf(struct dhcp_packet *mess, size_t len,
155		  struct in_addr iface_addr, struct ifreq *ifr)
156{
157   /* Hairy stuff, packet either has to go to the
158      net broadcast or the destination can't reply to ARP yet,
159      but we do know the physical address.
160      Build the packet by steam, and send directly, bypassing
161      the kernel IP stack */
162
163  struct ether_header ether;
164  struct ip ip;
165  struct udphdr {
166    u16 uh_sport;               /* source port */
167    u16 uh_dport;               /* destination port */
168    u16 uh_ulen;                /* udp length */
169    u16 uh_sum;                 /* udp checksum */
170  } udp;
171
172  u32 i, sum;
173  struct iovec iov[4];
174
175  /* Only know how to do ethernet on *BSD */
176  if (mess->htype != ARPHRD_ETHER || mess->hlen != ETHER_ADDR_LEN)
177    {
178      my_syslog(MS_DHCP | LOG_WARNING, _("DHCP request for unsupported hardware type (%d) received on %s"),
179		mess->htype, ifr->ifr_name);
180      return;
181    }
182
183  ifr->ifr_addr.sa_family = AF_LINK;
184  if (ioctl(daemon->dhcpfd, SIOCGIFADDR, ifr) < 0)
185    return;
186
187  memcpy(ether.ether_shost, LLADDR((struct sockaddr_dl *)&ifr->ifr_addr), ETHER_ADDR_LEN);
188  ether.ether_type = htons(ETHERTYPE_IP);
189
190  if (ntohs(mess->flags) & 0x8000)
191    {
192      memset(ether.ether_dhost, 255,  ETHER_ADDR_LEN);
193      ip.ip_dst.s_addr = INADDR_BROADCAST;
194    }
195  else
196    {
197      memcpy(ether.ether_dhost, mess->chaddr, ETHER_ADDR_LEN);
198      ip.ip_dst.s_addr = mess->yiaddr.s_addr;
199    }
200
201  ip.ip_p = IPPROTO_UDP;
202  ip.ip_src.s_addr = iface_addr.s_addr;
203  ip.ip_len = htons(sizeof(struct ip) +
204		    sizeof(struct udphdr) +
205		    len) ;
206  ip.ip_hl = sizeof(struct ip) / 4;
207  ip.ip_v = IPVERSION;
208  ip.ip_tos = 0;
209  ip.ip_id = htons(0);
210  ip.ip_off = htons(0x4000); /* don't fragment */
211  ip.ip_ttl = IPDEFTTL;
212  ip.ip_sum = 0;
213  for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
214    sum += ((u16 *)&ip)[i];
215  while (sum>>16)
216    sum = (sum & 0xffff) + (sum >> 16);
217  ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
218
219  udp.uh_sport = htons(daemon->dhcp_server_port);
220  udp.uh_dport = htons(daemon->dhcp_client_port);
221  if (len & 1)
222    ((char *)mess)[len] = 0; /* for checksum, in case length is odd. */
223  udp.uh_sum = 0;
224  udp.uh_ulen = sum = htons(sizeof(struct udphdr) + len);
225  sum += htons(IPPROTO_UDP);
226  sum += ip.ip_src.s_addr & 0xffff;
227  sum += (ip.ip_src.s_addr >> 16) & 0xffff;
228  sum += ip.ip_dst.s_addr & 0xffff;
229  sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
230  for (i = 0; i < sizeof(struct udphdr)/2; i++)
231    sum += ((u16 *)&udp)[i];
232  for (i = 0; i < (len + 1) / 2; i++)
233    sum += ((u16 *)mess)[i];
234  while (sum>>16)
235    sum = (sum & 0xffff) + (sum >> 16);
236  udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
237
238  ioctl(daemon->dhcp_raw_fd, BIOCSETIF, ifr);
239
240  iov[0].iov_base = &ether;
241  iov[0].iov_len = sizeof(ether);
242  iov[1].iov_base = &ip;
243  iov[1].iov_len = sizeof(ip);
244  iov[2].iov_base = &udp;
245  iov[2].iov_len = sizeof(udp);
246  iov[3].iov_base = mess;
247  iov[3].iov_len = len;
248
249  while (writev(daemon->dhcp_raw_fd, iov, 4) == -1 && retry_send());
250}
251
252#endif
253
254
255