#!/usr/bin/env python3 import argparse import ipaddress import json import re import subprocess from typing import List DEFAULT_ROUTE = "default" NS_IP_RE = re.compile(r'^nameserver\s+(\S+)$') RESOLV_CONF_PATH = '/etc/resolv.conf' DHCP_CONF_TEMPLATE = """ start {host_addr} end {host_addr} # avoid dhcpd complaining that we have # too many addresses maxleases 1 interface {dhcp_intf} option dns {dns} option router {gateway} option subnet {subnet} """ def nameservers() -> List[str]: """Returns the list of nameserver IPs in resolv.conf""" result = [] with open(RESOLV_CONF_PATH) as resolv_f: for line in resolv_f: match = NS_IP_RE.match(line) if match: result.append(match.group(1)) return result def default_route(routes): """Returns the host's default route""" for route in routes: if route['dst'] == DEFAULT_ROUTE: return route raise ValueError('no default route') def addr_of(addrs, dev : str) -> ipaddress.IPv4Interface: """Finds and returns the IP address of `dev`""" for addr in addrs: if addr['ifname'] != dev: continue if len(addr['addr_info']) != 1: raise ValueError('only exactly one address on dev is supported') info = addr['addr_info'][0] return ipaddress.IPv4Interface((info['local'], info['prefixlen'])) raise ValueError('dev {0} not found'.format(dev)) def generate_conf(intf_name : str) -> str: """Generates a dhcpd config. `intf_name` is the interface to listen on.""" with subprocess.Popen(['ip', '-json', 'route'], stdout=subprocess.PIPE) as proc: routes = json.load(proc.stdout) with subprocess.Popen(['ip', '-json', 'addr'], stdout=subprocess.PIPE) as proc: addrs = json.load(proc.stdout) droute = default_route(routes) host_addr = addr_of(addrs, droute['dev']) return DHCP_CONF_TEMPLATE.format( dhcp_intf = intf_name, dns = ' '.join(nameservers()), host_addr = host_addr.ip, gateway = droute['gateway'], subnet = host_addr.network.netmask, ) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('intf_name') args = parser.parse_args() print(generate_conf(args.intf_name))