summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2013-10-31 16:23:54 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2013-10-31 16:32:32 +0100
commitc1fe726970fc85118730f041c1721598eeed2f68 (patch)
tree91838a62797679ea9610cb42cba48b167cbcb742
parenta2fb4bb24bc986988372e7a122660273578819b4 (diff)
Import gdhcp from connman
Wifi-P2P needs dhcp to exchange addresses. As there is no decent dhcp daemon out there, which can be controlled from an application ad-hoc, we import gdhcp from connman. gdhcp is a client and server implementation of dhcp controllable via a simple API. We can use it to implement any kind of dhcp client and daemon we want. Unfortunately, gdhcp is GPLv2. That means all linking binaries are also GPLv2. We don't care, but we need to explicitly mention it so others will notice. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r--COPYING37
-rw-r--r--COPYING_GPL2340
-rw-r--r--Makefile.am23
-rw-r--r--configure.ac8
-rw-r--r--src/gdhcp/client.c3154
-rw-r--r--src/gdhcp/common.c759
-rw-r--r--src/gdhcp/common.h211
-rw-r--r--src/gdhcp/gdhcp.h230
-rw-r--r--src/gdhcp/ipv4ll.c139
-rw-r--r--src/gdhcp/ipv4ll.h55
-rw-r--r--src/gdhcp/server.c896
-rw-r--r--src/gdhcp/unaligned.h163
12 files changed, 6012 insertions, 3 deletions
diff --git a/COPYING b/COPYING
index 69feaf0..25e2ce5 100644
--- a/COPYING
+++ b/COPYING
@@ -8,12 +8,21 @@ This software was written by:
Copyright Notice
================
-This software is licensed under the terms of the MIT license. Please see each
-source file for the related copyright notice and license.
+Most of this software is licensed under the terms of the MIT license. Please see
+each source file for the related copyright notice and license. Note that parts
+of this distribution are licensed as GPLv2 or else. The MIT license only
+applies to all code written as part of OpenWFD.
+
+If you distribute this software as pre-compiled executable, you must choose the
+GPLv2 license as common denominator and include this file with your
+distribution! If you distribute only the source-code, each license applies only
+to the source code it is mentioned for.
If a file does not contain a copright notice, the following license shall
apply:
+ OpenWFD - Open-Source Wifi-Display Implementation
+
Copyright (c) 2013 David Herrmann <dh.herrmann@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
@@ -38,4 +47,26 @@ apply:
Third-Party Source
==================
- N/A
+gdhcp is copied from connman:
+ cgit: http://git.kernel.org/cgit/network/connman/connman.git
+ web: https://connman.net/
+
+ Connman is licensed as GPLv2 exclusively. The copyright is:
+
+ DHCP library with GLib integration
+
+ Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ 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., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301 USA
diff --git a/COPYING_GPL2 b/COPYING_GPL2
new file mode 100644
index 0000000..a26d897
--- /dev/null
+++ b/COPYING_GPL2
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Makefile.am b/Makefile.am
index af69526..d1aa135 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,6 +82,29 @@ libshl_la_LDFLAGS = $(AM_LDFLAGS)
libshl_la_LIBADD = $(AM_LIBADD)
#
+# gdhcp - dhcp implementation
+#
+
+noinst_LTLIBRARIES += libgdhcp.la
+
+libgdhcp_la_SOURCES = \
+ src/gdhcp/gdhcp.h \
+ src/gdhcp/unaligned.h \
+ src/gdhcp/common.h \
+ src/gdhcp/common.c \
+ src/gdhcp/ipv4ll.h \
+ src/gdhcp/ipv4ll.c \
+ src/gdhcp/client.c \
+ src/gdhcp/server.c
+libgdhcp_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(GDHCP_CFLAGS)
+libgdhcp_la_LDFLAGS = $(AM_LDFLAGS)
+libgdhcp_la_LIBADD = \
+ $(AM_LIBADD) \
+ $(GDHCP_LIBS)
+
+#
# owfd - shared helpers
#
diff --git a/configure.ac b/configure.ac
index 10b8cfc..d1f635f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -37,6 +37,14 @@ LT_PREREQ(2.2)
LT_INIT
#
+# Test for gdhcp dependencies
+#
+
+PKG_CHECK_MODULES([GDHCP], [glib-2.0])
+AC_SUBST(GDHCP_CFLAGS)
+AC_SUBST(GDHCP_LIBS)
+
+#
# Test for "check" which we use for our test-suite. If not found, we disable
# all tests.
#
diff --git a/src/gdhcp/client.c b/src/gdhcp/client.c
new file mode 100644
index 0000000..30ad0e7
--- /dev/null
+++ b/src/gdhcp/client.c
@@ -0,0 +1,3154 @@
+/*
+ *
+ * DHCP client library with GLib integration
+ *
+ * Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <resolv.h>
+
+#include <netpacket/packet.h>
+#include <netinet/if_ether.h>
+#include <net/ethernet.h>
+
+#include <linux/if.h>
+#include <linux/filter.h>
+
+#include <glib.h>
+
+#include "gdhcp.h"
+#include "common.h"
+#include "ipv4ll.h"
+
+#define DISCOVER_TIMEOUT 3
+#define DISCOVER_RETRIES 10
+
+#define REQUEST_TIMEOUT 3
+#define REQUEST_RETRIES 5
+
+typedef enum _listen_mode {
+ L_NONE,
+ L2,
+ L3,
+ L_ARP,
+} ListenMode;
+
+typedef enum _dhcp_client_state {
+ INIT_SELECTING,
+ REQUESTING,
+ BOUND,
+ RENEWING,
+ REBINDING,
+ RELEASED,
+ IPV4LL_PROBE,
+ IPV4LL_ANNOUNCE,
+ IPV4LL_MONITOR,
+ IPV4LL_DEFEND,
+ INFORMATION_REQ,
+ SOLICITATION,
+ REQUEST,
+ CONFIRM,
+ RENEW,
+ REBIND,
+ RELEASE,
+ DECLINE,
+} ClientState;
+
+struct _GDHCPClient {
+ int ref_count;
+ GDHCPType type;
+ ClientState state;
+ int ifindex;
+ char *interface;
+ uint8_t mac_address[6];
+ uint32_t xid;
+ uint32_t server_ip;
+ uint32_t requested_ip;
+ char *assigned_ip;
+ time_t start;
+ uint32_t lease_seconds;
+ ListenMode listen_mode;
+ int listener_sockfd;
+ uint8_t retry_times;
+ uint8_t ack_retry_times;
+ uint8_t conflicts;
+ guint timeout;
+ guint listener_watch;
+ GIOChannel *listener_channel;
+ GList *require_list;
+ GList *request_list;
+ GHashTable *code_value_hash;
+ GHashTable *send_value_hash;
+ GDHCPClientEventFunc lease_available_cb;
+ gpointer lease_available_data;
+ GDHCPClientEventFunc ipv4ll_available_cb;
+ gpointer ipv4ll_available_data;
+ GDHCPClientEventFunc no_lease_cb;
+ gpointer no_lease_data;
+ GDHCPClientEventFunc lease_lost_cb;
+ gpointer lease_lost_data;
+ GDHCPClientEventFunc ipv4ll_lost_cb;
+ gpointer ipv4ll_lost_data;
+ GDHCPClientEventFunc address_conflict_cb;
+ gpointer address_conflict_data;
+ GDHCPDebugFunc debug_func;
+ gpointer debug_data;
+ GDHCPClientEventFunc information_req_cb;
+ gpointer information_req_data;
+ GDHCPClientEventFunc solicitation_cb;
+ gpointer solicitation_data;
+ GDHCPClientEventFunc advertise_cb;
+ gpointer advertise_data;
+ GDHCPClientEventFunc request_cb;
+ gpointer request_data;
+ GDHCPClientEventFunc renew_cb;
+ gpointer renew_data;
+ GDHCPClientEventFunc rebind_cb;
+ gpointer rebind_data;
+ GDHCPClientEventFunc release_cb;
+ gpointer release_data;
+ GDHCPClientEventFunc confirm_cb;
+ gpointer confirm_data;
+ GDHCPClientEventFunc decline_cb;
+ gpointer decline_data;
+ char *last_address;
+ unsigned char *duid;
+ int duid_len;
+ unsigned char *server_duid;
+ int server_duid_len;
+ uint16_t status_code;
+ uint32_t iaid;
+ uint32_t T1, T2;
+ struct in6_addr ia_na;
+ struct in6_addr ia_ta;
+ time_t last_request;
+ uint32_t expire;
+ bool retransmit;
+ struct timeval start_time;
+};
+
+static inline void debug(GDHCPClient *client, const char *format, ...)
+{
+ char str[256];
+ va_list ap;
+
+ if (!client->debug_func)
+ return;
+
+ va_start(ap, format);
+
+ if (vsnprintf(str, sizeof(str), format, ap) > 0)
+ client->debug_func(str, client->debug_data);
+
+ va_end(ap);
+}
+
+/* Initialize the packet with the proper defaults */
+static void init_packet(GDHCPClient *dhcp_client, gpointer pkt, char type)
+{
+ if (dhcp_client->type == G_DHCP_IPV6)
+ dhcpv6_init_header(pkt, type);
+ else {
+ struct dhcp_packet *packet = pkt;
+
+ dhcp_init_header(packet, type);
+ memcpy(packet->chaddr, dhcp_client->mac_address, 6);
+ }
+}
+
+static void add_request_options(GDHCPClient *dhcp_client,
+ struct dhcp_packet *packet)
+{
+ int len = 0;
+ GList *list;
+ uint8_t code;
+ int end = dhcp_end_option(packet->options);
+
+ for (list = dhcp_client->request_list; list; list = list->next) {
+ code = (uint8_t) GPOINTER_TO_INT(list->data);
+
+ packet->options[end + OPT_DATA + len] = code;
+ 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;
+ }
+}
+
+struct hash_params {
+ unsigned char *buf;
+ int max_buf;
+ unsigned char **ptr_buf;
+};
+
+static void add_dhcpv6_binary_option(gpointer key, gpointer value,
+ gpointer user_data)
+{
+ uint8_t *option = value;
+ uint16_t len;
+ struct hash_params *params = user_data;
+
+ /* option[0][1] contains option code */
+ len = option[2] << 8 | option[3];
+
+ if ((*params->ptr_buf + len + 2 + 2) > (params->buf + params->max_buf))
+ return;
+
+ memcpy(*params->ptr_buf, option, len + 2 + 2);
+ (*params->ptr_buf) += len + 2 + 2;
+}
+
+static void add_dhcpv6_send_options(GDHCPClient *dhcp_client,
+ unsigned char *buf, int max_buf,
+ unsigned char **ptr_buf)
+{
+ struct hash_params params = {
+ .buf = buf,
+ .max_buf = max_buf,
+ .ptr_buf = ptr_buf
+ };
+
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+
+ g_hash_table_foreach(dhcp_client->send_value_hash,
+ add_dhcpv6_binary_option, &params);
+
+ *ptr_buf = *params.ptr_buf;
+}
+
+static void copy_option(uint8_t *buf, uint16_t code, uint16_t len,
+ uint8_t *msg)
+{
+ buf[0] = code >> 8;
+ buf[1] = code & 0xff;
+ buf[2] = len >> 8;
+ buf[3] = len & 0xff;
+ if (len > 0 && msg)
+ memcpy(&buf[4], msg, len);
+}
+
+static int32_t get_time_diff(struct timeval *tv)
+{
+ struct timeval now;
+ int32_t hsec;
+
+ gettimeofday(&now, NULL);
+
+ hsec = (now.tv_sec - tv->tv_sec) * 100;
+ hsec += (now.tv_usec - tv->tv_usec) / 10000;
+
+ return hsec;
+}
+
+static void add_dhcpv6_request_options(GDHCPClient *dhcp_client,
+ struct dhcpv6_packet *packet,
+ unsigned char *buf, int max_buf,
+ unsigned char **ptr_buf)
+{
+ GList *list;
+ uint16_t code, value;
+ bool added;
+ int32_t diff;
+ int len;
+
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+
+ for (list = dhcp_client->request_list; list; list = list->next) {
+ code = (uint16_t) GPOINTER_TO_INT(list->data);
+ added = false;
+
+ switch (code) {
+ case G_DHCPV6_CLIENTID:
+ if (!dhcp_client->duid)
+ return;
+
+ len = 2 + 2 + dhcp_client->duid_len;
+ if ((*ptr_buf + len) > (buf + max_buf)) {
+ debug(dhcp_client, "Too long dhcpv6 message "
+ "when writing client id option");
+ return;
+ }
+
+ copy_option(*ptr_buf, G_DHCPV6_CLIENTID,
+ dhcp_client->duid_len, dhcp_client->duid);
+ (*ptr_buf) += len;
+ added = true;
+ break;
+
+ case G_DHCPV6_SERVERID:
+ if (!dhcp_client->server_duid)
+ break;
+
+ len = 2 + 2 + dhcp_client->server_duid_len;
+ if ((*ptr_buf + len) > (buf + max_buf)) {
+ debug(dhcp_client, "Too long dhcpv6 message "
+ "when writing server id option");
+ return;
+ }
+
+ copy_option(*ptr_buf, G_DHCPV6_SERVERID,
+ dhcp_client->server_duid_len,
+ dhcp_client->server_duid);
+ (*ptr_buf) += len;
+ added = true;
+ break;
+
+ case G_DHCPV6_RAPID_COMMIT:
+ len = 2 + 2;
+ if ((*ptr_buf + len) > (buf + max_buf)) {
+ debug(dhcp_client, "Too long dhcpv6 message "
+ "when writing rapid commit option");
+ return;
+ }
+
+ copy_option(*ptr_buf, G_DHCPV6_RAPID_COMMIT, 0, 0);
+ (*ptr_buf) += len;
+ added = true;
+ break;
+
+ case G_DHCPV6_ORO:
+ break;
+
+ case G_DHCPV6_ELAPSED_TIME:
+ if (!dhcp_client->retransmit) {
+ /*
+ * Initial message, elapsed time is 0.
+ */
+ diff = 0;
+ } else {
+ diff = get_time_diff(&dhcp_client->start_time);
+ if (diff < 0 || diff > 0xffff)
+ diff = 0xffff;
+ }
+
+ len = 2 + 2 + 2;
+ if ((*ptr_buf + len) > (buf + max_buf)) {
+ debug(dhcp_client, "Too long dhcpv6 message "
+ "when writing elapsed time option");
+ return;
+ }
+
+ value = htons((uint16_t)diff);
+ copy_option(*ptr_buf, G_DHCPV6_ELAPSED_TIME,
+ 2, (uint8_t *)&value);
+ (*ptr_buf) += len;
+ added = true;
+ break;
+
+ case G_DHCPV6_DNS_SERVERS:
+ break;
+
+ case G_DHCPV6_DOMAIN_LIST:
+ break;
+
+ case G_DHCPV6_SNTP_SERVERS:
+ break;
+
+ default:
+ break;
+ }
+
+ if (added)
+ debug(dhcp_client, "option %d len %d added", code, len);
+ }
+}
+
+static void add_binary_option(gpointer key, gpointer value, gpointer user_data)
+{
+ uint8_t *option = value;
+ struct dhcp_packet *packet = user_data;
+
+ dhcp_add_binary_option(packet, option);
+}
+
+static void add_send_options(GDHCPClient *dhcp_client,
+ struct dhcp_packet *packet)
+{
+ g_hash_table_foreach(dhcp_client->send_value_hash,
+ add_binary_option, packet);
+}
+
+/*
+ * Return an RFC 951- and 2131-complaint BOOTP 'secs' value that
+ * represents the number of seconds elapsed from the start of
+ * attempting DHCP to satisfy some DHCP servers that allow for an
+ * "authoritative" reply before responding.
+ */
+static uint16_t dhcp_attempt_secs(GDHCPClient *dhcp_client)
+{
+ return htons(MIN(time(NULL) - dhcp_client->start, UINT16_MAX));
+}
+
+static int send_discover(GDHCPClient *dhcp_client, uint32_t requested)
+{
+ struct dhcp_packet packet;
+
+ debug(dhcp_client, "sending DHCP discover request");
+
+ init_packet(dhcp_client, &packet, DHCPDISCOVER);
+
+ packet.xid = dhcp_client->xid;
+ packet.secs = dhcp_attempt_secs(dhcp_client);
+
+ if (requested)
+ dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, requested);
+
+ /* Explicitly saying that we want RFC-compliant packets helps
+ * some buggy DHCP servers to NOT send bigger packets */
+ dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576);
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_select(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+
+ debug(dhcp_client, "sending DHCP select request");
+
+ init_packet(dhcp_client, &packet, DHCPREQUEST);
+
+ packet.xid = dhcp_client->xid;
+ packet.secs = dhcp_attempt_secs(dhcp_client);
+
+ dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP,
+ dhcp_client->requested_ip);
+ dhcp_add_option_uint32(&packet, DHCP_SERVER_ID,
+ dhcp_client->server_ip);
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_renew(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+
+ debug(dhcp_client, "sending DHCP renew request");
+
+ init_packet(dhcp_client , &packet, DHCPREQUEST);
+ packet.xid = dhcp_client->xid;
+ packet.ciaddr = htonl(dhcp_client->requested_ip);
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_kernel_packet(&packet,
+ dhcp_client->requested_ip, CLIENT_PORT,
+ dhcp_client->server_ip, SERVER_PORT);
+}
+
+static int send_rebound(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+
+ debug(dhcp_client, "sending DHCP rebound request");
+
+ init_packet(dhcp_client , &packet, DHCPREQUEST);
+ packet.xid = dhcp_client->xid;
+ packet.ciaddr = htonl(dhcp_client->requested_ip);
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_release(GDHCPClient *dhcp_client,
+ uint32_t server, uint32_t ciaddr)
+{
+ struct dhcp_packet packet;
+
+ debug(dhcp_client, "sending DHCP release request");
+
+ init_packet(dhcp_client, &packet, DHCPRELEASE);
+ packet.xid = rand();
+ packet.ciaddr = htonl(ciaddr);
+
+ dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, server);
+
+ return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT,
+ server, SERVER_PORT);
+}
+
+static gboolean ipv4ll_probe_timeout(gpointer dhcp_data);
+static int switch_listening_mode(GDHCPClient *dhcp_client,
+ ListenMode listen_mode);
+
+static gboolean send_probe_packet(gpointer dhcp_data)
+{
+ GDHCPClient *dhcp_client;
+ guint timeout;
+
+ dhcp_client = dhcp_data;
+ /* if requested_ip is not valid, pick a new address*/
+ if (dhcp_client->requested_ip == 0) {
+ debug(dhcp_client, "pick a new random address");
+ dhcp_client->requested_ip = ipv4ll_random_ip(0);
+ }
+
+ debug(dhcp_client, "sending IPV4LL probe request");
+
+ if (dhcp_client->retry_times == 1) {
+ dhcp_client->state = IPV4LL_PROBE;
+ switch_listening_mode(dhcp_client, L_ARP);
+ }
+ ipv4ll_send_arp_packet(dhcp_client->mac_address, 0,
+ dhcp_client->requested_ip, dhcp_client->ifindex);
+
+ if (dhcp_client->retry_times < PROBE_NUM) {
+ /*add a random timeout in range of PROBE_MIN to PROBE_MAX*/
+ timeout = ipv4ll_random_delay_ms(PROBE_MAX-PROBE_MIN);
+ timeout += PROBE_MIN*1000;
+ } else
+ timeout = (ANNOUNCE_WAIT * 1000);
+
+ dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH,
+ timeout,
+ ipv4ll_probe_timeout,
+ dhcp_client,
+ NULL);
+ return FALSE;
+}
+
+static gboolean ipv4ll_announce_timeout(gpointer dhcp_data);
+static gboolean ipv4ll_defend_timeout(gpointer dhcp_data);
+
+static gboolean send_announce_packet(gpointer dhcp_data)
+{
+ GDHCPClient *dhcp_client;
+
+ dhcp_client = dhcp_data;
+
+ debug(dhcp_client, "sending IPV4LL announce request");
+
+ ipv4ll_send_arp_packet(dhcp_client->mac_address,
+ dhcp_client->requested_ip,
+ dhcp_client->requested_ip,
+ dhcp_client->ifindex);
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+
+ if (dhcp_client->state == IPV4LL_DEFEND) {
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ DEFEND_INTERVAL,
+ ipv4ll_defend_timeout,
+ dhcp_client,
+ NULL);
+ return TRUE;
+ } else
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ ANNOUNCE_INTERVAL,
+ ipv4ll_announce_timeout,
+ dhcp_client,
+ NULL);
+ return TRUE;
+}
+
+static void get_interface_mac_address(int index, uint8_t *mac_address)
+{
+ struct ifreq ifr;
+ int sk, err;
+
+ sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = index;
+
+ err = ioctl(sk, SIOCGIFNAME, &ifr);
+ if (err < 0) {
+ perror("Get interface name error");
+ goto done;
+ }
+
+ err = ioctl(sk, SIOCGIFHWADDR, &ifr);
+ if (err < 0) {
+ perror("Get mac address error");
+ goto done;
+ }
+
+ memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
+
+done:
+ close(sk);
+}
+
+void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client)
+ return;
+
+ dhcp_client->retransmit = true;
+}
+
+void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client)
+ return;
+
+ dhcp_client->retransmit = false;
+}
+
+int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type,
+ unsigned char **duid, int *duid_len)
+{
+ time_t duid_time;
+
+ switch (duid_type) {
+ case G_DHCPV6_DUID_LLT:
+ *duid_len = 2 + 2 + 4 + ETH_ALEN;
+ *duid = g_try_malloc(*duid_len);
+ if (!*duid)
+ return -ENOMEM;
+
+ (*duid)[0] = 0;
+ (*duid)[1] = 1;
+ get_interface_mac_address(index, &(*duid)[2 + 2 + 4]);
+ (*duid)[2] = 0;
+ (*duid)[3] = type;
+ duid_time = time(NULL) - DUID_TIME_EPOCH;
+ (*duid)[4] = duid_time >> 24;
+ (*duid)[5] = duid_time >> 16;
+ (*duid)[6] = duid_time >> 8;
+ (*duid)[7] = duid_time & 0xff;
+ break;
+ case G_DHCPV6_DUID_EN:
+ return -EINVAL;
+ case G_DHCPV6_DUID_LL:
+ *duid_len = 2 + 2 + ETH_ALEN;
+ *duid = g_try_malloc(*duid_len);
+ if (!*duid)
+ return -ENOMEM;
+
+ (*duid)[0] = 0;
+ (*duid)[1] = 3;
+ get_interface_mac_address(index, &(*duid)[2 + 2]);
+ (*duid)[2] = 0;
+ (*duid)[3] = type;
+ break;
+ }
+
+ return 0;
+}
+
+static gchar *convert_to_hex(unsigned char *buf, int len)
+{
+ gchar *ret = g_try_malloc(len * 2 + 1);
+ int i;
+
+ for (i = 0; ret && i < len; i++)
+ g_snprintf(ret + i * 2, 3, "%02x", buf[i]);
+
+ return ret;
+}
+
+int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid,
+ int duid_len)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return -EINVAL;
+
+ g_free(dhcp_client->duid);
+
+ dhcp_client->duid = duid;
+ dhcp_client->duid_len = duid_len;
+
+ if (dhcp_client->debug_func) {
+ gchar *hex = convert_to_hex(duid, duid_len);
+ debug(dhcp_client, "DUID(%d) %s", duid_len, hex);
+ g_free(hex);
+ }
+
+ return 0;
+}
+
+int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1,
+ uint32_t *T2, GSList *prefixes)
+{
+ uint8_t options[1452];
+ unsigned int max_buf = sizeof(options);
+ int len, count = g_slist_length(prefixes);
+
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return -EINVAL;
+
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_PD);
+
+ memset(options, 0, sizeof(options));
+
+ options[0] = dhcp_client->iaid >> 24;
+ options[1] = dhcp_client->iaid >> 16;
+ options[2] = dhcp_client->iaid >> 8;
+ options[3] = dhcp_client->iaid;
+
+ if (T1) {
+ uint32_t t = htonl(*T1);
+ memcpy(&options[4], &t, 4);
+ }
+
+ if (T2) {
+ uint32_t t = htonl(*T2);
+ memcpy(&options[8], &t, 4);
+ }
+
+ len = 12;
+
+ if (count > 0) {
+ GSList *list;
+
+ for (list = prefixes; list; list = list->next) {
+ GDHCPIAPrefix *prefix = list->data;
+ uint8_t sub_option[4+4+1+16];
+
+ if ((len + 2 + 2 + sizeof(sub_option)) >= max_buf) {
+ debug(dhcp_client,
+ "Too long dhcpv6 message "
+ "when writing IA prefix option");
+ return -EINVAL;
+ }
+
+ memset(&sub_option, 0, sizeof(sub_option));
+
+ /* preferred and validity time are left zero */
+
+ sub_option[8] = prefix->prefixlen;
+ memcpy(&sub_option[9], &prefix->prefix, 16);
+
+ copy_option(&options[len], G_DHCPV6_IA_PREFIX,
+ sizeof(sub_option), sub_option);
+ len += 2 + 2 + sizeof(sub_option);
+ }
+ }
+
+ g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_PD,
+ options, len);
+
+ return 0;
+}
+
+uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return 0;
+
+ return dhcp_client->iaid;
+}
+
+void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return;
+
+ dhcp_client->iaid = iaid;
+}
+
+void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index,
+ unsigned char *iaid)
+{
+ uint8_t buf[6];
+
+ get_interface_mac_address(index, buf);
+
+ memcpy(iaid, &buf[2], 4);
+ dhcp_client->iaid = iaid[0] << 24 |
+ iaid[1] << 16 | iaid[2] << 8 | iaid[3];
+}
+
+int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
+ uint32_t *T1, uint32_t *T2,
+ time_t *started,
+ time_t *expire)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return -EINVAL;
+
+ if (T1)
+ *T1 = dhcp_client->T1;
+
+ if (T2)
+ *T2 = dhcp_client->T2;
+
+ if (started)
+ *started = dhcp_client->last_request;
+
+ if (expire)
+ *expire = dhcp_client->last_request + dhcp_client->expire;
+
+ return 0;
+}
+
+static uint8_t *create_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf,
+ uint16_t len)
+{
+ buf[0] = 0;
+ buf[1] = G_DHCPV6_IAADDR;
+ buf[2] = 0;
+ buf[3] = len;
+ memcpy(&buf[4], &dhcp_client->ia_na, 16);
+ memset(&buf[20], 0, 4); /* preferred */
+ memset(&buf[24], 0, 4); /* valid */
+ return buf;
+}
+
+static uint8_t *append_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf,
+ const char *address)
+{
+ struct in6_addr addr;
+
+ if (inet_pton(AF_INET6, address, &addr) != 1)
+ return NULL;
+
+ buf[0] = 0;
+ buf[1] = G_DHCPV6_IAADDR;
+ buf[2] = 0;
+ buf[3] = 24;
+ memcpy(&buf[4], &addr, 16);
+ memset(&buf[20], 0, 4); /* preferred */
+ memset(&buf[24], 0, 4); /* valid */
+ return &buf[28];
+}
+
+static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf)
+{
+ uint32_t iaid;
+
+ iaid = g_dhcpv6_client_get_iaid(dhcp_client);
+ if (iaid == 0) {
+ g_dhcpv6_client_create_iaid(dhcp_client, index, buf);
+ return;
+ }
+
+ buf[0] = iaid >> 24;
+ buf[1] = iaid >> 16;
+ buf[2] = iaid >> 8;
+ buf[3] = iaid;
+}
+
+int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
+ int code, uint32_t *T1, uint32_t *T2,
+ bool add_iaaddr, const char *ia_na)
+{
+ if (code == G_DHCPV6_IA_TA) {
+ uint8_t ia_options[4];
+
+ put_iaid(dhcp_client, index, ia_options);
+
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_TA);
+ g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_TA,
+ ia_options, sizeof(ia_options));
+
+ } else if (code == G_DHCPV6_IA_NA) {
+ struct in6_addr addr;
+
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA);
+
+ /*
+ * If caller has specified the IPv6 address it wishes to
+ * to use (ia_na != NULL and address is valid), then send
+ * the address to server.
+ * If caller did not specify the address (ia_na == NULL) and
+ * if the current address is not set, then we should not send
+ * the address sub-option.
+ */
+ if (add_iaaddr && ((!ia_na &&
+ !IN6_IS_ADDR_UNSPECIFIED(&dhcp_client->ia_na))
+ || (ia_na &&
+ inet_pton(AF_INET6, ia_na, &addr) == 1))) {
+#define IAADDR_LEN (16+4+4)
+ uint8_t ia_options[4+4+4+2+2+IAADDR_LEN];
+
+ if (ia_na)
+ memcpy(&dhcp_client->ia_na, &addr,
+ sizeof(struct in6_addr));
+
+ put_iaid(dhcp_client, index, ia_options);
+
+ if (T1) {
+ ia_options[4] = *T1 >> 24;
+ ia_options[5] = *T1 >> 16;
+ ia_options[6] = *T1 >> 8;
+ ia_options[7] = *T1;
+ } else
+ memset(&ia_options[4], 0x00, 4);
+
+ if (T2) {
+ ia_options[8] = *T2 >> 24;
+ ia_options[9] = *T2 >> 16;
+ ia_options[10] = *T2 >> 8;
+ ia_options[11] = *T2;
+ } else
+ memset(&ia_options[8], 0x00, 4);
+
+ create_iaaddr(dhcp_client, &ia_options[12],
+ IAADDR_LEN);
+
+ g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA,
+ ia_options, sizeof(ia_options));
+ } else {
+ uint8_t ia_options[4+4+4];
+
+ put_iaid(dhcp_client, index, ia_options);
+
+ memset(&ia_options[4], 0x00, 4); /* T1 (4 bytes) */
+ memset(&ia_options[8], 0x00, 4); /* T2 (4 bytes) */
+
+ g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA,
+ ia_options, sizeof(ia_options));
+ }
+
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index,
+ int code, uint32_t *T1, uint32_t *T2,
+ GSList *addresses)
+{
+ GSList *list;
+ uint8_t *ia_options, *pos;
+ int len, count, total_len;
+
+ count = g_slist_length(addresses);
+ if (count == 0)
+ return -EINVAL;
+
+ g_dhcp_client_set_request(dhcp_client, code);
+
+ if (code == G_DHCPV6_IA_TA)
+ len = 4; /* IAID */
+ else if (code == G_DHCPV6_IA_NA)
+ len = 4 + 4 + 4; /* IAID + T1 + T2 */
+ else
+ return -EINVAL;
+
+ total_len = len + count * (2 + 2 + 16 + 4 + 4);
+ ia_options = g_try_malloc0(total_len);
+ if (!ia_options)
+ return -ENOMEM;
+
+ put_iaid(dhcp_client, index, ia_options);
+
+ pos = &ia_options[len]; /* skip the IA_NA or IA_TA */
+
+ for (list = addresses; list; list = list->next) {
+ pos = append_iaaddr(dhcp_client, pos, list->data);
+ if (!pos)
+ break;
+ }
+
+ if (code == G_DHCPV6_IA_NA) {
+ if (T1) {
+ ia_options[4] = *T1 >> 24;
+ ia_options[5] = *T1 >> 16;
+ ia_options[6] = *T1 >> 8;
+ ia_options[7] = *T1;
+ } else
+ memset(&ia_options[4], 0x00, 4);
+
+ if (T2) {
+ ia_options[8] = *T2 >> 24;
+ ia_options[9] = *T2 >> 16;
+ ia_options[10] = *T2 >> 8;
+ ia_options[11] = *T2;
+ } else
+ memset(&ia_options[8], 0x00, 4);
+ }
+
+ g_dhcpv6_client_set_send(dhcp_client, code, ia_options, total_len);
+
+ g_free(ia_options);
+
+ return 0;
+}
+
+int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...)
+{
+ va_list va;
+ int i, j, len = sizeof(uint16_t) * args;
+ uint8_t *values;
+
+ values = g_try_malloc(len);
+ if (!values)
+ return -ENOMEM;
+
+ va_start(va, args);
+ for (i = 0, j = 0; i < args; i++) {
+ uint16_t value = va_arg(va, int);
+ values[j++] = value >> 8;
+ values[j++] = value & 0xff;
+ }
+ va_end(va);
+
+ g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_ORO, values, len);
+
+ g_free(values);
+
+ return 0;
+}
+
+static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg)
+{
+ struct dhcpv6_packet *packet;
+ uint8_t buf[MAX_DHCPV6_PKT_SIZE];
+ unsigned char *ptr;
+ int ret, max_buf;
+
+ memset(buf, 0, sizeof(buf));
+ packet = (struct dhcpv6_packet *)&buf[0];
+ ptr = buf + sizeof(struct dhcpv6_packet);
+
+ init_packet(dhcp_client, packet, type);
+
+ if (!dhcp_client->retransmit) {
+ dhcp_client->xid = packet->transaction_id[0] << 16 |
+ packet->transaction_id[1] << 8 |
+ packet->transaction_id[2];
+ gettimeofday(&dhcp_client->start_time, NULL);
+ } else {
+ packet->transaction_id[0] = dhcp_client->xid >> 16;
+ packet->transaction_id[1] = dhcp_client->xid >> 8 ;
+ packet->transaction_id[2] = dhcp_client->xid;
+ }
+
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_ELAPSED_TIME);
+
+ debug(dhcp_client, "sending DHCPv6 %s message xid 0x%04x", msg,
+ dhcp_client->xid);
+
+ max_buf = MAX_DHCPV6_PKT_SIZE - sizeof(struct dhcpv6_packet);
+
+ add_dhcpv6_request_options(dhcp_client, packet, buf, max_buf, &ptr);
+
+ add_dhcpv6_send_options(dhcp_client, buf, max_buf, &ptr);
+
+ ret = dhcpv6_send_packet(dhcp_client->ifindex, packet, ptr - buf);
+
+ debug(dhcp_client, "sent %d pkt %p len %d", ret, packet, ptr - buf);
+ return ret;
+}
+
+static int send_solicitation(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_SOLICIT, "solicit");
+}
+
+static int send_dhcpv6_request(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_REQUEST, "request");
+}
+
+static int send_dhcpv6_confirm(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_CONFIRM, "confirm");
+}
+
+static int send_dhcpv6_renew(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_RENEW, "renew");
+}
+
+static int send_dhcpv6_rebind(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_REBIND, "rebind");
+}
+
+static int send_dhcpv6_decline(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_DECLINE, "decline");
+}
+
+static int send_dhcpv6_release(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_RELEASE, "release");
+}
+
+static int send_information_req(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ,
+ "information-req");
+}
+
+static void remove_value(gpointer data, gpointer user_data)
+{
+ char *value = data;
+ g_free(value);
+}
+
+static void remove_option_value(gpointer data)
+{
+ GList *option_value = data;
+
+ g_list_foreach(option_value, remove_value, NULL);
+}
+
+GDHCPClient *g_dhcp_client_new(GDHCPType type,
+ int ifindex, GDHCPClientError *error)
+{
+ GDHCPClient *dhcp_client;
+
+ if (ifindex < 0) {
+ *error = G_DHCP_CLIENT_ERROR_INVALID_INDEX;
+ return NULL;
+ }
+
+ dhcp_client = g_try_new0(GDHCPClient, 1);
+ if (!dhcp_client) {
+ *error = G_DHCP_CLIENT_ERROR_NOMEM;
+ return NULL;
+ }
+
+ dhcp_client->interface = get_interface_name(ifindex);
+ if (!dhcp_client->interface) {
+ *error = G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE;
+ goto error;
+ }
+
+ if (!interface_is_up(ifindex)) {
+ *error = G_DHCP_CLIENT_ERROR_INTERFACE_DOWN;
+ goto error;
+ }
+
+ get_interface_mac_address(ifindex, dhcp_client->mac_address);
+
+ dhcp_client->listener_sockfd = -1;
+ dhcp_client->listener_channel = NULL;
+ dhcp_client->listen_mode = L_NONE;
+ dhcp_client->ref_count = 1;
+ dhcp_client->type = type;
+ dhcp_client->ifindex = ifindex;
+ dhcp_client->lease_available_cb = NULL;
+ dhcp_client->ipv4ll_available_cb = NULL;
+ dhcp_client->no_lease_cb = NULL;
+ dhcp_client->lease_lost_cb = NULL;
+ dhcp_client->ipv4ll_lost_cb = NULL;
+ dhcp_client->address_conflict_cb = NULL;
+ dhcp_client->listener_watch = 0;
+ dhcp_client->retry_times = 0;
+ dhcp_client->ack_retry_times = 0;
+ dhcp_client->code_value_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, remove_option_value);
+ dhcp_client->send_value_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, g_free);
+ dhcp_client->request_list = NULL;
+ dhcp_client->require_list = NULL;
+ dhcp_client->duid = NULL;
+ dhcp_client->duid_len = 0;
+ dhcp_client->last_request = time(NULL);
+ dhcp_client->expire = 0;
+
+ *error = G_DHCP_CLIENT_ERROR_NONE;
+
+ return dhcp_client;
+
+error:
+ g_free(dhcp_client->interface);
+ g_free(dhcp_client);
+ return NULL;
+}
+
+#define SERVER_AND_CLIENT_PORTS ((67 << 16) + 68)
+
+static int dhcp_l2_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 <sux@loplof.de>.
+ * License: GPL v2.
+ *
+ * TODO: make conditional?
+ */
+ static const struct sock_filter filter_instr[] = {
+ /* check for udp */
+ BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9),
+ /* L5, L1, is UDP? */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0),
+ /* ugly check for arp on ethernet-like and IPv4 */
+ BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), /* L1: */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4),/* L3, L4 */
+ /* skip IP header */
+ BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* L5: */
+ /* check udp source and destination ports */
+ BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0),
+ /* L3, L4 */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1),
+ /* returns */
+ BPF_STMT(BPF_RET|BPF_K, 0x0fffffff), /* L3: pass */
+ BPF_STMT(BPF_RET|BPF_K, 0), /* L4: reject */
+ };
+
+ static const struct sock_fprog filter_prog = {
+ .len = sizeof(filter_instr) / sizeof(filter_instr[0]),
+ /* casting const away: */
+ .filter = (struct sock_filter *) filter_instr,
+ };
+
+ fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP));
+ if (fd < 0)
+ return fd;
+
+ if (SERVER_PORT == 67 && CLIENT_PORT == 68)
+ /* Use only if standard ports are in use */
+ setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
+ sizeof(filter_prog));
+
+ memset(&sock, 0, sizeof(sock));
+ sock.sll_family = AF_PACKET;
+ sock.sll_protocol = htons(ETH_P_IP);
+ sock.sll_ifindex = ifindex;
+
+ if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) {
+ close(fd);
+ return -errno;
+ }
+
+ return fd;
+}
+
+static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes)
+{
+ if (packet->ip.protocol != IPPROTO_UDP)
+ return false;
+
+ if (packet->ip.version != IPVERSION)
+ return false;
+
+ if (packet->ip.ihl != sizeof(packet->ip) >> 2)
+ return false;
+
+ if (packet->udp.dest != htons(CLIENT_PORT))
+ return false;
+
+ if (ntohs(packet->udp.len) != (uint16_t)(bytes - sizeof(packet->ip)))
+ return false;
+
+ return true;
+}
+
+static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd)
+{
+ int bytes;
+ struct ip_udp_dhcp_packet packet;
+ uint16_t check;
+
+ memset(&packet, 0, sizeof(packet));
+
+ bytes = read(fd, &packet, sizeof(packet));
+ if (bytes < 0)
+ return -1;
+
+ if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp)))
+ return -1;
+
+ if (bytes < ntohs(packet.ip.tot_len))
+ /* packet is bigger than sizeof(packet), we did partial read */
+ return -1;
+
+ /* ignore any extra garbage bytes */
+ bytes = ntohs(packet.ip.tot_len);
+
+ if (!sanity_check(&packet, bytes))
+ return -1;
+
+ check = packet.ip.check;
+ packet.ip.check = 0;
+ if (check != dhcp_checksum(&packet.ip, sizeof(packet.ip)))
+ return -1;
+
+ /* 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 != dhcp_checksum(&packet, bytes))
+ return -1;
+
+ memcpy(dhcp_pkt, &packet.data, bytes - (sizeof(packet.ip) +
+ sizeof(packet.udp)));
+
+ if (dhcp_pkt->cookie != htonl(DHCP_MAGIC))
+ return -1;
+
+ return bytes - (sizeof(packet.ip) + sizeof(packet.udp));
+}
+
+static void ipv4ll_start(GDHCPClient *dhcp_client)
+{
+ guint timeout;
+ int seed;
+
+ if (dhcp_client->timeout > 0) {
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ }
+
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->retry_times = 0;
+ dhcp_client->requested_ip = 0;
+
+ /*try to start with a based mac address ip*/
+ seed = (dhcp_client->mac_address[4] << 8 | dhcp_client->mac_address[4]);
+ dhcp_client->requested_ip = ipv4ll_random_ip(seed);
+
+ /*first wait a random delay to avoid storm of arp request on boot*/
+ timeout = ipv4ll_random_delay_ms(PROBE_WAIT);
+
+ dhcp_client->retry_times++;
+ dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH,
+ timeout,
+ send_probe_packet,
+ dhcp_client,
+ NULL);
+}
+
+static void ipv4ll_stop(GDHCPClient *dhcp_client)
+{
+
+ switch_listening_mode(dhcp_client, L_NONE);
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+
+ if (dhcp_client->listener_watch > 0) {
+ g_source_remove(dhcp_client->listener_watch);
+ dhcp_client->listener_watch = 0;
+ }
+
+ dhcp_client->state = IPV4LL_PROBE;
+ dhcp_client->retry_times = 0;
+ dhcp_client->requested_ip = 0;
+
+ g_free(dhcp_client->assigned_ip);
+ dhcp_client->assigned_ip = NULL;
+}
+
+static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client)
+{
+ int bytes;
+ struct ether_arp arp;
+ uint32_t ip_requested;
+ int source_conflict;
+ int target_conflict;
+
+ memset(&arp, 0, sizeof(arp));
+ bytes = read(dhcp_client->listener_sockfd, &arp, sizeof(arp));
+ if (bytes < 0)
+ return bytes;
+
+ if (arp.arp_op != htons(ARPOP_REPLY) &&
+ arp.arp_op != htons(ARPOP_REQUEST))
+ return -EINVAL;
+
+ ip_requested = htonl(dhcp_client->requested_ip);
+ source_conflict = !memcmp(arp.arp_spa, &ip_requested,
+ sizeof(ip_requested));
+
+ target_conflict = !memcmp(arp.arp_tpa, &ip_requested,
+ sizeof(ip_requested));
+
+ if (!source_conflict && !target_conflict)
+ return 0;
+
+ dhcp_client->conflicts++;
+
+ debug(dhcp_client, "IPV4LL conflict detected");
+
+ if (dhcp_client->state == IPV4LL_MONITOR) {
+ if (!source_conflict)
+ return 0;
+ dhcp_client->state = IPV4LL_DEFEND;
+ debug(dhcp_client, "DEFEND mode conflicts : %d",
+ dhcp_client->conflicts);
+ /*Try to defend with a single announce*/
+ send_announce_packet(dhcp_client);
+ return 0;
+ }
+
+ if (dhcp_client->state == IPV4LL_DEFEND) {
+ if (!source_conflict)
+ return 0;
+ else if (dhcp_client->ipv4ll_lost_cb)
+ dhcp_client->ipv4ll_lost_cb(dhcp_client,
+ dhcp_client->ipv4ll_lost_data);
+ }
+
+ ipv4ll_stop(dhcp_client);
+
+ if (dhcp_client->conflicts < MAX_CONFLICTS) {
+ /*restart whole state machine*/
+ dhcp_client->retry_times++;
+ dhcp_client->timeout =
+ g_timeout_add_full(G_PRIORITY_HIGH,
+ ipv4ll_random_delay_ms(PROBE_WAIT),
+ send_probe_packet,
+ dhcp_client,
+ NULL);
+ }
+ /* Here we got a lot of conflicts, RFC3927 states that we have
+ * to wait RATE_LIMIT_INTERVAL before retrying,
+ * but we just report failure.
+ */
+ else if (dhcp_client->no_lease_cb)
+ dhcp_client->no_lease_cb(dhcp_client,
+ dhcp_client->no_lease_data);
+
+ return 0;
+}
+
+static bool check_package_owner(GDHCPClient *dhcp_client, gpointer pkt)
+{
+ if (dhcp_client->type == G_DHCP_IPV6) {
+ struct dhcpv6_packet *packet6 = pkt;
+ uint32_t xid;
+
+ if (!packet6)
+ return false;
+
+ xid = packet6->transaction_id[0] << 16 |
+ packet6->transaction_id[1] << 8 |
+ packet6->transaction_id[2];
+
+ if (xid != dhcp_client->xid)
+ return false;
+ } else {
+ struct dhcp_packet *packet = pkt;
+
+ if (packet->xid != dhcp_client->xid)
+ return false;
+
+ if (packet->hlen != 6)
+ return false;
+
+ if (memcmp(packet->chaddr, dhcp_client->mac_address, 6))
+ return false;
+ }
+
+ return true;
+}
+
+static void start_request(GDHCPClient *dhcp_client);
+
+static gboolean request_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ debug(dhcp_client, "request timeout (retries %d)",
+ dhcp_client->retry_times);
+
+ dhcp_client->retry_times++;
+
+ start_request(dhcp_client);
+
+ return FALSE;
+}
+
+static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data);
+
+static int switch_listening_mode(GDHCPClient *dhcp_client,
+ ListenMode listen_mode)
+{
+ GIOChannel *listener_channel;
+ int listener_sockfd;
+
+ if (dhcp_client->listen_mode == listen_mode)
+ return 0;
+
+ debug(dhcp_client, "switch listening mode (%d ==> %d)",
+ dhcp_client->listen_mode, listen_mode);
+
+ if (dhcp_client->listen_mode != L_NONE) {
+ if (dhcp_client->listener_watch > 0)
+ g_source_remove(dhcp_client->listener_watch);
+ dhcp_client->listener_channel = NULL;
+ dhcp_client->listen_mode = L_NONE;
+ dhcp_client->listener_sockfd = -1;
+ dhcp_client->listener_watch = 0;
+ }
+
+ if (listen_mode == L_NONE)
+ return 0;
+
+ if (listen_mode == L2)
+ listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex);
+ else if (listen_mode == L3) {
+ if (dhcp_client->type == G_DHCP_IPV6)
+ listener_sockfd = dhcp_l3_socket(DHCPV6_CLIENT_PORT,
+ dhcp_client->interface,
+ AF_INET6);
+ else
+ listener_sockfd = dhcp_l3_socket(CLIENT_PORT,
+ dhcp_client->interface,
+ AF_INET);
+ } else if (listen_mode == L_ARP)
+ listener_sockfd = ipv4ll_arp_socket(dhcp_client->ifindex);
+ else
+ return -EIO;
+
+ if (listener_sockfd < 0)
+ return -EIO;
+
+ listener_channel = g_io_channel_unix_new(listener_sockfd);
+ if (!listener_channel) {
+ /* Failed to create listener channel */
+ close(listener_sockfd);
+ return -EIO;
+ }
+
+ dhcp_client->listen_mode = listen_mode;
+ dhcp_client->listener_sockfd = listener_sockfd;
+ dhcp_client->listener_channel = listener_channel;
+
+ g_io_channel_set_close_on_unref(listener_channel, TRUE);
+ dhcp_client->listener_watch =
+ g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
+ G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
+ listener_event, dhcp_client,
+ NULL);
+ g_io_channel_unref(dhcp_client->listener_channel);
+
+ return 0;
+}
+
+static void start_request(GDHCPClient *dhcp_client)
+{
+ debug(dhcp_client, "start request (retries %d)",
+ dhcp_client->retry_times);
+
+ if (dhcp_client->retry_times == REQUEST_RETRIES) {
+ if (dhcp_client->no_lease_cb)
+ dhcp_client->no_lease_cb(dhcp_client,
+ dhcp_client->no_lease_data);
+ return;
+ }
+
+ if (dhcp_client->retry_times == 0) {
+ dhcp_client->state = REQUESTING;
+ switch_listening_mode(dhcp_client, L2);
+ }
+
+ send_select(dhcp_client);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ REQUEST_TIMEOUT,
+ request_timeout,
+ dhcp_client,
+ NULL);
+}
+
+static uint32_t get_lease(struct dhcp_packet *packet)
+{
+ uint8_t *option;
+ uint32_t lease_seconds;
+
+ option = dhcp_get_option(packet, DHCP_LEASE_TIME);
+ if (!option)
+ return 3600;
+
+ lease_seconds = get_be32(option);
+ /* paranoia: must not be prone to overflows */
+ lease_seconds &= 0x0fffffff;
+ if (lease_seconds < 10)
+ lease_seconds = 10;
+
+ return lease_seconds;
+}
+
+static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times)
+{
+ debug(dhcp_client, "restart DHCP (retries %d)", retry_times);
+
+ if (dhcp_client->timeout > 0) {
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ }
+
+ dhcp_client->retry_times = retry_times;
+ dhcp_client->requested_ip = 0;
+ dhcp_client->state = INIT_SELECTING;
+ switch_listening_mode(dhcp_client, L2);
+
+ g_dhcp_client_start(dhcp_client, dhcp_client->last_address);
+}
+
+static gboolean start_rebound_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ debug(dhcp_client, "start rebound timeout");
+
+ switch_listening_mode(dhcp_client, L2);
+
+ dhcp_client->lease_seconds >>= 1;
+
+ /* We need to have enough time to receive ACK package*/
+ if (dhcp_client->lease_seconds <= 6) {
+
+ /* ip need to be cleared */
+ if (dhcp_client->lease_lost_cb)
+ dhcp_client->lease_lost_cb(dhcp_client,
+ dhcp_client->lease_lost_data);
+
+ restart_dhcp(dhcp_client, 0);
+ } else {
+ send_rebound(dhcp_client);
+
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_rebound_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ return FALSE;
+}
+
+static void start_rebound(GDHCPClient *dhcp_client)
+{
+ debug(dhcp_client, "start rebound");
+
+ dhcp_client->state = REBINDING;
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_rebound_timeout,
+ dhcp_client,
+ NULL);
+}
+
+static gboolean start_renew_request_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ debug(dhcp_client, "renew request timeout");
+
+ if (dhcp_client->no_lease_cb)
+ dhcp_client->no_lease_cb(dhcp_client,
+ dhcp_client->no_lease_data);
+
+ return false;
+}
+
+static gboolean start_renew_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ debug(dhcp_client, "start renew timeout");
+
+ dhcp_client->state = RENEWING;
+
+ dhcp_client->lease_seconds >>= 1;
+
+ switch_listening_mode(dhcp_client, L3);
+ if (dhcp_client->lease_seconds <= 60)
+ start_rebound(dhcp_client);
+ else {
+ send_renew(dhcp_client);
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ REQUEST_TIMEOUT,
+ start_renew_request_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ return FALSE;
+}
+
+static void start_bound(GDHCPClient *dhcp_client)
+{
+ debug(dhcp_client, "start bound");
+
+ dhcp_client->state = BOUND;
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_renew_timeout, dhcp_client,
+ NULL);
+}
+
+static gboolean restart_dhcp_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ debug(dhcp_client, "restart DHCP timeout");
+
+ dhcp_client->ack_retry_times++;
+
+ restart_dhcp(dhcp_client, dhcp_client->ack_retry_times);
+
+ return FALSE;
+}
+
+static char *get_ip(uint32_t ip)
+{
+ struct in_addr addr;
+
+ addr.s_addr = ip;
+
+ return g_strdup(inet_ntoa(addr));
+}
+
+/* get a rough idea of how long an option will be */
+static const uint8_t len_of_option_as_string[] = {
+ [OPTION_IP] = sizeof("255.255.255.255 "),
+ [OPTION_STRING] = 1,
+ [OPTION_U8] = sizeof("255 "),
+ [OPTION_U16] = sizeof("65535 "),
+ [OPTION_U32] = sizeof("4294967295 "),
+};
+
+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]);
+}
+
+/* Create "opt_value1 option_value2 ..." string */
+static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type)
+{
+ unsigned upper_length;
+ int len, optlen;
+ char *dest, *ret;
+
+ len = option[OPT_LEN - OPT_DATA];
+ type &= OPTION_TYPE_MASK;
+ optlen = dhcp_option_lengths[type];
+ if (optlen == 0)
+ return NULL;
+ upper_length = len_of_option_as_string[type] *
+ ((unsigned)len / (unsigned)optlen);
+ dest = ret = g_malloc(upper_length + 1);
+ if (!ret)
+ return NULL;
+
+ while (len >= optlen) {
+ switch (type) {
+ case OPTION_IP:
+ dest += sprint_nip(dest, "", option);
+ break;
+ case OPTION_U16: {
+ uint16_t val_u16 = get_be16(option);
+ dest += sprintf(dest, "%u", val_u16);
+ break;
+ }
+ case OPTION_U32: {
+ uint32_t val_u32 = get_be32(option);
+ dest += sprintf(dest, "%u", val_u32);
+ break;
+ }
+ case OPTION_STRING:
+ memcpy(dest, option, len);
+ dest[len] = '\0';
+ return ret;
+ default:
+ break;
+ }
+ option += optlen;
+ len -= optlen;
+ if (len <= 0)
+ break;
+ *dest++ = ' ';
+ *dest = '\0';
+ }
+
+ return ret;
+}
+
+static GList *get_option_value_list(char *value, GDHCPOptionType type)
+{
+ char *pos = value;
+ GList *list = NULL;
+
+ if (!pos)
+ return NULL;
+
+ if (type == OPTION_STRING)
+ return g_list_append(list, g_strdup(value));
+
+ while ((pos = strchr(pos, ' '))) {
+ *pos = '\0';
+
+ list = g_list_append(list, g_strdup(value));
+
+ value = ++pos;
+ }
+
+ list = g_list_append(list, g_strdup(value));
+
+ return list;
+}
+
+static inline uint32_t get_uint32(unsigned char *value)
+{
+ return value[0] << 24 | value[1] << 16 |
+ value[2] << 8 | value[3];
+}
+
+static inline uint16_t get_uint16(unsigned char *value)
+{
+ return value[0] << 8 | value[1];
+}
+
+static GList *add_prefix(GDHCPClient *dhcp_client, GList *list,
+ struct in6_addr *addr,
+ unsigned char prefixlen, uint32_t preferred,
+ uint32_t valid)
+{
+ GDHCPIAPrefix *ia_prefix;
+
+ ia_prefix = g_try_new(GDHCPIAPrefix, 1);
+ if (!ia_prefix)
+ return list;
+
+ if (dhcp_client->debug_func) {
+ char addr_str[INET6_ADDRSTRLEN + 1];
+ inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
+ debug(dhcp_client, "prefix %s/%d preferred %u valid %u",
+ addr_str, prefixlen, preferred, valid);
+ }
+
+ memcpy(&ia_prefix->prefix, addr, sizeof(struct in6_addr));
+ ia_prefix->prefixlen = prefixlen;
+ ia_prefix->preferred = preferred;
+ ia_prefix->valid = valid;
+ ia_prefix->expire = time(NULL) + valid;
+
+ return g_list_prepend(list, ia_prefix);
+}
+
+static GList *get_addresses(GDHCPClient *dhcp_client,
+ int code, int len,
+ unsigned char *value,
+ uint16_t *status)
+{
+ GList *list = NULL;
+ struct in6_addr addr;
+ uint32_t iaid, T1 = 0, T2 = 0, preferred = 0, valid = 0;
+ uint16_t option_len, option_code, st = 0, max_len;
+ int addr_count = 0, prefix_count = 0, i, pos;
+ unsigned char prefixlen;
+ unsigned int shortest_valid = 0;
+ uint8_t *option;
+ char *str;
+
+ if (!value || len < 4)
+ return NULL;
+
+ iaid = get_uint32(&value[0]);
+ if (dhcp_client->iaid != iaid)
+ return NULL;
+
+ if (code == G_DHCPV6_IA_NA || code == G_DHCPV6_IA_PD) {
+ T1 = get_uint32(&value[4]);
+ T2 = get_uint32(&value[8]);
+
+ if (T1 > T2)
+ /* IA_NA: RFC 3315, 22.4 */
+ /* IA_PD: RFC 3633, ch 9 */
+ return NULL;
+
+ pos = 12;
+ } else
+ pos = 4;
+
+ if (len <= pos)
+ return NULL;
+
+ max_len = len - pos;
+
+ debug(dhcp_client, "header %d sub-option max len %d", pos, max_len);
+
+ /* We have more sub-options in this packet. */
+ do {
+ option = dhcpv6_get_sub_option(&value[pos], max_len,
+ &option_code, &option_len);
+
+ debug(dhcp_client, "pos %d option %p code %d len %d",
+ pos, option, option_code, option_len);
+
+ if (!option)
+ break;
+
+ if (pos >= len)
+ break;
+
+ switch (option_code) {
+ case G_DHCPV6_IAADDR:
+ i = 0;
+ memcpy(&addr, &option[0], sizeof(addr));
+ i += sizeof(addr);
+ preferred = get_uint32(&option[i]);
+ i += 4;
+ valid = get_uint32(&option[i]);
+
+ addr_count++;
+ break;
+
+ case G_DHCPV6_STATUS_CODE:
+ st = get_uint16(&option[0]);
+ debug(dhcp_client, "error code %d", st);
+ if (option_len > 2) {
+ str = g_strndup((gchar *)&option[2],
+ option_len - 2);
+ debug(dhcp_client, "error text: %s", str);
+ g_free(str);
+ }
+
+ *status = st;
+ break;
+
+ case G_DHCPV6_IA_PREFIX:
+ i = 0;
+ preferred = get_uint32(&option[i]);
+ i += 4;
+ valid = get_uint32(&option[i]);
+ i += 4;
+ prefixlen = option[i];
+ i += 1;
+ memcpy(&addr, &option[i], sizeof(addr));
+ i += sizeof(addr);
+ if (preferred < valid) {
+ /* RFC 3633, ch 10 */
+ list = add_prefix(dhcp_client, list, &addr,
+ prefixlen, preferred, valid);
+ if (shortest_valid > valid)
+ shortest_valid = valid;
+ prefix_count++;
+ }
+ break;
+ }
+
+ pos += 2 + 2 + option_len;
+
+ } while (pos < len);
+
+ if (addr_count > 0 && st == 0) {
+ /* We only support one address atm */
+ char addr_str[INET6_ADDRSTRLEN + 1];
+
+ if (preferred > valid)
+ /* RFC 3315, 22.6 */
+ return NULL;
+
+ dhcp_client->T1 = T1;
+ dhcp_client->T2 = T2;
+
+ inet_ntop(AF_INET6, &addr, addr_str, INET6_ADDRSTRLEN);
+ debug(dhcp_client, "address count %d addr %s T1 %u T2 %u",
+ addr_count, addr_str, T1, T2);
+
+ list = g_list_append(list, g_strdup(addr_str));
+
+ if (code == G_DHCPV6_IA_NA)
+ memcpy(&dhcp_client->ia_na, &addr,
+ sizeof(struct in6_addr));
+ else
+ memcpy(&dhcp_client->ia_ta, &addr,
+ sizeof(struct in6_addr));
+
+ if (valid > dhcp_client->expire)
+ dhcp_client->expire = valid;
+ }
+
+ if (prefix_count > 0 && list) {
+ /*
+ * This means we have a list of prefixes to delegate.
+ */
+ list = g_list_reverse(list);
+
+ debug(dhcp_client, "prefix count %d T1 %u T2 %u",
+ prefix_count, T1, T2);
+
+ dhcp_client->T1 = T1;
+ dhcp_client->T2 = T2;
+
+ dhcp_client->expire = shortest_valid;
+ }
+
+ if (status && *status != 0)
+ debug(dhcp_client, "status %d", *status);
+
+ return list;
+}
+
+static GList *get_domains(int maxlen, unsigned char *value)
+
+{
+ GList *list = NULL;
+ int pos = 0;
+ unsigned char *c;
+ char dns_name[NS_MAXDNAME + 1];
+
+ if (!value || maxlen < 3)
+ return NULL;
+
+ while (pos < maxlen) {
+ strncpy(dns_name, (char *)&value[pos], NS_MAXDNAME);
+
+ c = (unsigned char *)dns_name;
+ while (c && *c) {
+ int jump;
+ jump = *c;
+ *c = '.';
+ c += jump + 1;
+ }
+ list = g_list_prepend(list, g_strdup(&dns_name[1]));
+ pos += (char *)c - dns_name + 1;
+ }
+
+ return g_list_reverse(list);
+}
+
+static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client,
+ int code, int len,
+ unsigned char *value,
+ uint16_t *status)
+{
+ GList *list = NULL;
+ char *str;
+ int i;
+
+ if (!value)
+ return NULL;
+
+ switch (code) {
+ case G_DHCPV6_DNS_SERVERS: /* RFC 3646, chapter 3 */
+ case G_DHCPV6_SNTP_SERVERS: /* RFC 4075, chapter 4 */
+ if (len % 16) {
+ debug(dhcp_client,
+ "%s server list length (%d) is invalid",
+ code == G_DHCPV6_DNS_SERVERS ? "DNS" : "SNTP",
+ len);
+ return NULL;
+ }
+ for (i = 0; i < len; i += 16) {
+
+ str = g_try_malloc0(INET6_ADDRSTRLEN+1);
+ if (!str)
+ return list;
+
+ if (!inet_ntop(AF_INET6, &value[i], str,
+ INET6_ADDRSTRLEN))
+ g_free(str);
+ else
+ list = g_list_append(list, str);
+ }
+ break;
+
+ case G_DHCPV6_IA_NA: /* RFC 3315, chapter 22.4 */
+ case G_DHCPV6_IA_TA: /* RFC 3315, chapter 22.5 */
+ case G_DHCPV6_IA_PD: /* RFC 3633, chapter 9 */
+ list = get_addresses(dhcp_client, code, len, value, status);
+ break;
+
+ case G_DHCPV6_DOMAIN_LIST:
+ list = get_domains(len, value);
+ break;
+
+ default:
+ break;
+ }
+
+ return list;
+}
+
+static void get_dhcpv6_request(GDHCPClient *dhcp_client,
+ struct dhcpv6_packet *packet,
+ uint16_t pkt_len, uint16_t *status)
+{
+ GList *list, *value_list;
+ uint8_t *option;
+ uint16_t code;
+ uint16_t option_len;
+
+ for (list = dhcp_client->request_list; list; list = list->next) {
+ code = (uint16_t) GPOINTER_TO_INT(list->data);
+
+ option = dhcpv6_get_option(packet, pkt_len, code, &option_len,
+ NULL);
+ if (!option) {
+ g_hash_table_remove(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code));
+ continue;
+ }
+
+ value_list = get_dhcpv6_option_value_list(dhcp_client, code,
+ option_len, option, status);
+
+ debug(dhcp_client, "code %d %p len %d list %p", code, option,
+ option_len, value_list);
+
+ if (!value_list)
+ g_hash_table_remove(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code));
+ else
+ g_hash_table_insert(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code), value_list);
+ }
+}
+
+static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet)
+{
+ GDHCPOptionType type;
+ GList *list, *value_list;
+ char *option_value;
+ uint8_t *option;
+ uint8_t code;
+
+ for (list = dhcp_client->request_list; list; list = list->next) {
+ code = (uint8_t) GPOINTER_TO_INT(list->data);
+
+ option = dhcp_get_option(packet, code);
+ if (!option) {
+ g_hash_table_remove(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code));
+ continue;
+ }
+
+ type = dhcp_get_code_type(code);
+
+ option_value = malloc_option_value_string(option, type);
+ if (!option_value)
+ g_hash_table_remove(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code));
+
+ value_list = get_option_value_list(option_value, type);
+
+ g_free(option_value);
+
+ if (!value_list)
+ g_hash_table_remove(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code));
+ else
+ g_hash_table_insert(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) code), value_list);
+ }
+}
+
+static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+ struct dhcp_packet packet;
+ struct dhcpv6_packet *packet6 = NULL;
+ uint8_t *message_type = NULL, *client_id = NULL, *option,
+ *server_id = NULL;
+ uint16_t option_len = 0, status = 0;
+ uint32_t xid = 0;
+ gpointer pkt;
+ unsigned char buf[MAX_DHCPV6_PKT_SIZE];
+ uint16_t pkt_len = 0;
+ int count;
+ int re;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ dhcp_client->listener_watch = 0;
+ return FALSE;
+ }
+
+ if (dhcp_client->listen_mode == L_NONE)
+ return FALSE;
+
+ pkt = &packet;
+
+ dhcp_client->status_code = 0;
+
+ if (dhcp_client->listen_mode == L2) {
+ re = dhcp_recv_l2_packet(&packet,
+ dhcp_client->listener_sockfd);
+ } else if (dhcp_client->listen_mode == L3) {
+ if (dhcp_client->type == G_DHCP_IPV6) {
+ re = dhcpv6_recv_l3_packet(&packet6, buf, sizeof(buf),
+ dhcp_client->listener_sockfd);
+ pkt_len = re;
+ pkt = packet6;
+ xid = packet6->transaction_id[0] << 16 |
+ packet6->transaction_id[1] << 8 |
+ packet6->transaction_id[2];
+ } else {
+ re = dhcp_recv_l3_packet(&packet,
+ dhcp_client->listener_sockfd);
+ xid = packet.xid;
+ }
+ } else if (dhcp_client->listen_mode == L_ARP) {
+ ipv4ll_recv_arp_packet(dhcp_client);
+ return TRUE;
+ } else
+ re = -EIO;
+
+ if (re < 0)
+ return TRUE;
+
+ if (!check_package_owner(dhcp_client, pkt))
+ return TRUE;
+
+ if (dhcp_client->type == G_DHCP_IPV6) {
+ if (!packet6)
+ return TRUE;
+
+ count = 0;
+ client_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_CLIENTID, &option_len, &count);
+
+ if (!client_id || count == 0 || option_len == 0 ||
+ memcmp(dhcp_client->duid, client_id,
+ dhcp_client->duid_len) != 0) {
+ debug(dhcp_client,
+ "client duid error, discarding msg %p/%d/%d",
+ client_id, option_len, count);
+ return TRUE;
+ }
+
+ option = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_STATUS_CODE, &option_len, NULL);
+ if (option != 0 && option_len > 0) {
+ status = option[0]<<8 | option[1];
+ if (status != 0) {
+ debug(dhcp_client, "error code %d", status);
+ if (option_len > 2) {
+ gchar *txt = g_strndup(
+ (gchar *)&option[2],
+ option_len - 2);
+ debug(dhcp_client, "error text: %s",
+ txt);
+ g_free(txt);
+ }
+ }
+ dhcp_client->status_code = status;
+ }
+ } else {
+ message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (!message_type)
+ return TRUE;
+ }
+
+ if (!message_type && !client_id)
+ /* No message type / client id option, ignore package */
+ return TRUE;
+
+ debug(dhcp_client, "received DHCP packet xid 0x%04x "
+ "(current state %d)", xid, dhcp_client->state);
+
+ switch (dhcp_client->state) {
+ case INIT_SELECTING:
+ if (*message_type != DHCPOFFER)
+ return TRUE;
+
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ dhcp_client->retry_times = 0;
+
+ option = dhcp_get_option(&packet, DHCP_SERVER_ID);
+ dhcp_client->server_ip = get_be32(option);
+ dhcp_client->requested_ip = ntohl(packet.yiaddr);
+
+ dhcp_client->state = REQUESTING;
+
+ start_request(dhcp_client);
+
+ return TRUE;
+ case REQUESTING:
+ case RENEWING:
+ case REBINDING:
+ if (*message_type == DHCPACK) {
+ dhcp_client->retry_times = 0;
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+
+ dhcp_client->lease_seconds = get_lease(&packet);
+
+ get_request(dhcp_client, &packet);
+
+ switch_listening_mode(dhcp_client, L_NONE);
+
+ g_free(dhcp_client->assigned_ip);
+ dhcp_client->assigned_ip = get_ip(packet.yiaddr);
+
+ /* Address should be set up here */
+ if (dhcp_client->lease_available_cb)
+ dhcp_client->lease_available_cb(dhcp_client,
+ dhcp_client->lease_available_data);
+
+ start_bound(dhcp_client);
+ } else if (*message_type == DHCPNAK) {
+ dhcp_client->retry_times = 0;
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(
+ G_PRIORITY_HIGH, 3,
+ restart_dhcp_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ break;
+ case SOLICITATION:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return TRUE;
+
+ if (packet6->message != DHCPV6_REPLY &&
+ packet6->message != DHCPV6_ADVERTISE)
+ return TRUE;
+
+ count = 0;
+ server_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_SERVERID, &option_len, &count);
+ if (!server_id || count != 1 || option_len == 0) {
+ /* RFC 3315, 15.10 */
+ debug(dhcp_client,
+ "server duid error, discarding msg %p/%d/%d",
+ server_id, option_len, count);
+ return TRUE;
+ }
+ dhcp_client->server_duid = g_try_malloc(option_len);
+ if (!dhcp_client->server_duid)
+ return TRUE;
+ memcpy(dhcp_client->server_duid, server_id, option_len);
+ dhcp_client->server_duid_len = option_len;
+
+ if (packet6->message == DHCPV6_REPLY) {
+ uint8_t *rapid_commit;
+ count = 0;
+ option_len = 0;
+ rapid_commit = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_RAPID_COMMIT,
+ &option_len, &count);
+ if (!rapid_commit || option_len == 0 ||
+ count != 1)
+ /* RFC 3315, 17.1.4 */
+ return TRUE;
+ }
+
+ switch_listening_mode(dhcp_client, L_NONE);
+
+ if (dhcp_client->status_code == 0)
+ get_dhcpv6_request(dhcp_client, packet6, pkt_len,
+ &dhcp_client->status_code);
+
+ if (packet6->message == DHCPV6_ADVERTISE) {
+ if (dhcp_client->advertise_cb)
+ dhcp_client->advertise_cb(dhcp_client,
+ dhcp_client->advertise_data);
+ return TRUE;
+ }
+
+ if (dhcp_client->solicitation_cb) {
+ /*
+ * The dhcp_client might not be valid after the
+ * callback call so just return immediately.
+ */
+ dhcp_client->solicitation_cb(dhcp_client,
+ dhcp_client->solicitation_data);
+ return TRUE;
+ }
+ break;
+ case REBIND:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return TRUE;
+
+ server_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_SERVERID, &option_len, &count);
+ if (!dhcp_client->server_duid && server_id &&
+ count == 1) {
+ /*
+ * If we do not have server duid yet, then get it now.
+ * Prefix delegation renew support needs it.
+ */
+ dhcp_client->server_duid = g_try_malloc(option_len);
+ if (!dhcp_client->server_duid)
+ return TRUE;
+ memcpy(dhcp_client->server_duid, server_id, option_len);
+ dhcp_client->server_duid_len = option_len;
+ }
+ /* fall through */
+ case INFORMATION_REQ:
+ case REQUEST:
+ case RENEW:
+ case RELEASE:
+ case CONFIRM:
+ case DECLINE:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return TRUE;
+
+ if (packet6->message != DHCPV6_REPLY)
+ return TRUE;
+
+ count = 0;
+ option_len = 0;
+ server_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_SERVERID, &option_len, &count);
+ if (!server_id || count != 1 || option_len == 0 ||
+ (dhcp_client->server_duid_len > 0 &&
+ memcmp(dhcp_client->server_duid, server_id,
+ dhcp_client->server_duid_len) != 0)) {
+ /* RFC 3315, 15.10 */
+ debug(dhcp_client,
+ "server duid error, discarding msg %p/%d/%d",
+ server_id, option_len, count);
+ return TRUE;
+ }
+
+ switch_listening_mode(dhcp_client, L_NONE);
+
+ get_dhcpv6_request(dhcp_client, packet6, pkt_len,
+ &dhcp_client->status_code);
+
+ if (dhcp_client->information_req_cb) {
+ /*
+ * The dhcp_client might not be valid after the
+ * callback call so just return immediately.
+ */
+ dhcp_client->information_req_cb(dhcp_client,
+ dhcp_client->information_req_data);
+ return TRUE;
+ }
+ if (dhcp_client->request_cb) {
+ dhcp_client->request_cb(dhcp_client,
+ dhcp_client->request_data);
+ return TRUE;
+ }
+ if (dhcp_client->renew_cb) {
+ dhcp_client->renew_cb(dhcp_client,
+ dhcp_client->renew_data);
+ return TRUE;
+ }
+ if (dhcp_client->rebind_cb) {
+ dhcp_client->rebind_cb(dhcp_client,
+ dhcp_client->rebind_data);
+ return TRUE;
+ }
+ if (dhcp_client->release_cb) {
+ dhcp_client->release_cb(dhcp_client,
+ dhcp_client->release_data);
+ return TRUE;
+ }
+ if (dhcp_client->decline_cb) {
+ dhcp_client->decline_cb(dhcp_client,
+ dhcp_client->decline_data);
+ return TRUE;
+ }
+ if (dhcp_client->confirm_cb) {
+ count = 0;
+ server_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_SERVERID, &option_len,
+ &count);
+ if (!server_id || count != 1 ||
+ option_len == 0) {
+ /* RFC 3315, 15.10 */
+ debug(dhcp_client,
+ "confirm server duid error, "
+ "discarding msg %p/%d/%d",
+ server_id, option_len, count);
+ return TRUE;
+ }
+ dhcp_client->server_duid = g_try_malloc(option_len);
+ if (!dhcp_client->server_duid)
+ return TRUE;
+ memcpy(dhcp_client->server_duid, server_id, option_len);
+ dhcp_client->server_duid_len = option_len;
+
+ dhcp_client->confirm_cb(dhcp_client,
+ dhcp_client->confirm_data);
+ return TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ debug(dhcp_client, "processed DHCP packet (new state %d)",
+ dhcp_client->state);
+
+ return TRUE;
+}
+
+static gboolean discover_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ dhcp_client->retry_times++;
+
+ /*
+ * We do not send the REQUESTED IP option if we are retrying because
+ * if the server is non-authoritative it will ignore the request if the
+ * option is present.
+ */
+ g_dhcp_client_start(dhcp_client, NULL);
+
+ return FALSE;
+}
+
+static gboolean ipv4ll_defend_timeout(gpointer dhcp_data)
+{
+ GDHCPClient *dhcp_client = dhcp_data;
+
+ debug(dhcp_client, "back to MONITOR mode");
+
+ dhcp_client->conflicts = 0;
+ dhcp_client->state = IPV4LL_MONITOR;
+
+ return FALSE;
+}
+
+static gboolean ipv4ll_announce_timeout(gpointer dhcp_data)
+{
+ GDHCPClient *dhcp_client = dhcp_data;
+ uint32_t ip;
+
+ debug(dhcp_client, "request timeout (retries %d)",
+ dhcp_client->retry_times);
+
+ if (dhcp_client->retry_times != ANNOUNCE_NUM) {
+ dhcp_client->retry_times++;
+ send_announce_packet(dhcp_client);
+ return FALSE;
+ }
+
+ ip = htonl(dhcp_client->requested_ip);
+ debug(dhcp_client, "switching to monitor mode");
+ dhcp_client->state = IPV4LL_MONITOR;
+ dhcp_client->assigned_ip = get_ip(ip);
+
+ if (dhcp_client->ipv4ll_available_cb)
+ dhcp_client->ipv4ll_available_cb(dhcp_client,
+ dhcp_client->ipv4ll_available_data);
+ dhcp_client->conflicts = 0;
+
+ return FALSE;
+}
+
+static gboolean ipv4ll_probe_timeout(gpointer dhcp_data)
+{
+
+ GDHCPClient *dhcp_client = dhcp_data;
+
+ debug(dhcp_client, "IPV4LL probe timeout (retries %d)",
+ dhcp_client->retry_times);
+
+ if (dhcp_client->retry_times == PROBE_NUM) {
+ dhcp_client->state = IPV4LL_ANNOUNCE;
+ dhcp_client->retry_times = 0;
+
+ dhcp_client->retry_times++;
+ send_announce_packet(dhcp_client);
+ return FALSE;
+ }
+ dhcp_client->retry_times++;
+ send_probe_packet(dhcp_client);
+
+ return FALSE;
+}
+
+int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address)
+{
+ int re;
+ uint32_t addr;
+
+ if (dhcp_client->type == G_DHCP_IPV6) {
+ if (dhcp_client->information_req_cb) {
+ dhcp_client->state = INFORMATION_REQ;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_information_req(dhcp_client);
+
+ } else if (dhcp_client->solicitation_cb) {
+ dhcp_client->state = SOLICITATION;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_solicitation(dhcp_client);
+
+ } else if (dhcp_client->request_cb) {
+ dhcp_client->state = REQUEST;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_request(dhcp_client);
+
+ } else if (dhcp_client->confirm_cb) {
+ dhcp_client->state = CONFIRM;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_confirm(dhcp_client);
+
+ } else if (dhcp_client->renew_cb) {
+ dhcp_client->state = RENEW;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_renew(dhcp_client);
+
+ } else if (dhcp_client->rebind_cb) {
+ dhcp_client->state = REBIND;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_rebind(dhcp_client);
+
+ } else if (dhcp_client->release_cb) {
+ dhcp_client->state = RENEW;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_release(dhcp_client);
+ } else if (dhcp_client->decline_cb) {
+ dhcp_client->state = DECLINE;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_decline(dhcp_client);
+ }
+
+ return 0;
+ }
+
+ if (dhcp_client->type == G_DHCP_IPV4LL) {
+ dhcp_client->state = INIT_SELECTING;
+ ipv4ll_start(dhcp_client);
+ return 0;
+ }
+
+ if (dhcp_client->retry_times == DISCOVER_RETRIES) {
+ if (dhcp_client->no_lease_cb)
+ dhcp_client->no_lease_cb(dhcp_client,
+ dhcp_client->no_lease_data);
+ dhcp_client->retry_times = 0;
+ return 0;
+ }
+
+ if (dhcp_client->retry_times == 0) {
+ g_free(dhcp_client->assigned_ip);
+ dhcp_client->assigned_ip = NULL;
+
+ dhcp_client->state = INIT_SELECTING;
+ re = switch_listening_mode(dhcp_client, L2);
+ if (re != 0)
+ return re;
+
+ dhcp_client->xid = rand();
+ dhcp_client->start = time(NULL);
+ }
+
+ if (!last_address) {
+ addr = 0;
+ } else {
+ addr = ntohl(inet_addr(last_address));
+ if (addr == 0xFFFFFFFF) {
+ addr = 0;
+ } else {
+ g_free(dhcp_client->last_address);
+ dhcp_client->last_address = g_strdup(last_address);
+ }
+ }
+ send_discover(dhcp_client, addr);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ DISCOVER_TIMEOUT,
+ discover_timeout,
+ dhcp_client,
+ NULL);
+ return 0;
+}
+
+void g_dhcp_client_stop(GDHCPClient *dhcp_client)
+{
+ switch_listening_mode(dhcp_client, L_NONE);
+
+ if (dhcp_client->state == BOUND ||
+ dhcp_client->state == RENEWING ||
+ dhcp_client->state == REBINDING)
+ send_release(dhcp_client, dhcp_client->server_ip,
+ dhcp_client->requested_ip);
+
+ if (dhcp_client->timeout > 0) {
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ }
+
+ if (dhcp_client->listener_watch > 0) {
+ g_source_remove(dhcp_client->listener_watch);
+ dhcp_client->listener_watch = 0;
+ }
+
+ dhcp_client->listener_channel = NULL;
+
+ dhcp_client->retry_times = 0;
+ dhcp_client->ack_retry_times = 0;
+
+ dhcp_client->requested_ip = 0;
+ dhcp_client->state = RELEASED;
+ dhcp_client->lease_seconds = 0;
+}
+
+GList *g_dhcp_client_get_option(GDHCPClient *dhcp_client,
+ unsigned char option_code)
+{
+ return g_hash_table_lookup(dhcp_client->code_value_hash,
+ GINT_TO_POINTER((int) option_code));
+}
+
+void g_dhcp_client_register_event(GDHCPClient *dhcp_client,
+ GDHCPClientEvent event,
+ GDHCPClientEventFunc func,
+ gpointer data)
+{
+ switch (event) {
+ case G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE:
+ dhcp_client->lease_available_cb = func;
+ dhcp_client->lease_available_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE:
+ if (dhcp_client->type == G_DHCP_IPV6)
+ return;
+ dhcp_client->ipv4ll_available_cb = func;
+ dhcp_client->ipv4ll_available_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_NO_LEASE:
+ dhcp_client->no_lease_cb = func;
+ dhcp_client->no_lease_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_LEASE_LOST:
+ dhcp_client->lease_lost_cb = func;
+ dhcp_client->lease_lost_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_IPV4LL_LOST:
+ if (dhcp_client->type == G_DHCP_IPV6)
+ return;
+ dhcp_client->ipv4ll_lost_cb = func;
+ dhcp_client->ipv4ll_lost_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT:
+ dhcp_client->address_conflict_cb = func;
+ dhcp_client->address_conflict_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_INFORMATION_REQ:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->information_req_cb = func;
+ dhcp_client->information_req_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_SOLICITATION:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->solicitation_cb = func;
+ dhcp_client->solicitation_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_ADVERTISE:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->advertise_cb = func;
+ dhcp_client->advertise_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_REQUEST:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->request_cb = func;
+ dhcp_client->request_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_RENEW:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->renew_cb = func;
+ dhcp_client->renew_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_REBIND:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->rebind_cb = func;
+ dhcp_client->rebind_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_RELEASE:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->release_cb = func;
+ dhcp_client->release_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_CONFIRM:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->confirm_cb = func;
+ dhcp_client->confirm_data = data;
+ return;
+ case G_DHCP_CLIENT_EVENT_DECLINE:
+ if (dhcp_client->type != G_DHCP_IPV6)
+ return;
+ dhcp_client->decline_cb = func;
+ dhcp_client->decline_data = data;
+ return;
+ }
+}
+
+int g_dhcp_client_get_index(GDHCPClient *dhcp_client)
+{
+ return dhcp_client->ifindex;
+}
+
+char *g_dhcp_client_get_address(GDHCPClient *dhcp_client)
+{
+ return g_strdup(dhcp_client->assigned_ip);
+}
+
+char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client)
+{
+ GList *option = NULL;
+
+ if (dhcp_client->type == G_DHCP_IPV6)
+ return NULL;
+
+ switch (dhcp_client->state) {
+ case IPV4LL_DEFEND:
+ case IPV4LL_MONITOR:
+ return g_strdup("255.255.0.0");
+ case BOUND:
+ case RENEWING:
+ case REBINDING:
+ option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET);
+ if (option)
+ return g_strdup(option->data);
+ case INIT_SELECTING:
+ case REQUESTING:
+ case RELEASED:
+ case IPV4LL_PROBE:
+ case IPV4LL_ANNOUNCE:
+ case INFORMATION_REQ:
+ case SOLICITATION:
+ case REQUEST:
+ case CONFIRM:
+ case RENEW:
+ case REBIND:
+ case RELEASE:
+ case DECLINE:
+ break;
+ }
+ return NULL;
+}
+
+GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client,
+ unsigned int option_code)
+{
+ if (!g_list_find(dhcp_client->request_list,
+ GINT_TO_POINTER((int)option_code)))
+ dhcp_client->request_list = g_list_prepend(
+ dhcp_client->request_list,
+ (GINT_TO_POINTER((int) option_code)));
+
+ return G_DHCP_CLIENT_ERROR_NONE;
+}
+
+void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client)
+{
+ g_list_free(dhcp_client->request_list);
+ dhcp_client->request_list = NULL;
+}
+
+void g_dhcp_client_clear_values(GDHCPClient *dhcp_client)
+{
+ g_hash_table_remove_all(dhcp_client->send_value_hash);
+}
+
+static uint8_t *alloc_dhcp_option(int code, const uint8_t *data, unsigned size)
+{
+ uint8_t *storage;
+
+ storage = g_try_malloc(size + OPT_DATA);
+ if (!storage)
+ return NULL;
+
+ storage[OPT_CODE] = code;
+ storage[OPT_LEN] = size;
+ memcpy(&storage[OPT_DATA], data, size);
+
+ return storage;
+}
+
+static uint8_t *alloc_dhcp_data_option(int code, const uint8_t *data,
+ unsigned size)
+{
+ return alloc_dhcp_option(code, data, MIN(size, 255));
+}
+
+static uint8_t *alloc_dhcp_string_option(int code, const char *str)
+{
+ return alloc_dhcp_data_option(code, (const uint8_t *)str, strlen(str));
+}
+
+GDHCPClientError g_dhcp_client_set_id(GDHCPClient *dhcp_client)
+{
+ const unsigned maclen = 6;
+ const unsigned idlen = maclen + 1;
+ const uint8_t option_code = G_DHCP_CLIENT_ID;
+ uint8_t idbuf[idlen];
+ uint8_t *data_option;
+
+ idbuf[0] = ARPHRD_ETHER;
+
+ memcpy(&idbuf[1], dhcp_client->mac_address, maclen);
+
+ data_option = alloc_dhcp_data_option(option_code, idbuf, idlen);
+ if (!data_option)
+ return G_DHCP_CLIENT_ERROR_NOMEM;
+
+ g_hash_table_insert(dhcp_client->send_value_hash,
+ GINT_TO_POINTER((int) option_code), data_option);
+
+ return G_DHCP_CLIENT_ERROR_NONE;
+}
+
+/* Now only support send hostname */
+GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client,
+ unsigned char option_code, const char *option_value)
+{
+ uint8_t *binary_option;
+
+ if (option_code == G_DHCP_HOST_NAME && option_value) {
+ binary_option = alloc_dhcp_string_option(option_code,
+ option_value);
+ if (!binary_option)
+ return G_DHCP_CLIENT_ERROR_NOMEM;
+
+ g_hash_table_insert(dhcp_client->send_value_hash,
+ GINT_TO_POINTER((int) option_code), binary_option);
+ }
+
+ return G_DHCP_CLIENT_ERROR_NONE;
+}
+
+static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option,
+ uint16_t len)
+{
+ uint8_t *storage;
+
+ storage = g_malloc(2 + 2 + len);
+ if (!storage)
+ return NULL;
+
+ storage[0] = code >> 8;
+ storage[1] = code & 0xff;
+ storage[2] = len >> 8;
+ storage[3] = len & 0xff;
+ memcpy(storage + 2 + 2, option, len);
+
+ return storage;
+}
+
+gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code)
+{
+ return g_hash_table_remove(dhcp_client->send_value_hash,
+ GINT_TO_POINTER((int)code));
+}
+
+void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client,
+ uint16_t option_code,
+ uint8_t *option_value,
+ uint16_t option_len)
+{
+ if (option_value) {
+ uint8_t *binary_option;
+
+ debug(dhcp_client, "setting option %d to %p len %d",
+ option_code, option_value, option_len);
+
+ binary_option = alloc_dhcpv6_option(option_code, option_value,
+ option_len);
+ if (binary_option)
+ g_hash_table_insert(dhcp_client->send_value_hash,
+ GINT_TO_POINTER((int) option_code),
+ binary_option);
+ }
+}
+
+void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return;
+
+ dhcp_client->last_request = time(NULL);
+}
+
+uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6)
+ return 0;
+
+ return dhcp_client->status_code;
+}
+
+GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client)
+ return NULL;
+
+ __sync_fetch_and_add(&dhcp_client->ref_count, 1);
+
+ return dhcp_client;
+}
+
+void g_dhcp_client_unref(GDHCPClient *dhcp_client)
+{
+ if (!dhcp_client)
+ return;
+
+ if (__sync_fetch_and_sub(&dhcp_client->ref_count, 1) != 1)
+ return;
+
+ g_dhcp_client_stop(dhcp_client);
+
+ g_free(dhcp_client->interface);
+ g_free(dhcp_client->assigned_ip);
+ g_free(dhcp_client->last_address);
+ g_free(dhcp_client->duid);
+ g_free(dhcp_client->server_duid);
+
+ g_list_free(dhcp_client->request_list);
+ g_list_free(dhcp_client->require_list);
+
+ g_hash_table_destroy(dhcp_client->code_value_hash);
+ g_hash_table_destroy(dhcp_client->send_value_hash);
+
+ g_free(dhcp_client);
+}
+
+void g_dhcp_client_set_debug(GDHCPClient *dhcp_client,
+ GDHCPDebugFunc func, gpointer user_data)
+{
+ if (!dhcp_client)
+ return;
+
+ dhcp_client->debug_func = func;
+ dhcp_client->debug_data = user_data;
+}
+
+static GDHCPIAPrefix *copy_prefix(gpointer data)
+{
+ GDHCPIAPrefix *copy, *prefix = data;
+
+ copy = g_try_new(GDHCPIAPrefix, 1);
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, prefix, sizeof(GDHCPIAPrefix));
+
+ return copy;
+}
+
+GSList *g_dhcpv6_copy_prefixes(GSList *prefixes)
+{
+ GSList *copy = NULL;
+ GSList *list;
+
+ for (list = prefixes; list; list = list->next)
+ copy = g_slist_prepend(copy, copy_prefix(list->data));
+
+ return copy;
+}
diff --git a/src/gdhcp/common.c b/src/gdhcp/common.c
new file mode 100644
index 0000000..91897b1
--- /dev/null
+++ b/src/gdhcp/common.c
@@ -0,0 +1,759 @@
+/*
+ * DHCP library with GLib integration
+ *
+ * Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <stdint.h>
+#include <string.h>
+#include <endian.h>
+#include <net/if_arp.h>
+#include <linux/if.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "gdhcp.h"
+#include "common.h"
+
+static const DHCPOption client_options[] = {
+ { OPTION_IP, 0x01 }, /* subnet-mask */
+ { OPTION_IP | OPTION_LIST, 0x03 }, /* routers */
+ { OPTION_IP | OPTION_LIST, 0x06 }, /* domain-name-servers */
+ { OPTION_STRING, 0x0c }, /* hostname */
+ { OPTION_STRING, 0x0f }, /* domain-name */
+ { OPTION_IP | OPTION_LIST, 0x2a }, /* ntp-servers */
+ { OPTION_U32, 0x33 }, /* dhcp-lease-time */
+ /* Options below will not be exposed to user */
+ { OPTION_IP, 0x32 }, /* requested-ip */
+ { OPTION_U8, 0x35 }, /* message-type */
+ { OPTION_U32, 0x36 }, /* server-id */
+ { OPTION_U16, 0x39 }, /* max-size */
+ { OPTION_STRING, 0x3c }, /* vendor */
+ { OPTION_STRING, 0x3d }, /* client-id */
+ { OPTION_STRING, 0xfc }, /* UNOFFICIAL proxy-pac */
+ { OPTION_UNKNOWN, 0x00 },
+};
+
+GDHCPOptionType dhcp_get_code_type(uint8_t code)
+{
+ int i;
+
+ for (i = 0; client_options[i].code; i++) {
+ if (client_options[i].code == code)
+ return client_options[i].type;
+ }
+
+ return OPTION_UNKNOWN;
+}
+
+uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code)
+{
+ int len, rem;
+ uint8_t *optionptr;
+ uint8_t overload = 0;
+
+ /* option bytes: [code][len][data1][data2]..[dataLEN] */
+ optionptr = packet->options;
+ rem = sizeof(packet->options);
+
+ while (1) {
+ if (rem <= 0)
+ /* 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_FIELD) {
+ overload &= ~FILE_FIELD;
+
+ optionptr = packet->file;
+ rem = sizeof(packet->file);
+
+ continue;
+ } else if (overload & SNAME_FIELD) {
+ overload &= ~SNAME_FIELD;
+
+ 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)
+ return optionptr + OPT_DATA;
+
+ if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD)
+ overload |= optionptr[OPT_DATA];
+
+ optionptr += len;
+ }
+
+ return NULL;
+}
+
+int dhcp_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;
+}
+
+uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
+ int code, uint16_t *option_len, int *option_count)
+{
+ int rem, count = 0;
+ uint8_t *optionptr, *found = NULL;
+ uint16_t opt_code, opt_len, len;
+
+ optionptr = packet->options;
+ rem = pkt_len - 1 - 3;
+
+ if (rem <= 0)
+ goto bad_packet;
+
+ while (1) {
+ opt_code = optionptr[0] << 8 | optionptr[1];
+ opt_len = len = optionptr[2] << 8 | optionptr[3];
+ len += 2 + 2; /* skip code and len */
+
+ if (len < 4)
+ goto bad_packet;
+
+ rem -= len;
+ if (rem < 0)
+ break;
+
+ if (opt_code == code) {
+ if (option_len)
+ *option_len = opt_len;
+ if (rem < 0)
+ goto bad_packet;
+ else
+ found = optionptr + 2 + 2;
+ count++;
+ }
+
+ if (rem == 0)
+ break;
+
+ optionptr += len;
+ }
+
+ if (option_count)
+ *option_count = count;
+
+ return found;
+
+bad_packet:
+ if (option_len)
+ *option_len = 0;
+ if (option_count)
+ *option_count = 0;
+ return NULL;
+}
+
+uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
+ uint16_t *option_code, uint16_t *option_len)
+{
+ int rem;
+ uint16_t code, len;
+
+ rem = max_len - 2 - 2;
+
+ if (rem <= 0)
+ /* Bad option */
+ return NULL;
+
+ code = option[0] << 8 | option[1];
+ len = option[2] << 8 | option[3];
+
+ rem -= len;
+ if (rem < 0)
+ return NULL;
+
+ *option_code = code;
+ *option_len = len;
+
+ return &option[4];
+}
+
+/*
+ * Add an option (supplied in binary form) to the options.
+ * Option format: [code][len][data1][data2]..[dataLEN]
+ */
+void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
+{
+ unsigned len;
+ uint8_t *optionptr = packet->options;
+ unsigned end = dhcp_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)
+ /* option did not fit into the packet */
+ return;
+
+ memcpy(optionptr + end, addopt, len);
+
+ optionptr[end + len] = DHCP_END;
+}
+
+/*
+ * Add an option (supplied in binary form) to the options.
+ * Option format: [code][len][data1][data2]..[dataLEN]
+ */
+void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
+ uint16_t *pkt_len, uint8_t *addopt)
+{
+ unsigned len;
+ uint8_t *optionptr = packet->options;
+
+ len = 2 + 2 + (addopt[2] << 8 | addopt[3]);
+
+ /* end position + (option code/length + addopt length) */
+ if (*pkt_len + len >= max_len)
+ /* option did not fit into the packet */
+ return;
+
+ memcpy(optionptr + *pkt_len, addopt, len);
+ *pkt_len += len;
+}
+
+static GDHCPOptionType check_option(uint8_t code, uint8_t data_len)
+{
+ GDHCPOptionType type = dhcp_get_code_type(code);
+ uint8_t len;
+
+ if (type == OPTION_UNKNOWN)
+ return type;
+
+ len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
+ if (len != data_len) {
+ printf("Invalid option len %d (expecting %d) for code 0x%x\n",
+ data_len, len, code);
+ return OPTION_UNKNOWN;
+ }
+
+ return type;
+}
+
+void dhcp_add_option_uint32(struct dhcp_packet *packet, uint8_t code,
+ uint32_t data)
+{
+ uint8_t option[6];
+
+ if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
+ return;
+
+ option[OPT_CODE] = code;
+ option[OPT_LEN] = sizeof(data);
+ put_be32(data, option + OPT_DATA);
+
+ dhcp_add_binary_option(packet, option);
+
+ return;
+}
+
+void dhcp_add_option_uint16(struct dhcp_packet *packet, uint8_t code,
+ uint16_t data)
+{
+ uint8_t option[6];
+
+ if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
+ return;
+
+ option[OPT_CODE] = code;
+ option[OPT_LEN] = sizeof(data);
+ put_be16(data, option + OPT_DATA);
+
+ dhcp_add_binary_option(packet, option);
+
+ return;
+}
+
+void dhcp_add_option_uint8(struct dhcp_packet *packet, uint8_t code,
+ uint8_t data)
+{
+ uint8_t option[6];
+
+ if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
+ return;
+
+ option[OPT_CODE] = code;
+ option[OPT_LEN] = sizeof(data);
+ option[OPT_DATA] = data;
+
+ dhcp_add_binary_option(packet, option);
+
+ return;
+}
+
+void dhcp_init_header(struct dhcp_packet *packet, char type)
+{
+ memset(packet, 0, sizeof(*packet));
+
+ packet->op = BOOTREQUEST;
+
+ switch (type) {
+ case DHCPOFFER:
+ case DHCPACK:
+ case DHCPNAK:
+ packet->op = BOOTREPLY;
+ }
+
+ packet->htype = 1;
+ packet->hlen = 6;
+ packet->cookie = htonl(DHCP_MAGIC);
+ packet->options[0] = DHCP_END;
+
+ dhcp_add_option_uint8(packet, DHCP_MESSAGE_TYPE, type);
+}
+
+void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type)
+{
+ int id;
+
+ memset(packet, 0, sizeof(*packet));
+
+ packet->message = type;
+
+ id = random();
+
+ packet->transaction_id[0] = (id >> 16) & 0xff;
+ packet->transaction_id[1] = (id >> 8) & 0xff;
+ packet->transaction_id[2] = id & 0xff;
+}
+
+static bool check_vendor(uint8_t *option_vendor, const char *vendor)
+{
+ uint8_t vendor_length = sizeof(vendor) - 1;
+
+ if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length)
+ return false;
+
+ if (memcmp(option_vendor, vendor, vendor_length) != 0)
+ return false;
+
+ return true;
+}
+
+static void check_broken_vendor(struct dhcp_packet *packet)
+{
+ uint8_t *vendor;
+
+ if (packet->op != BOOTREQUEST)
+ return;
+
+ vendor = dhcp_get_option(packet, DHCP_VENDOR);
+ if (!vendor)
+ return;
+
+ if (check_vendor(vendor, "MSFT 98"))
+ packet->flags |= htons(BROADCAST_FLAG);
+}
+
+int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
+{
+ int n;
+
+ memset(packet, 0, sizeof(*packet));
+
+ n = read(fd, packet, sizeof(*packet));
+ if (n < 0)
+ return -errno;
+
+ if (packet->cookie != htonl(DHCP_MAGIC))
+ return -EPROTO;
+
+ check_broken_vendor(packet);
+
+ return n;
+}
+
+int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf,
+ int buf_len, int fd)
+{
+ int n;
+
+ n = read(fd, buf, buf_len);
+ if (n < 0)
+ return -errno;
+
+ *packet = (struct dhcpv6_packet *)buf;
+
+ return n;
+}
+
+/* TODO: Use glib checksum */
+uint16_t dhcp_checksum(void *addr, int count)
+{
+ /*
+ * Compute Internet Checksum for "count" bytes
+ * beginning at location "addr".
+ */
+ int32_t sum = 0;
+ uint16_t *source = (uint16_t *) addr;
+
+ while (count > 1) {
+ /* This is the inner loop */
+ sum += *source++;
+ count -= 2;
+ }
+
+ /* Add left-over byte, if any */
+ if (count > 0) {
+ /* Make sure that the left-over byte is added correctly both
+ * with little and big endian hosts */
+ uint16_t tmp = 0;
+ *(uint8_t *) &tmp = *(uint8_t *) source;
+ sum += tmp;
+ }
+ /* Fold 32-bit sum to 16 bits */
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+#define IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT \
+ { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0x1,0,0x2 } } } /* ff02::1:2 */
+static const struct in6_addr in6addr_all_dhcp_relay_agents_and_servers_mc =
+ IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT;
+
+int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len)
+{
+ struct msghdr m;
+ struct iovec v;
+ struct in6_pktinfo *pktinfo;
+ struct cmsghdr *cmsg;
+ int fd, ret;
+ struct sockaddr_in6 dst;
+ void *control_buf;
+ size_t control_buf_len;
+
+ fd = socket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+ if (fd < 0)
+ return -errno;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+ dst.sin6_port = htons(DHCPV6_SERVER_PORT);
+
+ dst.sin6_addr = in6addr_all_dhcp_relay_agents_and_servers_mc;
+
+ control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ control_buf = g_try_malloc0(control_buf_len);
+ if (!control_buf) {
+ close(fd);
+ return -ENOMEM;
+ }
+
+ memset(&m, 0, sizeof(m));
+ memset(&v, 0, sizeof(v));
+
+ m.msg_name = &dst;
+ m.msg_namelen = sizeof(dst);
+
+ v.iov_base = (char *)dhcp_pkt;
+ v.iov_len = len;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ m.msg_control = control_buf;
+ m.msg_controllen = control_buf_len;
+ cmsg = CMSG_FIRSTHDR(&m);
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+
+ pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(*pktinfo));
+ pktinfo->ipi6_ifindex = index;
+ m.msg_controllen = cmsg->cmsg_len;
+
+ ret = sendmsg(fd, &m, 0);
+ if (ret < 0) {
+ char *msg = "DHCPv6 msg send failed";
+
+ if (errno == EADDRNOTAVAIL) {
+ char *str = g_strdup_printf("%s (index %d)",
+ msg, index);
+ perror(str);
+ g_free(str);
+ } else
+ perror(msg);
+ }
+
+ g_free(control_buf);
+ close(fd);
+
+ return ret;
+}
+
+int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_ip, int source_port, uint32_t dest_ip,
+ int dest_port, const uint8_t *dest_arp, int ifindex)
+{
+ struct sockaddr_ll dest;
+ struct ip_udp_dhcp_packet packet;
+ int fd, n;
+
+ enum {
+ IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) -
+ EXTEND_FOR_BUGGY_SERVERS,
+ UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE -
+ offsetof(struct ip_udp_dhcp_packet, udp),
+ };
+
+ fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP));
+ if (fd < 0)
+ return -errno;
+
+ memset(&dest, 0, sizeof(dest));
+ memset(&packet, 0, sizeof(packet));
+ packet.data = *dhcp_pkt;
+
+ dest.sll_family = AF_PACKET;
+ dest.sll_protocol = htons(ETH_P_IP);
+ dest.sll_ifindex = ifindex;
+ dest.sll_halen = 6;
+ memcpy(dest.sll_addr, dest_arp, 6);
+ if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ packet.ip.protocol = IPPROTO_UDP;
+ packet.ip.saddr = source_ip;
+ packet.ip.daddr = dest_ip;
+ packet.udp.source = htons(source_port);
+ packet.udp.dest = htons(dest_port);
+ /* size, excluding IP header: */
+ packet.udp.len = htons(UPD_DHCP_SIZE);
+ /* for UDP checksumming, ip.len is set to UDP packet len */
+ packet.ip.tot_len = packet.udp.len;
+ packet.udp.check = dhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
+ /* but for sending, it is set to IP packet len */
+ packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
+ packet.ip.ihl = sizeof(packet.ip) >> 2;
+ packet.ip.version = IPVERSION;
+ packet.ip.ttl = IPDEFTTL;
+ packet.ip.check = dhcp_checksum(&packet.ip, sizeof(packet.ip));
+
+ /*
+ * Currently we send full-sized DHCP packets (zero padded).
+ * If you need to change this: last byte of the packet is
+ * packet.data.options[dhcp_end_option(packet.data.options)]
+ */
+ n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
+ (struct sockaddr *) &dest, sizeof(dest));
+ close(fd);
+
+ if (n < 0)
+ return -errno;
+
+ return n;
+}
+
+int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port)
+{
+ struct sockaddr_in client;
+ int fd, n, opt = 1;
+
+ enum {
+ DHCP_SIZE = sizeof(struct dhcp_packet) -
+ EXTEND_FOR_BUGGY_SERVERS,
+ };
+
+ fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+ if (fd < 0)
+ return -errno;
+
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ memset(&client, 0, sizeof(client));
+ client.sin_family = AF_INET;
+ client.sin_port = htons(source_port);
+ client.sin_addr.s_addr = htonl(source_ip);
+ if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ memset(&client, 0, sizeof(client));
+ client.sin_family = AF_INET;
+ client.sin_port = htons(dest_port);
+ client.sin_addr.s_addr = htonl(dest_ip);
+ if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ n = write(fd, dhcp_pkt, DHCP_SIZE);
+
+ close(fd);
+
+ if (n < 0)
+ return -errno;
+
+ return n;
+}
+
+int dhcp_l3_socket(int port, const char *interface, int family)
+{
+ int fd, opt = 1, len;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ struct sockaddr *addr;
+
+ fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+ if (fd < 0)
+ return -errno;
+
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
+ interface, strlen(interface) + 1) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (family == AF_INET) {
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = family;
+ addr4.sin_port = htons(port);
+ addr = (struct sockaddr *)&addr4;
+ len = sizeof(addr4);
+ } else if (family == AF_INET6) {
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = family;
+ addr6.sin6_port = htons(port);
+ addr = (struct sockaddr *)&addr6;
+ len = sizeof(addr6);
+ } else {
+ close(fd);
+ return -EINVAL;
+ }
+
+ if (bind(fd, addr, len) != 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+char *get_interface_name(int index)
+{
+ struct ifreq ifr;
+ int sk, err;
+
+ if (index < 0)
+ return NULL;
+
+ sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return NULL;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = index;
+
+ err = ioctl(sk, SIOCGIFNAME, &ifr);
+ if (err < 0) {
+ perror("Get interface name error");
+ close(sk);
+ return NULL;
+ }
+
+ close(sk);
+
+ return g_strdup(ifr.ifr_name);
+}
+
+bool interface_is_up(int index)
+{
+ int sk, err;
+ struct ifreq ifr;
+ bool ret = false;
+
+ sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return false;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = index;
+
+ err = ioctl(sk, SIOCGIFNAME, &ifr);
+ if (err < 0) {
+ perror("Get interface name error");
+ goto done;
+ }
+
+ err = ioctl(sk, SIOCGIFFLAGS, &ifr);
+ if (err < 0) {
+ perror("Get interface flags error");
+ goto done;
+ }
+
+ if (ifr.ifr_flags & IFF_UP)
+ ret = true;
+
+done:
+ close(sk);
+
+ return ret;
+}
diff --git a/src/gdhcp/common.h b/src/gdhcp/common.h
new file mode 100644
index 0000000..e4a4251
--- /dev/null
+++ b/src/gdhcp/common.h
@@ -0,0 +1,211 @@
+/*
+ *
+ * DHCP client library with GLib integration
+ *
+ * Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+#include <glib.h>
+
+#include "unaligned.h"
+#include "gdhcp.h"
+
+#define CLIENT_PORT 68
+#define SERVER_PORT 67
+
+#define DHCPV6_CLIENT_PORT 546
+#define DHCPV6_SERVER_PORT 547
+#define MAX_DHCPV6_PKT_SIZE 1500
+
+#define EXTEND_FOR_BUGGY_SERVERS 80
+
+static const uint8_t MAC_BCAST_ADDR[ETH_ALEN] __attribute__((aligned(2))) = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+static const uint8_t MAC_ANY_ADDR[ETH_ALEN] __attribute__((aligned(2))) = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* DHCP packet */
+#define DHCP_MAGIC 0x63825363
+#define DHCP_OPTIONS_BUFSIZE 308
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+
+#define BROADCAST_FLAG 0x8000
+
+/* See RFC 2131 */
+struct dhcp_packet {
+ uint8_t op;
+ uint8_t htype;
+ uint8_t hlen;
+ uint8_t hops;
+ uint32_t xid;
+ uint16_t secs;
+ uint16_t flags;
+ uint32_t ciaddr;
+ uint32_t yiaddr;
+ uint32_t siaddr_nip;
+ uint32_t gateway_nip;
+ uint8_t chaddr[16];
+ uint8_t sname[64];
+ uint8_t file[128];
+ uint32_t cookie;
+ uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS];
+} __attribute__((packed));
+
+struct ip_udp_dhcp_packet {
+ struct iphdr ip;
+ struct udphdr udp;
+ struct dhcp_packet data;
+} __attribute__((packed));
+
+/* See RFC 3315 */
+struct dhcpv6_packet {
+ uint8_t message;
+ uint8_t transaction_id[3];
+ uint8_t options[];
+} __attribute__((packed));
+
+
+/* See RFC 2132 */
+#define DHCP_PADDING 0x00
+#define DHCP_SUBNET 0x01
+#define DHCP_ROUTER 0x03
+#define DHCP_TIME_SERVER 0x04
+#define DHCP_NAME_SERVER 0x05
+#define DHCP_DNS_SERVER 0x06
+#define DHCP_HOST_NAME 0x0c
+#define DHCP_DOMAIN_NAME 0x0f
+#define DHCP_NTP_SERVER 0x2a
+#define DHCP_REQUESTED_IP 0x32
+#define DHCP_LEASE_TIME 0x33
+#define DHCP_OPTION_OVERLOAD 0x34
+#define DHCP_MESSAGE_TYPE 0x35
+#define DHCP_SERVER_ID 0x36
+#define DHCP_PARAM_REQ 0x37
+#define DHCP_ERR_MESSAGE 0x38
+#define DHCP_MAX_SIZE 0x39
+#define DHCP_VENDOR 0x3c
+#define DHCP_CLIENT_ID 0x3d
+#define DHCP_END 0xff
+
+#define OPT_CODE 0
+#define OPT_LEN 1
+#define OPT_DATA 2
+#define OPTION_FIELD 0
+#define FILE_FIELD 1
+#define SNAME_FIELD 2
+
+/* DHCP_MESSAGE_TYPE values */
+#define DHCPDISCOVER 1
+#define DHCPOFFER 2
+#define DHCPREQUEST 3
+#define DHCPDECLINE 4
+#define DHCPACK 5
+#define DHCPNAK 6
+#define DHCPRELEASE 7
+#define DHCPINFORM 8
+#define DHCP_MINTYPE DHCPDISCOVER
+#define DHCP_MAXTYPE DHCPINFORM
+
+/* Message types for DHCPv6, RFC 3315 sec 5.3 */
+#define DHCPV6_SOLICIT 1
+#define DHCPV6_ADVERTISE 2
+#define DHCPV6_REQUEST 3
+#define DHCPV6_CONFIRM 4
+#define DHCPV6_RENEW 5
+#define DHCPV6_REBIND 6
+#define DHCPV6_REPLY 7
+#define DHCPV6_RELEASE 8
+#define DHCPV6_DECLINE 9
+#define DHCPV6_RECONFIGURE 10
+#define DHCPV6_INFORMATION_REQ 11
+
+/*
+ * DUID time starts 2000-01-01.
+ */
+#define DUID_TIME_EPOCH 946684800
+
+typedef enum {
+ OPTION_UNKNOWN,
+ OPTION_IP,
+ OPTION_STRING,
+ OPTION_U8,
+ OPTION_U16,
+ OPTION_U32,
+ OPTION_TYPE_MASK = 0x0f,
+ OPTION_LIST = 0x10,
+} GDHCPOptionType;
+
+typedef struct dhcp_option {
+ GDHCPOptionType type;
+ uint8_t code;
+} DHCPOption;
+
+/* Length of the option types in binary form */
+static const uint8_t dhcp_option_lengths[] = {
+ [OPTION_IP] = 4,
+ [OPTION_STRING] = 1,
+ [OPTION_U8] = 1,
+ [OPTION_U16] = 2,
+ [OPTION_U32] = 4,
+};
+
+uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code);
+uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
+ int code, uint16_t *option_len, int *option_count);
+uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
+ uint16_t *code, uint16_t *option_len);
+int dhcp_end_option(uint8_t *optionptr);
+void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt);
+void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
+ uint16_t *pkt_len, uint8_t *addopt);
+void dhcp_add_option_uint8(struct dhcp_packet *packet,
+ uint8_t code, uint8_t data);
+void dhcp_add_option_uint16(struct dhcp_packet *packet,
+ uint8_t code, uint16_t data);
+void dhcp_add_option_uint32(struct dhcp_packet *packet,
+ uint8_t code, uint32_t data);
+GDHCPOptionType dhcp_get_code_type(uint8_t code);
+GDHCPOptionType dhcpv6_get_code_type(uint16_t code);
+
+uint16_t dhcp_checksum(void *addr, int count);
+
+void dhcp_init_header(struct dhcp_packet *packet, char type);
+void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type);
+
+int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port,
+ const uint8_t *dest_arp, int ifindex);
+int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len);
+int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port);
+int dhcp_l3_socket(int port, const char *interface, int family);
+int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd);
+int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf,
+ int buf_len, int fd);
+int dhcp_l3_socket_send(int index, int port, int family);
+
+char *get_interface_name(int index);
+bool interface_is_up(int index);
diff --git a/src/gdhcp/gdhcp.h b/src/gdhcp/gdhcp.h
new file mode 100644
index 0000000..0d79361
--- /dev/null
+++ b/src/gdhcp/gdhcp.h
@@ -0,0 +1,230 @@
+/*
+ *
+ * DHCP library with GLib integration
+ *
+ * Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __G_DHCP_H
+#define __G_DHCP_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* DHCP Client part*/
+struct _GDHCPClient;
+
+typedef struct _GDHCPClient GDHCPClient;
+
+typedef enum {
+ G_DHCP_CLIENT_ERROR_NONE,
+ G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE,
+ G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE,
+ G_DHCP_CLIENT_ERROR_INTERFACE_DOWN,
+ G_DHCP_CLIENT_ERROR_NOMEM,
+ G_DHCP_CLIENT_ERROR_INVALID_INDEX,
+ G_DHCP_CLIENT_ERROR_INVALID_OPTION
+} GDHCPClientError;
+
+typedef enum {
+ G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
+ G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE,
+ G_DHCP_CLIENT_EVENT_NO_LEASE,
+ G_DHCP_CLIENT_EVENT_LEASE_LOST,
+ G_DHCP_CLIENT_EVENT_IPV4LL_LOST,
+ G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT,
+ G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
+ G_DHCP_CLIENT_EVENT_SOLICITATION,
+ G_DHCP_CLIENT_EVENT_ADVERTISE,
+ G_DHCP_CLIENT_EVENT_REQUEST,
+ G_DHCP_CLIENT_EVENT_RENEW,
+ G_DHCP_CLIENT_EVENT_REBIND,
+ G_DHCP_CLIENT_EVENT_RELEASE,
+ G_DHCP_CLIENT_EVENT_CONFIRM,
+ G_DHCP_CLIENT_EVENT_DECLINE,
+} GDHCPClientEvent;
+
+typedef enum {
+ G_DHCP_IPV4,
+ G_DHCP_IPV6,
+ G_DHCP_IPV4LL,
+} GDHCPType;
+
+#define G_DHCP_SUBNET 0x01
+#define G_DHCP_ROUTER 0x03
+#define G_DHCP_TIME_SERVER 0x04
+#define G_DHCP_DNS_SERVER 0x06
+#define G_DHCP_DOMAIN_NAME 0x0f
+#define G_DHCP_HOST_NAME 0x0c
+#define G_DHCP_NTP_SERVER 0x2a
+#define G_DHCP_CLIENT_ID 0x3d
+
+#define G_DHCPV6_CLIENTID 1
+#define G_DHCPV6_SERVERID 2
+#define G_DHCPV6_IA_NA 3
+#define G_DHCPV6_IA_TA 4
+#define G_DHCPV6_IAADDR 5
+#define G_DHCPV6_ORO 6
+#define G_DHCPV6_PREFERENCE 7
+#define G_DHCPV6_ELAPSED_TIME 8
+#define G_DHCPV6_STATUS_CODE 13
+#define G_DHCPV6_RAPID_COMMIT 14
+#define G_DHCPV6_DNS_SERVERS 23
+#define G_DHCPV6_DOMAIN_LIST 24
+#define G_DHCPV6_IA_PD 25
+#define G_DHCPV6_IA_PREFIX 26
+#define G_DHCPV6_SNTP_SERVERS 31
+
+#define G_DHCPV6_ERROR_SUCCESS 0
+#define G_DHCPV6_ERROR_FAILURE 1
+#define G_DHCPV6_ERROR_NO_ADDR 2
+#define G_DHCPV6_ERROR_BINDING 3
+#define G_DHCPV6_ERROR_LINK 4
+#define G_DHCPV6_ERROR_MCAST 5
+#define G_DHCPV6_ERROR_NO_PREFIX 6
+
+typedef enum {
+ G_DHCPV6_DUID_LLT = 1,
+ G_DHCPV6_DUID_EN = 2,
+ G_DHCPV6_DUID_LL = 3,
+} GDHCPDuidType;
+
+typedef struct {
+ /*
+ * Note that no field in this struct can be allocated
+ * from heap or there will be a memory leak when the
+ * struct is freed by client.c:remove_option_value()
+ */
+ struct in6_addr prefix;
+ unsigned char prefixlen;
+ uint32_t preferred;
+ uint32_t valid;
+ time_t expire;
+} GDHCPIAPrefix;
+
+typedef void (*GDHCPClientEventFunc) (GDHCPClient *client, gpointer user_data);
+
+typedef void (*GDHCPDebugFunc)(const char *str, gpointer user_data);
+
+GDHCPClient *g_dhcp_client_new(GDHCPType type, int index,
+ GDHCPClientError *error);
+
+int g_dhcp_client_start(GDHCPClient *client, const char *last_address);
+void g_dhcp_client_stop(GDHCPClient *client);
+
+GDHCPClient *g_dhcp_client_ref(GDHCPClient *client);
+void g_dhcp_client_unref(GDHCPClient *client);
+
+void g_dhcp_client_register_event(GDHCPClient *client,
+ GDHCPClientEvent event,
+ GDHCPClientEventFunc func,
+ gpointer user_data);
+
+GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client,
+ unsigned int option_code);
+void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client);
+void g_dhcp_client_clear_values(GDHCPClient *dhcp_client);
+GDHCPClientError g_dhcp_client_set_id(GDHCPClient *client);
+GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client,
+ unsigned char option_code,
+ const char *option_value);
+
+char *g_dhcp_client_get_address(GDHCPClient *client);
+char *g_dhcp_client_get_netmask(GDHCPClient *client);
+GList *g_dhcp_client_get_option(GDHCPClient *client,
+ unsigned char option_code);
+int g_dhcp_client_get_index(GDHCPClient *client);
+
+void g_dhcp_client_set_debug(GDHCPClient *client,
+ GDHCPDebugFunc func, gpointer user_data);
+int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type,
+ unsigned char **duid, int *duid_len);
+int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid,
+ int duid_len);
+int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1, uint32_t *T2,
+ GSList *prefixes);
+GSList *g_dhcpv6_copy_prefixes(GSList *prefixes);
+gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code);
+void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code,
+ uint8_t *option_value, uint16_t option_len);
+uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client);
+int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...);
+void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index,
+ unsigned char *iaid);
+int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
+ uint32_t *T1, uint32_t *T2,
+ time_t *started, time_t *expire);
+uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client);
+void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid);
+int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
+ int code, uint32_t *T1, uint32_t *T2,
+ bool add_addresses, const char *address);
+int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index,
+ int code, uint32_t *T1, uint32_t *T2,
+ GSList *addresses);
+void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client);
+void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client);
+void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client);
+
+/* DHCP Server */
+typedef enum {
+ G_DHCP_SERVER_ERROR_NONE,
+ G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE,
+ G_DHCP_SERVER_ERROR_INTERFACE_IN_USE,
+ G_DHCP_SERVER_ERROR_INTERFACE_DOWN,
+ G_DHCP_SERVER_ERROR_NOMEM,
+ G_DHCP_SERVER_ERROR_INVALID_INDEX,
+ G_DHCP_SERVER_ERROR_INVALID_OPTION,
+ G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID
+} GDHCPServerError;
+
+typedef void (*GDHCPSaveLeaseFunc) (unsigned char *mac,
+ unsigned int nip, unsigned int expire);
+struct _GDHCPServer;
+
+typedef struct _GDHCPServer GDHCPServer;
+
+GDHCPServer *g_dhcp_server_new(GDHCPType type,
+ int ifindex, GDHCPServerError *error);
+int g_dhcp_server_start(GDHCPServer *server);
+void g_dhcp_server_stop(GDHCPServer *server);
+
+GDHCPServer *g_dhcp_server_ref(GDHCPServer *server);
+void g_dhcp_server_unref(GDHCPServer *server);
+
+int g_dhcp_server_set_option(GDHCPServer *server,
+ unsigned char option_code, const char *option_value);
+int g_dhcp_server_set_ip_range(GDHCPServer *server,
+ const char *start_ip, const char *end_ip);
+void g_dhcp_server_set_debug(GDHCPServer *server,
+ GDHCPDebugFunc func, gpointer user_data);
+void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
+ unsigned int lease_time);
+void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
+ GDHCPSaveLeaseFunc func, gpointer user_data);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __G_DHCP_H */
diff --git a/src/gdhcp/ipv4ll.c b/src/gdhcp/ipv4ll.c
new file mode 100644
index 0000000..033ef81
--- /dev/null
+++ b/src/gdhcp/ipv4ll.c
@@ -0,0 +1,139 @@
+/*
+ *
+ * IPV4 Local Link library with GLib integration
+ *
+ * Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <glib.h>
+#include "ipv4ll.h"
+
+/**
+ * Return a random link local IP (in host byte order)
+ */
+uint32_t ipv4ll_random_ip(int seed)
+{
+ unsigned tmp;
+
+ if (seed)
+ srand(seed);
+ else {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ srand(tv.tv_usec);
+ }
+ do {
+ tmp = rand();
+ tmp = tmp & IN_CLASSB_HOST;
+ } while (tmp > (IN_CLASSB_HOST - 0x0200));
+ return ((LINKLOCAL_ADDR + 0x0100) + tmp);
+}
+
+/**
+ * Return a random delay in range of zero to secs*1000
+ */
+guint ipv4ll_random_delay_ms(guint secs)
+{
+ struct timeval tv;
+ guint tmp;
+
+ gettimeofday(&tv, NULL);
+ srand(tv.tv_usec);
+ tmp = rand();
+ return tmp % (secs * 1000);
+}
+
+int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip,
+ uint32_t target_ip, int ifindex)
+{
+ struct sockaddr_ll dest;
+ struct ether_arp p;
+ uint32_t ip_source;
+ uint32_t ip_target;
+ int fd, n;
+
+ fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP));
+ if (fd < 0)
+ return -errno;
+
+ memset(&dest, 0, sizeof(dest));
+ memset(&p, 0, sizeof(p));
+
+ dest.sll_family = AF_PACKET;
+ dest.sll_protocol = htons(ETH_P_ARP);
+ dest.sll_ifindex = ifindex;
+ dest.sll_halen = ETH_ALEN;
+ memset(dest.sll_addr, 0xFF, ETH_ALEN);
+ if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ ip_source = htonl(source_ip);
+ ip_target = htonl(target_ip);
+ p.arp_hrd = htons(ARPHRD_ETHER);
+ p.arp_pro = htons(ETHERTYPE_IP);
+ p.arp_hln = ETH_ALEN;
+ p.arp_pln = 4;
+ p.arp_op = htons(ARPOP_REQUEST);
+
+ memcpy(&p.arp_sha, source_eth, ETH_ALEN);
+ memcpy(&p.arp_spa, &ip_source, sizeof(p.arp_spa));
+ memcpy(&p.arp_tpa, &ip_target, sizeof(p.arp_tpa));
+
+ n = sendto(fd, &p, sizeof(p), 0,
+ (struct sockaddr*) &dest, sizeof(dest));
+ if (n < 0)
+ n = -errno;
+
+ close(fd);
+
+ return n;
+}
+
+int ipv4ll_arp_socket(int ifindex)
+{
+ int fd;
+ struct sockaddr_ll sock;
+ fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP));
+ if (fd < 0)
+ return fd;
+
+ sock.sll_family = AF_PACKET;
+ sock.sll_protocol = htons(ETH_P_ARP);
+ sock.sll_ifindex = ifindex;
+
+ if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) {
+ close(fd);
+ return -errno;
+ }
+
+ return fd;
+}
diff --git a/src/gdhcp/ipv4ll.h b/src/gdhcp/ipv4ll.h
new file mode 100644
index 0000000..aaac33e
--- /dev/null
+++ b/src/gdhcp/ipv4ll.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * IPV4 Local Link library with GLib integration
+ *
+ * Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __G_IPV4LL_H
+#define __G_IPV4LL_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 169.254.0.0 */
+#define LINKLOCAL_ADDR 0xa9fe0000
+
+/* See RFC 3927 */
+#define PROBE_WAIT 1
+#define PROBE_NUM 3
+#define PROBE_MIN 1
+#define PROBE_MAX 2
+#define ANNOUNCE_WAIT 2
+#define ANNOUNCE_NUM 2
+#define ANNOUNCE_INTERVAL 2
+#define MAX_CONFLICTS 10
+#define RATE_LIMIT_INTERVAL 60
+#define DEFEND_INTERVAL 10
+
+uint32_t ipv4ll_random_ip(int seed);
+guint ipv4ll_random_delay_ms(guint secs);
+int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip,
+ uint32_t target_ip, int ifindex);
+int ipv4ll_arp_socket(int ifindex);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !IPV4LL_H_ */
diff --git a/src/gdhcp/server.c b/src/gdhcp/server.c
new file mode 100644
index 0000000..728992d
--- /dev/null
+++ b/src/gdhcp/server.c
@@ -0,0 +1,896 @@
+/*
+ *
+ * DHCP Server library with GLib integration
+ *
+ * Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+
+#include <linux/if.h>
+#include <linux/filter.h>
+
+#include <glib.h>
+
+#include "common.h"
+
+/* 8 hours */
+#define DEFAULT_DHCP_LEASE_SEC (8*60*60)
+
+/* 5 minutes */
+#define OFFER_TIME (5*60)
+
+struct _GDHCPServer {
+ int ref_count;
+ GDHCPType type;
+ bool started;
+ int ifindex;
+ char *interface;
+ uint32_t start_ip;
+ uint32_t end_ip;
+ uint32_t server_nip;
+ uint32_t lease_seconds;
+ int listener_sockfd;
+ guint listener_watch;
+ GIOChannel *listener_channel;
+ GList *lease_list;
+ GHashTable *nip_lease_hash;
+ GHashTable *option_hash; /* Options send to client */
+ GDHCPSaveLeaseFunc save_lease_func;
+ GDHCPDebugFunc debug_func;
+ gpointer debug_data;
+};
+
+struct dhcp_lease {
+ time_t expire;
+ uint32_t lease_nip;
+ uint8_t lease_mac[ETH_ALEN];
+};
+
+static inline void debug(GDHCPServer *server, const char *format, ...)
+{
+ char str[256];
+ va_list ap;
+
+ if (!server->debug_func)
+ return;
+
+ va_start(ap, format);
+
+ if (vsnprintf(str, sizeof(str), format, ap) > 0)
+ server->debug_func(str, server->debug_data);
+
+ va_end(ap);
+}
+
+static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server,
+ const uint8_t *mac)
+{
+ GList *list;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+
+ if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0)
+ return lease;
+ }
+
+ return NULL;
+}
+
+static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease)
+{
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list, lease);
+
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease->lease_nip));
+ g_free(lease);
+}
+
+/* Clear the old lease and create the new one */
+static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
+ const uint8_t *mac, struct dhcp_lease **lease)
+{
+ struct dhcp_lease *lease_nip, *lease_mac;
+
+ if (yiaddr == 0)
+ return -ENXIO;
+
+ if (ntohl(yiaddr) < dhcp_server->start_ip)
+ return -ENXIO;
+
+ if (ntohl(yiaddr) > dhcp_server->end_ip)
+ return -ENXIO;
+
+ if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0)
+ return -ENXIO;
+
+ if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0)
+ return -ENXIO;
+
+ lease_mac = find_lease_by_mac(dhcp_server, mac);
+
+ lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) ntohl(yiaddr)));
+ debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
+
+ if (lease_nip) {
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list,
+ lease_nip);
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) ntohl(yiaddr)));
+
+ if (!lease_mac)
+ *lease = lease_nip;
+ else if (lease_nip != lease_mac) {
+ remove_lease(dhcp_server, lease_mac);
+ *lease = lease_nip;
+ } else
+ *lease = lease_nip;
+
+ return 0;
+ }
+
+ if (lease_mac) {
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list,
+ lease_mac);
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease_mac->lease_nip));
+ *lease = lease_mac;
+
+ return 0;
+ }
+
+ *lease = g_try_new0(struct dhcp_lease, 1);
+ if (!*lease)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static gint compare_expire(gconstpointer a, gconstpointer b)
+{
+ const struct dhcp_lease *lease1 = a;
+ const struct dhcp_lease *lease2 = b;
+
+ return lease2->expire - lease1->expire;
+}
+
+static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
+ const uint8_t *chaddr, uint32_t yiaddr)
+{
+ struct dhcp_lease *lease = NULL;
+ int ret;
+
+ ret = get_lease(dhcp_server, yiaddr, chaddr, &lease);
+ if (ret != 0)
+ return NULL;
+
+ memset(lease, 0, sizeof(*lease));
+
+ memcpy(lease->lease_mac, chaddr, ETH_ALEN);
+ lease->lease_nip = ntohl(yiaddr);
+
+ if (expire == 0)
+ lease->expire = time(NULL) + dhcp_server->lease_seconds;
+ else
+ lease->expire = expire;
+
+ dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
+ lease, compare_expire);
+
+ g_hash_table_insert(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease->lease_nip), lease);
+
+ return lease;
+}
+
+static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
+ uint32_t nip)
+{
+ return g_hash_table_lookup(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) nip));
+}
+
+/* Check if the IP is taken; if it is, add it to the lease table */
+static bool arp_check(uint32_t nip, const uint8_t *safe_mac)
+{
+ /* TODO: Add ARP checking */
+ return true;
+}
+
+static bool is_expired_lease(struct dhcp_lease *lease)
+{
+ if (lease->expire < time(NULL))
+ return true;
+
+ return false;
+}
+
+static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
+ const uint8_t *safe_mac)
+{
+ uint32_t ip_addr;
+ struct dhcp_lease *lease;
+ GList *list;
+ ip_addr = dhcp_server->start_ip;
+ for (; ip_addr <= dhcp_server->end_ip; ip_addr++) {
+ /* e.g. 192.168.55.0 */
+ if ((ip_addr & 0xff) == 0)
+ continue;
+
+ /* e.g. 192.168.55.255 */
+ if ((ip_addr & 0xff) == 0xff)
+ continue;
+
+ lease = find_lease_by_nip(dhcp_server, ip_addr);
+ if (lease)
+ continue;
+
+ if (arp_check(htonl(ip_addr), safe_mac))
+ return ip_addr;
+ }
+
+ /* The last lease is the oldest one */
+ list = g_list_last(dhcp_server->lease_list);
+ if (!list)
+ return 0;
+
+ lease = list->data;
+ if (!lease)
+ return 0;
+
+ if (!is_expired_lease(lease))
+ return 0;
+
+ if (!arp_check(lease->lease_nip, safe_mac))
+ return 0;
+
+ return lease->lease_nip;
+}
+
+static void lease_set_expire(GDHCPServer *dhcp_server,
+ struct dhcp_lease *lease, uint32_t expire)
+{
+ dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
+
+ lease->expire = expire;
+
+ dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
+ lease, compare_expire);
+}
+
+static void destroy_lease_table(GDHCPServer *dhcp_server)
+{
+ GList *list;
+
+ g_hash_table_destroy(dhcp_server->nip_lease_hash);
+
+ dhcp_server->nip_lease_hash = NULL;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+
+ g_free(lease);
+ }
+
+ g_list_free(dhcp_server->lease_list);
+
+ dhcp_server->lease_list = NULL;
+}
+static uint32_t get_interface_address(int index)
+{
+ struct ifreq ifr;
+ int sk, err;
+ struct sockaddr_in *server_ip;
+ uint32_t ret = 0;
+
+ sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return 0;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = index;
+
+ err = ioctl(sk, SIOCGIFNAME, &ifr);
+ if (err < 0) {
+ perror("Get interface name error");
+ goto done;
+ }
+
+ err = ioctl(sk, SIOCGIFADDR, &ifr);
+ if (err < 0) {
+ perror("Get ip address error");
+ goto done;
+ }
+
+ server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
+ ret = server_ip->sin_addr.s_addr;
+
+done:
+ close(sk);
+
+ return ret;
+}
+
+GDHCPServer *g_dhcp_server_new(GDHCPType type,
+ int ifindex, GDHCPServerError *error)
+{
+ GDHCPServer *dhcp_server = NULL;
+
+ if (ifindex < 0) {
+ *error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
+ return NULL;
+ }
+
+ dhcp_server = g_try_new0(GDHCPServer, 1);
+ if (!dhcp_server) {
+ *error = G_DHCP_SERVER_ERROR_NOMEM;
+ return NULL;
+ }
+
+ dhcp_server->interface = get_interface_name(ifindex);
+ if (!dhcp_server->interface) {
+ *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
+ goto error;
+ }
+
+ if (!interface_is_up(ifindex)) {
+ *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
+ goto error;
+ }
+
+ dhcp_server->server_nip = get_interface_address(ifindex);
+ if (dhcp_server->server_nip == 0) {
+ *error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
+ goto error;
+ }
+
+ dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, NULL);
+ dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, NULL);
+
+ dhcp_server->started = FALSE;
+
+ /* All the leases have the same fixed lease time,
+ * do not support DHCP_LEASE_TIME option from client.
+ */
+ dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
+
+ dhcp_server->type = type;
+ dhcp_server->ref_count = 1;
+ dhcp_server->ifindex = ifindex;
+ dhcp_server->listener_sockfd = -1;
+ dhcp_server->listener_watch = -1;
+ dhcp_server->listener_channel = NULL;
+ dhcp_server->save_lease_func = NULL;
+ dhcp_server->debug_func = NULL;
+ dhcp_server->debug_data = NULL;
+
+ *error = G_DHCP_SERVER_ERROR_NONE;
+
+ return dhcp_server;
+
+error:
+ g_free(dhcp_server->interface);
+ g_free(dhcp_server);
+ return NULL;
+}
+
+
+static uint8_t check_packet_type(struct dhcp_packet *packet)
+{
+ uint8_t *type;
+
+ if (packet->hlen != ETH_ALEN)
+ return 0;
+
+ if (packet->op != BOOTREQUEST)
+ return 0;
+
+ type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
+
+ if (!type)
+ return 0;
+
+ if (*type < DHCP_MINTYPE)
+ return 0;
+
+ if (*type > DHCP_MAXTYPE)
+ return 0;
+
+ return *type;
+}
+
+static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
+ struct dhcp_packet *client_packet, char type)
+{
+ /* Sets op, htype, hlen, cookie fields
+ * and adds DHCP_MESSAGE_TYPE option */
+ dhcp_init_header(packet, type);
+
+ packet->xid = client_packet->xid;
+ memcpy(packet->chaddr, client_packet->chaddr,
+ sizeof(client_packet->chaddr));
+ packet->flags = client_packet->flags;
+ packet->gateway_nip = client_packet->gateway_nip;
+ packet->ciaddr = client_packet->ciaddr;
+ dhcp_add_option_uint32(packet, DHCP_SERVER_ID,
+ dhcp_server->server_nip);
+}
+
+static void add_option(gpointer key, gpointer value, gpointer user_data)
+{
+ const char *option_value = value;
+ uint8_t option_code = GPOINTER_TO_INT(key);
+ struct in_addr nip;
+ struct dhcp_packet *packet = user_data;
+
+ if (!option_value)
+ return;
+
+ switch (option_code) {
+ case G_DHCP_SUBNET:
+ case G_DHCP_ROUTER:
+ case G_DHCP_DNS_SERVER:
+ if (inet_aton(option_value, &nip) == 0)
+ return;
+
+ dhcp_add_option_uint32(packet, (uint8_t) option_code,
+ ntohl(nip.s_addr));
+ break;
+ default:
+ return;
+ }
+}
+
+static void add_server_options(GDHCPServer *dhcp_server,
+ struct dhcp_packet *packet)
+{
+ g_hash_table_foreach(dhcp_server->option_hash,
+ add_option, packet);
+}
+
+static bool check_requested_nip(GDHCPServer *dhcp_server,
+ uint32_t requested_nip)
+{
+ struct dhcp_lease *lease;
+
+ if (requested_nip == 0)
+ return false;
+
+ if (requested_nip < dhcp_server->start_ip)
+ return false;
+
+ if (requested_nip > dhcp_server->end_ip)
+ return false;
+
+ lease = find_lease_by_nip(dhcp_server, requested_nip);
+ if (!lease)
+ return true;
+
+ if (!is_expired_lease(lease))
+ return false;
+
+ return true;
+}
+
+static void send_packet_to_client(GDHCPServer *dhcp_server,
+ struct dhcp_packet *dhcp_pkt)
+{
+ const uint8_t *chaddr;
+ uint32_t ciaddr;
+
+ if ((dhcp_pkt->flags & htons(BROADCAST_FLAG))
+ || dhcp_pkt->ciaddr == 0) {
+ debug(dhcp_server, "Broadcasting packet to client");
+ ciaddr = INADDR_BROADCAST;
+ chaddr = MAC_BCAST_ADDR;
+ } else {
+ debug(dhcp_server, "Unicasting packet to client ciaddr");
+ ciaddr = dhcp_pkt->ciaddr;
+ chaddr = dhcp_pkt->chaddr;
+ }
+
+ dhcp_send_raw_packet(dhcp_pkt,
+ dhcp_server->server_nip, SERVER_PORT,
+ ciaddr, CLIENT_PORT, chaddr,
+ dhcp_server->ifindex);
+}
+
+static void send_offer(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet,
+ struct dhcp_lease *lease,
+ uint32_t requested_nip)
+{
+ struct dhcp_packet packet;
+ struct in_addr addr;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
+
+ if (lease)
+ packet.yiaddr = htonl(lease->lease_nip);
+ else if (check_requested_nip(dhcp_server, requested_nip))
+ packet.yiaddr = htonl(requested_nip);
+ else
+ packet.yiaddr = htonl(find_free_or_expired_nip(
+ dhcp_server, client_packet->chaddr));
+
+ debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
+
+ if (!packet.yiaddr) {
+ debug(dhcp_server, "Err: Can not found lease and send offer");
+ return;
+ }
+
+ lease = add_lease(dhcp_server, OFFER_TIME,
+ packet.chaddr, packet.yiaddr);
+ if (!lease) {
+ debug(dhcp_server,
+ "Err: No free IP addresses. OFFER abandoned");
+ return;
+ }
+
+ dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME,
+ dhcp_server->lease_seconds);
+ add_server_options(dhcp_server, &packet);
+
+ addr.s_addr = packet.yiaddr;
+
+ debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr));
+ send_packet_to_client(dhcp_server, &packet);
+}
+
+static void save_lease(GDHCPServer *dhcp_server)
+{
+ GList *list;
+
+ if (!dhcp_server->save_lease_func)
+ return;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+ dhcp_server->save_lease_func(lease->lease_mac,
+ lease->lease_nip, lease->expire);
+ }
+}
+
+static void send_ACK(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet, uint32_t dest)
+{
+ struct dhcp_packet packet;
+ uint32_t lease_time_sec;
+ struct in_addr addr;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPACK);
+ packet.yiaddr = htonl(dest);
+
+ lease_time_sec = dhcp_server->lease_seconds;
+
+ dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec);
+
+ add_server_options(dhcp_server, &packet);
+
+ addr.s_addr = htonl(dest);
+
+ debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
+
+ send_packet_to_client(dhcp_server, &packet);
+
+ add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
+}
+
+static void send_NAK(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPNAK);
+
+ debug(dhcp_server, "Sending NAK");
+
+ dhcp_send_raw_packet(&packet,
+ dhcp_server->server_nip, SERVER_PORT,
+ INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
+ dhcp_server->ifindex);
+}
+
+static void send_inform(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPACK);
+ add_server_options(dhcp_server, &packet);
+ send_packet_to_client(dhcp_server, &packet);
+}
+
+static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data)
+{
+ GDHCPServer *dhcp_server = user_data;
+ struct dhcp_packet packet;
+ struct dhcp_lease *lease;
+ uint32_t requested_nip = 0;
+ uint8_t type, *server_id_option, *request_ip_option;
+ int re;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ dhcp_server->listener_watch = 0;
+ return FALSE;
+ }
+
+ re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
+ if (re < 0)
+ return TRUE;
+
+ type = check_packet_type(&packet);
+ if (type == 0)
+ return TRUE;
+
+ server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
+ if (server_id_option) {
+ uint32_t server_nid = get_be32(server_id_option);
+
+ if (server_nid != dhcp_server->server_nip)
+ return TRUE;
+ }
+
+ request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
+ if (request_ip_option)
+ requested_nip = get_be32(request_ip_option);
+
+ lease = find_lease_by_mac(dhcp_server, packet.chaddr);
+
+ switch (type) {
+ case DHCPDISCOVER:
+ debug(dhcp_server, "Received DISCOVER");
+
+ send_offer(dhcp_server, &packet, lease, requested_nip);
+ break;
+ case DHCPREQUEST:
+ debug(dhcp_server, "Received REQUEST NIP %d",
+ requested_nip);
+ if (requested_nip == 0) {
+ requested_nip = packet.ciaddr;
+ if (requested_nip == 0)
+ break;
+ }
+
+ if (lease && requested_nip == lease->lease_nip) {
+ debug(dhcp_server, "Sending ACK");
+ send_ACK(dhcp_server, &packet,
+ lease->lease_nip);
+ break;
+ }
+
+ if (server_id_option || !lease) {
+ debug(dhcp_server, "Sending NAK");
+ send_NAK(dhcp_server, &packet);
+ }
+
+ break;
+ case DHCPDECLINE:
+ debug(dhcp_server, "Received DECLINE");
+
+ if (!server_id_option)
+ break;
+
+ if (!request_ip_option)
+ break;
+
+ if (!lease)
+ break;
+
+ if (requested_nip == lease->lease_nip)
+ remove_lease(dhcp_server, lease);
+
+ break;
+ case DHCPRELEASE:
+ debug(dhcp_server, "Received RELEASE");
+
+ if (!server_id_option)
+ break;
+
+ if (!lease)
+ break;
+
+ if (packet.ciaddr == lease->lease_nip)
+ lease_set_expire(dhcp_server, lease,
+ time(NULL));
+ break;
+ case DHCPINFORM:
+ debug(dhcp_server, "Received INFORM");
+ send_inform(dhcp_server, &packet);
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Caller need to load leases before call it */
+int g_dhcp_server_start(GDHCPServer *dhcp_server)
+{
+ GIOChannel *listener_channel;
+ int listener_sockfd;
+
+ if (dhcp_server->started)
+ return 0;
+
+ listener_sockfd = dhcp_l3_socket(SERVER_PORT,
+ dhcp_server->interface, AF_INET);
+ if (listener_sockfd < 0)
+ return -EIO;
+
+ listener_channel = g_io_channel_unix_new(listener_sockfd);
+ if (!listener_channel) {
+ close(listener_sockfd);
+ return -EIO;
+ }
+
+ dhcp_server->listener_sockfd = listener_sockfd;
+ dhcp_server->listener_channel = listener_channel;
+
+ g_io_channel_set_close_on_unref(listener_channel, TRUE);
+ dhcp_server->listener_watch =
+ g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
+ G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
+ listener_event, dhcp_server,
+ NULL);
+ g_io_channel_unref(dhcp_server->listener_channel);
+
+ dhcp_server->started = TRUE;
+
+ return 0;
+}
+
+int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
+ unsigned char option_code, const char *option_value)
+{
+ struct in_addr nip;
+
+ if (!option_value)
+ return -EINVAL;
+
+ debug(dhcp_server, "option_code %d option_value %s",
+ option_code, option_value);
+ switch (option_code) {
+ case G_DHCP_SUBNET:
+ case G_DHCP_ROUTER:
+ case G_DHCP_DNS_SERVER:
+ if (inet_aton(option_value, &nip) == 0)
+ return -ENXIO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ g_hash_table_replace(dhcp_server->option_hash,
+ GINT_TO_POINTER((int) option_code),
+ (gpointer) option_value);
+ return 0;
+}
+
+void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
+ GDHCPSaveLeaseFunc func, gpointer user_data)
+{
+ if (!dhcp_server)
+ return;
+
+ dhcp_server->save_lease_func = func;
+}
+
+GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
+{
+ if (!dhcp_server)
+ return NULL;
+
+ __sync_fetch_and_add(&dhcp_server->ref_count, 1);
+
+ return dhcp_server;
+}
+
+void g_dhcp_server_stop(GDHCPServer *dhcp_server)
+{
+ /* Save leases, before stop; load them before start */
+ save_lease(dhcp_server);
+
+ if (dhcp_server->listener_watch > 0) {
+ g_source_remove(dhcp_server->listener_watch);
+ dhcp_server->listener_watch = 0;
+ }
+
+ dhcp_server->listener_channel = NULL;
+
+ dhcp_server->started = FALSE;
+}
+
+void g_dhcp_server_unref(GDHCPServer *dhcp_server)
+{
+ if (!dhcp_server)
+ return;
+
+ if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1)
+ return;
+
+ g_dhcp_server_stop(dhcp_server);
+
+ g_hash_table_destroy(dhcp_server->option_hash);
+
+ destroy_lease_table(dhcp_server);
+
+ g_free(dhcp_server->interface);
+
+ g_free(dhcp_server);
+}
+
+int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
+ const char *start_ip, const char *end_ip)
+{
+ struct in_addr _host_addr;
+
+ if (inet_aton(start_ip, &_host_addr) == 0)
+ return -ENXIO;
+
+ dhcp_server->start_ip = ntohl(_host_addr.s_addr);
+
+ if (inet_aton(end_ip, &_host_addr) == 0)
+ return -ENXIO;
+
+ dhcp_server->end_ip = ntohl(_host_addr.s_addr);
+
+ return 0;
+}
+
+void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
+ unsigned int lease_time)
+{
+ if (!dhcp_server)
+ return;
+
+ dhcp_server->lease_seconds = lease_time;
+}
+
+void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
+ GDHCPDebugFunc func, gpointer user_data)
+{
+ if (!dhcp_server)
+ return;
+
+ dhcp_server->debug_func = func;
+ dhcp_server->debug_data = user_data;
+}
diff --git a/src/gdhcp/unaligned.h b/src/gdhcp/unaligned.h
new file mode 100644
index 0000000..ffdaae2
--- /dev/null
+++ b/src/gdhcp/unaligned.h
@@ -0,0 +1,163 @@
+/*
+ *
+ * Connection Manager
+ *
+ * Copyright (C) 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <endian.h>
+#include <byteswap.h>
+
+#define get_unaligned(ptr) \
+({ \
+ struct __attribute__((packed)) { \
+ typeof(*(ptr)) __v; \
+ } *__p = (typeof(__p)) (ptr); \
+ __p->__v; \
+})
+
+#define put_unaligned(val, ptr) \
+do { \
+ struct __attribute__((packed)) { \
+ typeof(*(ptr)) __v; \
+ } *__p = (typeof(__p)) (ptr); \
+ __p->__v = (val); \
+} while(0)
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+static inline uint64_t get_le64(const void *ptr)
+{
+ return get_unaligned((const uint64_t *) ptr);
+}
+
+static inline uint64_t get_be64(const void *ptr)
+{
+ return bswap_64(get_unaligned((const uint64_t *) ptr));
+}
+
+static inline uint32_t get_le32(const void *ptr)
+{
+ return get_unaligned((const uint32_t *) ptr);
+}
+
+static inline uint32_t get_be32(const void *ptr)
+{
+ return bswap_32(get_unaligned((const uint32_t *) ptr));
+}
+
+static inline uint16_t get_le16(const void *ptr)
+{
+ return get_unaligned((const uint16_t *) ptr);
+}
+
+static inline uint16_t get_be16(const void *ptr)
+{
+ return bswap_16(get_unaligned((const uint16_t *) ptr));
+}
+
+static inline void put_be16(uint16_t val, void *ptr)
+{
+ put_unaligned(bswap_16(val), (uint16_t *) ptr);
+}
+
+static inline void put_be32(uint32_t val, void *ptr)
+{
+ put_unaligned(bswap_32(val), (uint32_t *) ptr);
+}
+
+static inline void put_le16(uint16_t val, void *ptr)
+{
+ put_unaligned(val, (uint16_t *) ptr);
+}
+
+static inline void put_le32(uint32_t val, void *ptr)
+{
+ put_unaligned(val, (uint32_t *) ptr);
+}
+
+static inline void put_be64(uint64_t val, void *ptr)
+{
+ put_unaligned(bswap_64(val), (uint64_t *) ptr);
+}
+
+static inline void put_le64(uint64_t val, void *ptr)
+{
+ put_unaligned(val, (uint64_t *) ptr);
+}
+#elif __BYTE_ORDER == __BIG_ENDIAN
+static inline uint64_t get_le64(const void *ptr)
+{
+ return bswap_64(get_unaligned((const uint64_t *) ptr));
+}
+
+static inline uint64_t get_be64(const void *ptr)
+{
+ return get_unaligned((const uint64_t *) ptr);
+}
+
+static inline uint32_t get_le32(const void *ptr)
+{
+ return bswap_32(get_unaligned((const uint32_t *) ptr));
+}
+
+static inline uint32_t get_be32(const void *ptr)
+{
+ return get_unaligned((const uint32_t *) ptr);
+}
+
+static inline uint16_t get_le16(const void *ptr)
+{
+ return bswap_16(get_unaligned((const uint16_t *) ptr));
+}
+
+static inline uint16_t get_be16(const void *ptr)
+{
+ return get_unaligned((const uint16_t *) ptr);
+}
+
+static inline void put_be16(uint16_t val, void *ptr)
+{
+ put_unaligned(val, (uint16_t *) ptr);
+}
+
+static inline void put_be32(uint32_t val, void *ptr)
+{
+ put_unaligned(val, (uint32_t *) ptr);
+}
+
+static inline void put_le16(uint16_t val, void *ptr)
+{
+ put_unaligned(bswap_16(val), (uint16_t *) ptr);
+}
+
+static inline void put_le32(uint32_t val, void *ptr)
+{
+ put_unaligned(bswap_32(val), (uint32_t *) ptr);
+}
+
+static inline void put_be64(uint64_t val, void *ptr)
+{
+ put_unaligned(val, (uint64_t *) ptr);
+}
+
+static inline void put_le64(uint64_t val, void *ptr)
+{
+ put_unaligned(bswap_64(val), (uint64_t *) ptr);
+}
+#else
+#error "Unknown byte order"
+#endif