/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * bootpc.m * - BOOTP client used for two purposes: * 1) act as a BOOTP client to retrieve the IP address and other * options like the subnet mask, router for dynamic host configuration * 2) testing the BOOTP server */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "rfc_options.h" #import "dhcp_options.h" #import "macnc_options.h" #import "dhcp.h" #import "interfaces.h" #import "util.h" #import "dhcplib.h" #import #define USER_ERROR 1 #define UNEXPECTED_ERROR 2 #define TIMEOUT_ERROR 3 #define USECS_PER_TICK (100 * 1000) /* 1/10th second */ #define TICKS_PER_SEC (USECS_PER_SEC / USECS_PER_TICK) #define INITIAL_WAIT_SECS 4 #define MAX_WAIT_SECS 64 #define RAND_TICKS (1 * TICKS_PER_SEC) /* add a random value -1...+1 */ #define MAX_RETRIES 3 #define RECEIVE_TIMEOUT_SECS 0 #define RECEIVE_TIMEOUT_USECS USECS_PER_TICK #define GATHER_TIME_USECS (2 * 1000 * 1000) /* 2 second default */ #define GATHER_TIME_TICKS (GATHER_TIME_USECS / USECS_PER_TICK) static u_short client_port = IPPORT_BOOTPC; static boolean_t exit_quick = FALSE; static u_short server_port = IPPORT_BOOTPS; static u_long gather_ticks = GATHER_TIME_TICKS; static u_long max_retries = MAX_RETRIES; static boolean_t must_broadcast = FALSE; static int sockfd; static boolean_t testing = FALSE; /* tags_search: these are the tags we look for: */ static dhcptag_t tags_search[] = { dhcptag_host_name_e, dhcptag_subnet_mask_e, dhcptag_router_e, }; int n_tags_search = sizeof(tags_search) / sizeof(tags_search[0]); /* tags_print: these are the tags we print in the response */ static dhcptag_t tags_print[] = { dhcptag_host_name_e, dhcptag_subnet_mask_e, dhcptag_router_e, dhcptag_domain_name_server_e, dhcptag_domain_name_e, dhcptag_netinfo_server_address_e, dhcptag_netinfo_server_tag_e, }; int n_tags_print = sizeof(tags_print) / sizeof(tags_print[0]); #define NONE 0 #define MACNC 1 #define RFC 2 int client = RFC; struct in_addr dest_ip; unsigned char rfc_magic[4] = RFC_OPTIONS_MAGIC; extern struct ether_addr *ether_aton(char *); void make_bootp_request(struct bootp * bp, u_char * hwaddr, u_char hwtype, u_char hwlen, struct in_addr ciaddr) { bzero(bp, sizeof(*bp)); bp->bp_op = BOOTREQUEST; bp->bp_htype = hwtype; bp->bp_hlen = hwlen; bp->bp_ciaddr = ciaddr; /* should normally be 0.0.0.0 */ bp->bp_hops = 0; if (must_broadcast) bp->bp_unused = htons(0x1); bcopy((caddr_t)hwaddr, bp->bp_chaddr, hwlen); switch (client) { case RFC: { bcopy(rfc_magic, bp->bp_vend, sizeof(rfc_magic)); bp->bp_vend[4] = dhcptag_end_e; break; } case MACNC: { /* make packet look like we're a Mac NC */ unsigned long client_version; dhcpoa_t options; bcopy(rfc_magic, bp->bp_vend, sizeof(rfc_magic)); dhcpoa_init(&options, bp->bp_vend + sizeof(rfc_magic), sizeof(bp->bp_vend) - sizeof(rfc_magic)); #define VERSION 0 client_version = htonl(VERSION); if ((dhcpoa_add(&options, macNCtag_client_version_e, sizeof(client_version), &client_version) != dhcpoa_success_e) || (dhcpoa_add(&options, macNCtag_client_info_e, strlen(MACNC_CLIENT_INFO), MACNC_CLIENT_INFO) != dhcpoa_success_e) || (dhcpoa_add(&options, dhcptag_end_e, 0, 0) != dhcpoa_success_e)) { fprintf(stderr, "%s\n", dhcpoa_err(&options)); exit (1); } if (testing) { dhcpol_t parsed; dhcpol_init(&parsed); if (dhcpol_parse_packet(&parsed, (struct dhcp *)bp, sizeof(*bp), NULL)) { dhcpol_print(&parsed); } else fprintf(stderr, "parse failed\n"); dhcpol_free(&parsed); } break; } case NONE: default: break; } return; } static void on_alarm(int sigraised) { exit(0); return; } void wait_for_bootp_responses() { u_char buf[2048]; int buf_len = sizeof(buf); struct sockaddr_in from; int fromlen; bzero(buf, buf_len); signal(SIGALRM, on_alarm); ualarm(gather_ticks * USECS_PER_TICK, 0); for(;;) { int n_recv; from.sin_family = AF_INET; fromlen = sizeof(struct sockaddr); n_recv = recvfrom(sockfd, buf, buf_len, 0, (struct sockaddr *)&from, &fromlen); if (n_recv > 0) { printf("reply from %s\n", inet_ntoa(from.sin_addr)); dhcp_print_packet((struct dhcp *)buf, n_recv); printf("\n"); } } return; } void send_packet(void * pkt, int pkt_len) { struct sockaddr_in dst; int status; bzero(&dst, sizeof(dst)); dst.sin_len = sizeof(struct sockaddr_in); dst.sin_family = AF_INET; dst.sin_port = htons(server_port); dst.sin_addr = dest_ip; status = sendto(sockfd, pkt, pkt_len, 0, (struct sockaddr *)&dst, sizeof(struct sockaddr_in)); if (status < 0) { perror("sendto"); exit(UNEXPECTED_ERROR); } return; } u_char saved_rx_pkt[2048]; int saved_rx_len = 0; dhcpol_t saved_rx_options; boolean_t check_response(void * pkt, int pkt_size) { boolean_t better = FALSE; int i; dhcpol_t pkt_options; int pkt_tag_count; dhcpol_init(&pkt_options); pkt_tag_count = 0; if (dhcpol_parse_packet(&pkt_options, pkt, pkt_size, NULL)) { for (i = 0; i < n_tags_search; i++) { int len; if (dhcpol_find(&pkt_options, tags_search[i], &len, NULL)) { pkt_tag_count++; if (dhcpol_find(&saved_rx_options, tags_search[i], &len, NULL) == NULL) { better = TRUE; } } } } dhcpol_free(&pkt_options); if (saved_rx_len == 0 || better) { saved_rx_len = pkt_size; bcopy(pkt, saved_rx_pkt, pkt_size); dhcpol_free(&saved_rx_options); dhcpol_parse_packet(&saved_rx_options, (struct dhcp *)saved_rx_pkt, saved_rx_len, NULL); } if (pkt_tag_count == n_tags_search) return (TRUE); return (FALSE); } void print_option(FILE * file, void * option, int len, int tag) { int i; int count; int size; dhcptag_info_t * tag_info = dhcptag_info(tag); int type; dhcptype_info_t * type_info; if (tag_info == NULL) return; type = tag_info->type; type_info = dhcptype_info(type); if (type_info == NULL) return; size = 0; count = 1; if (type_info->multiple_of != dhcptype_none_e) { dhcptype_info_t * base_type_info = dhcptype_info(type_info->multiple_of); size = base_type_info->size; count = len / size; len = size; type = type_info->multiple_of; fprintf(file, "%s_count=%d\n", tag_info->name, count); } for (i = 0; i < count; i++) { u_char tmp[512]; if (dhcptype_to_str(tmp, option, len, type, NULL) == TRUE) { fprintf(file, "%s", tag_info->name); if (i > 0) fprintf(file, "%d", i + 1); fprintf(file, "=%s\n", tmp); } option += size; } return; } void echo_variables(void * pkt, int pkt_len, dhcpol_t * options) { struct bootp * bp = (struct bootp *)pkt; if (bp->bp_yiaddr.s_addr) printf("ip_address=%s\n", inet_ntoa(bp->bp_yiaddr)); else printf("ip_address=%s\n", inet_ntoa(bp->bp_ciaddr)); { int i; int len; void * option; for (i = 0; i < n_tags_print; i++) { option = dhcpol_find(options, tags_print[i], &len, NULL); if (option) print_option(stdout, option, len, tags_print[i]); } } if (bp->bp_siaddr.s_addr) printf("server_ip_address=%s\n", inet_ntoa(bp->bp_siaddr)); if (bp->bp_sname[0]) printf("server_name=%s\n", bp->bp_sname); return; } void bootp_loop(u_char * hwaddr, u_char hwtype, u_char hwlen, struct in_addr ciaddr) { struct timeval current_time; struct sockaddr_in from; int fromlen; int gather_tick_count = 0; int retries = 0; struct bootp request; u_char rxpkt[2048]; struct bootp * reply = (struct bootp *)rxpkt; struct timeval start_time; struct timeval timeout; int wait_ticks = INITIAL_WAIT_SECS * TICKS_PER_SEC; u_long xid; dhcpol_init(&saved_rx_options); make_bootp_request(&request, hwaddr, hwtype, hwlen, ciaddr); timeout.tv_sec = 0; timeout.tv_usec = RECEIVE_TIMEOUT_USECS; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (caddr_t)&timeout, sizeof(timeout)) < 0) { perror("setsockopt SO_RCVTIMEO"); exit(1); } gettimeofday(&start_time, 0); current_time = start_time; srandom(start_time.tv_usec & ~start_time.tv_sec); xid = random(); for (retries = 0; retries < (max_retries + 1); retries++) { int ticks; request.bp_secs = htons((u_short)(current_time.tv_sec - start_time.tv_sec)); request.bp_xid = htonl(xid); /* send the packet */ send_packet(&request, sizeof(request)); if (testing) return; /* wait for a response */ ticks = wait_ticks + random_range(-RAND_TICKS, RAND_TICKS); for (;ticks > 0;) { int n_recv; from.sin_family = AF_INET; fromlen = sizeof(struct sockaddr); n_recv = recvfrom(sockfd, rxpkt, sizeof(rxpkt), 0, (struct sockaddr *)&from, &fromlen); if (n_recv < 0) { if (errno == EAGAIN) { ticks--; if (saved_rx_len) { gather_tick_count++; if (gather_tick_count >= gather_ticks) goto output_values; } continue; } perror("bootp_loop(): recvfrom"); exit(UNEXPECTED_ERROR); } else if (n_recv < sizeof(struct bootp)) { continue; } if (ntohl(reply->bp_xid) == xid && (reply->bp_yiaddr.s_addr || reply->bp_ciaddr.s_addr) && hwtype == reply->bp_htype && hwlen == reply->bp_hlen && bcmp(hwaddr, reply->bp_chaddr, hwlen) == 0) { /* check if this is a better response */ if (check_response((void *)reply, n_recv)) goto output_values; } } wait_ticks *= 2; if (wait_ticks >= (MAX_WAIT_SECS * TICKS_PER_SEC)) wait_ticks = MAX_WAIT_SECS * TICKS_PER_SEC; xid++; gettimeofday(¤t_time, 0); } output_values: if (saved_rx_len == 0) exit(TIMEOUT_ERROR); echo_variables(saved_rx_pkt, saved_rx_len, &saved_rx_options); dhcpol_free(&saved_rx_options); saved_rx_len = 0; return; } void usage(u_char * progname) { fprintf(stderr, "useage: %s | [options]\n" "where options is one of:\n" "-g : gather response time (1 tick = 1/10th sec)\n" "-r : retry count\n", progname); exit(USER_ERROR); } int main(int argc, char *argv[]) { char * cp; struct in_addr client_ip = { 0 }; char * client_ip_str = NULL; u_char hwaddr[16]; u_char hwtype = ARPHRD_ETHER; u_char hwlen = 6; u_char * progname = argv[0]; if (argc < 2) usage(progname); { struct ether_addr * en_p = ether_aton(argv[1]); if (en_p) { *((struct ether_addr *)hwaddr) = *en_p; } else { interface_t * if_p; interface_list_t * list_p; list_p = ifl_init(FALSE); if (list_p == NULL) { fprintf(stderr, "no interfaces\n"); exit(UNEXPECTED_ERROR); } if_p = ifl_find_name(list_p, argv[1]); if (if_p == NULL || if_p->link_valid == FALSE) { fprintf(stderr, "link interface %s doesn't exist\n", argv[1]); exit(USER_ERROR); } if (if_p->link.sdl_type == IFT_ETHER) { hwtype = ARPHRD_ETHER; hwlen = if_p->link.sdl_alen; if (hwlen > sizeof(hwaddr)) { fprintf(stderr, "interface %s has bogus len %d\n", argv[1], hwlen); exit(UNEXPECTED_ERROR); } bcopy(if_p->link.sdl_data + if_p->link.sdl_nlen, hwaddr, hwlen); } else { fprintf(stderr, "interface %s is not ethernet\n", argv[1]); exit(USER_ERROR); } ifl_free(&list_p); } } dest_ip.s_addr = htonl(INADDR_BROADCAST); argc--, argv++; argc--, argv++; while (argc > 0 && *argv[0] == '-') { for (cp = &argv[0][1]; *cp; cp++) { switch (*cp) { case 'b': must_broadcast = TRUE; break; case 'C': /* specify the ciaddr value */ if (argc > 1) { client_ip_str = argv[1]; argc--; argv++; } break; case 'c': /* client port - for testing */ if (argc > 1) { client_port = atoi(argv[1]); argc--; argv++; } break; case 'e': /* send the request and exit */ exit_quick = TRUE; break; case 'g': /* gather time */ if (argc > 1) { argc--; argv++; gather_ticks = strtoul(*argv, NULL, NULL); } break; case 'm': /* act like a Mac NC */ client = MACNC; break; case 'N': /* act like a generic BOOTP client */ client = NONE; break; case 'r': /* retry count */ if (argc > 1) { argc--; argv++; max_retries = strtoul(*argv, NULL, NULL); } break; case 'S': /* server ip address */ if (argc > 1) { argc--; argv++; dest_ip.s_addr = inet_addr(*argv); } break; case 's': /* server port - for testing */ if (argc > 1) { server_port = atoi(argv[1]); argc--; argv++; } break; case 'T': /* log and wait for all responses */ testing = TRUE; break; default: fprintf(stderr,"bootpc: unknown flag -%c ignored",*cp); case 'H': case 'h': usage(progname); break; } } argc--, argv++; } sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(UNEXPECTED_ERROR); } { struct sockaddr_in me; int status; int opt = 1; bzero((char *)&me, sizeof(me)); me.sin_family = AF_INET; me.sin_port = htons(client_port); me.sin_addr.s_addr = htonl(INADDR_ANY); status = bind(sockfd, (struct sockaddr *)&me, sizeof(me)); if (status != 0) { perror("bind"); exit(UNEXPECTED_ERROR); } status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)); if (status < 0) { perror("setsockopt SO_BROADCAST"); exit(UNEXPECTED_ERROR); } status = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (status < 0) { perror("setsockopt SO_SOREUSEADDR"); exit(UNEXPECTED_ERROR); } } if (client_ip_str == NULL) client_ip.s_addr = 0; else { client_ip.s_addr = inet_addr(client_ip_str); if (client_ip.s_addr == -1) { fprintf(stderr, "bad ip address %s\n", client_ip_str); exit(USER_ERROR); } } if (testing) { bootp_loop(hwaddr, hwtype, hwlen, client_ip); if (exit_quick == FALSE) wait_for_bootp_responses(); } else { bootp_loop(hwaddr, hwtype, hwlen, client_ip); } exit(0); }