├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── etc ├── kea-pxe-replace4.conf └── kea.example.conf ├── src ├── library_common.h ├── load_unload.cc ├── pkt4_send.cc └── version.cc └── zmq_examples ├── README.md ├── pkt4_receive.cc ├── zmq_libary.h └── zmq_load_unload.cc /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Michael Gugino 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kea-pxe-replace-mod 2 | Kea module utilizing hooks api to replace pxe options (siaddr, option 66, 67) via web request. 3 | 4 | # Usage 5 | Install the compiled library somewhere sensible (such as /usr/local/lib/kea-pxe-replace4.so) 6 | 7 | Configure /etc/kea/kea-pxe-replace4.conf 8 | 9 | Please see the example config in the etc/ directory. Each key is mandatory, 10 | however the value can be a value of your choosing. These refer to the fields 11 | in the json response that this module will use for each parameter; this is so 12 | you can configure an existing API service to interact with the module. Nested 13 | fields should be dot (.) separated. IE: 'myserver.params.siaddr'. 14 | 15 | For the response, the field does not have to be present. If the field is not 16 | present, it will not attempt to override the value. 17 | 18 | These values are only applied to fields already set. If next-server is not defined 19 | somewhere in your kea config, or otherwise not set during packet processing, it 20 | will not be overridden. The same logic applies to the option fields; they are 21 | only updated if they are set in the first place. 22 | 23 | Configure /etc/kea/kea-dhcp4.conf 24 | 25 | Will result in requesting json from "url" in the form of "url+mac" 26 | Example: http://myurl.com/aa:bb:cc:dd:ee:ff 27 | Note: a '/' is not inserted between the url and the mac address for you, you 28 | must provide that in the url if necessary. 29 | 30 | ## Build requirements 31 | This software has been developed on Debian Stretch and Ubuntu 16.04. This 32 | software is currently experimental, use at your own risk (as always). 33 | 34 | apt-get install g++ libcurl4-gnutls-dev libboost-dev kea-dev 35 | 36 | ./build.sh 37 | 38 | ## Testing locally 39 | 40 | You can create a veth pair: 41 | 42 | ip link add veth0 type veth peer name veth1 43 | 44 | ip l set veth0 up && ip l set veth1 up 45 | 46 | ip a add dev veth0 192.0.2.1/24 47 | 48 | dhclient -d -v veth1 49 | 50 | ## Testing with libvirt locally 51 | You probably need to kill the dnsmasq agents that are running. 52 | 53 | brctl addbr brx && brctl addif brx veth1 54 | 55 | virt-install --pxe --network bridge=brx --name pxe1 --memory 128 --disk none 56 | 57 | Also checkout dhcpdump! 58 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | g++ -ggdb -I /usr/include/kea -L /usr/lib/kea/lib -fpic -shared -o kea-pxe-replace4.so \ 2 | src/load_unload.cc src/pkt4_send.cc src/version.cc \ 3 | -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util \ 4 | -lkea-exceptions -lcurl 5 | -------------------------------------------------------------------------------- /etc/kea-pxe-replace4.conf: -------------------------------------------------------------------------------- 1 | {"url": "https://www.foaas.com/linus/", 2 | "siaddr": "siaddr" 3 | "tftp_server": "siaddr", 4 | "bootfile_name": "bootfile_name" 5 | } 6 | -------------------------------------------------------------------------------- /etc/kea.example.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": 3 | { 4 | # For testing, you can use veth pair as described in README.md 5 | "interfaces-config": { 6 | "interfaces": ["veth0" ] 7 | }, 8 | 9 | # How to load the hook library. 10 | "hooks-libraries": [ 11 | {"library": "/usr/local/lib/kea-pxe-replace4.so"} 12 | ], 13 | 14 | "lease-database": { 15 | "type": "memfile" 16 | }, 17 | 18 | "expired-leases-processing": { 19 | "reclaim-timer-wait-time": 10, 20 | "flush-reclaimed-timer-wait-time": 25, 21 | "hold-reclaimed-time": 3600, 22 | "max-reclaim-leases": 100, 23 | "max-reclaim-time": 250, 24 | "unwarned-reclaim-cycles": 5 25 | }, 26 | 27 | "valid-lifetime": 4000, 28 | 29 | # Ensure you set some sensible defaults for the siaddr and option-data, 30 | # otherwise the options won't be added at all. 31 | # Also keep in mind that if kea doesn't receive the desired values for some 32 | # reason, these values will be sent to the client. 33 | "subnet4": [ 34 | { "subnet": "192.0.2.0/24", 35 | "pools" : [ { "pool": "192.0.2.2 - 192.0.2.128"} ], 36 | "next-server": "192.168.1.8", 37 | "option-data": [ 38 | {"name": "tftp-server-name", 39 | "data": "192.168.9.9"}, 40 | {"name": "boot-file-name", 41 | "data": "/dev/null"} 42 | ] 43 | } 44 | ] 45 | 46 | }, 47 | 48 | "Logging": 49 | { 50 | "loggers": [ 51 | { 52 | "name": "kea-dhcp4", 53 | "output_options": [ 54 | { 55 | "output": "/var/log/kea-dhcp4.log" 56 | } 57 | ], 58 | "severity": "INFO", 59 | "debuglevel": 0 60 | }, 61 | ] 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/library_common.h: -------------------------------------------------------------------------------- 1 | // library_common.h 2 | #ifndef LIBRARY_COMMON_H 3 | #define LIBRARY_COMMON_H 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | extern std::string json_params[3]; 11 | #endif // LIBRARY_COMMON_H 12 | -------------------------------------------------------------------------------- /src/load_unload.cc: -------------------------------------------------------------------------------- 1 | // load_unload.cc 2 | #include "library_common.h" 3 | 4 | using namespace isc::hooks; 5 | namespace pt = boost::property_tree; 6 | 7 | std::string param_url; 8 | std::string json_params[3]; 9 | 10 | extern "C" { 11 | int load(LibraryHandle& handle) { 12 | pt::ptree root; 13 | pt::read_json("/etc/kea/kea-pxe-replace4.conf", root); 14 | 15 | json_params[0] = (root.get("url")); 16 | json_params[1] = (root.get("siaddr")); 17 | json_params[2] = (root.get("tftp_server")); 18 | json_params[3] = (root.get("bootfile_name")); 19 | 20 | std::cout << "example.so loaded\n" << std::endl; 21 | return 0; 22 | } 23 | int unload() { 24 | return (0); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/pkt4_send.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "library_common.h" 6 | #include 7 | #include 8 | 9 | using namespace isc::dhcp; 10 | using namespace isc::hooks; 11 | using namespace std; 12 | using namespace isc::log; 13 | namespace pt = boost::property_tree; 14 | 15 | 16 | isc::log::Logger logger("pxe-replace-logger"); 17 | const char* log_messages[] = { 18 | "PRL_BASE", "message: %1", 19 | "PRL_PKT_SEND", "Outgoing packet: \n%1", 20 | "PRL_CURL_RECEIVED", "Received: \n%1", 21 | "PRL_CURL_FAILED", "Unable to receive data from %1", 22 | "PRL_REPLACE_FIELD", "Replacing: %1 with %2", 23 | "PRL_NO_SIADDR", "siaddr field not being replaced, this is probably an error.", 24 | NULL 25 | }; 26 | 27 | /// Initializer for log messages. 28 | const MessageInitializer message_initializer(log_messages); 29 | 30 | extern "C" { 31 | int pkt4_send(CalloutHandle& handle) { 32 | 33 | // Variables related to curl (there are "C" variables) 34 | CURL *curl; 35 | CURLcode res; 36 | struct curl_slist *list=NULL; 37 | bool perform_updates = 0; 38 | int curl_opt_res = 0; 39 | 40 | // "C" varaibles. 41 | char *bp; 42 | size_t size; 43 | FILE *response_memfile; 44 | 45 | // string variables 46 | string hwaddr; 47 | string final_url; 48 | 49 | // pkt4 related variables. 50 | Pkt4Ptr response4_ptr; 51 | HWAddrPtr hwaddr_ptr; 52 | OptionPtr tftp_server_name_opt_ptr; 53 | OptionPtr bootfile_name_opt_ptr; 54 | // isc::asiolink::IOAddress orig_siaddr; 55 | // isc::asiolink::IOAddress new_siaddr; 56 | 57 | // Boost / json related variables. 58 | pt::ptree root; 59 | std::stringstream ss; 60 | boost::optional siaddr_json_field; 61 | boost::optional tftp_server_json_field; 62 | boost::optional bootfile_json_field; 63 | 64 | /* Begin Code */ 65 | handle.getArgument("response4", response4_ptr); 66 | hwaddr_ptr = response4_ptr->getHWAddr(); 67 | isc::asiolink::IOAddress orig_siaddr(response4_ptr->getSiaddr()); 68 | hwaddr = hwaddr_ptr->toText(false); 69 | 70 | final_url = json_params[0] + hwaddr; 71 | LOG_DEBUG(logger, 0, "PRL_BASE").arg(final_url); 72 | 73 | tftp_server_name_opt_ptr = response4_ptr->getOption((uint16_t)66); 74 | bootfile_name_opt_ptr = response4_ptr->getOption((uint16_t)67); 75 | 76 | if (tftp_server_name_opt_ptr != NULL || bootfile_name_opt_ptr != NULL 77 | || orig_siaddr.toText() != "0.0.0.0") 78 | perform_updates = 1; 79 | 80 | if (!perform_updates) { 81 | LOG_WARN(logger, "PRL_BASE").arg("Nothing to update."); 82 | return(0); 83 | } 84 | 85 | curl_global_init(CURL_GLOBAL_DEFAULT); 86 | curl = curl_easy_init(); 87 | if(!curl) { 88 | curl_global_cleanup(); 89 | LOG_ERROR(logger, "PRL_BASE").arg("Could not initialize curl"); 90 | return(1); 91 | } 92 | 93 | list = curl_slist_append(list, "Accept: application/json"); 94 | if (list == NULL) { 95 | curl_easy_cleanup(curl); 96 | curl_global_cleanup(); 97 | LOG_ERROR(logger, "PRL_BASE").arg("Could not create curl slist."); 98 | return(1); 99 | } 100 | 101 | response_memfile = open_memstream (&bp, &size); 102 | if (response_memfile == NULL) { 103 | curl_easy_cleanup(curl); 104 | curl_global_cleanup(); 105 | LOG_ERROR(logger, "PRL_BASE").arg("Could not create memfile."); 106 | return(1); 107 | } 108 | 109 | // If we don't set a timeout, curl will try for 300 seconds by default. 110 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L); 111 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1L); 112 | // libcurl's docs say to cast as void, don't blame me. 113 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response_memfile); 114 | // CURLOPT_URL takes a char* 115 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_URL, (final_url).c_str()); 116 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); 117 | 118 | #ifdef SKIP_PEER_VERIFICATION 119 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 120 | #endif 121 | 122 | #ifdef SKIP_HOSTNAME_VERIFICATION 123 | curl_opt_res += curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); 124 | #endif 125 | if (curl_opt_res > 0) { 126 | fclose(response_memfile); 127 | free(bp); 128 | curl_easy_cleanup(curl); 129 | curl_global_cleanup(); 130 | LOG_ERROR(logger, "PRL_BASE").arg("Error setting curl options."); 131 | return(1); 132 | } 133 | res = curl_easy_perform(curl); 134 | /* Check for errors */ 135 | if(res != CURLE_OK) { 136 | fclose(response_memfile); 137 | free(bp); 138 | curl_easy_cleanup(curl); 139 | curl_global_cleanup(); 140 | LOG_ERROR(logger, "PRL_CURL_FAILED").arg(ss.str()); 141 | return(1); 142 | } 143 | // make bp available for reading. 144 | fclose(response_memfile); 145 | curl_easy_cleanup(curl); 146 | curl_global_cleanup(); 147 | // stringstream << FILE * 148 | ss << bp; 149 | free(bp); 150 | LOG_DEBUG(logger, 0, "PRL_CURL_RECEIVED").arg(ss.str()); 151 | // Load the json file in this ptree 152 | // TODO: this might throw an error if json invalid. 153 | pt::read_json(ss, root); 154 | 155 | // Fetch values from fieldnames defined in kea-pxe-replace.conf 156 | siaddr_json_field = root.get_optional(json_params[1]); 157 | tftp_server_json_field = root.get_optional(json_params[2]); 158 | bootfile_json_field = root.get_optional(json_params[3]); 159 | 160 | if (orig_siaddr.toText() != "0.0.0.0" && siaddr_json_field) { 161 | LOG_DEBUG(logger, 0, "PRL_REPLACE_FIELD").arg("siaddr").arg(*siaddr_json_field); 162 | isc::asiolink::IOAddress new_siaddr(*siaddr_json_field); 163 | response4_ptr->setSiaddr(new_siaddr); 164 | } 165 | else 166 | LOG_WARN(logger, "PRL_NO_SIADDR"); 167 | 168 | if (tftp_server_name_opt_ptr != NULL && tftp_server_json_field) { 169 | LOG_DEBUG(logger, 0, "PRL_REPLACE_FIELD").arg("tftp_server_name").arg(*tftp_server_json_field); 170 | tftp_server_name_opt_ptr->setData((*tftp_server_json_field).begin(), (*tftp_server_json_field).end()); 171 | } 172 | if (bootfile_name_opt_ptr != NULL && bootfile_json_field) { 173 | LOG_DEBUG(logger, 0, "PRL_REPLACE_FIELD").arg("bootfile_name").arg(*bootfile_json_field); 174 | bootfile_name_opt_ptr->setData((*bootfile_json_field).begin(), (*bootfile_json_field).end()); 175 | } 176 | 177 | LOG_DEBUG(logger, 0, "PRL_PKT_SEND").arg(response4_ptr->toText()); 178 | 179 | return(0); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/version.cc: -------------------------------------------------------------------------------- 1 | // version.cc 2 | #include 3 | extern "C" { 4 | int version() { 5 | return (KEA_HOOKS_VERSION); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /zmq_examples/README.md: -------------------------------------------------------------------------------- 1 | This directory houses some example code that I have no other place for ATM. 2 | -------------------------------------------------------------------------------- /zmq_examples/pkt4_receive.cc: -------------------------------------------------------------------------------- 1 | // pkt_receive4.cc 2 | #include 3 | #include 4 | #include "library_common.h" 5 | #include 6 | using namespace isc::dhcp; 7 | using namespace isc::hooks; 8 | using namespace std; 9 | extern "C" { 10 | // This callout is called at the "pkt4_receive" hook. 11 | int pkt4_receive(CalloutHandle& handle) { 12 | // A pointer to the packet is passed to the callout via a "boost" smart 13 | // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4 14 | // object as Pkt4Ptr. Retrieve a pointer to the object. 15 | Pkt4Ptr query4_ptr; 16 | handle.getArgument("query4", query4_ptr); 17 | // Point to the hardware address. 18 | HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr(); 19 | 20 | // Store the text form of the hardware address in the context to pass 21 | // to the next callout. 22 | string hwaddr = hwaddr_ptr->toText(false); 23 | handle.setContext("hwaddr", hwaddr); 24 | return (0); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /zmq_examples/zmq_libary.h: -------------------------------------------------------------------------------- 1 | // library_common.h 2 | #ifndef LIBRARY_COMMON_H 3 | #define LIBRARY_COMMON_H 4 | #include 5 | #include 6 | // "Interesting clients" log file handle declaration. 7 | extern std::fstream interesting; 8 | extern zmq::context_t my_context; 9 | extern zmq::socket_t my_socket; 10 | -------------------------------------------------------------------------------- /zmq_examples/zmq_load_unload.cc: -------------------------------------------------------------------------------- 1 | // load_unload.cc 2 | // Example to utilize zeromq 3 | // Based on tutorial. 4 | 5 | #include 6 | #include "library_common.h" 7 | #include 8 | #include 9 | 10 | using namespace isc::hooks; 11 | // Need to declare objects in top scope to be able to access them in later hook calls. 12 | std::fstream interesting; 13 | zmq::context_t my_context(1); 14 | zmq::socket_t my_socket(my_context, ZMQ_REQ); 15 | 16 | extern "C" { 17 | int load(LibraryHandle&) { 18 | interesting.open("/data/clients/interesting.log", 19 | std::fstream::out | std::fstream::app); 20 | interesting << "Connecting to hello world server…" << std::endl; 21 | 22 | // We should do some checking on my_socket before returning as well. 23 | my_socket.connect ("tcp://localhost:5555"); 24 | return (interesting ? 0 : 1); 25 | } 26 | int unload() { 27 | if (interesting) { 28 | interesting.close(); 29 | my_socket.close(); 30 | } 31 | return (0); 32 | } 33 | } 34 | --------------------------------------------------------------------------------