1/*
2 * Copyright (c) 2000-2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * bootp_session.c
26 * - maintain BOOTP client socket session
27 * - maintain list of BOOTP clients
28 * - distribute packet reception to enabled clients
29 */
30/*
31 * Modification History
32 *
33 * May 10, 2000		Dieter Siegmund (dieter@apple.com)
34 * - created
35 */
36
37
38#include <stdlib.h>
39#include <unistd.h>
40#include <string.h>
41#include <stdio.h>
42#include <sys/types.h>
43#include <sys/wait.h>
44#include <sys/errno.h>
45#include <sys/socket.h>
46#include <sys/ioctl.h>
47#include <sys/sockio.h>
48#include <sys/filio.h>
49#include <ctype.h>
50#include <net/if.h>
51#include <net/ethernet.h>
52#include <netinet/in.h>
53#include <netinet/udp.h>
54#include <netinet/in_systm.h>
55#include <netinet/ip.h>
56#include <netinet/bootp.h>
57#include <arpa/inet.h>
58#include <net/if_types.h>
59#include <net/if_dl.h>
60#include "dhcp_options.h"
61#include "util.h"
62#include <syslog.h>
63#include <sys/uio.h>
64
65
66#include "dhcplib.h"
67#include "dynarray.h"
68#include "bootp_session.h"
69#include "bootp_transmit.h"
70#include "ipconfigd_globals.h"
71#include "timer.h"
72
73static bool S_verbose;
74
75struct bootp_session {
76    dynarray_t			clients;
77    FDCalloutRef		read_fd;
78    int				read_fd_refcount;
79    uint16_t			client_port;
80    timer_callout_t *		timer_callout;
81    bool			verbose;
82};
83
84struct bootp_client {
85    bootp_session_t *		session; /* pointer to parent */
86    interface_t *		if_p;
87    boolean_t			fd_open;
88    bootp_receive_func_t *	receive;
89    void *			receive_arg1;
90    void *			receive_arg2;
91};
92
93
94static void
95bootp_session_read(void * arg1, void * arg2);
96
97static int
98S_open_bootp_socket(uint16_t client_port)
99{
100    struct sockaddr_in 		me;
101    int 			status;
102    int 			opt;
103    int				sockfd;
104
105    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
106    if (sockfd < 0) {
107	perror("socket");
108	return (-1);
109    }
110    bzero((char *)&me, sizeof(me));
111    me.sin_family = AF_INET;
112    me.sin_port = htons(client_port);
113    me.sin_addr.s_addr = htonl(INADDR_ANY);
114    status = bind(sockfd, (struct sockaddr *)&me, sizeof(me));
115    if (status != 0) {
116	my_log(LOG_ERR, "bootp_session: bind port %d failed, %s",
117	       client_port, strerror(errno));
118	goto failed;
119    }
120    opt = 1;
121    status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
122    if (status < 0) {
123	my_log(LOG_ERR, "setsockopt SO_BROADCAST failed, %s",
124	       strerror(errno));
125	goto failed;
126    }
127    status = ioctl(sockfd, FIONBIO, &opt);
128    if (status < 0) {
129	my_log(LOG_ERR, "ioctl FIONBIO failed, %s", strerror(errno));
130	goto failed;
131    }
132    status = setsockopt(sockfd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt));
133    if (status < 0) {
134	my_log(LOG_ERR, "setsockopt IP_RECVIF failed, %s",
135	       strerror(errno));
136	goto failed;
137    }
138#ifdef SO_TC_CTL
139    opt = SO_TC_CTL;
140    /* set traffic class, we don't care if it failed. */
141    (void)setsockopt(sockfd, SOL_SOCKET, SO_TRAFFIC_CLASS, &opt,
142		     sizeof(opt));
143#endif /* SO_TC_CTL */
144    return sockfd;
145
146 failed:
147    if (sockfd >= 0) {
148	close(sockfd);
149    }
150    return (-1);
151}
152
153bootp_client_t *
154bootp_client_init(bootp_session_t * session, interface_t * if_p)
155{
156    bootp_client_t *	client;
157
158    client = malloc(sizeof(*client));
159    if (client == NULL)
160	return (NULL);
161    bzero(client, sizeof(*client));
162    if (dynarray_add(&session->clients, client) == FALSE) {
163	free(client);
164	return (NULL);
165    }
166    client->session = session;
167    client->if_p = if_p;
168    return (client);
169}
170
171static void
172bootp_client_free_element(void * arg)
173{
174    bootp_client_t * 	client = (bootp_client_t *)arg;
175
176    bootp_client_disable_receive(client);
177    free(client);
178    return;
179}
180
181void
182bootp_client_free(bootp_client_t * * client_p)
183{
184    bootp_client_t * 	client = *client_p;
185    int 		i;
186    bootp_session_t * 	session;
187
188    if (client == NULL) {
189	return;
190    }
191    session = client->session;
192    i = dynarray_index(&session->clients, client);
193    if (i != -1) {
194	dynarray_remove(&session->clients, i, NULL);
195    }
196    else {
197	my_log(LOG_ERR, "bootp_client_free(%s) not in list?",
198	       if_name(client->if_p));
199    }
200    bootp_client_free_element(client);
201    *client_p = NULL;
202    return;
203}
204
205static void
206bootp_session_delayed_close(void * arg1, void * arg2, void * arg3)
207{
208    bootp_session_t * 	session = (bootp_session_t *)arg1;
209
210    if (session->read_fd == NULL) {
211	my_log(LOG_ERR,
212	       "bootp_session_delayed_close(): socket is already closed");
213	return;
214    }
215    if (session->read_fd_refcount > 0) {
216	my_log(LOG_ERR,
217	       "bootp_session_delayed_close(): called when socket in use");
218	return;
219    }
220    my_log(LOG_DEBUG,
221	   "bootp_session_delayed_close(): closing bootp socket %d",
222	   FDCalloutGetFD(session->read_fd));
223
224    /* this closes the file descriptor */
225    FDCalloutRelease(&session->read_fd);
226    return;
227}
228
229static void
230bootp_client_close_socket(bootp_client_t * client)
231{
232    bootp_session_t * 	session = client->session;
233
234    if (client->fd_open == FALSE) {
235	return;
236    }
237    if (session->read_fd_refcount <= 0) {
238	my_log(LOG_NOTICE, "bootp_client_close_socket(%s): refcount %d",
239	       if_name(client->if_p), session->read_fd_refcount);
240	return;
241    }
242    session->read_fd_refcount--;
243    my_log(LOG_DEBUG, "bootp_client_close_socket(%s): refcount %d",
244	   if_name(client->if_p), session->read_fd_refcount);
245    client->fd_open = FALSE;
246    if (session->read_fd_refcount == 0) {
247	struct timeval tv;
248
249	my_log(LOG_DEBUG,
250	       "bootp_client_close_socket(): scheduling delayed close");
251
252	tv.tv_sec = 1; /* close it after 1 second of non-use */
253	tv.tv_usec = 0;
254	timer_set_relative(session->timer_callout, tv,
255			   bootp_session_delayed_close,
256			   session, NULL, NULL);
257    }
258    return;
259}
260
261static boolean_t
262bootp_client_open_socket(bootp_client_t * client)
263{
264    bootp_session_t *	session = client->session;
265
266    if (client->fd_open) {
267	return (TRUE);
268    }
269    timer_cancel(session->timer_callout);
270    session->read_fd_refcount++;
271    my_log(LOG_DEBUG, "bootp_client_open_socket (%s): refcount %d",
272	   if_name(client->if_p), session->read_fd_refcount);
273    client->fd_open = TRUE;
274    if (session->read_fd_refcount > 1) {
275	/* already open */
276	return (TRUE);
277    }
278    if (session->read_fd != NULL) {
279	my_log(LOG_DEBUG, "bootp_client_open_socket(): socket is still open");
280    }
281    else {
282	int	sockfd;
283
284	sockfd =  S_open_bootp_socket(session->client_port);
285	if (sockfd < 0) {
286	    my_log(LOG_ERR,
287		   "bootp_client_open_socket: S_get_bootp_socket() failed, %s",
288		   strerror(errno));
289	    goto failed;
290	}
291	my_log(LOG_DEBUG,
292	       "bootp_client_open_socket(): opened bootp socket %d",
293	       sockfd);
294	/* register as a reader */
295	session->read_fd = FDCalloutCreate(sockfd,
296					   bootp_session_read,
297					   session, NULL);
298    }
299    return (TRUE);
300
301 failed:
302    bootp_client_close_socket(client);
303    return (FALSE);
304}
305
306void
307bootp_client_enable_receive(bootp_client_t * client,
308			    bootp_receive_func_t * func,
309			    void * arg1, void * arg2)
310{
311    client->receive = func;
312    client->receive_arg1 = arg1;
313    client->receive_arg2 = arg2;
314    if (bootp_client_open_socket(client) == FALSE) {
315	my_log(LOG_ERR, "bootp_client_enable_receive(%s): failed",
316	       if_name(client->if_p));
317    }
318    return;
319}
320
321void
322bootp_client_disable_receive(bootp_client_t * client)
323{
324    client->receive = NULL;
325    client->receive_arg1 = NULL;
326    client->receive_arg2 = NULL;
327    bootp_client_close_socket(client);
328    return;
329}
330
331static void
332bootp_client_bind_socket_to_if(bootp_client_t * client, int opt)
333{
334    bootp_session_t *	session = client->session;
335    int			fd = -1;
336
337    if (session->read_fd != NULL) {
338	fd = FDCalloutGetFD(session->read_fd);
339    }
340    if (fd < 0) {
341	my_log(LOG_ERR,
342	       "bootp_client_bind_socket_to_if(%s, %d):"
343	       " session socket isn't open", if_name(client->if_p), opt);
344    }
345    else if (setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &opt, sizeof(opt)) < 0) {
346	my_log(LOG_ERR,
347	       "bootp_client_bind_socket_to_if(%s, %d):"
348	       " setsockopt IP_BOUND_IF failed",
349	       if_name(client->if_p), opt, strerror(errno));
350    }
351    return;
352}
353
354int
355bootp_client_transmit(bootp_client_t * client,
356		      struct in_addr dest_ip,
357		      struct in_addr src_ip,
358		      uint16_t dest_port,
359		      uint16_t src_port,
360		      void * data, int len)
361{
362    int			error;
363    int			if_index = 0;
364    boolean_t		needs_close = FALSE;
365    /*
366     * send_buf is cast to some struct types containing short fields;
367     * force it to be aligned as much as an int
368     */
369    int 		send_buf_aligned[512];
370    char * 		send_buf = (char *)send_buf_aligned;
371    bootp_session_t *	session = client->session;
372    int			sockfd = -1;
373
374    /* if we're not broadcasting, bind the socket to the interface */
375    if (dest_ip.s_addr != INADDR_BROADCAST) {
376	if (client->fd_open == FALSE) {
377	    /* open the BOOTP socket in case it's needed */
378	    (void)bootp_client_open_socket(client);
379	    needs_close = TRUE;
380	}
381	if_index = if_link_index(client->if_p);
382	if (if_index != 0) {
383	    bootp_client_bind_socket_to_if(client, if_index);
384	}
385    }
386    if (S_verbose) {
387	CFMutableStringRef	str;
388
389	str = CFStringCreateMutable(NULL, 0);
390	dhcp_packet_print_cfstr(str, (struct dhcp *)data, len);
391	if (if_index != 0) {
392	    my_log(-LOG_DEBUG,
393		   "[%s] Transmit %d byte packet dest "
394		   IP_FORMAT
395		   " scope %d\n%@",
396		   if_name(client->if_p), len,
397		   IP_LIST(&dest_ip), if_index, str);
398	}
399	else {
400	    my_log(-LOG_DEBUG,
401		   "[%s] Transmit %d byte packet\n%@",
402		   if_name(client->if_p), len, str);
403	}
404	CFRelease(str);
405    }
406    if (session->read_fd != NULL) {
407	sockfd = FDCalloutGetFD(session->read_fd);
408    }
409    error = bootp_transmit(sockfd, send_buf,
410			   if_name(client->if_p),
411			   if_link_arptype(client->if_p), NULL, 0,
412			   dest_ip, src_ip, dest_port, src_port, data, len);
413    if (needs_close) {
414	bootp_client_close_socket(client);
415    }
416    if (if_index != 0) {
417	bootp_client_bind_socket_to_if(client, 0);
418    }
419    return (error);
420}
421
422
423
424bootp_session_t *
425bootp_session_init(uint16_t client_port)
426{
427    bootp_session_t * session = malloc(sizeof(*session));
428    if (session == NULL)
429	return (NULL);
430    bzero(session, sizeof(*session));
431    dynarray_init(&session->clients, bootp_client_free_element, NULL);
432    session->client_port = client_port;
433    session->timer_callout = timer_callout_init();
434
435    return (session);
436}
437
438void
439bootp_session_free(bootp_session_t * * session_p)
440{
441    bootp_session_t * session = *session_p;
442
443    dynarray_free(&session->clients);
444    FDCalloutRelease(&session->read_fd);
445    timer_callout_free(&session->timer_callout);
446    bzero(session, sizeof(*session));
447    free(session);
448    *session_p = NULL;
449    return;
450}
451
452static void
453bootp_session_deliver(bootp_session_t * session, const char * ifname,
454		      void * data, int size)
455{
456    bootp_receive_data_t	event;
457    int 			i;
458
459    if (size < sizeof(struct dhcp)) {
460	return;
461    }
462
463    if (S_verbose) {
464	CFMutableStringRef	str;
465
466	str = CFStringCreateMutable(NULL, 0);
467	dhcp_packet_print_cfstr(str, (struct dhcp *)data, size);
468	my_log(-LOG_DEBUG,
469	       "[%s] Receive %d byte packet\n%@",
470	       ifname, size, str);
471	CFRelease(str);
472    }
473
474    bzero(&event, sizeof(event));
475    event.data = (struct dhcp *)data;
476    event.size = size;
477    dhcpol_parse_packet(&event.options, (struct dhcp *)data, size, NULL);
478    for (i = 0; i < dynarray_count(&session->clients); i++) {
479	bootp_client_t *	client;
480
481	client = dynarray_element(&session->clients, i);
482	if (strcmp(if_name(client->if_p), ifname) != 0) {
483	    continue;
484	}
485	if (client->receive) {
486	    (*client->receive)(client->receive_arg1, client->receive_arg2,
487			       &event);
488	}
489    }
490    dhcpol_free(&event.options); /* free malloc'd data */
491    return;
492}
493
494static void *
495msghdr_parse_control(struct msghdr * msg_p, int level, int type, int * len)
496{
497    struct cmsghdr *	cmsg;
498
499    *len = 0;
500    for (cmsg = CMSG_FIRSTHDR(msg_p); cmsg; cmsg = CMSG_NXTHDR(msg_p, cmsg)) {
501	if (cmsg->cmsg_level == level
502	    && cmsg->cmsg_type == type) {
503	    if (cmsg->cmsg_len < sizeof(*cmsg))
504		return (NULL);
505	    *len = cmsg->cmsg_len - sizeof(*cmsg);
506	    return (CMSG_DATA(cmsg));
507	}
508    }
509    return (NULL);
510}
511
512static boolean_t
513msghdr_copy_ifname(struct msghdr * msg_p, char * ifname, int ifname_size)
514{
515    int 			len = 0;
516    struct sockaddr_dl *	dl_p;
517
518    dl_p = (struct sockaddr_dl *)msghdr_parse_control(msg_p,
519						      IPPROTO_IP, IP_RECVIF,
520						      &len);
521    if (dl_p == NULL || len == 0 || dl_p->sdl_nlen >= ifname_size) {
522	return (FALSE);
523    }
524    bcopy(dl_p->sdl_data, ifname, dl_p->sdl_nlen);
525    ifname[dl_p->sdl_nlen] = '\0';
526    return (TRUE);
527}
528
529static void
530bootp_session_read(void * arg1, void * arg2)
531{
532    char	 		control[512];
533    struct sockaddr_in 		from;
534    char			ifname[IFNAMSIZ + 1];
535    struct iovec 	 	iov;
536    struct msghdr 		msg;
537    int 			n;
538    uint32_t 			receive_buf[2048/(sizeof(uint32_t))];
539    bootp_session_t * 		session = (bootp_session_t *)arg1;
540
541    msg.msg_name = (caddr_t)&from;
542    msg.msg_namelen = sizeof(from);
543    msg.msg_iov = &iov;
544    msg.msg_iovlen = 1;
545    msg.msg_control = control;
546    msg.msg_controllen = sizeof(control);
547    msg.msg_flags = 0;
548    iov.iov_base = (caddr_t)receive_buf;
549    iov.iov_len = sizeof(receive_buf);
550    n = recvmsg(FDCalloutGetFD(session->read_fd), &msg, 0);
551    if (n > 0) {
552	if (msghdr_copy_ifname(&msg, ifname, sizeof(ifname))) {
553	    /* ALIGN: receive_buf aligned to uint64_t bytes */
554	    bootp_session_deliver(session, ifname,
555				  (void *)receive_buf, n);
556	}
557    }
558    else if (n < 0) {
559	if (errno != EAGAIN) {
560	    my_log(LOG_ERR, "bootp_session_read(): recvmsg failed, %s",
561		   strerror(errno));
562	}
563    }
564    return;
565}
566
567void
568bootp_session_set_verbose(bool verbose)
569{
570    S_verbose = verbose;
571    return;
572}
573