diff --git a/.gitignore b/.gitignore index 2f1c82c..e830092 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ config.log config.status .deps src/network-inador +src/dhcpc/nidhcpc *.o stamp-h1 diff --git a/configure.ac b/configure.ac index 8c4ab2b..2d4f179 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,7 @@ AC_CONFIG_FILES([ Makefile src/Makefile po/Makefile.in + src/dhcpc/Makefile ]) # data/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index bdee3fb..501579e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,13 +2,15 @@ bin_PROGRAMS = network-inador network_inador_SOURCES = network-inador.c network-inador.h \ - events.c events.h \ + netlink-events.c netlink-events.h \ interfaces.c interfaces.h \ manager.c manager.h \ utils.c utils.h \ bridge.c bridge.h \ rta_aux.c rta_aux.h \ - routes.c routes.h + routes.c routes.h \ + manager-events.c manager-events.h \ + dhcp.c dhcp.h #network_inador_CPPFLAGS = -DGAMEDATA_DIR=\"$(gamedatadir)/\" -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) network_inador_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) @@ -16,3 +18,4 @@ network_inador_CFLAGS = $(GLIB_CFLAGS) $(AM_CFLAGS) network_inador_LDADD = $(GLIB_LIBS) LDADD = $(LIBINTL) +SUBDIRS = dhcpc diff --git a/src/bridge.c b/src/bridge.c index 583e7c7..fc9d4e0 100644 --- a/src/bridge.c +++ b/src/bridge.c @@ -2,7 +2,7 @@ * bridge.c * This file is part of Network-inador * - * Copyright (C) 2011 - Félix Arreola Rodríguez + * Copyright (C) 2018 - Félix Arreola Rodríguez * * Network-inador is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/bridge.h b/src/bridge.h index 587840e..842871c 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -2,7 +2,7 @@ * bridge.h * This file is part of Network-inador * - * Copyright (C) 2011 - Félix Arreola Rodríguez + * Copyright (C) 2018 - Félix Arreola Rodríguez * * Network-inador is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/dhcp.c b/src/dhcp.c new file mode 100644 index 0000000..de21e0b --- /dev/null +++ b/src/dhcp.c @@ -0,0 +1,277 @@ +/* + * dhcp.c + * This file is part of Network-inador + * + * Copyright (C) 2018 - Félix Arreola Rodríguez + * + * Network-inador is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Network-inador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Network-inador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "network-inador.h" +#include "dhcp.h" +#include "interfaces.h" +#include "utils.h" + +#define DHCPC_PIPEOUT_HAS_IP 0x01 +#define DHCPC_PIPEOUT_HAS_SERVER_IP 0x02 +#define DHCPC_PIPEOUT_HAS_OPTS 0x04 + +static void _dhcp_parse_client_packet (NetworkInadorHandle *handle, Interface *iface, unsigned char *buffer, int len) { + int type, flags, current_opt, count_opt; + IPv4 address; + struct in_addr mask, siaddr_nip, route; + type = buffer[0]; + int pos; + int has_gateway = 0; + + memset (&address, 0, sizeof (address)); + + /* Máscara por default */ + inet_pton (AF_INET, "255.255.255.0", &mask); + + iface->dhcp_info.client_state = type; + switch (type) { + case DHCP_CLIENT_DECONFIG: + /* Desconfigurar la interfaz, borrar las IPs */ + interfaces_clear_all_ipv4_address (handle, iface); + interfaces_bring_up (handle->netlink_sock_request, iface); + break; + case DHCP_CLIENT_LEASEFAIL: + case DHCP_CLIENT_NAK: + /* leasefail, nak */ + break; + case DHCP_CLIENT_BOUND: + case DHCP_CLIENT_RENEW: + /* Bound, renew */ + flags = buffer[1]; + + pos = 2; + if (flags & DHCPC_PIPEOUT_HAS_IP) { + memcpy (&address.sin_addr, &buffer[pos], 4); + pos += 4; + } + + if (flags & DHCPC_PIPEOUT_HAS_SERVER_IP) { + memcpy (&siaddr_nip, &buffer[pos], 4); + pos += 4; + } + + if (flags & DHCPC_PIPEOUT_HAS_OPTS) { + current_opt = buffer[pos]; + + while (current_opt != 255) { + pos++; + + if (current_opt == 0x01) { + memcpy (&mask, &buffer[pos], 4); + pos += 4; + } else if (current_opt == 0x03) { + count_opt = buffer[pos]; + pos++; + + /* TODO: Solo tomamos la ultima ruta, arreglar */ + while (count_opt) { + memcpy (&route, &buffer[pos], 4); + pos += 4; + has_gateway = 1; + + count_opt--; + } + } else if (current_opt == 0x06) { + /* TODO: Procesar la lista de DNS */ + count_opt = buffer[pos]; + pos++; + + while (count_opt) { + count_opt--; + pos += 4; + } + } else if (current_opt == 0x0c || current_opt == 0x0f) { + /* TODO: Procesar el hostname o domain name */ + while (buffer[pos] != 0) { + pos++; + } + } else if (current_opt == 0x2a) { + /* TODO: Procesar la lista de NTP */ + count_opt = buffer[pos]; + pos++; + + while (count_opt) { + count_opt--; + pos += 4; + } + } + + current_opt = buffer[pos]; + } + + /* Ejecutar la configuración de la IP */ + address.prefix = utils_ip4_netmask_to_prefix (mask.s_addr); + interfaces_manual_add_ipv4 (handle->netlink_sock_request, iface, &address); + + /* Y esperar a que se active la IP para luego configurar la ruta */ + } + break; + } +} + +gboolean _dhcp_read_client_data (GIOChannel *source, GIOCondition condition, gpointer data) { + NetworkInadorHandle *handle = (NetworkInadorHandle *) data; + int sock; + char buffer[256]; + int len; + Interface *iface; + int exit_estatus; + int ret; + + sock = g_io_channel_unix_get_fd (source); + + len = read (sock, buffer, sizeof (buffer)); + + printf ("\n-------------> Read DHCP client from PIPE (%i) = %i\n", sock, len); + + iface = handle->interfaces; + while (iface != NULL) { + if (sock == iface->dhcp_info.read_pipe) { + /* Esta es la interfaz activa */ + break; + } + iface = iface->next; + } + + if (iface == NULL) { + /* Que raro... no debería ocurrir */ + + return FALSE; + } + + if (len == 0) { + close (sock); + + /* El proceso del cliente de dhcp murió, si lo reiniciamos se maneja en g_child_wait */ + iface->dhcp_info.read_pipe = 0; + + return FALSE; + } else if (len < 0) { + printf ("_--------------_____________ Soy el error que buscas\n"); + return FALSE; + } else { + /* Parsear lo leido por el dhcp client */ + _dhcp_parse_client_packet (handle, iface, buffer, len); + } + + return TRUE; +} + +static void _dhcp_client_exited (GPid pid, gint status, gpointer data) { + NetworkInadorHandle *handle = (NetworkInadorHandle *) data; + Interface *iface; + + iface = handle->interfaces; + while (iface != NULL) { + if (pid == iface->dhcp_info.process_pid) { + /* Esta es la interfaz activa */ + break; + } + iface = iface->next; + } + + if (iface == NULL) { + /* El dhcp para una interfaz eliminada murió */ + + g_spawn_close_pid (pid); + return; + } + + iface->dhcp_info.process_pid = 0; + + /* Si el estado quedó en "running", reiniciar el proceso de dhcp */ + if (iface->dhcp_info.type == IFACE_DHCP_CLIENT) { + iface->dhcp_info.type = IFACE_NO_DHCP_RUNNING; + dhcp_run_client (handle, iface); + } + + g_spawn_close_pid (pid); +} + +void dhcp_run_client (NetworkInadorHandle *handle, Interface *iface) { + GIOChannel *channel; + gboolean result; + gint read_from_child; + GPid child_pid; + + /* FIXME: Arreglar el path */ + char *argv[] = {"/tmp/abc/sbin/nidhcpc", iface->name, NULL}; + + if (iface->dhcp_info.type != IFACE_NO_DHCP_RUNNING) { + printf ("DHCP (client or server) already running\n"); + return; + } + + interfaces_bring_up (handle->netlink_sock_request, iface); + + result = g_spawn_async_with_pipes ( + NULL, /* working directory */ + argv, /* Argumentos */ + NULL, /* envp */ + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_CLOEXEC_PIPES, /* Banderas */ + NULL, NULL, /* Child setup func and data */ + &child_pid, + NULL, /* Entrada estándar */ + &read_from_child, /* Salida estándar */ + NULL, /* Salida de errores */ + NULL); /* Gerror */ + + if (result == FALSE) { + printf ("Falló al lanzar proceso de cliente de dhcp\n"); + return; + } + + iface->dhcp_info.type = IFACE_DHCP_CLIENT; + iface->dhcp_info.read_pipe = read_from_child; + iface->dhcp_info.process_pid = child_pid; + + iface->dhcp_info.client_state = DHCP_CLIENT_DECONFIG; + + /* Instalar un GIOChannel */ + channel = g_io_channel_unix_new (read_from_child); + + g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, _dhcp_read_client_data, handle); + + /* Para manejar la muerte del proceso */ + g_child_watch_add (child_pid, _dhcp_client_exited, handle); +} + +void dhcp_stop_client (NetworkInadorHandle *handle, Interface *iface) { + if (iface->dhcp_info.type == IFACE_DHCP_CLIENT) { + iface->dhcp_info.type = IFACE_NO_DHCP_RUNNING; + kill (iface->dhcp_info.process_pid, SIGTERM); + } +} diff --git a/src/dhcp.h b/src/dhcp.h new file mode 100644 index 0000000..4f8ae99 --- /dev/null +++ b/src/dhcp.h @@ -0,0 +1,28 @@ +/* + * dhcp.h + * This file is part of Network-inador + * + * Copyright (C) 2018 - Félix Arreola Rodríguez + * + * Network-inador is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Network-inador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Network-inador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __DHCP_H__ +#define __DHCP_H__ + +void dhcp_run_client (NetworkInadorHandle *handle, Interface *iface); + +#endif diff --git a/src/dhcpc/Makefile.am b/src/dhcpc/Makefile.am new file mode 100644 index 0000000..5c159a2 --- /dev/null +++ b/src/dhcpc/Makefile.am @@ -0,0 +1,17 @@ +# Automake file for NetworkInador DHCP Client + +sbin_PROGRAMS = nidhcpc +nidhcpc_SOURCES = nidhcpc.c \ + common.c common.h \ + dhcpc.c dhcpc.h dhcpd.h \ + extra.c extra.h \ + packet.c \ + signalpipe.c \ + socket.c + +#client_network_CPPFLAGS = -DGAMEDATA_DIR=\"$(gamedatadir)/\" -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) +nidhcpc_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) +nidhcpc_CFLAGS = $(AM_CFLAGS) +nidhcpc_LDADD = +LDADD = $(LIBINTL) + diff --git a/src/dhcpc/common.c b/src/dhcpc/common.c new file mode 100644 index 0000000..e3bc4d0 --- /dev/null +++ b/src/dhcpc/common.c @@ -0,0 +1,441 @@ +/* vi: set sw=4 ts=4: */ +/* + * Rewrite by Russ Dill July 2001 + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ + +#include +#include + +#include +#include + +#include "common.h" + +#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 +unsigned dhcp_verbose; +#endif + +const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* Supported options are easily added here. + * See RFC2132 for more options. + * OPTION_REQ: these options are requested by udhcpc (unless -o). + */ +const struct dhcp_optflag dhcp_optflags[] = { + /* flags code */ + { OPTION_IP | OPTION_REQ, 0x01 }, /* DHCP_SUBNET */ + { OPTION_S32 , 0x02 }, /* DHCP_TIME_OFFSET */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03 }, /* DHCP_ROUTER */ +// { OPTION_IP | OPTION_LIST , 0x04 }, /* DHCP_TIME_SERVER */ +// { OPTION_IP | OPTION_LIST , 0x05 }, /* DHCP_NAME_SERVER */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06 }, /* DHCP_DNS_SERVER */ +// { OPTION_IP | OPTION_LIST , 0x07 }, /* DHCP_LOG_SERVER */ +// { OPTION_IP | OPTION_LIST , 0x08 }, /* DHCP_COOKIE_SERVER */ + { OPTION_IP | OPTION_LIST , 0x09 }, /* DHCP_LPR_SERVER */ + { OPTION_STRING_HOST | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */ + { OPTION_U16 , 0x0d }, /* DHCP_BOOT_SIZE */ + { OPTION_STRING_HOST | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME */ + { OPTION_IP , 0x10 }, /* DHCP_SWAP_SERVER */ + { OPTION_STRING , 0x11 }, /* DHCP_ROOT_PATH */ + { OPTION_U8 , 0x17 }, /* DHCP_IP_TTL */ + { OPTION_U16 , 0x1a }, /* DHCP_MTU */ +//TODO: why do we request DHCP_BROADCAST? Can't we assume that +//in the unlikely case it is different from typical N.N.255.255, +//server would let us know anyway? + { OPTION_IP | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST */ + { OPTION_IP_PAIR | OPTION_LIST , 0x21 }, /* DHCP_ROUTES */ + { OPTION_STRING_HOST , 0x28 }, /* DHCP_NIS_DOMAIN */ + { OPTION_IP | OPTION_LIST , 0x29 }, /* DHCP_NIS_SERVER */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER */ + { OPTION_IP | OPTION_LIST , 0x2c }, /* DHCP_WINS_SERVER */ + { OPTION_U32 , 0x33 }, /* DHCP_LEASE_TIME */ + { OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */ + { OPTION_STRING , 0x38 }, /* DHCP_ERR_MESSAGE */ +//TODO: must be combined with 'sname' and 'file' handling: + { OPTION_STRING_HOST , 0x42 }, /* DHCP_TFTP_SERVER_NAME */ + { OPTION_STRING , 0x43 }, /* DHCP_BOOT_FILE */ +//TODO: not a string, but a set of LASCII strings: +// { OPTION_STRING , 0x4D }, /* DHCP_USER_CLASS */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + { OPTION_DNS_STRING | OPTION_LIST , 0x77 }, /* DHCP_DOMAIN_SEARCH */ + { OPTION_SIP_SERVERS , 0x78 }, /* DHCP_SIP_SERVERS */ +#endif + { OPTION_STATIC_ROUTES | OPTION_LIST , 0x79 }, /* DHCP_STATIC_ROUTES */ +#if ENABLE_FEATURE_UDHCP_8021Q + { OPTION_U16 , 0x84 }, /* DHCP_VLAN_ID */ + { OPTION_U8 , 0x85 }, /* DHCP_VLAN_PRIORITY */ +#endif + { OPTION_STRING , 0xd1 }, /* DHCP_PXE_CONF_FILE */ + { OPTION_6RD , 0xd4 }, /* DHCP_6RD */ + { OPTION_STATIC_ROUTES | OPTION_LIST , 0xf9 }, /* DHCP_MS_STATIC_ROUTES */ + { OPTION_STRING , 0xfc }, /* DHCP_WPAD */ + + /* Options below have no match in dhcp_option_strings[], + * are not passed to dhcpc scripts, and cannot be specified + * with "option XXX YYY" syntax in dhcpd config file. + * These entries are only used internally by udhcp[cd] + * to correctly encode options into packets. + */ + + { OPTION_IP , 0x32 }, /* DHCP_REQUESTED_IP */ + { OPTION_U8 , 0x35 }, /* DHCP_MESSAGE_TYPE */ + { OPTION_U16 , 0x39 }, /* DHCP_MAX_SIZE */ +//looks like these opts will work just fine even without these defs: +// { OPTION_STRING , 0x3c }, /* DHCP_VENDOR */ +// /* not really a string: */ +// { OPTION_STRING , 0x3d }, /* DHCP_CLIENT_ID */ + { 0, 0 } /* zeroed terminating entry */ +}; + +/* Used for converting options from incoming packets to env variables + * for udhcpc stript, and for setting options for udhcpd via + * "opt OPTION_NAME OPTION_VALUE" directives in udhcpd.conf file. + */ +/* Must match dhcp_optflags[] order */ +const char dhcp_option_strings[] ALIGN1 = + "subnet" "\0" /* DHCP_SUBNET */ + "timezone" "\0" /* DHCP_TIME_OFFSET */ + "router" "\0" /* DHCP_ROUTER */ +// "timesrv" "\0" /* DHCP_TIME_SERVER */ +// "namesrv" "\0" /* DHCP_NAME_SERVER */ + "dns" "\0" /* DHCP_DNS_SERVER */ +// "logsrv" "\0" /* DHCP_LOG_SERVER */ +// "cookiesrv" "\0" /* DHCP_COOKIE_SERVER */ + "lprsrv" "\0" /* DHCP_LPR_SERVER */ + "hostname" "\0" /* DHCP_HOST_NAME */ + "bootsize" "\0" /* DHCP_BOOT_SIZE */ + "domain" "\0" /* DHCP_DOMAIN_NAME */ + "swapsrv" "\0" /* DHCP_SWAP_SERVER */ + "rootpath" "\0" /* DHCP_ROOT_PATH */ + "ipttl" "\0" /* DHCP_IP_TTL */ + "mtu" "\0" /* DHCP_MTU */ + "broadcast" "\0" /* DHCP_BROADCAST */ + "routes" "\0" /* DHCP_ROUTES */ + "nisdomain" "\0" /* DHCP_NIS_DOMAIN */ + "nissrv" "\0" /* DHCP_NIS_SERVER */ + "ntpsrv" "\0" /* DHCP_NTP_SERVER */ + "wins" "\0" /* DHCP_WINS_SERVER */ + "lease" "\0" /* DHCP_LEASE_TIME */ + "serverid" "\0" /* DHCP_SERVER_ID */ + "message" "\0" /* DHCP_ERR_MESSAGE */ + "tftp" "\0" /* DHCP_TFTP_SERVER_NAME */ + "bootfile" "\0" /* DHCP_BOOT_FILE */ +// "userclass" "\0" /* DHCP_USER_CLASS */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + "search" "\0" /* DHCP_DOMAIN_SEARCH */ +// doesn't work in udhcpd.conf since OPTION_SIP_SERVERS +// is not handled yet by "string->option" conversion code: + "sipsrv" "\0" /* DHCP_SIP_SERVERS */ +#endif + "staticroutes" "\0"/* DHCP_STATIC_ROUTES */ +#if ENABLE_FEATURE_UDHCP_8021Q + "vlanid" "\0" /* DHCP_VLAN_ID */ + "vlanpriority" "\0"/* DHCP_VLAN_PRIORITY */ +#endif + "pxeconffile" "\0" /* DHCP_PXE_CONF_FILE */ + "ip6rd" "\0" /* DHCP_6RD */ + "msstaticroutes""\0"/* DHCP_MS_STATIC_ROUTES */ + "wpad" "\0" /* DHCP_WPAD */ + ; + +/* Lengths of the option types in binary form. + * Used by: + * udhcp_str2optset: to determine how many bytes to allocate. + * xmalloc_optname_optval: to estimate string length + * from binary option length: (option[LEN] / dhcp_option_lengths[opt_type]) + * is the number of elements, multiply in by one element's string width + * (len_of_option_as_string[opt_type]) and you know how wide string you need. + */ +const uint8_t dhcp_option_lengths[] ALIGN1 = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, +// [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, /* ignored by udhcp_str2optset */ + [OPTION_STRING_HOST] = 1, /* ignored by udhcp_str2optset */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + [OPTION_DNS_STRING] = 1, /* ignored by both udhcp_str2optset and xmalloc_optname_optval */ + [OPTION_SIP_SERVERS] = 1, +#endif + [OPTION_U8] = 1, + [OPTION_U16] = 2, +// [OPTION_S16] = 2, + [OPTION_U32] = 4, + [OPTION_S32] = 4, + /* Just like OPTION_STRING, we use minimum length here */ + [OPTION_STATIC_ROUTES] = 5, + [OPTION_6RD] = 22, /* ignored by udhcp_str2optset */ +}; + + +#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2 +static void log_option(const char *pfx, const uint8_t *opt) +{ + if (dhcp_verbose >= 2) { + char buf[256 * 2 + 2]; + *bin2hex(buf, (void*) (opt + OPT_DATA), opt[OPT_LEN]) = '\0'; + bb_info_msg("%s: 0x%02x %s", pfx, opt[OPT_CODE], buf); + } +} +#else +# define log_option(pfx, opt) ((void)0) +#endif + +/* Get an option with bounds checking (warning, result is not aligned) */ +uint8_t* udhcp_get_option(struct dhcp_packet *packet, int code) +{ + uint8_t *optionptr; + int len; + int rem; + int overload = 0; + enum { + FILE_FIELD101 = FILE_FIELD * 0x101, + SNAME_FIELD101 = SNAME_FIELD * 0x101, + }; + + /* option bytes: [code][len][data1][data2]..[dataLEN] */ + optionptr = packet->options; + rem = sizeof(packet->options); + while (1) { + if (rem <= 0) { + fprintf (stderr, "bad packet, malformed option field"); + return NULL; + } + if (optionptr[OPT_CODE] == DHCP_PADDING) { + rem--; + optionptr++; + continue; + } + if (optionptr[OPT_CODE] == DHCP_END) { + if ((overload & FILE_FIELD101) == FILE_FIELD) { + /* can use packet->file, and didn't look at it yet */ + overload |= FILE_FIELD101; /* "we looked at it" */ + optionptr = packet->file; + rem = sizeof(packet->file); + continue; + } + if ((overload & SNAME_FIELD101) == SNAME_FIELD) { + /* can use packet->sname, and didn't look at it yet */ + overload |= SNAME_FIELD101; /* "we looked at it" */ + optionptr = packet->sname; + rem = sizeof(packet->sname); + continue; + } + break; + } + len = 2 + optionptr[OPT_LEN]; + rem -= len; + if (rem < 0) + continue; /* complain and return NULL */ + + if (optionptr[OPT_CODE] == code) { + log_option("Option found", optionptr); + return optionptr + OPT_DATA; + } + + if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) { + overload |= optionptr[OPT_DATA]; + /* fall through */ + } + optionptr += len; + } + + /* log3 because udhcpc uses it a lot - very noisy */ + log3("Option 0x%02x not found", code); + return NULL; +} + +/* Return the position of the 'end' option (no bounds checking) */ +int udhcp_end_option(uint8_t *optionptr) +{ + int i = 0; + + while (optionptr[i] != DHCP_END) { + if (optionptr[i] != DHCP_PADDING) + i += optionptr[i + OPT_LEN] + OPT_DATA-1; + i++; + } + return i; +} + +/* Add an option (supplied in binary form) to the options. + * Option format: [code][len][data1][data2]..[dataLEN] + */ +void udhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt) +{ + unsigned len; + uint8_t *optionptr = packet->options; + unsigned end = udhcp_end_option(optionptr); + + len = OPT_DATA + addopt[OPT_LEN]; + /* end position + (option code/length + addopt length) + end option */ + if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE) { +//TODO: learn how to use overflow option if we exhaust packet->options[] + fprintf (stderr, "option 0x%02x did not fit into the packet", + addopt[OPT_CODE]); + return; + } + log_option("Adding option", addopt); + memcpy(optionptr + end, addopt, len); + optionptr[end + len] = DHCP_END; +} + +/* Add an one to four byte option to a packet */ +void udhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code, uint32_t data) +{ + const struct dhcp_optflag *dh; + + for (dh = dhcp_optflags; dh->code; dh++) { + if (dh->code == code) { + uint8_t option[6], len; + + option[OPT_CODE] = code; + len = dhcp_option_lengths[dh->flags & OPTION_TYPE_MASK]; + option[OPT_LEN] = len; + if (BB_BIG_ENDIAN) + data <<= 8 * (4 - len); + /* Assignment is unaligned! */ + move_to_unaligned32(&option[OPT_DATA], data); + udhcp_add_binary_option(packet, option); + return; + } + } + + fprintf (stderr, "can't add option 0x%02x", code); +} + +/* Find option 'code' in opt_list */ +struct option_set* udhcp_find_option(struct option_set *opt_list, uint8_t code) +{ + while (opt_list && opt_list->data[OPT_CODE] < code) + opt_list = opt_list->next; + + if (opt_list && opt_list->data[OPT_CODE] == code) + return opt_list; + return NULL; +} + +/* Parse string to IP in network order */ +int udhcp_str2nip(const char *str, void *arg) +{ + len_and_sockaddr *lsa; + + lsa = host_and_af2sockaddr(str, 0, AF_INET); + if (!lsa) + return 0; + /* arg maybe unaligned */ + move_to_unaligned32((uint32_t*)arg, lsa->u.sin.sin_addr.s_addr); + free(lsa); + return 1; +} + +/* udhcp_str2optset: + * Parse string option representation to binary form and add it to opt_list. + * Called to parse "udhcpc -x OPTNAME:OPTVAL" + * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives. + */ +/* helper for the helper */ +static char *allocate_tempopt_if_needed( + const struct dhcp_optflag *optflag, + char *buffer, + int *length_p) +{ + char *allocated = NULL; + if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) { + const char *end; + allocated = xstrdup(buffer); /* more than enough */ + end = hex2bin(allocated, buffer, 255); + if (errno) { + fprintf (stderr, "malformed hex string '%s'", buffer); + exit (EXIT_FAILURE); + } + *length_p = end - allocated; + } + return allocated; +} +/* helper: add an option to the opt_list */ +static NOINLINE void attach_option( + struct option_set **opt_list, + const struct dhcp_optflag *optflag, + char *buffer, + int length) +{ + struct option_set *existing; + char *allocated; + + allocated = allocate_tempopt_if_needed(optflag, buffer, &length); +#if ENABLE_FEATURE_UDHCP_RFC3397 + if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) { + /* reuse buffer and length for RFC1035-formatted string */ + allocated = buffer = (char *)dname_enc(NULL, 0, buffer, &length); + } +#endif + + existing = udhcp_find_option(*opt_list, optflag->code); + if (!existing) { + struct option_set *new, **curr; + + /* make a new option */ + log2("Attaching option %02x to list", optflag->code); + new = xmalloc(sizeof(*new)); + new->data = xmalloc(length + OPT_DATA); + new->data[OPT_CODE] = optflag->code; + new->data[OPT_LEN] = length; + memcpy(new->data + OPT_DATA, (allocated ? allocated : buffer), length); + + curr = opt_list; + while (*curr && (*curr)->data[OPT_CODE] < optflag->code) + curr = &(*curr)->next; + + new->next = *curr; + *curr = new; + goto ret; + } + + if (optflag->flags & OPTION_LIST) { + unsigned old_len; + + /* add it to an existing option */ + log2("Attaching option %02x to existing member of list", optflag->code); + old_len = existing->data[OPT_LEN]; + if (old_len + length < 255) { + /* actually 255 is ok too, but adding a space can overlow it */ + + existing->data = xrealloc(existing->data, OPT_DATA + 1 + old_len + length); + if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING + || (optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING_HOST + ) { + /* add space separator between STRING options in a list */ + existing->data[OPT_DATA + old_len] = ' '; + old_len++; + } + memcpy(existing->data + OPT_DATA + old_len, (allocated ? allocated : buffer), length); + existing->data[OPT_LEN] = old_len + length; + } /* else, ignore the data, we could put this in a second option in the future */ + } /* else, ignore the new data */ + + ret: + free(allocated); +} + +/* note: ip is a pointer to an IPv6 in network order, possibly misaliged */ +int sprint_nip6(char *dest, /*const char *pre,*/ const uint8_t *ip) +{ + char hexstrbuf[16 * 2]; + bin2hex(hexstrbuf, (void*)ip, 16); + return sprintf(dest, /* "%s" */ + "%.4s:%.4s:%.4s:%.4s:%.4s:%.4s:%.4s:%.4s", + /* pre, */ + hexstrbuf + 0 * 4, + hexstrbuf + 1 * 4, + hexstrbuf + 2 * 4, + hexstrbuf + 3 * 4, + hexstrbuf + 4 * 4, + hexstrbuf + 5 * 4, + hexstrbuf + 6 * 4, + hexstrbuf + 7 * 4 + ); +} diff --git a/src/dhcpc/common.h b/src/dhcpc/common.h new file mode 100644 index 0000000..afc4c3a --- /dev/null +++ b/src/dhcpc/common.h @@ -0,0 +1,317 @@ +/* vi: set sw=4 ts=4: */ +/* + * Russ Dill September 2001 + * Rewritten by Vladimir Oleynik (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +#ifndef UDHCP_COMMON_H +#define UDHCP_COMMON_H 1 + +#include +#include + +#include "extra.h" + +extern const uint8_t MAC_BCAST_ADDR[6] ALIGN2; /* six all-ones */ + + +/*** DHCP packet ***/ + +/* DHCP protocol. See RFC 2131 */ +#define DHCP_MAGIC 0x63825363 +#define DHCP_OPTIONS_BUFSIZE 308 +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +//TODO: rename ciaddr/yiaddr/chaddr +struct dhcp_packet { + uint8_t op; /* BOOTREQUEST or BOOTREPLY */ + uint8_t htype; /* hardware address type. 1 = 10mb ethernet */ + uint8_t hlen; /* hardware address length */ + uint8_t hops; /* used by relay agents only */ + uint32_t xid; /* unique id */ + uint16_t secs; /* elapsed since client began acquisition/renewal */ + uint16_t flags; /* only one flag so far: */ +#define BROADCAST_FLAG 0x8000 /* "I need broadcast replies" */ + uint32_t ciaddr; /* client IP (if client is in BOUND, RENEW or REBINDING state) */ + uint32_t yiaddr; /* 'your' (client) IP address */ + /* IP address of next server to use in bootstrap, returned in DHCPOFFER, DHCPACK by server */ + uint32_t siaddr_nip; + uint32_t gateway_nip; /* relay agent IP address */ + uint8_t chaddr[16]; /* link-layer client hardware address (MAC) */ + uint8_t sname[64]; /* server host name (ASCIZ) */ + uint8_t file[128]; /* boot file name (ASCIZ) */ + uint32_t cookie; /* fixed first four option bytes (99,130,83,99 dec) */ + uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS]; +} PACKED; +#define DHCP_PKT_SNAME_LEN 64 +#define DHCP_PKT_FILE_LEN 128 +#define DHCP_PKT_SNAME_LEN_STR "64" +#define DHCP_PKT_FILE_LEN_STR "128" + +struct ip_udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcp_packet data; +} PACKED; + +struct udp_dhcp_packet { + struct udphdr udp; + struct dhcp_packet data; +} PACKED; + +enum { + IP_UDP_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, + UDP_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, + DHCP_SIZE = sizeof(struct dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, +}; + +/* Let's see whether compiler understood us right */ +struct BUG_bad_sizeof_struct_ip_udp_dhcp_packet { + char c[IP_UDP_DHCP_SIZE == 576 ? 1 : -1]; +}; + + +/*** Options ***/ + +enum { + OPTION_IP = 1, + OPTION_IP_PAIR, + OPTION_STRING, + /* Opts of STRING_HOST type will be sanitized before they are passed + * to udhcpc script's environment: */ + OPTION_STRING_HOST, +// OPTION_BOOLEAN, + OPTION_U8, + OPTION_U16, +// OPTION_S16, + OPTION_U32, + OPTION_S32, + OPTION_BIN, + OPTION_STATIC_ROUTES, + OPTION_6RD, +#if ENABLE_FEATURE_UDHCP_RFC3397 + OPTION_DNS_STRING, /* RFC1035 compressed domain name list */ + OPTION_SIP_SERVERS, +#endif + + OPTION_TYPE_MASK = 0x0f, + /* Client requests this option by default */ + OPTION_REQ = 0x10, + /* There can be a list of 1 or more of these */ + OPTION_LIST = 0x20, +}; + +/* DHCP option codes (partial list). See RFC 2132 and + * http://www.iana.org/assignments/bootp-dhcp-parameters/ + * Commented out options are handled by common option machinery, + * uncommented ones have special cases (grep for them to see). + */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +//#define DHCP_TIME_OFFSET 0x02 /* (localtime - UTC_time) in seconds. signed */ +//#define DHCP_ROUTER 0x03 +//#define DHCP_TIME_SERVER 0x04 /* RFC 868 time server (32-bit, 0 = 1.1.1900) */ +//#define DHCP_NAME_SERVER 0x05 /* IEN 116 _really_ ancient kind of NS */ +//#define DHCP_DNS_SERVER 0x06 +//#define DHCP_LOG_SERVER 0x07 /* port 704 UDP log (not syslog) +//#define DHCP_COOKIE_SERVER 0x08 /* "quote of the day" server */ +//#define DHCP_LPR_SERVER 0x09 +#define DHCP_HOST_NAME 0x0c /* either client informs server or server gives name to client */ +//#define DHCP_BOOT_SIZE 0x0d +//#define DHCP_DOMAIN_NAME 0x0f /* server gives domain suffix */ +//#define DHCP_SWAP_SERVER 0x10 +//#define DHCP_ROOT_PATH 0x11 +//#define DHCP_IP_TTL 0x17 +//#define DHCP_MTU 0x1a +//#define DHCP_BROADCAST 0x1c +//#define DHCP_ROUTES 0x21 +//#define DHCP_NIS_DOMAIN 0x28 +//#define DHCP_NIS_SERVER 0x29 +//#define DHCP_NTP_SERVER 0x2a +//#define DHCP_WINS_SERVER 0x2c +#define DHCP_REQUESTED_IP 0x32 /* sent by client if specific IP is wanted */ +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVERLOAD 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 /* by default server's IP */ +#define DHCP_PARAM_REQ 0x37 /* list of options client wants */ +//#define DHCP_ERR_MESSAGE 0x38 /* error message when sending NAK etc */ +#define DHCP_MAX_SIZE 0x39 +#define DHCP_VENDOR 0x3c /* client's vendor (a string) */ +#define DHCP_CLIENT_ID 0x3d /* by default client's MAC addr, but may be arbitrarily long */ +//#define DHCP_TFTP_SERVER_NAME 0x42 /* same as 'sname' field */ +//#define DHCP_BOOT_FILE 0x43 /* same as 'file' field */ +//#define DHCP_USER_CLASS 0x4d /* RFC 3004. set of LASCII strings. "I am a printer" etc */ +#define DHCP_FQDN 0x51 /* client asks to update DNS to map its FQDN to its new IP */ +//#define DHCP_DOMAIN_SEARCH 0x77 /* RFC 3397. set of ASCIZ string, DNS-style compressed */ +//#define DHCP_SIP_SERVERS 0x78 /* RFC 3361. flag byte, then: 0: domain names, 1: IP addrs */ +//#define DHCP_STATIC_ROUTES 0x79 /* RFC 3442. (mask,ip,router) tuples */ +//#define DHCP_VLAN_ID 0x84 /* 802.1P VLAN ID */ +//#define DHCP_VLAN_PRIORITY 0x85 /* 802.1Q VLAN priority */ +//#define DHCP_PXE_CONF_FILE 0xd1 /* RFC 5071 Configuration File */ +//#define DHCP_MS_STATIC_ROUTES 0xf9 /* Microsoft's pre-RFC 3442 code for 0x79? */ +//#define DHCP_WPAD 0xfc /* MSIE's Web Proxy Autodiscovery Protocol */ +#define DHCP_END 0xff + +/* Offsets in option byte sequence */ +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 +/* Bits in "overload" option */ +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* DHCP_MESSAGE_TYPE values */ +#define DHCPDISCOVER 1 /* client -> server */ +#define DHCPOFFER 2 /* client <- server */ +#define DHCPREQUEST 3 /* client -> server */ +#define DHCPDECLINE 4 /* client -> server */ +#define DHCPACK 5 /* client <- server */ +#define DHCPNAK 6 /* client <- server */ +#define DHCPRELEASE 7 /* client -> server */ +#define DHCPINFORM 8 /* client -> server */ +#define DHCP_MINTYPE DHCPDISCOVER +#define DHCP_MAXTYPE DHCPINFORM + +struct dhcp_optflag { + uint8_t flags; + uint8_t code; +}; + +struct option_set { + uint8_t *data; + struct option_set *next; +}; + +extern const struct dhcp_optflag dhcp_optflags[]; +extern const char dhcp_option_strings[] ALIGN1; +extern const uint8_t dhcp_option_lengths[] ALIGN1; + +unsigned udhcp_option_idx(const char *name); + +uint8_t *udhcp_get_option(struct dhcp_packet *packet, int code); +int udhcp_end_option(uint8_t *optionptr); +void udhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt); +void udhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code, uint32_t data); +#if ENABLE_FEATURE_UDHCP_RFC3397 +char *dname_dec(const uint8_t *cstr, int clen, const char *pre); +uint8_t *dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen); +#endif +struct option_set *udhcp_find_option(struct option_set *opt_list, uint8_t code); + + +// RFC 2131 Table 5: Fields and options used by DHCP clients +// +// Fields 'hops', 'yiaddr', 'siaddr', 'giaddr' are always zero +// +// Field DHCPDISCOVER DHCPINFORM DHCPREQUEST DHCPDECLINE DHCPRELEASE +// ----- ------------ ------------ ----------- ----------- ----------- +// 'xid' selected by client selected by client 'xid' from server selected by client selected by client +// DHCPOFFER message +// 'secs' 0 or seconds since 0 or seconds since 0 or seconds since 0 0 +// DHCP process started DHCP process started DHCP process started +// 'flags' Set 'BROADCAST' Set 'BROADCAST' Set 'BROADCAST' 0 0 +// flag if client flag if client flag if client +// requires broadcast requires broadcast requires broadcast +// reply reply reply +// 'ciaddr' 0 client's IP 0 or client's IP 0 client's IP +// (BOUND/RENEW/REBIND) +// 'chaddr' client's MAC client's MAC client's MAC client's MAC client's MAC +// 'sname' options or sname options or sname options or sname (unused) (unused) +// 'file' options or file options or file options or file (unused) (unused) +// 'options' options options options message type opt message type opt +// +// Option DHCPDISCOVER DHCPINFORM DHCPREQUEST DHCPDECLINE DHCPRELEASE +// ------ ------------ ---------- ----------- ----------- ----------- +// Requested IP address MAY MUST NOT MUST (in MUST MUST NOT +// SELECTING or +// INIT-REBOOT) +// MUST NOT (in +// BOUND or +// RENEWING) +// IP address lease time MAY MUST NOT MAY MUST NOT MUST NOT +// Use 'file'/'sname' fields MAY MAY MAY MAY MAY +// Client identifier MAY MAY MAY MAY MAY +// Vendor class identifier MAY MAY MAY MUST NOT MUST NOT +// Server identifier MUST NOT MUST NOT MUST (after MUST MUST +// SELECTING) +// MUST NOT (after +// INIT-REBOOT, +// BOUND, RENEWING +// or REBINDING) +// Parameter request list MAY MAY MAY MUST NOT MUST NOT +// Maximum message size MAY MAY MAY MUST NOT MUST NOT +// Message SHOULD NOT SHOULD NOT SHOULD NOT SHOULD SHOULD +// Site-specific MAY MAY MAY MUST NOT MUST NOT +// All others MAY MAY MAY MUST NOT MUST NOT + + +/*** Logging ***/ + +#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 +# define IF_UDHCP_VERBOSE(...) __VA_ARGS__ +extern unsigned dhcp_verbose; +# define log1(...) do { if (dhcp_verbose >= 1) bb_info_msg(__VA_ARGS__); } while (0) +# if CONFIG_UDHCP_DEBUG >= 2 +void udhcp_dump_packet(struct dhcp_packet *packet); +# define log2(...) do { if (dhcp_verbose >= 2) bb_info_msg(__VA_ARGS__); } while (0) +# else +# define udhcp_dump_packet(...) ((void)0) +# define log2(...) ((void)0) +# endif +# if CONFIG_UDHCP_DEBUG >= 3 +# define log3(...) do { if (dhcp_verbose >= 3) bb_info_msg(__VA_ARGS__); } while (0) +# else +# define log3(...) ((void)0) +# endif +#else +# define IF_UDHCP_VERBOSE(...) +# define udhcp_dump_packet(...) ((void)0) +# define log1(...) ((void)0) +# define log2(...) ((void)0) +# define log3(...) ((void)0) +#endif + + +/*** Other shared functions ***/ + +/* 2nd param is "uint32_t*" */ +int udhcp_str2nip(const char *str, void *arg); +/* 2nd param is "struct option_set**" */ +int udhcp_str2optset(const char *str, void *arg); + +void udhcp_init_header(struct dhcp_packet *packet, char type); + +int udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd); + +int udhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_nip, int source_port, + uint32_t dest_nip, int dest_port, const uint8_t *dest_arp, + int ifindex); + +int udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_nip, int source_port, + uint32_t dest_nip, int dest_port); + +void udhcp_sp_setup(void); +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd); +int udhcp_sp_read(const fd_set *rfds); + +int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *nip, uint8_t *mac); + +int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf); + +/* Returns 1 if no reply received */ +int arpping(uint32_t test_nip, + const uint8_t *safe_mac, + uint32_t from_ip, + uint8_t *from_mac, + const char *interface); + +/* note: ip is a pointer to an IPv6 in network order, possibly misaliged */ +int sprint_nip6(char *dest, /*const char *pre,*/ const uint8_t *ip); + +#endif diff --git a/src/dhcpc/dhcpc.c b/src/dhcpc/dhcpc.c new file mode 100644 index 0000000..e3c4563 --- /dev/null +++ b/src/dhcpc/dhcpc.c @@ -0,0 +1,1444 @@ +/* vi: set sw=4 ts=4: */ +/* + * udhcp client + * + * Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include + +#include + +#include + +/* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */ +#define WANT_PIDFILE 1 +#include "common.h" +#include "extra.h" + +//#include "dhcpd.h" +#include "dhcpc.h" + +#include +#include +#include + +#include +#include +#include + +struct client_config_t client_config; + +/* "struct client_config_t client_config" is in bb_common_bufsiz1 */ + +/*** Script execution code ***/ + +/* get a rough idea of how long an option will be (rounding up...) */ +static const uint8_t len_of_option_as_string[] = { + [OPTION_IP ] = sizeof("255.255.255.255 "), + [OPTION_IP_PAIR ] = sizeof("255.255.255.255 ") * 2, + [OPTION_STATIC_ROUTES ] = sizeof("255.255.255.255/32 255.255.255.255 "), + [OPTION_6RD ] = sizeof("32 128 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 255.255.255.255 "), + [OPTION_STRING ] = 1, + [OPTION_STRING_HOST ] = 1, +#if ENABLE_FEATURE_UDHCP_RFC3397 + [OPTION_DNS_STRING ] = 1, /* unused */ + /* Hmmm, this severely overestimates size if SIP_SERVERS option + * is in domain name form: N-byte option in binary form + * mallocs ~16*N bytes. But it is freed almost at once. + */ + [OPTION_SIP_SERVERS ] = sizeof("255.255.255.255 "), +#endif +// [OPTION_BOOLEAN ] = sizeof("yes "), + [OPTION_U8 ] = sizeof("255 "), + [OPTION_U16 ] = sizeof("65535 "), +// [OPTION_S16 ] = sizeof("-32768 "), + [OPTION_U32 ] = sizeof("4294967295 "), + [OPTION_S32 ] = sizeof("-2147483684 "), +}; + +/* note: ip is a pointer to an IP in network order, possibly misaliged */ +static int sprint_nip(char *dest, const char *pre, const uint8_t *ip) +{ + return sprintf(dest, "%s%u.%u.%u.%u", pre, ip[0], ip[1], ip[2], ip[3]); +} + +/* really simple implementation, just count the bits */ +static int mton(uint32_t mask) +{ + int i = 0; + mask = ntohl(mask); /* 111110000-like bit pattern */ + while (mask) { + i++; + mask <<= 1; + } + return i; +} + +/* Check if a given label represents a valid DNS label + * Return pointer to the first character after the label upon success, + * NULL otherwise. + * See RFC1035, 2.3.1 + */ +/* We don't need to be particularly anal. For example, allowing _, hyphen + * at the end, or leading and trailing dots would be ok, since it + * can't be used for attacks. (Leading hyphen can be, if someone uses + * cmd "$hostname" + * in the script: then hostname may be treated as an option) + */ +static const char *valid_domain_label(const char *label) +{ + unsigned char ch; + unsigned pos = 0; + + for (;;) { + ch = *label; + if ((ch|0x20) < 'a' || (ch|0x20) > 'z') { + if (pos == 0) { + /* label must begin with letter */ + return NULL; + } + if (ch < '0' || ch > '9') { + if (ch == '\0' || ch == '.') + return label; + /* DNS allows only '-', but we are more permissive */ + if (ch != '-' && ch != '_') + return NULL; + } + } + label++; + pos++; + //Do we want this? + //if (pos > 63) /* NS_MAXLABEL; labels must be 63 chars or less */ + // return NULL; + } +} + +/* Check if a given name represents a valid DNS name */ +/* See RFC1035, 2.3.1 */ +static int good_hostname(const char *name) +{ + //const char *start = name; + + for (;;) { + name = valid_domain_label(name); + if (!name) + return 0; + if (!name[0]) + return 1; + //Do we want this? + //return ((name - start) < 1025); /* NS_MAXDNAME */ + name++; + } +} + +#define DHCPC_PIPEOUT_HAS_IP 0x01 +#define DHCPC_PIPEOUT_HAS_SERVER_IP 0x02 +#define DHCPC_PIPEOUT_HAS_OPTS 0x04 + +static void fill_dhcpc_pipe_data (struct dhcp_packet *packet, unsigned char *output_buffer, int *output_len) { + int g; + uint8_t *option; + unsigned char opt_code, byte; + uint8_t type; + int optlen, len; + char *dest; + + uint8_t critical_opts[] = { + 0x01, /* DHCP_SUBNET */ + 0x03, /* DHCP_ROUTER */ + 0x06, /* DHCP_DNS_SERVER */ + 0x0c, /* DHCP_HOST_NAME */ + 0x0f, /* DHCP_DOMAIN_NAME */ + 0x2a, /* DHCP_NTP_SERVER */ + 0 + }; + + uint8_t opts_types[] = { + OPTION_IP, + OPTION_IP | OPTION_LIST, + OPTION_IP | OPTION_LIST, + OPTION_STRING_HOST, + OPTION_STRING_HOST, + OPTION_IP | OPTION_LIST, + }; + + if (packet == NULL) { + /* Enviar un cero para indicar que no hay ni IP o server address */ + byte = 0; + output_buffer[*output_len] = byte; + (*output_len)++; + + return; + } + + byte = DHCPC_PIPEOUT_HAS_IP | DHCPC_PIPEOUT_HAS_OPTS; + + if (packet->siaddr_nip) { + byte |= DHCPC_PIPEOUT_HAS_SERVER_IP; + } + + /* Enviar la lista de banderas activadas para esta configuración */ + output_buffer[*output_len] = byte; + (*output_len)++; + + /* Enviar la IP */ + memcpy (&output_buffer[*output_len], &packet->yiaddr, 4); + *output_len += 4; + + /* Enviar el servidor IP si existe */ + if (packet->siaddr_nip) { + memcpy (&output_buffer[*output_len], &packet->siaddr_nip, 4); + *output_len += 4; + } + + g = 0; + while (critical_opts[g] != 0) { + opt_code = critical_opts[g]; + option = udhcp_get_option(packet, opt_code); + + if (option) { + type = opts_types[g] & OPTION_TYPE_MASK; + optlen = dhcp_option_lengths[type]; + + byte = opt_code; + output_buffer[*output_len] = byte; + (*output_len)++; + + len = option[-1]; + if (opts_types[g] & OPTION_LIST) { + /* Para las opciones que son lista, enviar un byte indicando cuantos elementos hay en la lista */ + byte = len / optlen; + output_buffer[*output_len] = byte; + (*output_len)++; + } + + while (len >= optlen) { + switch (type) { + case OPTION_IP: + memcpy (&output_buffer[*output_len], option, 4); + *output_len += 4; + break; + case OPTION_STRING_HOST: + dest = malloc (sizeof (char) * (len + 3)); + memcpy (dest, option, len); + dest[len] = '\0'; + if (type == OPTION_STRING_HOST && !good_hostname(dest)) { + memcpy (&output_buffer[*output_len], "bad", 4); + *output_len += 4; + } else { + memcpy (&output_buffer[*output_len], dest, len + 1); + *output_len += (len + 1); + } + break; + } + + if (type == OPTION_STRING_HOST) { + break; + } + + option += optlen; + len -= optlen; + } + } + g++; + } + + /* Enviar un 255 para indicar el final de las opciones */ + byte = 255; + output_buffer[*output_len] = byte; + (*output_len)++; +} + +/* Call a script with a par file and env vars */ +static void udhcp_run_script (int output_pipe, struct dhcp_packet *packet, const char *name) { + unsigned char type; + + unsigned char output_buffer[1024]; + int output_len = 0; + + if (strcmp (name, "deconfig") == 0) { + type = 1; + } else if (strcmp (name, "leasefail") == 0) { + type = 2; + } else if (strcmp (name, "bound") == 0) { + type = 3; + } else if (strcmp (name, "renew") == 0) { + type = 4; + } else if (strcmp (name, "nak") == 0) { + type = 5; + } + + output_buffer[output_len] = type; + output_len++; + + fill_dhcpc_pipe_data (packet, output_buffer, &output_len); + + /* Enviar todos los datos de un solo jalón, para que el otro extremo los lea en un solo bloque */ + write (output_pipe, output_buffer, output_len); +} + + +/*** Sending/receiving packets ***/ + +static uint32_t random_xid(void) +{ + return rand(); +} + +/* Initialize the packet with the proper defaults */ +static void init_packet(struct dhcp_packet *packet, char type) +{ + uint16_t secs; + + /* Fill in: op, htype, hlen, cookie fields; message type option: */ + udhcp_init_header(packet, type); + + packet->xid = random_xid(); + + client_config.last_secs = monotonic_sec(); + if (client_config.first_secs == 0) + client_config.first_secs = client_config.last_secs; + secs = client_config.last_secs - client_config.first_secs; + packet->secs = htons(secs); + + memcpy(packet->chaddr, client_config.client_mac, 6); + if (client_config.clientid) + udhcp_add_binary_option(packet, client_config.clientid); +} + +static void add_client_options(struct dhcp_packet *packet) +{ + int i, end, len; + + udhcp_add_simple_option(packet, DHCP_MAX_SIZE, htons(IP_UDP_DHCP_SIZE)); + + /* Add a "param req" option with the list of options we'd like to have + * from stubborn DHCP servers. Pull the data from the struct in common.c. + * No bounds checking because it goes towards the head of the packet. */ + end = udhcp_end_option(packet->options); + len = 0; + for (i = 1; i < DHCP_END; i++) { + if (client_config.opt_mask[i >> 3] & (1 << (i & 7))) { + packet->options[end + OPT_DATA + len] = i; + len++; + } + } + if (len) { + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + } + + if (client_config.vendorclass) + udhcp_add_binary_option(packet, client_config.vendorclass); + if (client_config.hostname) + udhcp_add_binary_option(packet, client_config.hostname); + if (client_config.fqdn) + udhcp_add_binary_option(packet, client_config.fqdn); + + /* Request broadcast replies if we have no IP addr + if ((option_mask32 & OPT_B) && packet->ciaddr == 0) + packet->flags |= htons(BROADCAST_FLAG);*/ + + /* Add -x options if any */ + { + struct option_set *curr = client_config.options; + while (curr) { + udhcp_add_binary_option(packet, curr->data); + curr = curr->next; + } +// if (client_config.sname) +// strncpy((char*)packet->sname, client_config.sname, sizeof(packet->sname) - 1); +// if (client_config.boot_file) +// strncpy((char*)packet->file, client_config.boot_file, sizeof(packet->file) - 1); + } + + // This will be needed if we remove -V VENDOR_STR in favor of + // -x vendor:VENDOR_STR + //if (!udhcp_find_option(packet.options, DHCP_VENDOR)) + // /* not set, set the default vendor ID */ + // ...add (DHCP_VENDOR, "udhcp "BB_VER) opt... +} + +/* RFC 2131 + * 4.4.4 Use of broadcast and unicast + * + * The DHCP client broadcasts DHCPDISCOVER, DHCPREQUEST and DHCPINFORM + * messages, unless the client knows the address of a DHCP server. + * The client unicasts DHCPRELEASE messages to the server. Because + * the client is declining the use of the IP address supplied by the server, + * the client broadcasts DHCPDECLINE messages. + * + * When the DHCP client knows the address of a DHCP server, in either + * INIT or REBOOTING state, the client may use that address + * in the DHCPDISCOVER or DHCPREQUEST rather than the IP broadcast address. + * The client may also use unicast to send DHCPINFORM messages + * to a known DHCP server. If the client receives no response to DHCP + * messages sent to the IP address of a known DHCP server, the DHCP + * client reverts to using the IP broadcast address. + */ + +static int raw_bcast_from_client_config_ifindex(struct dhcp_packet *packet) +{ + return udhcp_send_raw_packet(packet, + /*src*/ INADDR_ANY, CLIENT_PORT, + /*dst*/ INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR, + client_config.ifindex); +} + +static int bcast_or_ucast(struct dhcp_packet *packet, uint32_t ciaddr, uint32_t server) +{ + if (server) + return udhcp_send_kernel_packet(packet, + ciaddr, CLIENT_PORT, + server, SERVER_PORT); + return raw_bcast_from_client_config_ifindex(packet); +} + +/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ +/* NOINLINE: limit stack usage in caller */ +static NOINLINE int send_discover(uint32_t xid, uint32_t requested) +{ + struct dhcp_packet packet; + + /* Fill in: op, htype, hlen, cookie, chaddr fields, + * random xid field (we override it below), + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPDISCOVER); + + packet.xid = xid; + if (requested) + udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested); + + /* Add options: maxsize, + * optionally: hostname, fqdn, vendorclass, + * "param req" option according to -O, options specified with -x + */ + add_client_options(&packet); + + fprintf (stderr, "Sending discover..."); + return raw_bcast_from_client_config_ifindex(&packet); +} + +/* Broadcast a DHCP request message */ +/* RFC 2131 3.1 paragraph 3: + * "The client _broadcasts_ a DHCPREQUEST message..." + */ +/* NOINLINE: limit stack usage in caller */ +static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requested) +{ + struct dhcp_packet packet; + struct in_addr addr; + +/* + * RFC 2131 4.3.2 DHCPREQUEST message + * ... + * If the DHCPREQUEST message contains a 'server identifier' + * option, the message is in response to a DHCPOFFER message. + * Otherwise, the message is a request to verify or extend an + * existing lease. If the client uses a 'client identifier' + * in a DHCPREQUEST message, it MUST use that same 'client identifier' + * in all subsequent messages. If the client included a list + * of requested parameters in a DHCPDISCOVER message, it MUST + * include that list in all subsequent messages. + */ + /* Fill in: op, htype, hlen, cookie, chaddr fields, + * random xid field (we override it below), + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPREQUEST); + + packet.xid = xid; + udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested); + + udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server); + + /* Add options: maxsize, + * optionally: hostname, fqdn, vendorclass, + * "param req" option according to -O, and options specified with -x + */ + add_client_options(&packet); + + addr.s_addr = requested; + fprintf (stderr, "Sending select for %s...", inet_ntoa(addr)); + return raw_bcast_from_client_config_ifindex(&packet); +} + +/* Unicast or broadcast a DHCP renew message */ +/* NOINLINE: limit stack usage in caller */ +static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) +{ + struct dhcp_packet packet; + +/* + * RFC 2131 4.3.2 DHCPREQUEST message + * ... + * DHCPREQUEST generated during RENEWING state: + * + * 'server identifier' MUST NOT be filled in, 'requested IP address' + * option MUST NOT be filled in, 'ciaddr' MUST be filled in with + * client's IP address. In this situation, the client is completely + * configured, and is trying to extend its lease. This message will + * be unicast, so no relay agents will be involved in its + * transmission. Because 'giaddr' is therefore not filled in, the + * DHCP server will trust the value in 'ciaddr', and use it when + * replying to the client. + */ + /* Fill in: op, htype, hlen, cookie, chaddr fields, + * random xid field (we override it below), + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPREQUEST); + + packet.xid = xid; + packet.ciaddr = ciaddr; + + /* Add options: maxsize, + * optionally: hostname, fqdn, vendorclass, + * "param req" option according to -O, and options specified with -x + */ + add_client_options(&packet); + + fprintf (stderr, "Sending renew..."); + return bcast_or_ucast(&packet, ciaddr, server); +} + +#if ENABLE_FEATURE_UDHCPC_ARPING +/* Broadcast a DHCP decline message */ +/* NOINLINE: limit stack usage in caller */ +static NOINLINE int send_decline(/*uint32_t xid,*/ uint32_t server, uint32_t requested) +{ + struct dhcp_packet packet; + + /* Fill in: op, htype, hlen, cookie, chaddr, random xid fields, + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPDECLINE); + +#if 0 + /* RFC 2131 says DHCPDECLINE's xid is randomly selected by client, + * but in case the server is buggy and wants DHCPDECLINE's xid + * to match the xid which started entire handshake, + * we use the same xid we used in initial DHCPDISCOVER: + */ + packet.xid = xid; +#endif + /* DHCPDECLINE uses "requested ip", not ciaddr, to store offered IP */ + udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested); + + udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server); + + fprintf(stderr, "Sending decline..."); + return raw_bcast_from_client_config_ifindex(&packet); +} +#endif + +/* Unicast a DHCP release message */ +static int send_release(uint32_t server, uint32_t ciaddr) +{ + struct dhcp_packet packet; + + /* Fill in: op, htype, hlen, cookie, chaddr, random xid fields, + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPRELEASE); + + /* DHCPRELEASE uses ciaddr, not "requested ip", to store IP being released */ + packet.ciaddr = ciaddr; + + udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server); + + fprintf (stderr, "Sending release..."); + /* Note: normally we unicast here since "server" is not zero. + * However, there _are_ people who run "address-less" DHCP servers, + * and reportedly ISC dhcp client and Windows allow that. + */ + return bcast_or_ucast(&packet, ciaddr, server); +} + +/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */ +/* NOINLINE: limit stack usage in caller */ +static NOINLINE int udhcp_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd) +{ + int bytes; + struct ip_udp_dhcp_packet packet; + uint16_t check; + unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + + /* used to use just safe_read(fd, &packet, sizeof(packet)) + * but we need to check for TP_STATUS_CSUMNOTREADY :( + */ + iov.iov_base = &packet; + iov.iov_len = sizeof(packet); + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + for (;;) { + bytes = recvmsg(fd, &msg, 0); + if (bytes < 0) { + if (errno == EINTR) + continue; + log1("Packet read error, ignoring"); + /* NB: possible down interface, etc. Caller should pause. */ + return bytes; /* returns -1 */ + } + break; + } + + if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) { + log1("Packet is too short, ignoring"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + /* packet is bigger than sizeof(packet), we did partial read */ + log1("Oversized packet, ignoring"); + return -2; + } + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + /* make sure its the right packet for us, and that it passes sanity checks */ + if (packet.ip.protocol != IPPROTO_UDP + || packet.ip.version != IPVERSION + || packet.ip.ihl != (sizeof(packet.ip) >> 2) + || packet.udp.dest != htons(CLIENT_PORT) + /* || bytes > (int) sizeof(packet) - can't happen */ + || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) + ) { + log1("Unrelated/bogus packet, ignoring"); + return -2; + } + + /* verify IP checksum */ + check = packet.ip.check; + packet.ip.check = 0; + if (check != inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip))) { + log1("Bad IP header checksum, ignoring"); + return -2; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_PACKET + && cmsg->cmsg_type == PACKET_AUXDATA + ) { + /* some VMs don't checksum UDP and TCP data + * they send to the same physical machine, + * here we detect this case: + */ + struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg); + if (aux->tp_status & TP_STATUS_CSUMNOTREADY) + goto skip_udp_sum_check; + } + } + + /* verify UDP checksum. IP header has to be modified for this */ + memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); + /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ + packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ + check = packet.udp.check; + packet.udp.check = 0; + if (check && check != inet_cksum((uint16_t *)&packet, bytes)) { + log1("Packet with bad UDP checksum received, ignoring"); + return -2; + } + skip_udp_sum_check: + + if (packet.data.cookie != htonl(DHCP_MAGIC)) { + fprintf (stderr, "Packet with bad magic, ignoring"); + return -2; + } + + //log1("Received a packet"); + udhcp_dump_packet(&packet.data); + + bytes -= sizeof(packet.ip) + sizeof(packet.udp); + memcpy(dhcp_pkt, &packet.data, bytes); + return bytes; +} + + +/*** Main ***/ + +static int sockfd = -1; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static smallint listen_mode; + +/* initial state: (re)start DHCP negotiation */ +#define INIT_SELECTING 0 +/* discover was sent, DHCPOFFER reply received */ +#define REQUESTING 1 +/* select/renew was sent, DHCPACK reply received */ +#define BOUND 2 +/* half of lease passed, want to renew it by sending unicast renew requests */ +#define RENEWING 3 +/* renew requests were not answered, lease is almost over, send broadcast renew */ +#define REBINDING 4 +/* manually requested renew (SIGUSR1) */ +#define RENEW_REQUESTED 5 +/* release, possibly manually requested (SIGUSR2) */ +#define RELEASED 6 +static smallint state; + +static int udhcp_raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + /* + * Comment: + * + * I've selected not to see LL header, so BPF doesn't see it, too. + * The filter may also pass non-IP and non-ARP packets, but we do + * a more complete check when receiving the message in userspace. + * + * and filter shamelessly stolen from: + * + * http://www.flamewarmaster.de/software/dhcpclient/ + * + * There are a few other interesting ideas on that page (look under + * "Motivation"). Use of netlink events is most interesting. Think + * of various network servers listening for events and reconfiguring. + * That would obsolete sending HUP signals and/or make use of restarts. + * + * Copyright: 2006, 2007 Stefan Rompf . + * License: GPL v2. + * + * TODO: make conditional? + */ + static const struct sock_filter filter_instr[] = { + /* load 9th byte (protocol) */ + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), + /* jump to L1 if it is IPPROTO_UDP, else to L4 */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 0, 6), + /* L1: load halfword from offset 6 (flags and frag offset) */ + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6), + /* jump to L4 if any bits in frag offset field are set, else to L2 */ + BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x1fff, 4, 0), + /* L2: skip IP header (load index reg with header len) */ + BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), + /* load udp destination port from halfword[header_len + 2] */ + BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2), + /* jump to L3 if udp dport is CLIENT_PORT, else to L4 */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1), + /* L3: accept packet */ + BPF_STMT(BPF_RET|BPF_K, 0xffffffff), + /* L4: discard packet */ + BPF_STMT(BPF_RET|BPF_K, 0), + }; + static const struct sock_fprog filter_prog = { + .len = sizeof(filter_instr) / sizeof(filter_instr[0]), + /* casting const away: */ + .filter = (struct sock_filter *) filter_instr, + }; + + log1("Opening raw socket on ifindex %d", ifindex); //log2? + + fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + log1("Got raw socket fd"); //log2? + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + xbind(fd, (struct sockaddr *) &sock, sizeof(sock)); + + if (CLIENT_PORT == 68) { + /* Use only if standard port is in use */ + /* Ignoring error (kernel may lack support for this) */ + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, + sizeof(filter_prog)) >= 0) + log1("Attached filter to raw socket fd"); // log? + } + + if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, + &const_int_1, sizeof(int)) < 0 + ) { + if (errno != ENOPROTOOPT) { + //log1("Can't set PACKET_AUXDATA on raw socket"); + } + } + + log1("Created raw socket"); + + return fd; +} + +static void change_listen_mode(int new_mode) +{ + /*log1("Entering listen mode: %s", + new_mode != LISTEN_NONE + ? (new_mode == LISTEN_KERNEL ? "kernel" : "raw") + : "none" + );*/ + + listen_mode = new_mode; + if (sockfd >= 0) { + close(sockfd); + sockfd = -1; + } + if (new_mode == LISTEN_KERNEL) + sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface); + else if (new_mode != LISTEN_NONE) + sockfd = udhcp_raw_socket(client_config.ifindex); + /* else LISTEN_NONE: sockfd stays closed */ +} + +/* Called only on SIGUSR1 */ +static void perform_renew(int pipe_fd) +{ + fprintf (stderr, "Performing a DHCP renew"); + switch (state) { + case BOUND: + change_listen_mode(LISTEN_KERNEL); + case RENEWING: + case REBINDING: + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ + /* FIXME: Correr script aquí */ + udhcp_run_script(pipe_fd, NULL, "deconfig"); + case REQUESTING: + case RELEASED: + change_listen_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } +} + +static void perform_release(int pipe_fd, uint32_t server_addr, uint32_t requested_ip) +{ + char buffer[sizeof("255.255.255.255")]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + strcpy(buffer, inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + fprintf (stderr, "Unicasting a release of %s to %s", + inet_ntoa(temp_addr), buffer); + send_release(server_addr, requested_ip); /* unicast */ + /* FIXME: Aquí SCRIPT */ + udhcp_run_script (pipe_fd, NULL, "deconfig"); + } + fprintf (stderr, "Entering released state"); + + change_listen_mode(LISTEN_NONE); + state = RELEASED; +} + +static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) +{ + uint8_t *storage; + int len = strnlen(str, 255); + storage = xzalloc(len + extra + OPT_DATA); + storage[OPT_CODE] = code; + storage[OPT_LEN] = len + extra; + memcpy(storage + extra + OPT_DATA, str, len); + return storage; +} + +#if BB_MMU +static void client_background(void) +{ + bb_daemonize(0); + logmode &= ~LOGMODE_STDIO; + /* rewrite pidfile, as our pid is different now */ + write_pidfile(client_config.pidfile); +} +#endif + +//usage:#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 +//usage:# define IF_UDHCP_VERBOSE(...) __VA_ARGS__ +//usage:#else +//usage:# define IF_UDHCP_VERBOSE(...) +//usage:#endif +//usage:#define udhcpc_trivial_usage +//usage: "[-fbq"IF_UDHCP_VERBOSE("v")IF_FEATURE_UDHCPC_ARPING("a")"RB] [-t N] [-T SEC] [-A SEC/-n]\n" +//usage: " [-i IFACE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-s PROG] [-p PIDFILE]\n" +//usage: " [-oC] [-r IP] [-V VENDOR] [-F NAME] [-x OPT:VAL]... [-O OPT]..." +//usage:#define udhcpc_full_usage "\n" +//usage: IF_LONG_OPTS( +//usage: "\n -i,--interface IFACE Interface to use (default eth0)" +//usage: IF_FEATURE_UDHCP_PORT( +//usage: "\n -P,--client-port PORT Use PORT (default 68)" +//usage: ) +//usage: "\n -s,--script PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" +//usage: "\n -p,--pidfile FILE Create pidfile" +//usage: "\n -B,--broadcast Request broadcast replies" +//usage: "\n -t,--retries N Send up to N discover packets (default 3)" +//usage: "\n -T,--timeout SEC Pause between packets (default 3)" +//usage: "\n -A,--tryagain SEC Wait if lease is not obtained (default 20)" +//usage: "\n -n,--now Exit if lease is not obtained" +//usage: "\n -q,--quit Exit after obtaining lease" +//usage: "\n -R,--release Release IP on exit" +//usage: "\n -f,--foreground Run in foreground" +//usage: USE_FOR_MMU( +//usage: "\n -b,--background Background if lease is not obtained" +//usage: ) +//usage: "\n -S,--syslog Log to syslog too" +//usage: IF_FEATURE_UDHCPC_ARPING( +//usage: "\n -a,--arping Use arping to validate offered address" +//usage: ) +//usage: "\n -r,--request IP Request this IP address" +//usage: "\n -o,--no-default-options Don't request any options (unless -O is given)" +//usage: "\n -O,--request-option OPT Request option OPT from server (cumulative)" +//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)" +//usage: "\n Examples of string, numeric, and hex byte opts:" +//usage: "\n -x hostname:bbox - option 12" +//usage: "\n -x lease:3600 - option 51 (lease time)" +//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" +//usage: "\n -F,--fqdn NAME Ask server to update DNS mapping for NAME" +//usage: "\n -V,--vendorclass VENDOR Vendor identifier (default 'udhcp VERSION')" +//usage: "\n -C,--clientid-none Don't send MAC as client identifier" +//usage: IF_UDHCP_VERBOSE( +//usage: "\n -v Verbose" +//usage: ) +//usage: ) +//usage: IF_NOT_LONG_OPTS( +//usage: "\n -i IFACE Interface to use (default eth0)" +//usage: IF_FEATURE_UDHCP_PORT( +//usage: "\n -P PORT Use PORT (default 68)" +//usage: ) +//usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" +//usage: "\n -p FILE Create pidfile" +//usage: "\n -B Request broadcast replies" +//usage: "\n -t N Send up to N discover packets (default 3)" +//usage: "\n -T SEC Pause between packets (default 3)" +//usage: "\n -A SEC Wait if lease is not obtained (default 20)" +//usage: "\n -n Exit if lease is not obtained" +//usage: "\n -q Exit after obtaining lease" +//usage: "\n -R Release IP on exit" +//usage: "\n -f Run in foreground" +//usage: USE_FOR_MMU( +//usage: "\n -b Background if lease is not obtained" +//usage: ) +//usage: "\n -S Log to syslog too" +//usage: IF_FEATURE_UDHCPC_ARPING( +//usage: "\n -a Use arping to validate offered address" +//usage: ) +//usage: "\n -r IP Request this IP address" +//usage: "\n -o Don't request any options (unless -O is given)" +//usage: "\n -O OPT Request option OPT from server (cumulative)" +//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)" +//usage: "\n Examples of string, numeric, and hex byte opts:" +//usage: "\n -x hostname:bbox - option 12" +//usage: "\n -x lease:3600 - option 51 (lease time)" +//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" +//usage: "\n -F NAME Ask server to update DNS mapping for NAME" +//usage: "\n -V VENDOR Vendor identifier (default 'udhcp VERSION')" +//usage: "\n -C Don't send MAC as client identifier" +//usage: IF_UDHCP_VERBOSE( +//usage: "\n -v Verbose" +//usage: ) +//usage: ) +//usage: "\nSignals:" +//usage: "\n USR1 Renew lease" +//usage: "\n USR2 Release lease" + + +int udhcpc_main(int pipe_fd, char *interface) +{ + uint8_t *temp, *message; + // str_V se usa como argumento -V,--vendorclass VENDOR + // + const char *str_V, *str_h, *str_F, *str_r; + void *clientid_mac_ptr; + /*llist_t *list_O = NULL; + llist_t *list_x = NULL;*/ + int tryagain_timeout = 20; + int discover_timeout = 3; + int discover_retries = 3; + uint32_t server_addr = server_addr; /* for compiler */ + uint32_t requested_ip = 0; + uint32_t xid = xid; /* for compiler */ + int packet_num; + int timeout; /* must be signed */ + unsigned already_waited_sec; + unsigned opt; + int max_fd; + int retval; + fd_set rfds; + + /* Default options */ + /*IF_FEATURE_UDHCP_PORT(SERVER_PORT = 67;) + IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)*/ + client_config.interface = interface; + //client_config.script = CONFIG_UDHCPC_DEFAULT_SCRIPT; + str_V = "nidhcpc 0.1"; + +#if 0 + while (list_O) { + char *optstr = llist_pop(&list_O); + unsigned n = bb_strtou(optstr, NULL, 0); + if (errno || n > 254) { + n = udhcp_option_idx(optstr); + n = dhcp_optflags[n].code; + } + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + } +#endif + /* Agregar la opciones request */ + unsigned n; + n = 0x01; // DHCP_SUBNET + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + + n = 0x03; // DHCP_ROUTER + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + + n = 0x06; // DHCP_DNS_SERVER + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + + n = 0x0c; // DHCP_HOST_NAME + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + + n = 0x0f; // DHCP_DOMAIN_NAME + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + + n = 0x2a; // DHCP_NTP_SERVER + client_config.opt_mask[n >> 3] |= 1 << (n & 7); +#if 0 + while (list_x) { + char *optstr = llist_pop(&list_x); + char *colon = strchr(optstr, ':'); + if (colon) + *colon = ' '; + /* now it looks similar to udhcpd's config file line: + * "optname optval", using the common routine: */ + udhcp_str2optset(optstr, &client_config.options); + } +#endif + if (udhcp_read_interface(client_config.interface, + &client_config.ifindex, + NULL, + client_config.client_mac) + ) { + return 1; + } + + clientid_mac_ptr = NULL; + /* not suppressed and not set, set the default client ID */ + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7); + client_config.clientid[OPT_DATA] = 1; /* type: ethernet */ + clientid_mac_ptr = client_config.clientid + OPT_DATA+1; + memcpy(clientid_mac_ptr, client_config.client_mac, 6); + if (str_V[0] != '\0') { + // can drop -V, str_V, client_config.vendorclass, + // but need to add "vendor" to the list of recognized + // string opts for this to work; + // and need to tweak add_client_options() too... + // ...so the question is, should we? + //bb_error_msg("option -V VENDOR is deprecated, use -x vendor:VENDOR"); + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); + } + + openlog("udhcpc", LOG_PID, LOG_DAEMON); + //logmode |= LOGMODE_SYSLOG; + + /* Make sure fd 0,1,2 are open */ + //bb_sanitize_stdio(); + /* Equivalent of doing a fflush after every \n */ + //setlinebuf(stdout); + /* Create pidfile */ + //write_pidfile(client_config.pidfile); + /* Goes to stdout (unless NOMMU) and possibly syslog */ + //bb_info_msg("%s (v"BB_VER") started", applet_name); + /* Set up the signal pipe */ + udhcp_sp_setup(); + /* We want random_xid to be random... */ + srand((unsigned int) getpid ()); + + state = INIT_SELECTING; + /* FIXME: AQUI SCRIPT */ + udhcp_run_script (pipe_fd, NULL, "deconfig"); + change_listen_mode(LISTEN_RAW); + packet_num = 0; + timeout = 0; + already_waited_sec = 0; + + /* Main event loop. select() waits on signal pipe and possibly + * on sockfd. + * "continue" statements in code below jump to the top of the loop. + */ + for (;;) { + struct timeval tv; + struct dhcp_packet packet; + /* silence "uninitialized!" warning */ + unsigned timestamp_before_wait = timestamp_before_wait; + + //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode); + + /* Was opening raw or udp socket here + * if (listen_mode != LISTEN_NONE && sockfd < 0), + * but on fast network renew responses return faster + * than we open sockets. Thus this code is moved + * to change_listen_mode(). Thus we open listen socket + * BEFORE we send renew request (see "case BOUND:"). */ + + max_fd = udhcp_sp_fd_set(&rfds, sockfd); + + tv.tv_sec = timeout - already_waited_sec; + tv.tv_usec = 0; + retval = 0; + /* If we already timed out, fall through with retval = 0, else... */ + if ((int)tv.tv_sec > 0) { + //log1("Waiting on select %u seconds", (int)tv.tv_sec); + timestamp_before_wait = (unsigned)monotonic_sec(); + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + if (retval < 0) { + /* EINTR? A signal was caught, don't panic */ + if (errno == EINTR) { + already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait; + continue; + } + /* Else: an error occured, panic! */ + fprintf (stderr, "Error: select ()\n"); + exit (EXIT_FAILURE); + } + } + + /* If timeout dropped to zero, time to become active: + * resend discover/renew/whatever + */ + if (retval == 0) { + /* When running on a bridge, the ifindex may have changed + * (e.g. if member interfaces were added/removed + * or if the status of the bridge changed). + * Refresh ifindex and client_mac: + */ + if (udhcp_read_interface(client_config.interface, + &client_config.ifindex, + NULL, + client_config.client_mac) + ) { + goto ret0; /* iface is gone? */ + } + if (clientid_mac_ptr) + memcpy(clientid_mac_ptr, client_config.client_mac, 6); + + /* We will restart the wait in any case */ + already_waited_sec = 0; + + switch (state) { + case INIT_SELECTING: + if (!discover_retries || packet_num < discover_retries) { + if (packet_num == 0) + xid = random_xid(); + /* broadcast */ + send_discover(xid, requested_ip); + timeout = discover_timeout; + packet_num++; + continue; + } + leasefail: + /* FIXME: AQUI SCRIPT */ + udhcp_run_script (pipe_fd, NULL, "leasefail"); + /* wait before trying again */ + timeout = tryagain_timeout; + packet_num = 0; + continue; + case REQUESTING: + if (!discover_retries || packet_num < discover_retries) { + /* send broadcast select packet */ + send_select(xid, server_addr, requested_ip); + timeout = discover_timeout; + packet_num++; + continue; + } + /* Timed out, go back to init state. + * "discover...select...discover..." loops + * were seen in the wild. Treat them similarly + * to "no response to discover" case */ + change_listen_mode(LISTEN_RAW); + state = INIT_SELECTING; + goto leasefail; + case BOUND: + /* 1/2 lease passed, enter renewing state */ + state = RENEWING; + client_config.first_secs = 0; /* make secs field count from 0 */ + change_listen_mode(LISTEN_KERNEL); + //log1("Entering renew state"); + /* fall right through */ + case RENEW_REQUESTED: /* manual (SIGUSR1) renew */ + case_RENEW_REQUESTED: + case RENEWING: + if (timeout > 60) { + /* send an unicast renew request */ + /* Sometimes observed to fail (EADDRNOTAVAIL) to bind + * a new UDP socket for sending inside send_renew. + * I hazard to guess existing listening socket + * is somehow conflicting with it, but why is it + * not deterministic then?! Strange. + * Anyway, it does recover by eventually failing through + * into INIT_SELECTING state. + */ + send_renew(xid, server_addr, requested_ip); + timeout >>= 1; + continue; + } + /* Timed out, enter rebinding state */ + //log1("Entering rebinding state"); + state = REBINDING; + /* fall right through */ + case REBINDING: + /* Switch to bcast receive */ + change_listen_mode(LISTEN_RAW); + /* Lease is *really* about to run out, + * try to find DHCP server using broadcast */ + if (timeout > 0) { + /* send a broadcast renew request */ + send_renew(xid, 0 /*INADDR_ANY*/, requested_ip); + timeout >>= 1; + continue; + } + /* Timed out, enter init state */ + fprintf (stderr, "Lease lost, entering init state"); + /* FIXME: AQUI SCRIPT */ + udhcp_run_script (pipe_fd, NULL, "deconfig"); + state = INIT_SELECTING; + client_config.first_secs = 0; /* make secs field count from 0 */ + /*timeout = 0; - already is */ + packet_num = 0; + continue; + /* case RELEASED: */ + } + /* yah, I know, *you* say it would never happen */ + timeout = INT_MAX; + continue; /* back to main loop */ + } /* if select timed out */ + + /* select() didn't timeout, something happened */ + + /* Is it a signal? */ + /* note: udhcp_sp_read checks FD_ISSET before reading */ + switch (udhcp_sp_read(&rfds)) { + case SIGUSR1: + fprintf (stderr, "SIGUSR1\n"); + client_config.first_secs = 0; /* make secs field count from 0 */ + already_waited_sec = 0; + perform_renew(pipe_fd); + if (state == RENEW_REQUESTED) { + /* We might be either on the same network + * (in which case renew might work), + * or we might be on a completely different one + * (in which case renew won't ever succeed). + * For the second case, must make sure timeout + * is not too big, or else we can send + * futile renew requests for hours. + * (Ab)use -A TIMEOUT value (usually 20 sec) + * as a cap on the timeout. + */ + if (timeout > tryagain_timeout) + timeout = tryagain_timeout; + goto case_RENEW_REQUESTED; + } + /* Start things over */ + packet_num = 0; + /* Kill any timeouts, user wants this to hurry along */ + timeout = 0; + continue; + case SIGUSR2: + perform_release (pipe_fd, server_addr, requested_ip); + timeout = INT_MAX; + continue; + case SIGTERM: + fprintf (stderr, "Received SIGTERM"); + goto ret0; + } + + /* Is it a packet? */ + if (listen_mode == LISTEN_NONE || !FD_ISSET(sockfd, &rfds)) + continue; /* no */ + + { + int len; + + /* A packet is ready, read it */ + if (listen_mode == LISTEN_KERNEL) + len = udhcp_recv_kernel_packet(&packet, sockfd); + else + len = udhcp_recv_raw_packet(&packet, sockfd); + if (len == -1) { + /* Error is severe, reopen socket */ + fprintf (stderr, "Read error: %s, reopening socket", strerror(errno)); + sleep(discover_timeout); /* 3 seconds by default */ + change_listen_mode(listen_mode); /* just close and reopen */ + } + /* If this packet will turn out to be unrelated/bogus, + * we will go back and wait for next one. + * Be sure timeout is properly decreased. */ + already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait; + if (len < 0) + continue; + } + + if (packet.xid != xid) { + log1("xid %x (our is %x), ignoring packet", + (unsigned)packet.xid, (unsigned)xid); + continue; + } + + /* Ignore packets that aren't for us */ + if (packet.hlen != 6 + || memcmp(packet.chaddr, client_config.client_mac, 6) != 0 + ) { +//FIXME: need to also check that last 10 bytes are zero + //log1("chaddr does not match, ignoring packet"); // log2? + continue; + } + + message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE); + if (message == NULL) { + fprintf (stderr, "no message type option, ignoring packet"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER */ + if (*message == DHCPOFFER) { +/* What exactly is server's IP? There are several values. + * Example DHCP offer captured with tchdump: + * + * 10.34.25.254:67 > 10.34.25.202:68 // IP header's src + * BOOTP fields: + * Your-IP 10.34.25.202 + * Server-IP 10.34.32.125 // "next server" IP + * Gateway-IP 10.34.25.254 // relay's address (if DHCP relays are in use) + * DHCP options: + * DHCP-Message Option 53, length 1: Offer + * Server-ID Option 54, length 4: 10.34.255.7 // "server ID" + * Default-Gateway Option 3, length 4: 10.34.25.254 // router + * + * We think that real server IP (one to use in renew/release) + * is one in Server-ID option. But I am not 100% sure. + * IP header's src and Gateway-IP (same in this example) + * might work too. + * "Next server" and router are definitely wrong ones to use, though... + */ +/* We used to ignore pcakets without DHCP_SERVER_ID. + * I've got user reports from people who run "address-less" servers. + * They either supply DHCP_SERVER_ID of 0.0.0.0 or don't supply it at all. + * They say ISC DHCP client supports this case. + */ + server_addr = 0; + temp = udhcp_get_option(&packet, DHCP_SERVER_ID); + if (!temp) { + fprintf (stderr, "no server ID, using 0.0.0.0"); + } else { + /* it IS unaligned sometimes, don't "optimize" */ + move_from_unaligned32(server_addr, temp); + } + /*xid = packet.xid; - already is */ + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = 0; + packet_num = 0; + already_waited_sec = 0; + } + continue; + case REQUESTING: + case RENEWING: + case RENEW_REQUESTED: + case REBINDING: + if (*message == DHCPACK) { + uint32_t lease_seconds; + struct in_addr temp_addr; + + temp = udhcp_get_option(&packet, DHCP_LEASE_TIME); + if (!temp) { + fprintf (stderr, "no lease time with ACK, using 1 hour lease"); + lease_seconds = 60 * 60; + } else { + /* it IS unaligned sometimes, don't "optimize" */ + move_from_unaligned32(lease_seconds, temp); + lease_seconds = ntohl(lease_seconds); + /* paranoia: must not be too small and not prone to overflows */ + if (lease_seconds < 0x10) + lease_seconds = 0x10; + if (lease_seconds >= 0x10000000) + lease_seconds = 0x0fffffff; + } + /* enter bound state */ + timeout = lease_seconds / 2; + temp_addr.s_addr = packet.yiaddr; + fprintf (stderr, "Lease of %s obtained, lease time %u", + inet_ntoa(temp_addr), (unsigned)lease_seconds); + requested_ip = packet.yiaddr; + /* FIXME: Aquí SCRIPT */ + udhcp_run_script (pipe_fd, &packet, state == REQUESTING ? "bound" : "renew"); + + state = BOUND; + change_listen_mode(LISTEN_NONE); + /* future renew failures should not exit (JM) */ + //opt &= ~OPT_n; + /* make future renew packets use different xid */ + /* xid = random_xid(); ...but why bother? */ + already_waited_sec = 0; + continue; /* back to main loop */ + } + if (*message == DHCPNAK) { + /* return to init state */ + fprintf (stderr, "Received DHCP NAK"); + /* FIXME: AQUI SCRIPT */ + udhcp_run_script (pipe_fd, &packet, "nak"); + if (state != REQUESTING) { + /* FIXME: AQUI SCRIPT */ + udhcp_run_script (pipe_fd, NULL, "deconfig"); + } + change_listen_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + state = INIT_SELECTING; + client_config.first_secs = 0; /* make secs field count from 0 */ + requested_ip = 0; + timeout = 0; + packet_num = 0; + already_waited_sec = 0; + } + continue; + /* case BOUND: - ignore all packets */ + /* case RELEASED: - ignore all packets */ + } + /* back to main loop */ + } /* for (;;) - main loop ends */ + + ret0: + perform_release (pipe_fd, server_addr, requested_ip); + retval = 0; + ret: + /*if (client_config.pidfile) - remove_pidfile has its own check */ + //remove_pidfile(client_config.pidfile); + return retval; +} diff --git a/src/dhcpc/dhcpc.h b/src/dhcpc/dhcpc.h new file mode 100644 index 0000000..b63c58a --- /dev/null +++ b/src/dhcpc/dhcpc.h @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +#include + +#ifndef UDHCP_DHCPC_H +#define UDHCP_DHCPC_H 1 + +int udhcpc_main(int pipefd, char *interface); + +struct client_config_t { + uint8_t client_mac[6]; /* Our mac address */ + int ifindex; /* Index number of the interface to use */ + uint8_t opt_mask[256 / 8]; /* Bitmask of options to send (-O option) */ + const char *interface; /* The name of the interface to use */ + char *pidfile; /* Optionally store the process ID */ + const char *script; /* User script to run at dhcp events */ + struct option_set *options; /* list of DHCP options to send to server */ + uint8_t *clientid; /* Optional client id to use */ + uint8_t *vendorclass; /* Optional vendor class-id to use */ + uint8_t *hostname; /* Optional hostname to use */ + uint8_t *fqdn; /* Optional fully qualified domain name to use */ + + uint16_t first_secs; + uint16_t last_secs; +} FIX_ALIASING; + +/* server_config sits in 1st half of bb_common_bufsiz1 */ +//#define client_config (*(struct client_config_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE / 2])) +extern struct client_config_t client_config; + +#define CLIENT_PORT 68 +#define CLIENT_PORT6 546 + +#define SERVER_PORT 67 +#define SERVER_PORT6 547 + +#endif diff --git a/src/dhcpc/dhcpd.h b/src/dhcpc/dhcpd.h new file mode 100644 index 0000000..1ae6d04 --- /dev/null +++ b/src/dhcpc/dhcpd.h @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +#ifndef UDHCP_DHCPD_H +#define UDHCP_DHCPD_H 1 + +PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN + +/* Defaults you may want to tweak */ +/* Default max_lease_sec */ +#define DEFAULT_LEASE_TIME (60*60*24 * 10) +#define LEASES_FILE CONFIG_DHCPD_LEASES_FILE +/* Where to find the DHCP server configuration file */ +#define DHCPD_CONF_FILE "/etc/udhcpd.conf" + + +struct static_lease { + struct static_lease *next; + uint32_t nip; + uint8_t mac[6]; +}; + +struct server_config_t { + char *interface; /* interface to use */ +//TODO: ifindex, server_nip, server_mac +// are obtained from interface name. +// Instead of querying them *once*, create update_server_network_data_cache() +// and call it before any usage of these fields. +// update_server_network_data_cache() must re-query data +// if more than N seconds have passed after last use. + int ifindex; + uint32_t server_nip; +#if ENABLE_FEATURE_UDHCP_PORT + uint16_t port; +#endif + uint8_t server_mac[6]; /* our MAC address (used only for ARP probing) */ + struct option_set *options; /* list of DHCP options loaded from the config file */ + /* start,end are in host order: we need to compare start <= ip <= end */ + uint32_t start_ip; /* start address of leases, in host order */ + uint32_t end_ip; /* end of leases, in host order */ + uint32_t max_lease_sec; /* maximum lease time (host order) */ + uint32_t min_lease_sec; /* minimum lease time a client can request */ + uint32_t max_leases; /* maximum number of leases (including reserved addresses) */ + uint32_t auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + uint32_t decline_time; /* how long an address is reserved if a client returns a + * decline message */ + uint32_t conflict_time; /* how long an arp conflict offender is leased for */ + uint32_t offer_time; /* how long an offered address is reserved */ + uint32_t siaddr_nip; /* "next server" bootp option */ + char *lease_file; + char *pidfile; + char *notify_file; /* what to run whenever leases are written */ + char *sname; /* bootp server name */ + char *boot_file; /* bootp boot file option */ + struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */ +} FIX_ALIASING; + +#define server_config (*(struct server_config_t*)&bb_common_bufsiz1) +/* client_config sits in 2nd half of bb_common_bufsiz1 */ + +#if ENABLE_FEATURE_UDHCP_PORT +#define SERVER_PORT (server_config.port) +#define SERVER_PORT6 (server_config.port) +#else +#define SERVER_PORT 67 +#define SERVER_PORT6 547 +#endif + + +typedef uint32_t leasetime_t; +typedef int32_t signed_leasetime_t; + +struct dyn_lease { + /* Unix time when lease expires. Kept in memory in host order. + * When written to file, converted to network order + * and adjusted (current time subtracted) */ + leasetime_t expires; + /* "nip": IP in network order */ + uint32_t lease_nip; + /* We use lease_mac[6], since e.g. ARP probing uses + * only 6 first bytes anyway. We check received dhcp packets + * that their hlen == 6 and thus chaddr has only 6 significant bytes + * (dhcp packet has chaddr[16], not [6]) + */ + uint8_t lease_mac[6]; + char hostname[20]; + uint8_t pad[2]; + /* total size is a multiply of 4 */ +} PACKED; + +extern struct dyn_lease *g_leases; + +struct dyn_lease *add_lease( + const uint8_t *chaddr, uint32_t yiaddr, + leasetime_t leasetime, + const char *hostname, int hostname_len + ); +int is_expired_lease(struct dyn_lease *lease) FAST_FUNC; +struct dyn_lease *find_lease_by_mac(const uint8_t *mac) FAST_FUNC; +struct dyn_lease *find_lease_by_nip(uint32_t nip) FAST_FUNC; +uint32_t find_free_or_expired_nip(const uint8_t *safe_mac) FAST_FUNC; + + +/* Config file parser will pass static lease info to this function + * which will add it to a data structure that can be searched later */ +void add_static_lease(struct static_lease **st_lease_pp, uint8_t *mac, uint32_t nip) FAST_FUNC; +/* Find static lease IP by mac */ +uint32_t get_static_nip_by_mac(struct static_lease *st_lease, void *arg) FAST_FUNC; +/* Check to see if an IP is reserved as a static IP */ +int is_nip_reserved(struct static_lease *st_lease, uint32_t nip) FAST_FUNC; +/* Print out static leases just to check what's going on (debug code) */ +#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2 +void log_static_leases(struct static_lease **st_lease_pp) FAST_FUNC; +#else +# define log_static_leases(st_lease_pp) ((void)0) +#endif + + +void read_config(const char *file) FAST_FUNC; +void write_leases(void) FAST_FUNC; +void read_leases(const char *file) FAST_FUNC; + + +POP_SAVED_FUNCTION_VISIBILITY + +#endif diff --git a/src/dhcpc/extra.c b/src/dhcpc/extra.c new file mode 100644 index 0000000..d640591 --- /dev/null +++ b/src/dhcpc/extra.c @@ -0,0 +1,499 @@ +#define _GNU_SOURCE +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "extra.h" + +const int const_int_1 = 1; +const char bb_hexdigits_upcase[] ALIGN1 = "0123456789ABCDEF"; + +void xpipe(int filedes[2]) { + if (pipe(filedes)) { + fprintf (stderr, "can't create pipe"); + exit (EXIT_FAILURE); + } +} + +void bb_signals(int sigs, void (*f)(int)) { + int sig_no = 0; + int bit = 1; + + while (sigs) { + if (sigs & bit) { + sigs -= bit; + signal(sig_no, f); + } + sig_no++; + bit <<= 1; + } +} + +void close_on_exec_on(int fd) { + fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +/* Turn on nonblocking I/O on a fd */ +void ndelay_on(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags & O_NONBLOCK) + return; + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +void ndelay_off(int fd) { + int flags = fcntl(fd, F_GETFL); + if (!(flags & O_NONBLOCK)) + return; + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); +} + +// Die if we can't copy a string to freshly allocated memory. +char* xstrdup(const char *s) +{ + char *t; + + if (s == NULL) + return NULL; + + t = strdup(s); + + if (t == NULL) { + fprintf (stderr, "Memory allocation failed"); + exit (EXIT_FAILURE); + } + + return t; +} + +char* hex2bin(char *dst, const char *str, int count) +{ + errno = EINVAL; + while (*str && count) { + uint8_t val; + uint8_t c = *str++; + if (isdigit(c)) + val = c - '0'; + else if ((c|0x20) >= 'a' && (c|0x20) <= 'f') + val = (c|0x20) - ('a' - 10); + else + return NULL; + val <<= 4; + c = *str; + if (isdigit(c)) + val |= c - '0'; + else if ((c|0x20) >= 'a' && (c|0x20) <= 'f') + val |= (c|0x20) - ('a' - 10); + else if (c == ':' || c == '\0') + val >>= 4; + else + return NULL; + + *dst++ = val; + if (c != '\0') + str++; + if (*str == ':') + str++; + count--; + } + errno = (*str ? ERANGE : 0); + return dst; +} + +#define DIE_ON_ERROR AI_CANONNAME + +/* host: "1.2.3.4[:port]", "www.google.com[:port]" + * port: if neither of above specifies port # */ +static len_and_sockaddr* str2sockaddr( + const char *host, int port, + sa_family_t af, + int ai_flags) +{ + int rc; + len_and_sockaddr *r; + struct addrinfo *result = NULL; + struct addrinfo *used_res; + const char *org_host = host; /* only for error msg */ + const char *cp; + struct addrinfo hint; + + r = NULL; + + /* Ugly parsing of host:addr */ + if (host[0] == '[') { + /* Even uglier parsing of [xx]:nn */ + host++; + cp = strchr(host, ']'); + if (!cp || (cp[1] != ':' && cp[1] != '\0')) { + /* Malformed: must be [xx]:nn or [xx] */ + fprintf(stderr, "bad address '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) { + exit (EXIT_FAILURE); + } + return NULL; + } + } else { + cp = strrchr(host, ':'); + if (cp && strchr(host, ':') != cp) { + /* There is more than one ':' (e.g. "::1") */ + cp = NULL; /* it's not a port spec */ + } + } + if (cp) { /* points to ":" or "]:" */ + int sz = cp - host + 1; + + host = safe_strncpy(alloca(sz), host, sz); + if (*cp != ':') { + cp++; /* skip ']' */ + if (*cp == '\0') /* [xx] without port */ + goto skip; + } + cp++; /* skip ':' */ + port = bb_strtou(cp, NULL, 10); + if (errno || (unsigned)port > 0xffff) { + fprintf(stderr, "bad port spec '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) { + exit (EXIT_FAILURE); + } + return NULL; + } + skip: ; + } + + /* Next two if blocks allow to skip getaddrinfo() + * in case host name is a numeric IP(v6) address. + * getaddrinfo() initializes DNS resolution machinery, + * scans network config and such - tens of syscalls. + */ + /* If we were not asked specifically for IPv6, + * check whether this is a numeric IPv4 */ + if(af != AF_INET6) { + struct in_addr in4; + if (inet_aton(host, &in4) != 0) { + r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_in)); + r->len = sizeof(struct sockaddr_in); + r->u.sa.sa_family = AF_INET; + r->u.sin.sin_addr = in4; + goto set_port; + } + } + /* If we were not asked specifically for IPv4, + * check whether this is a numeric IPv6 */ + if (af != AF_INET) { + struct in6_addr in6; + if (inet_pton(AF_INET6, host, &in6) > 0) { + r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_in6)); + r->len = sizeof(struct sockaddr_in6); + r->u.sa.sa_family = AF_INET6; + r->u.sin6.sin6_addr = in6; + goto set_port; + } + } + + memset(&hint, 0 , sizeof(hint)); + hint.ai_family = af; + /* Need SOCK_STREAM, or else we get each address thrice (or more) + * for each possible socket type (tcp,udp,raw...): */ + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = ai_flags & ~DIE_ON_ERROR; + rc = getaddrinfo(host, NULL, &hint, &result); + if (rc || !result) { + fprintf (stderr, "bad address '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) { + exit (EXIT_FAILURE); + } + goto ret; + } + used_res = result; + r = xmalloc(LSA_LEN_SIZE + used_res->ai_addrlen); + r->len = used_res->ai_addrlen; + memcpy(&r->u.sa, used_res->ai_addr, used_res->ai_addrlen); + + set_port: + set_nport(&r->u.sa, htons(port)); + ret: + if (result) + freeaddrinfo(result); + return r; +} + +len_and_sockaddr* host2sockaddr(const char *host, int port) { + return str2sockaddr(host, port, AF_UNSPEC, 0); +} + +len_and_sockaddr* xhost2sockaddr(const char *host, int port) { + return str2sockaddr(host, port, AF_UNSPEC, DIE_ON_ERROR); +} + +void* xmalloc(size_t size) { + void *ptr = malloc(size); + if (ptr == NULL && size != 0) { + fprintf (stderr, "Memory allocation failed"); + exit (EXIT_FAILURE); + } + return ptr; +} + +void set_nport(struct sockaddr *sa, unsigned port) +{ + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (void*) sa; + sin6->sin6_port = port; + return; + } + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (void*) sa; + sin->sin_port = port; + return; + } + /* What? UNIX socket? IPX?? :) */ +} + +int xsocket(int domain, int type, int protocol) { + int r = socket(domain, type, protocol); + + if (r < 0) { + /* Hijack vaguely related config option */ + const char *s = "INET"; +# ifdef AF_PACKET + if (domain == AF_PACKET) s = "PACKET"; +# endif +# ifdef AF_NETLINK + if (domain == AF_NETLINK) s = "NETLINK"; +# endif + if (domain == AF_INET6) s = "INET6"; + + fprintf (stderr, "Error socket (AF_%s,%d,%d)", s, type, protocol); + exit (EXIT_FAILURE); + } + + return r; +} + +// Die with an error message if we can't bind a socket to an address. +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) { + if (bind(sockfd, my_addr, addrlen)) { + fprintf (stderr, "Error: bind"); + exit (EXIT_FAILURE); + } +} + +int ioctl_or_perror(int fd, unsigned request, void *argp) { + va_list p; + int ret = ioctl(fd, request, argp); + + if (ret < 0) { + perror ("Error ioctl"); + } + return ret; +} + +/* Like strncpy but make sure the resulting string is always 0 terminated. */ +char* safe_strncpy(char *dst, const char *src, size_t size) { + if (!size) return dst; + dst[--size] = '\0'; + return strncpy(dst, src, size); +} + +/* Like strcpy but can copy overlapping strings. */ +void overlapping_strcpy(char *dst, const char *src) { + /* Cheap optimization for dst == src case - + * better to have it here than in many callers. + */ + if (dst != src) { + while ((*dst = *src) != '\0') { + dst++; + src++; + } + } +} + +// Die if we can't allocate and zero size bytes of memory. +void* xzalloc(size_t size) { + void *ptr = xmalloc(size); + memset(ptr, 0, size); + return ptr; +} + +void* xrealloc(void *ptr, size_t size) { + ptr = realloc(ptr, size); + if (ptr == NULL && size != 0) { + fprintf (stderr, "Memory allocation failed"); + exit (EXIT_FAILURE); + } + return ptr; +} + +ssize_t safe_read(int fd, void *buf, size_t count) { + ssize_t n; + + do { + n = read(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +uint16_t inet_cksum(uint16_t *addr, int nleft) { + /* + * Our algorithm is simple, using a 32 bit accumulator, + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + unsigned sum = 0; + while (nleft > 1) { + sum += *addr++; + nleft -= 2; + } + + /* Mop up an odd byte, if necessary */ + if (nleft == 1) { + if (BB_LITTLE_ENDIAN) + sum += *(uint8_t*)addr; + else + sum += *(uint8_t*)addr << 8; + } + + /* Add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + + return (uint16_t)~sum; +} + +/* Emit a string of hex representation of bytes */ +char* bin2hex(char *p, const char *cp, int count) { + while (count) { + unsigned char c = *cp++; + /* put lowercase hex digits */ + *p++ = 0x20 | bb_hexdigits_upcase[c >> 4]; + *p++ = 0x20 | bb_hexdigits_upcase[c & 0xf]; + count--; + } + return p; +} + +char* xasprintf(const char *format, ...) { + va_list p; + int r; + char *string_ptr; + + va_start(p, format); + r = vasprintf(&string_ptr, format, p); + va_end(p); + + if (r < 0) { + fprintf (stderr, "Memory allocation failed"); + exit (EXIT_FAILURE); + } + return string_ptr; +} + +static unsigned long long ret_ERANGE(void) +{ + errno = ERANGE; /* this ain't as small as it looks (on glibc) */ + return ULLONG_MAX; +} + +static unsigned long long handle_errors(unsigned long long v, char **endp) +{ + char next_ch = **endp; + + /* errno is already set to ERANGE by strtoXXX if value overflowed */ + if (next_ch) { + /* "1234abcg" or out-of-range? */ + if (isalnum(next_ch) || errno) + return ret_ERANGE(); + /* good number, just suspicious terminator */ + errno = EINVAL; + } + return v; +} + +unsigned bb_strtou(const char *arg, char **endp, int base) { + unsigned long v; + char *endptr; + + if (!endp) endp = &endptr; + *endp = (char*) arg; + + if (!isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoul(arg, endp, base); + if (v > UINT_MAX) return ret_ERANGE(); + return handle_errors(v, endp); +} + +char* strncpy_IFNAMSIZ(char *dst, const char *src) { +#ifndef IFNAMSIZ + enum { IFNAMSIZ = 16 }; +#endif + return strncpy(dst, src, IFNAMSIZ); +} + +void setsockopt_reuseaddr(int fd) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &const_int_1, sizeof(const_int_1)); +} + +int setsockopt_broadcast(int fd) { + return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1)); +} + +#ifdef SO_BINDTODEVICE +int setsockopt_bindtodevice(int fd, const char *iface) { + int r; + struct ifreq ifr; + strncpy_IFNAMSIZ(ifr.ifr_name, iface); + /* NB: passing (iface, strlen(iface) + 1) does not work! + * (maybe it works on _some_ kernels, but not on 2.6.26) + * Actually, ifr_name is at offset 0, and in practice + * just giving char[IFNAMSIZ] instead of struct ifreq works too. + * But just in case it's not true on some obscure arch... */ + r = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); + if (r) + fprintf (stderr, "can't bind to interface %s", iface); + return r; +} +#else +int setsockopt_bindtodevice(int fd, const char *iface) { + fprintf (stderr, "SO_BINDTODEVICE is not supported on this system"); + return -1; +} +#endif + +unsigned monotonic_sec(void) { + return time(NULL); +} + +ssize_t safe_write(int fd, const void *buf, size_t count) { + ssize_t n; + + do { + n = write(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + diff --git a/src/dhcpc/extra.h b/src/dhcpc/extra.h new file mode 100644 index 0000000..157978e --- /dev/null +++ b/src/dhcpc/extra.h @@ -0,0 +1,198 @@ +#ifndef __EXTRA_H__ +#define __EXTRA_H__ 1 + +#include +#include + +/* In this form code with pipes is much more readable */ +struct fd_pair { int rd; int wr; }; +#define piped_pair(pair) pipe(&((pair).rd)) +#define xpiped_pair(pair) xpipe(&((pair).rd)) + +#define ALIGN1 __attribute__((aligned(1))) +#define ALIGN2 __attribute__((aligned(2))) +#define PACKED __attribute__ ((__packed__)) +#define ALIGNED(m) __attribute__ ((__aligned__(m))) +#define NOINLINE __attribute__((__noinline__)) + +#define CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS 80 + +#define host_and_af2sockaddr(host, port, af) host2sockaddr((host), (port)) +#define xhost_and_af2sockaddr(host, port, af) xhost2sockaddr((host), (port)) + +/* ---- Endian Detection ------------------------------------ */ + +#include +#if defined(__digital__) && defined(__unix__) +# include +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__APPLE__) +# include /* rlimit */ +# include +# define bswap_64 __bswap64 +# define bswap_32 __bswap32 +# define bswap_16 __bswap16 +#else +# include +# include +#endif + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#elif defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#elif defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#elif defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#elif defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#elif defined(__386__) +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#else +# error "Can't determine endianness" +#endif + +#if ULONG_MAX > 0xffffffff +# define bb_bswap_64(x) bswap_64(x) +#endif + +/* SWAP_LEnn means "convert CPU<->little_endian by swapping bytes" */ +#if BB_BIG_ENDIAN +# define SWAP_BE16(x) (x) +# define SWAP_BE32(x) (x) +# define SWAP_BE64(x) (x) +# define SWAP_LE16(x) bswap_16(x) +# define SWAP_LE32(x) bswap_32(x) +# define SWAP_LE64(x) bb_bswap_64(x) +# define IF_BIG_ENDIAN(...) __VA_ARGS__ +# define IF_LITTLE_ENDIAN(...) +#else +# define SWAP_BE16(x) bswap_16(x) +# define SWAP_BE32(x) bswap_32(x) +# define SWAP_BE64(x) bb_bswap_64(x) +# define SWAP_LE16(x) (x) +# define SWAP_LE32(x) (x) +# define SWAP_LE64(x) (x) +# define IF_BIG_ENDIAN(...) +# define IF_LITTLE_ENDIAN(...) __VA_ARGS__ +#endif + +/* At 4.4 gcc become much more anal about this, need to use "aliased" types */ +#if __GNUC_PREREQ(4,4) +# define FIX_ALIASING __attribute__((__may_alias__)) +#else +# define FIX_ALIASING +#endif + +/* ---- Unaligned access ------------------------------------ */ + +#include +typedef int bb__aliased_int FIX_ALIASING; +typedef long bb__aliased_long FIX_ALIASING; +typedef uint16_t bb__aliased_uint16_t FIX_ALIASING; +typedef uint32_t bb__aliased_uint32_t FIX_ALIASING; +typedef uint64_t bb__aliased_uint64_t FIX_ALIASING; + +/* NB: unaligned parameter should be a pointer, aligned one - + * a lvalue. This makes it more likely to not swap them by mistake + */ +#if defined(i386) || defined(__x86_64__) || defined(__powerpc__) +# define move_from_unaligned_int(v, intp) ((v) = *(bb__aliased_int*)(intp)) +# define move_from_unaligned_long(v, longp) ((v) = *(bb__aliased_long*)(longp)) +# define move_from_unaligned16(v, u16p) ((v) = *(bb__aliased_uint16_t*)(u16p)) +# define move_from_unaligned32(v, u32p) ((v) = *(bb__aliased_uint32_t*)(u32p)) +# define move_to_unaligned16(u16p, v) (*(bb__aliased_uint16_t*)(u16p) = (v)) +# define move_to_unaligned32(u32p, v) (*(bb__aliased_uint32_t*)(u32p) = (v)) +/* #elif ... - add your favorite arch today! */ +#else +/* performs reasonably well (gcc usually inlines memcpy here) */ +# define move_from_unaligned_int(v, intp) (memcpy(&(v), (intp), sizeof(int))) +# define move_from_unaligned_long(v, longp) (memcpy(&(v), (longp), sizeof(long))) +# define move_from_unaligned16(v, u16p) (memcpy(&(v), (u16p), 2)) +# define move_from_unaligned32(v, u32p) (memcpy(&(v), (u32p), 4)) +# define move_to_unaligned16(u16p, v) do { \ + uint16_t __t = (v); \ + memcpy((u16p), &__t, 2); \ +} while (0) +# define move_to_unaligned32(u32p, v) do { \ + uint32_t __t = (v); \ + memcpy((u32p), &__t, 4); \ +} while (0) +#endif + +#if defined(i386) || defined(__x86_64__) || defined(__mips__) || defined(__cris__) +/* add other arches which benefit from this... */ +typedef signed char smallint; +typedef unsigned char smalluint; +#else +/* for arches where byte accesses generate larger code: */ +typedef int smallint; +typedef unsigned smalluint; +#endif + +extern const int const_int_1; + +typedef struct len_and_sockaddr { + socklen_t len; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } u; +} len_and_sockaddr; + +enum { + LSA_LEN_SIZE = offsetof(len_and_sockaddr, u), + LSA_SIZEOF_SA = sizeof( + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } + ) +}; + +void bb_signals(int sigs, void (*f)(int)); +void close_on_exec_on(int fd); +void xpipe(int filedes[2]); +void ndelay_on(int fd); +void ndelay_off(int fd); +char* xstrdup(const char *s); +char* hex2bin(char *dst, const char *str, int count); +char* bin2hex(char *p, const char *cp, int count); + +len_and_sockaddr* host2sockaddr(const char *host, int port); +len_and_sockaddr* xhost2sockaddr(const char *host, int port); +void* xmalloc(size_t size); +void set_nport(struct sockaddr *sa, unsigned port); +int xsocket(int domain, int type, int protocol); +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); +int ioctl_or_perror(int fd, unsigned request, void *argp); +char* safe_strncpy(char *dst, const char *src, size_t size); +void overlapping_strcpy(char *dst, const char *src); +void* xzalloc(size_t size); +void* xrealloc(void *ptr, size_t size); +ssize_t safe_read(int fd, void *buf, size_t count); +ssize_t safe_write(int fd, const void *buf, size_t count); + +uint16_t inet_cksum(uint16_t *addr, int nleft); +char* xasprintf(const char *format, ...); + +unsigned bb_strtou(const char *arg, char **endp, int base); +char* strncpy_IFNAMSIZ(char *dst, const char *src); +void setsockopt_reuseaddr(int fd); +int setsockopt_broadcast(int fd); +int setsockopt_bindtodevice(int fd, const char *iface); +unsigned monotonic_sec(void); + +#endif diff --git a/src/dhcpc/nidhcpc.c b/src/dhcpc/nidhcpc.c new file mode 100644 index 0000000..d5b6594 --- /dev/null +++ b/src/dhcpc/nidhcpc.c @@ -0,0 +1,40 @@ +#include +#include +#include + +#include + +#include +#include +#include + +#include "dhcpc.h" + +int main (int argc, char *argv[]) { + /* Preparar el pipe que viene en STDOUT */ + int pipe; + int null_fd; + + pipe = dup (STDOUT_FILENO); + null_fd = open ("/dev/null", O_RDWR); + + close (STDOUT_FILENO); + close (STDIN_FILENO); + + dup2 (null_fd, STDOUT_FILENO); + dup2 (null_fd, STDIN_FILENO); + + close (null_fd); + + /* Parsear aquí los argumentos */ + if (argc < 2) { + fprintf (stderr, "Missing interface argument"); + return EXIT_FAILURE; + } + + udhcpc_main (pipe, argv[1]); + + close (STDOUT_FILENO); + + return 0; +} diff --git a/src/dhcpc/packet.c b/src/dhcpc/packet.c new file mode 100644 index 0000000..59a7f4f --- /dev/null +++ b/src/dhcpc/packet.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * Packet ops + * + * Rewrite by Russ Dill July 2001 + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ + +#include +#include +#include +#include + +#include "common.h" +#include +#include +#include + +void udhcp_init_header(struct dhcp_packet *packet, char type) +{ + memset(packet, 0, sizeof(*packet)); + packet->op = BOOTREQUEST; /* if client to a server */ + switch (type) { + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; /* if server to client */ + } + packet->htype = 1; /* ethernet */ + packet->hlen = 6; + packet->cookie = htonl(DHCP_MAGIC); + if (DHCP_END != 0) + packet->options[0] = DHCP_END; + udhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type); +} + +/* Read a packet from socket fd, return -1 on read error, -2 on packet error */ +int udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd) +{ + int bytes; + + memset(packet, 0, sizeof(*packet)); + bytes = safe_read(fd, packet, sizeof(*packet)); + if (bytes < 0) { + log1("Packet read error, ignoring"); + return bytes; /* returns -1 */ + } + + if (bytes < offsetof(struct dhcp_packet, options) + || packet->cookie != htonl(DHCP_MAGIC) + ) { + fprintf (stderr, "Packet with bad magic, ignoring"); + return -2; + } + log1("Received a packet"); + udhcp_dump_packet(packet); + + return bytes; +} + +/* Construct a ip/udp header for a packet, send packet */ +int udhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_nip, int source_port, + uint32_t dest_nip, int dest_port, const uint8_t *dest_arp, + int ifindex) +{ + struct sockaddr_ll dest_sll; + struct ip_udp_dhcp_packet packet; + unsigned padding; + int fd; + int result = -1; + const char *msg; + + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + msg = "socket(%s)"; + goto ret_msg; + } + + memset(&dest_sll, 0, sizeof(dest_sll)); + memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data)); + packet.data = *dhcp_pkt; /* struct copy */ + + dest_sll.sll_family = AF_PACKET; + dest_sll.sll_protocol = htons(ETH_P_IP); + dest_sll.sll_ifindex = ifindex; + dest_sll.sll_halen = 6; + memcpy(dest_sll.sll_addr, dest_arp, 6); + + if (bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)) < 0) { + msg = "bind(%s)"; + goto ret_close; + } + + /* We were sending full-sized DHCP packets (zero padded), + * but some badly configured servers were seen dropping them. + * Apparently they drop all DHCP packets >576 *ethernet* octets big, + * whereas they may only drop packets >576 *IP* octets big + * (which for typical Ethernet II means 590 octets: 6+6+2 + 576). + * + * In order to work with those buggy servers, + * we truncate packets after end option byte. + * + * However, RFC 1542 says "The IP Total Length and UDP Length + * must be large enough to contain the minimal BOOTP header of 300 octets". + * Thus, we retain enough padding to not go below 300 BOOTP bytes. + * Some devices have filters which drop DHCP packets shorter than that. + */ + padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(packet.data.options); + if (padding > DHCP_SIZE - 300) + padding = DHCP_SIZE - 300; + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_nip; + packet.ip.daddr = dest_nip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + /* size, excluding IP header: */ + packet.udp.len = htons(UDP_DHCP_SIZE - padding); + /* for UDP checksumming, ip.len is set to UDP packet len */ + packet.ip.tot_len = packet.udp.len; + packet.udp.check = inet_cksum((uint16_t *)&packet, + IP_UDP_DHCP_SIZE - padding); + /* but for sending, it is set to IP packet len */ + packet.ip.tot_len = htons(IP_UDP_DHCP_SIZE - padding); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip)); + + udhcp_dump_packet(dhcp_pkt); + result = sendto(fd, &packet, IP_UDP_DHCP_SIZE - padding, /*flags:*/ 0, + (struct sockaddr *) &dest_sll, sizeof(dest_sll)); + msg = "sendto"; + ret_close: + close(fd); + if (result < 0) { + ret_msg: + fprintf(stderr, msg, "PACKET"); + } + return result; +} + +/* Let the kernel do all the work for packet generation */ +int udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_nip, int source_port, + uint32_t dest_nip, int dest_port) +{ + struct sockaddr_in sa; + unsigned padding; + int fd; + int result = -1; + const char *msg; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + msg = "socket(%s)"; + goto ret_msg; + } + setsockopt_reuseaddr(fd); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(source_port); + sa.sin_addr.s_addr = source_nip; + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + msg = "bind(%s)"; + goto ret_close; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(dest_port); + sa.sin_addr.s_addr = dest_nip; + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + msg = "connect"; + goto ret_close; + } + + udhcp_dump_packet(dhcp_pkt); + padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(dhcp_pkt->options); + if (padding > DHCP_SIZE - 300) + padding = DHCP_SIZE - 300; + result = safe_write(fd, dhcp_pkt, DHCP_SIZE - padding); + msg = "write"; + ret_close: + close(fd); + if (result < 0) { + ret_msg: + fprintf(stderr, msg, "UDP"); + } + return result; +} diff --git a/src/dhcpc/signalpipe.c b/src/dhcpc/signalpipe.c new file mode 100644 index 0000000..8d03ed4 --- /dev/null +++ b/src/dhcpc/signalpipe.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * Signal pipe infrastructure. A reliable way of delivering signals. + * + * Russ Dill December 2003 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include + +#include "common.h" +#include "extra.h" + +/* Global variable: we access it from signal handler */ +static struct fd_pair signal_pipe; + +static void signal_handler(int sig) +{ + unsigned char ch = sig; /* use char, avoid dealing with partial writes */ + if (write(signal_pipe.wr, &ch, 1) != 1) { + fprintf (stderr, "can't send signal"); + } +} + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void udhcp_sp_setup(void) +{ + /* was socketpair, but it needs AF_UNIX in kernel */ + xpiped_pair(signal_pipe); + close_on_exec_on(signal_pipe.rd); + close_on_exec_on(signal_pipe.wr); + ndelay_on(signal_pipe.wr); + bb_signals(0 + + (1 << SIGUSR1) + + (1 << SIGUSR2) + + (1 << SIGTERM) + , signal_handler); +} + +/* Quick little function to setup the rfds. Will return the + * max_fd for use with select. Limited in that you can only pass + * one extra fd */ +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) +{ + FD_ZERO(rfds); + FD_SET(signal_pipe.rd, rfds); + if (extra_fd >= 0) { + close_on_exec_on(extra_fd); + FD_SET(extra_fd, rfds); + } + return signal_pipe.rd > extra_fd ? signal_pipe.rd : extra_fd; +} + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int udhcp_sp_read(const fd_set *rfds) +{ + unsigned char sig; + + if (!FD_ISSET(signal_pipe.rd, rfds)) + return 0; + + if (safe_read(signal_pipe.rd, &sig, 1) != 1) + return -1; + + return sig; +} diff --git a/src/dhcpc/socket.c b/src/dhcpc/socket.c new file mode 100644 index 0000000..6326a9d --- /dev/null +++ b/src/dhcpc/socket.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* + * DHCP server client/server socket creation + * + * udhcp client/server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include + +#include +#include +#include + +#include + +#include "extra.h" +#include "common.h" + +int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *nip, uint8_t *mac) +{ + /* char buffer instead of bona-fide struct avoids aliasing warning */ + char ifr_buf[sizeof(struct ifreq)]; + struct ifreq *const ifr = (void *)ifr_buf; + + int fd; + struct sockaddr_in *our_ip; + + memset(ifr, 0, sizeof(*ifr)); + fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + + ifr->ifr_addr.sa_family = AF_INET; + strncpy_IFNAMSIZ(ifr->ifr_name, interface); + if (nip) { + if (ioctl_or_perror(fd, SIOCGIFADDR, ifr) + ) { + close(fd); + return -1; + } + our_ip = (struct sockaddr_in *) &ifr->ifr_addr; + *nip = our_ip->sin_addr.s_addr; + log1("IP %s", inet_ntoa(our_ip->sin_addr)); + } + + if (ifindex) { + if (ioctl_or_perror(fd, SIOCGIFINDEX, ifr) != 0) { + close(fd); + return -1; + } + log1("Adapter index %d", ifr->ifr_ifindex); + *ifindex = ifr->ifr_ifindex; + } + + if (mac) { + if (ioctl_or_perror(fd, SIOCGIFHWADDR, ifr) != 0) { + close(fd); + return -1; + } + memcpy(mac, ifr->ifr_hwaddr.sa_data, 6); + log1("MAC %02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + + close(fd); + return 0; +} + +/* 1. None of the callers expects it to ever fail */ +/* 2. ip was always INADDR_ANY */ +int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) +{ + int fd; + struct sockaddr_in addr; + char *colon; + + log1("Opening listen socket on *:%d %s", port, inf); + fd = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + + setsockopt_reuseaddr(fd); + if (setsockopt_broadcast(fd) == -1) { + fprintf (stderr, "SO_BROADCAST"); + exit (EXIT_FAILURE); + } + + /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */ + colon = strrchr(inf, ':'); + if (colon) + *colon = '\0'; + + if (setsockopt_bindtodevice(fd, inf)) { + exit (EXIT_FAILURE); /* warning is already printed */ + } + + if (colon) + *colon = ':'; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + /* addr.sin_addr.s_addr = ip; - all-zeros is INADDR_ANY */ + xbind(fd, (struct sockaddr *)&addr, sizeof(addr)); + + return fd; +} diff --git a/src/interfaces.c b/src/interfaces.c index 25ce8a1..167abce 100644 --- a/src/interfaces.c +++ b/src/interfaces.c @@ -2,7 +2,7 @@ * interfaces.c * This file is part of Network-inador * - * Copyright (C) 2011 - Félix Arreola Rodríguez + * Copyright (C) 2018 - Félix Arreola Rodríguez * * Network-inador is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ #include #include "interfaces.h" +#include "manager-events.h" Interface * interfaces_locate_by_index (Interface *list, int index); static IPv4 * _interfaces_append_ipv4_to_struct (Interface *interface, struct in_addr address, uint32_t prefix); @@ -183,7 +184,7 @@ void interfaces_add_or_update_rtnl_link (NetworkInadorHandle *handle, struct nlm struct rtattr *attribute; int len; Interface *new, *last; - + int was_new = 0; iface = NLMSG_DATA(h); len = h->nlmsg_len - NLMSG_LENGTH (sizeof (struct ifinfomsg)); @@ -208,6 +209,8 @@ void interfaces_add_or_update_rtnl_link (NetworkInadorHandle *handle, struct nlm last->next = new; } + + was_new = 1; } if (iface->ifi_family == AF_BRIDGE) { @@ -443,6 +446,7 @@ void interfaces_add_or_update_ipv4 (NetworkInadorHandle *handle, struct nlmsghdr if (new == NULL) { printf ("Agregando IP a la lista de IP's\n"); new = _interfaces_append_ipv4_to_struct (iface, ip, prefix); + manager_events_notify_ipv4_address_added (iface, new); } new->flags = addr->ifa_flags; diff --git a/src/interfaces.h b/src/interfaces.h index c9b25f0..3a3c498 100644 --- a/src/interfaces.h +++ b/src/interfaces.h @@ -2,7 +2,7 @@ * interfaces.h * This file is part of Network-inador * - * Copyright (C) 2011 - Félix Arreola Rodríguez + * Copyright (C) 2018 - Félix Arreola Rodríguez * * Network-inador is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/manager-events.c b/src/manager-events.c new file mode 100644 index 0000000..172be68 --- /dev/null +++ b/src/manager-events.c @@ -0,0 +1,195 @@ +/* + * manager-events.c + * This file is part of Network-inador + * + * Copyright (C) 2018 - Félix Arreola Rodríguez + * + * Network-inador is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Network-inador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Network-inador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "network-inador.h" +#include "manager-events.h" + +#define EVENTS_SOCKET_PATH "/tmp/network-inador.events" +#define MANAGER_EVENTS_MAX_CLIENT 50 + +static int _manager_events_clients[MANAGER_EVENTS_MAX_CLIENT]; +static int _manager_events_client_count = 0; + +static gboolean _manager_events_handle_read (GIOChannel *source, GIOCondition condition, gpointer data) { + char buffer[128]; + + int sock; + int ret; + int g; + + sock = g_io_channel_unix_get_fd (source); + + ret = read (sock, buffer, sizeof (buffer)); + + if (ret == 0) { + /* El socket del cliente se cerró */ + for (g = 0; g < _manager_events_client_count; g++) { + if (_manager_events_clients[g] == sock) { + /* Te encontré */ + if (_manager_events_client_count - 1 == g) { + /* Es el último socket del arreglo */ + _manager_events_clients[g] = 0; + _manager_events_client_count--; + } else { + /* Recorrer el último en posición de éste */ + _manager_events_clients[g] = _manager_events_clients[_manager_events_client_count - 1]; + _manager_events_clients[_manager_events_client_count - 1] = 0; + _manager_events_client_count--; + } + break; + } + } + + close (sock); + } else { + /* Procesar data o errores de lectura */ + } +} + +static gboolean _manager_events_handle_new_conn (GIOChannel *source, GIOCondition condition, gpointer data) { + NetworkInadorHandle *handle = (NetworkInadorHandle *) data; + int sock; + int new_c; + GIOChannel *channel; + + sock = g_io_channel_unix_get_fd (source); + + new_c = accept (sock, NULL, NULL); + if (new_c < 0) { + printf ("Error al aceptar cliente\n"); + return TRUE; + } + + if (fcntl (new_c, F_SETFD, FD_CLOEXEC) < 0) { + printf ("Error set close-on-exec"); + } + + if (_manager_events_client_count == MANAGER_EVENTS_MAX_CLIENT) { + /* Rechazar el cliente por estar al máximo de capacidad */ + close (new_c); + + return TRUE; + } + + channel = g_io_channel_unix_new (new_c); + + g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, _manager_events_handle_read, handle); + + /* Agregar a la lista de clientes */ + _manager_events_clients [_manager_events_client_count] = new_c; + _manager_events_client_count++; + + return TRUE; +} + +void manager_events_notify_ipv4_address_added (Interface *iface, IPv4 *address) { + char buffer[128]; + int size; + int g; + + buffer[0] = 0; + buffer[1] = MANAGER_EVENT_IPV4_ADDED; + + buffer[2] = iface->index; + memcpy (&buffer[3], &address->sin_addr, sizeof (address->sin_addr)); + buffer[7] = address->prefix; + + size = 8; + + for (g = 0; g < _manager_events_client_count; g++) { + write (_manager_events_clients [g], buffer, size); + } +} + +void manager_events_setup (NetworkInadorHandle *handle) { + int sock; + struct sockaddr_un socket_name; + GIOChannel *channel; + + sock = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + + if (sock < 0) { + perror ("Failed to create AF_UNIX socket"); + + return; + } + + memset (_manager_events_clients, 0, sizeof (_manager_events_clients)); + memset (&socket_name, 0, sizeof (struct sockaddr_un)); + + socket_name.sun_family = AF_UNIX; + strncpy (socket_name.sun_path, EVENTS_SOCKET_PATH, sizeof (socket_name.sun_path) - 1); + + unlink (EVENTS_SOCKET_PATH); + + if (bind (sock, (struct sockaddr *) &socket_name, sizeof (struct sockaddr_un)) < 0) { + perror ("Bind"); + + close (sock); + return; + } + + if (listen (sock, 20)) { + perror ("Listen"); + + close (sock); + unlink (EVENTS_SOCKET_PATH); + + return; + } + + /* TODO: Aplicar permisos aquí */ + chmod (EVENTS_SOCKET_PATH, 0666); + + channel = g_io_channel_unix_new (sock); + + g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, _manager_events_handle_new_conn, handle); +} + diff --git a/src/manager-events.h b/src/manager-events.h new file mode 100644 index 0000000..6d057da --- /dev/null +++ b/src/manager-events.h @@ -0,0 +1,37 @@ +/* + * manager-events.h + * This file is part of Network-inador + * + * Copyright (C) 2018 - Félix Arreola Rodríguez + * + * Network-inador is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Network-inador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Network-inador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __MANAGER_EVENTS_H__ +#define __MANAGER_EVENTS_H__ + +#include "network-inador.h" + +enum { + MANAGER_EVENT_IPV4_ADDED = 1, + MANAGER_EVENT_IPV4_REMOVED, +}; + +void manager_events_notify_ipv4_address_added (Interface *iface, IPv4 *address); +void manager_events_setup (NetworkInadorHandle *handle); + +#endif + diff --git a/src/manager.c b/src/manager.c index a08ca1d..a2220b9 100644 --- a/src/manager.c +++ b/src/manager.c @@ -39,11 +39,13 @@ #include "interfaces.h" #include "network-inador.h" -#define SOCKET_PATH "/tmp/network-inador.socket" +#define COMMAND_SOCKET_PATH "/tmp/network-inador.socket" enum { - MANAGER_COMMAND_REQUEST = 0, - MANAGER_COMMAND_LIST_IFACES = 0, + MANAGER_EVENT = 0, + + MANAGER_COMMAND_REQUEST = 1, + MANAGER_COMMAND_LIST_IFACES = 1, MANAGER_COMMAND_BRING_UP_IFACE, MANAGER_COMMAND_BRING_DOWN_IFACE, @@ -55,6 +57,7 @@ enum { MANAGER_COMMAND_LIST_IPV4, MANAGER_COMMAND_RUN_DHCP_CLIENT, + MANAGER_COMMAND_STOP_DHCP_CLIENT, MANAGER_COMMAND_LIST_ROUTES, @@ -475,6 +478,12 @@ static gboolean _manager_client_data (GIOChannel *source, GIOCondition condition break; case MANAGER_COMMAND_REMOVE_IPV4: _manager_handle_interface_del_ipv4 (handle, &request); + break; + case MANAGER_COMMAND_RUN_DHCP_CLIENT: + + break; + case MANAGER_COMMAND_STOP_DHCP_CLIENT: + break; case MANAGER_COMMAND_LIST_IPV4: _manager_send_list_ipv4 (handle, &request); @@ -506,9 +515,9 @@ int manager_setup_socket (NetworkInadorHandle *handle) { memset (&socket_name, 0, sizeof (struct sockaddr_un)); socket_name.sun_family = AF_UNIX; - strncpy (socket_name.sun_path, SOCKET_PATH, sizeof (socket_name.sun_path) - 1); + strncpy (socket_name.sun_path, COMMAND_SOCKET_PATH, sizeof (socket_name.sun_path) - 1); - unlink (SOCKET_PATH); + unlink (COMMAND_SOCKET_PATH); if (bind (sock, (struct sockaddr *) &socket_name, sizeof (struct sockaddr_un)) < 0) { perror ("Bind"); @@ -517,11 +526,11 @@ int manager_setup_socket (NetworkInadorHandle *handle) { } /* TODO: Aplicar permisos aquí */ - chmod (SOCKET_PATH, 0666); + chmod (COMMAND_SOCKET_PATH, 0666); channel = g_io_channel_unix_new (sock); - g_io_add_watch (channel, G_IO_IN | G_IO_PRI, _manager_client_data, handle); + g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, _manager_client_data, handle); return 0; } diff --git a/src/events.c b/src/netlink-events.c similarity index 90% rename from src/events.c rename to src/netlink-events.c index 4b3d4f1..5f3d200 100644 --- a/src/events.c +++ b/src/netlink-events.c @@ -1,5 +1,5 @@ /* - * events.c + * netlink-events.c * This file is part of Network-inador * * Copyright (C) 2018 - Félix Arreola Rodríguez @@ -45,12 +45,12 @@ #include -#include "events.h" +#include "netlink-events.h" #include "network-inador.h" #include "interfaces.h" #include "routes.h" -static gboolean _events_handle_read (GIOChannel *source, GIOCondition condition, gpointer data) { +static gboolean _netlink_events_handle_read (GIOChannel *source, GIOCondition condition, gpointer data) { NetworkInadorHandle *handle = (NetworkInadorHandle *) data; int sock; @@ -116,11 +116,11 @@ static gboolean _events_handle_read (GIOChannel *source, GIOCondition condition, return TRUE; } -void events_setup_loop (NetworkInadorHandle *handle, int sock) { +void netlink_events_setup_loop (NetworkInadorHandle *handle, int sock) { GIOChannel *channel; channel = g_io_channel_unix_new (sock); - g_io_add_watch (channel, G_IO_IN | G_IO_PRI, _events_handle_read, handle); + g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, _netlink_events_handle_read, handle); } diff --git a/src/events.h b/src/netlink-events.h similarity index 80% rename from src/events.h rename to src/netlink-events.h index 5ccc353..6a3baf4 100644 --- a/src/events.h +++ b/src/netlink-events.h @@ -1,8 +1,8 @@ /* - * events.h + * netlink-events.h * This file is part of Network-inador * - * Copyright (C) 2011 - Félix Arreola Rodríguez + * Copyright (C) 2018 - Félix Arreola Rodríguez * * Network-inador is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,12 @@ * Boston, MA 02110-1301 USA */ -#ifndef __EVENTS_H__ -#define __EVENTS_H__ +#ifndef __NETLINK_EVENTS_H__ +#define __NETLINK_EVENTS_H__ #include "network-inador.h" -void events_setup_loop (NetworkInadorHandle *handle, int sock); +void netlink_events_setup_loop (NetworkInadorHandle *handle, int sock); #endif diff --git a/src/network-inador.c b/src/network-inador.c index f6246bc..098aeab 100644 --- a/src/network-inador.c +++ b/src/network-inador.c @@ -42,10 +42,12 @@ #include "network-inador.h" #include "interfaces.h" -#include "events.h" +#include "netlink-events.h" #include "manager.h" #include "bridge.h" #include "routes.h" +#include "manager-events.h" +#include "dhcp.h" static GMainLoop *loop = NULL; @@ -78,7 +80,6 @@ int main (int argc, char *argv[]) { int nl_sock; int nl_watch; - signal (SIGPIPE, SIG_IGN); #if !defined(GLIB_VERSION_2_36) g_type_init (); @@ -95,11 +96,13 @@ int main (int argc, char *argv[]) { handle.netlink_sock_request = nl_sock; nl_watch = create_ntlink_socket (-1); + manager_events_setup (&handle); + interfaces_list_all (&handle, nl_sock); routes_list (&handle, nl_sock); - events_setup_loop (&handle, nl_watch); + netlink_events_setup_loop (&handle, nl_watch); manager_setup_socket (&handle); diff --git a/src/network-inador.h b/src/network-inador.h index 64f6142..66bf168 100644 --- a/src/network-inador.h +++ b/src/network-inador.h @@ -29,6 +29,33 @@ #include #include +#include + +#include + +/* Para almacenar la información de DHCP */ +enum { + IFACE_NO_DHCP_RUNNING = 0, + IFACE_DHCP_CLIENT, +}; + +enum { + DHCP_CLIENT_DECONFIG = 1, + DHCP_CLIENT_LEASEFAIL, + DHCP_CLIENT_BOUND, + DHCP_CLIENT_RENEW, + DHCP_CLIENT_NAK +}; + +typedef struct _DHCPStateInfo { + int type; + + int read_pipe; + GPid process_pid; + + int client_state; +} DHCPStateInfo; + typedef struct _IPv4 { struct in_addr sin_addr; uint32_t prefix; @@ -63,6 +90,8 @@ typedef struct _Interface { IPv4 *v4_address; + DHCPStateInfo dhcp_info; + struct _Interface *next; } Interface;