summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2013-11-04 12:16:09 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2013-11-04 12:16:09 +0100
commit0e7bec30be2b8d770130f235d934526b7d3cee37 (patch)
treed5662b5613ed7015de8295385a64ac12d5bda649
parente62c1f44dbb07d96a37674c0fc424785013fca24 (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.c304
1 files changed, 303 insertions, 1 deletions
diff --git a/src/dhcp.c b/src/dhcp.c
index 5b37f5f..58a63eb 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -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);