diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2013-11-04 12:16:09 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2013-11-04 12:16:09 +0100 |
commit | 0e7bec30be2b8d770130f235d934526b7d3cee37 (patch) | |
tree | d5662b5613ed7015de8295385a64ac12d5bda649 | |
parent | e62c1f44dbb07d96a37674c0fc424785013fca24 (diff) |
owfd: dhcp: implement basic DHCP client
Implement the --client option and spawn gdhcp_client accordingly. Once we
get a lease, set the information via "ip" for the given network interface.
Note that we don't support any routing/gateways, DNS or other stuff. This
is not needed for WFD so it's unlikely to get implemented. Ever.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r-- | src/dhcp.c | 304 |
1 files changed, 303 insertions, 1 deletions
@@ -23,6 +23,29 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* + * Small DHCP Client/Server for OpenWFD + * Wifi-P2P requires us to use DHCP to set up a private P2P network. As all + * DHCP daemons available have horrible interfaces for ad-hoc setups, we have + * this small replacement for all DHCP operations. + * + * This program implements a DHCP server and daemon. See --help for usage + * information. We build on gdhcp from connman as the underlying DHCP protocol + * implementation. To configure network devices, we actually invoke the "ip" + * binary. + * + * Note that this is a gross hack! We don't intend to provide a fully functional + * DHCP server or client here. This is only a replacement for the current lack + * of Wifi-P2P support in common network managers. Once they gain proper + * support, we will drop this helper! + * + * The "ip" invokation is quite fragile and ugly. However, performing these + * steps directly involves netlink operations and more. As no-one came up with + * patches, yet, we keep the hack. To anyone trying to fix it: Please, spend + * this time hacking on NetworkManager, connman and friends instead! If they + * gain Wifi-P2P support, this whole thing will get trashed. + */ + #include <arpa/inet.h> #include <errno.h> #include <glib.h> @@ -35,6 +58,7 @@ #include <sys/epoll.h> #include <sys/signalfd.h> #include <sys/socket.h> +#include <sys/wait.h> #include <unistd.h> #include "dhcp.h" #include "gdhcp/gdhcp.h" @@ -50,8 +74,202 @@ struct owfd_dhcp { int sfd; GIOChannel *sfd_chan; guint sfd_id; + + GDHCPClient *client; + char *client_addr; }; +static int flush_if_addr(struct owfd_dhcp *dhcp) +{ + char *argv[64]; + int i, r; + pid_t pid, rp; + sigset_t mask; + + pid = fork(); + if (pid < 0) { + return log_ERRNO(); + } else if (!pid) { + /* child */ + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout to stderr */ + dup2(2, 1); + + i = 0; + argv[i++] = dhcp->config.ip_binary; + argv[i++] = "addr"; + argv[i++] = "flush"; + argv[i++] = "dev"; + argv[i++] = dhcp->config.interface; + argv[i++] = "label"; + argv[i++] = dhcp->iflabel; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + log_info("flushing local if-addr"); + rp = waitpid(pid, &r, 0); + if (rp != pid) { + log_error("cannot flush local if-addr via '%s'", + dhcp->config.ip_binary); + return -EFAULT; + } else if (!WIFEXITED(r)) { + log_error("flushing local if-addr via '%s' failed", + dhcp->config.ip_binary); + return -EFAULT; + } else if (WEXITSTATUS(r)) { + log_error("flushing local if-addr via '%s' failed with: %d", + dhcp->config.ip_binary, WEXITSTATUS(r)); + return -EFAULT; + } + + log_debug("successfully flushed local if-addr via %s", + dhcp->config.ip_binary); + + return 0; +} + +static int add_if_addr(struct owfd_dhcp *dhcp, char *addr) +{ + char *argv[64]; + int i, r; + pid_t pid, rp; + sigset_t mask; + + pid = fork(); + if (pid < 0) { + return log_ERRNO(); + } else if (!pid) { + /* child */ + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout to stderr */ + dup2(2, 1); + + i = 0; + argv[i++] = dhcp->config.ip_binary; + argv[i++] = "addr"; + argv[i++] = "add"; + argv[i++] = addr; + argv[i++] = "dev"; + argv[i++] = dhcp->config.interface; + argv[i++] = "label"; + argv[i++] = dhcp->iflabel; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + log_info("adding local if-addr %s", addr); + rp = waitpid(pid, &r, 0); + if (rp != pid) { + log_error("cannot set local if-addr %s via '%s'", + addr, dhcp->config.ip_binary); + return -EFAULT; + } else if (!WIFEXITED(r)) { + log_error("setting local if-addr %s via '%s' failed", + addr, dhcp->config.ip_binary); + return -EFAULT; + } else if (WEXITSTATUS(r)) { + log_error("setting local if-addr %s via '%s' failed with: %d", + addr, dhcp->config.ip_binary, WEXITSTATUS(r)); + return -EFAULT; + } + + log_debug("successfully set local if-addr %s via %s", + addr, dhcp->config.ip_binary); + + return 0; +} + +static void client_lease_fn(GDHCPClient *client, gpointer data) +{ + struct owfd_dhcp *dhcp = data; + char *addr, *a, *subnet = NULL; + GList *l; + int r; + + log_info("lease available"); + + addr = g_dhcp_client_get_address(client); + log_info("lease: address: %s", addr); + + l = g_dhcp_client_get_option(client, G_DHCP_SUBNET); + for ( ; l; l = l->next) { + subnet = subnet ? : (char*)l->data; + log_info("lease: subnet: %s", (char*)l->data); + } + + l = g_dhcp_client_get_option(client, G_DHCP_DNS_SERVER); + for ( ; l; l = l->next) + log_info("lease: dns-server: %s", (char*)l->data); + + l = g_dhcp_client_get_option(client, G_DHCP_ROUTER); + for ( ; l; l = l->next) + log_info("lease: router: %s", (char*)l->data); + + if (!addr) { + log_error("lease without IP address"); + goto error; + } + if (!subnet) { + log_warning("lease without subnet mask, using 24"); + subnet = "24"; + } + + r = asprintf(&a, "%s/%s", addr, subnet); + if (r < 0) { + log_vENOMEM(); + g_free(addr); + goto error; + } else { + g_free(addr); + } + + if (dhcp->client_addr && !strcmp(dhcp->client_addr, a)) { + log_info("given address already set"); + free(a); + } else { + free(dhcp->client_addr); + dhcp->client_addr = a; + + r = flush_if_addr(dhcp); + if (r < 0) { + log_error("cannot flush addr on local interface %s", + dhcp->config.interface); + goto error; + } + + r = add_if_addr(dhcp, dhcp->client_addr); + if (r < 0) { + log_error("cannot set parameters on local interface %s", + dhcp->config.interface); + goto error; + } + } + + return; + +error: + g_main_loop_quit(dhcp->loop); +} + +static void client_no_lease_fn(GDHCPClient *client, gpointer data) +{ + struct owfd_dhcp *dhcp = data; + + log_error("no lease available"); + g_main_loop_quit(dhcp->loop); +} + static gboolean owfd_dhcp_sfd_fn(GIOChannel *chan, GIOCondition mask, gpointer data) { @@ -84,6 +302,22 @@ static gboolean owfd_dhcp_sfd_fn(GIOChannel *chan, GIOCondition mask, static int owfd_dhcp_run(struct owfd_dhcp *dhcp) { + int r; + + if (dhcp->config.client) { + log_info("running dhcp client on %s via '%s'", + dhcp->config.interface, dhcp->config.ip_binary); + + r = g_dhcp_client_start(dhcp->client, NULL); + if (r != 0) { + log_error("cannot start DHCP client: %d", r); + return -EFAULT; + } + } else { + log_info("running dhcp server on %s via '%s'", + dhcp->config.interface, dhcp->config.ip_binary); + } + g_main_loop_run(dhcp->loop); return 0; @@ -91,6 +325,20 @@ static int owfd_dhcp_run(struct owfd_dhcp *dhcp) static void owfd_dhcp_teardown(struct owfd_dhcp *dhcp) { + if (dhcp->config.client) { + if (dhcp->client) { + g_dhcp_client_stop(dhcp->client); + + if (dhcp->client_addr) { + flush_if_addr(dhcp); + free(dhcp->client_addr); + } + + g_dhcp_client_unref(dhcp->client); + } + } else { + } + if (dhcp->sfd >= 0) { g_source_remove(dhcp->sfd_id); g_io_channel_unref(dhcp->sfd_chan); @@ -120,6 +368,7 @@ static int owfd_dhcp_setup(struct owfd_dhcp *dhcp) int r, i; sigset_t mask; struct sigaction sig; + GDHCPClientError cerr; if (geteuid()) log_warning("not running as uid=0, dhcp might not work"); @@ -172,6 +421,60 @@ static int owfd_dhcp_setup(struct owfd_dhcp *dhcp) owfd_dhcp_sfd_fn, dhcp); + if (dhcp->config.client) { + dhcp->client = g_dhcp_client_new(G_DHCP_IPV4, dhcp->ifindex, + &cerr); + if (!dhcp->client) { + r = -EINVAL; + + switch (cerr) { + case G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE: + log_error("cannot create GDHCP client: interface %s unavailable", + dhcp->config.interface); + break; + case G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE: + log_error("cannot create GDHCP client: interface %s in use", + dhcp->config.interface); + break; + case G_DHCP_CLIENT_ERROR_INTERFACE_DOWN: + log_error("cannot create GDHCP client: interface %s down", + dhcp->config.interface); + break; + case G_DHCP_CLIENT_ERROR_NOMEM: + r = log_ENOMEM(); + break; + case G_DHCP_CLIENT_ERROR_INVALID_INDEX: + log_error("cannot create GDHCP client: invalid interface %s", + dhcp->config.interface); + break; + case G_DHCP_CLIENT_ERROR_INVALID_OPTION: + log_error("cannot create GDHCP client: invalid options"); + break; + default: + log_error("cannot create GDHCP client (%d)", + cerr); + break; + } + + goto error; + } + + g_dhcp_client_set_send(dhcp->client, G_DHCP_HOST_NAME, + "<hostname>"); + + g_dhcp_client_set_request(dhcp->client, G_DHCP_SUBNET); + g_dhcp_client_set_request(dhcp->client, G_DHCP_DNS_SERVER); + g_dhcp_client_set_request(dhcp->client, G_DHCP_ROUTER); + + g_dhcp_client_register_event(dhcp->client, + G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE, + client_lease_fn, dhcp); + g_dhcp_client_register_event(dhcp->client, + G_DHCP_CLIENT_EVENT_NO_LEASE, + client_no_lease_fn, dhcp); + } else { + } + return 0; error: @@ -211,7 +514,6 @@ int main(int argc, char **argv) if (r < 0) goto err_conf; - log_info("running"); r = owfd_dhcp_run(&dhcp); owfd_dhcp_teardown(&dhcp); |