i officially hate netctl now i think
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
import configparser
|
||||
import io
|
||||
import os
|
||||
##
|
||||
import aif.utils
|
||||
import aif.network._common
|
||||
|
||||
|
||||
class Connection(aif.network._common.BaseConnection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
# TODO: disabling default route is not supported in-band.
|
||||
# https://bugs.archlinux.org/task/64651
|
||||
# TODO: disabling autoroutes is not supported in-band.
|
||||
# https://bugs.archlinux.org/task/64651
|
||||
# TODO: netctl profiles only support a single gateway.
|
||||
# is there a way to manually add alternative gateways?
|
||||
if not self.dhcp_client:
|
||||
self.dhcp_client = 'dhcpcd'
|
||||
self.provider_type = 'netctl'
|
||||
self.packages = {'netctl', 'openresolv'}
|
||||
self.services = {('/usr/lib/systemd/system/netctl@.service'): ('etc/systemd/system'
|
||||
'/multi-user.target.wants'
|
||||
'/netctl@{0}.service').format(self.id)}
|
||||
# Only used if we need to override default dhcp/dhcp6 behaviour. I don't *think* we can customize SLAAC?
|
||||
self.chroot_dir = os.path.join('etc', 'netctl', 'custom', self.dhcp_client)
|
||||
self.chroot_cfg = os.path.join(self.chroot_dir, self.id)
|
||||
self.desc = None
|
||||
|
||||
def _initCfg(self):
|
||||
if self.device == 'auto':
|
||||
self.device = aif.network._common.getDefIface(self.connection_type)
|
||||
self.desc = ('A {0} profile for {1} (generated by AIF-NG)').format(self.connection_type,
|
||||
self.device)
|
||||
self._cfg = configparser.ConfigParser()
|
||||
self._cfg.optionxform = str
|
||||
# configparser *requires* sections. netctl doesn't use them. We strip it when we write.
|
||||
self._cfg['BASE'] = {'Description': self.desc,
|
||||
'Interface': self.device,
|
||||
'Connection': self.connection_type}
|
||||
# Addresses
|
||||
if self.auto['addresses']['ipv4']:
|
||||
self.packages.add(self.dhcp_client)
|
||||
self._cfg['BASE']['IP'] = 'dhcp'
|
||||
self._cfg['BASE']['DHCPClient'] = self.dhcp_client
|
||||
else:
|
||||
if self.addrs['ipv4']:
|
||||
self._cfg['BASE']['IP'] = 'static'
|
||||
else:
|
||||
self._cfg['BASE']['IP'] = 'no'
|
||||
if self.domain:
|
||||
self._cfg['BASE']['DNSSearch'] = self.domain
|
||||
if self.auto['addresses']['ipv6']:
|
||||
if self.auto['addresses']['ipv6'] == 'slaac':
|
||||
self._cfg['BASE']['IP6'] = 'stateless'
|
||||
elif self.auto['addresses']['ipv6'] == 'dhcp6':
|
||||
self._cfg['BASE']['IP6'] = 'dhcp'
|
||||
self._cfg['BASE']['DHCP6Client'] = self.dhcp_client
|
||||
self.packages.add(self.dhcp_client)
|
||||
else:
|
||||
if not self.addrs['ipv6']:
|
||||
self._cfg['BASE']['IP6'] = 'no'
|
||||
else:
|
||||
self._cfg['BASE']['IP6'] = 'static'
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
keysuffix = ('6' if addrtype == 'ipv6' else '')
|
||||
addrkey = 'Address{0}'.format(keysuffix)
|
||||
gwkey = 'Gateway{0}'.format(keysuffix)
|
||||
str_addrs = []
|
||||
if self.addrs[addrtype] and not self.auto['addresses'][addrtype]:
|
||||
for ip, cidr, gw in self.addrs[addrtype]:
|
||||
if not self.is_defroute:
|
||||
self._cfg['BASE'][gwkey] = str(gw)
|
||||
str_addrs.append("'{0}/{1}'".format(str(ip), str(cidr.prefixlen)))
|
||||
self._cfg['BASE'][addrkey] = '({0})'.format(' '.join(str_addrs))
|
||||
elif self.addrs[addrtype]:
|
||||
if 'IPCustom' not in self._cfg['BASE']:
|
||||
# TODO: do this more cleanly somehow? Might conflict with other changes earlier/later.
|
||||
# Weird hack because netctl doesn't natively support assigning add'l addrs to
|
||||
# a dhcp/dhcp6/slaac iface.
|
||||
self._cfg['BASE']['IPCustom'] = []
|
||||
for ip, cidr, gw in self.addrs[addrtype]:
|
||||
self._cfg['BASE']['IPCustom'].append("'ip address add {0}/{1} dev {2}'".format(str(ip),
|
||||
str(cidr.prefixlen),
|
||||
self.device))
|
||||
# Resolvers may also require a change to /etc/resolvconf.conf?
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
if self.resolvers:
|
||||
resolverkey = 'DNS'
|
||||
str_resolvers = []
|
||||
for r in self.resolvers:
|
||||
str_resolvers.append("'{0}'".format(str(r)))
|
||||
self._cfg['BASE'][resolverkey] = '({0})'.format(' '.join(str_resolvers))
|
||||
# Routes
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
if self.routes[addrtype]:
|
||||
keysuffix = ('6' if addrtype == 'ipv6' else '')
|
||||
routekey = 'Routes{0}'.format(keysuffix)
|
||||
str_routes = []
|
||||
for dest, net, gw in self.routes[addrtype]:
|
||||
str_routes.append("'{0}/{1} via {2}'".format(str(dest),
|
||||
str(net.prefixlen),
|
||||
str(gw)))
|
||||
self._cfg['BASE'][routekey] = '({0})'.format(' '.join(str_routes))
|
||||
# Weird hack because netctl doesn't natively support assigning add'l addrs to a dhcp/dhcp6/slaac iface.
|
||||
if 'IPCustom' in self._cfg['BASE'].keys() and isinstance(self._cfg['BASE']['IPCustom'], list):
|
||||
self._cfg['BASE']['IPCustom'] = '({0})'.format(' '.join(self._cfg['BASE']['IPCustom']))
|
||||
return()
|
||||
|
||||
def writeConf(self, chroot_base):
|
||||
systemd_base = os.path.join(chroot_base, 'etc', 'systemd', 'system')
|
||||
systemd_file = os.path.join(systemd_base, 'netctl@{0}.service.d'.format(self.id), 'profile.conf')
|
||||
netctl_file = os.path.join(chroot_base, 'etc', 'netctl', self.id)
|
||||
for f in (systemd_file, netctl_file):
|
||||
dpath = os.path.dirname(f)
|
||||
os.makedirs(dpath, exist_ok = True)
|
||||
os.chmod(dpath, 0o0755)
|
||||
os.chown(dpath, 0, 0)
|
||||
for root, dirs, files in os.walk(dpath):
|
||||
for d in dirs:
|
||||
fulld = os.path.join(root, d)
|
||||
os.chmod(fulld, 0o0755)
|
||||
os.chown(fulld, 0, 0)
|
||||
systemd_cfg = configparser.ConfigParser()
|
||||
systemd_cfg.optionxform = str
|
||||
systemd_cfg['Unit'] = {'Description': self.desc,
|
||||
'BindsTo': 'sys-subsystem-net-devices-{0}.device'.format(self.device),
|
||||
'After': 'sys-subsystem-net-devices-{0}.device'.format(self.device)}
|
||||
with open(systemd_file, 'w') as fh:
|
||||
systemd_cfg.write(fh, space_around_delimiters = False)
|
||||
# This is where it gets... weird.
|
||||
# Gross hacky workarounds because netctl, while great for simple setups, sucks for complex/advanced ones.
|
||||
no_auto = not all((self.auto['resolvers']['ipv4'],
|
||||
self.auto['resolvers']['ipv6'],
|
||||
self.auto['routes']['ipv4'],
|
||||
self.auto['routes']['ipv6']))
|
||||
no_dhcp = not any((self.auto['addresses']['ipv4'],
|
||||
self.auto['addresses']['ipv6']))
|
||||
if (no_auto and not no_dhcp) or (not self.is_defroute and not no_dhcp):
|
||||
if self.dhcp_client == 'dhcpcd':
|
||||
if not all((self.auto['resolvers']['ipv4'],
|
||||
self.auto['routes']['ipv4'],
|
||||
self.auto['addresses']['ipv4'])):
|
||||
self._cfg['BASE']['DhcpcdOptions'] = "'--config {0}'".format(os.path.join('/', self.chroot_cfg))
|
||||
if not all((self.auto['resolvers']['ipv6'],
|
||||
self.auto['routes']['ipv6'],
|
||||
self.auto['addresses']['ipv6'])):
|
||||
self._cfg['BASE']['DhcpcdOptions6'] = "'--config {0}'".format(os.path.join('/', self.chroot_cfg))
|
||||
elif self.dhcp_client == 'dhclient':
|
||||
if not all((self.auto['resolvers']['ipv4'],
|
||||
self.auto['routes']['ipv4'],
|
||||
self.auto['addresses']['ipv4'])):
|
||||
self._cfg['BASE']['DhcpcdOptions'] = "'-cf {0}'".format(os.path.join('/', self.chroot_cfg))
|
||||
if not all((self.auto['resolvers']['ipv6'],
|
||||
self.auto['routes']['ipv6'],
|
||||
self.auto['addresses']['ipv6'])):
|
||||
self._cfg['BASE']['DhcpcdOptions6'] = "'-cf {0}'".format(os.path.join('/', self.chroot_cfg))
|
||||
custom_dir = os.path.join(chroot_base, self.chroot_dir)
|
||||
custom_cfg = os.path.join(chroot_base, self.chroot_cfg)
|
||||
os.makedirs(custom_dir, exist_ok = True)
|
||||
for root, dirs, files in os.walk(custom_dir):
|
||||
os.chown(root, 0, 0)
|
||||
os.chmod(root, 0o0755)
|
||||
for d in dirs:
|
||||
dpath = os.path.join(root, d)
|
||||
os.chown(dpath, 0, 0)
|
||||
os.chmod(dpath, 0o0755)
|
||||
for f in files:
|
||||
fpath = os.path.join(root, f)
|
||||
os.chown(fpath, 0, 0)
|
||||
os.chmod(fpath, 0o0644)
|
||||
# Modify DHCP options. WHAT a mess.
|
||||
# The default requires are VERY sparse, and fine to remain unmangled for what we do.
|
||||
opts = {}
|
||||
for x in ('requests', 'requires'):
|
||||
opts[x] = {}
|
||||
for t in ('ipv4', 'ipv6'):
|
||||
opts[x][t] = list(self.dhcp_defaults[self.dhcp_client][x][t])
|
||||
opt_map = {
|
||||
'dhclient': {
|
||||
'resolvers': {
|
||||
'ipv4': ('domain-name-servers', ),
|
||||
'ipv6': ('dhcp6.domain-name-servers', )},
|
||||
'routes': {
|
||||
'ipv4': ('rfc3442-classless-static-routes', 'static-routes'),
|
||||
'ipv6': tuple()}, # ???
|
||||
# There is no way, as far as I can tell, to tell dhclient to NOT request an address.
|
||||
'addresses': {
|
||||
'ipv4': tuple(),
|
||||
'ipv6': tuple()}},
|
||||
'dhcpcd': {
|
||||
'resolvers': {
|
||||
'ipv4': ('domain_name_servers', ),
|
||||
'ipv6': ('dhcp6_domain_name_servers', )},
|
||||
'routes': {
|
||||
'ipv4': ('classless_static_routes', 'static_routes'),
|
||||
'ipv6': tuple()}, # ???
|
||||
# I don't think dhcpcd lets us refuse an address.
|
||||
'addresses': {
|
||||
'ipv4': tuple(),
|
||||
'ipv6': tuple()}}}
|
||||
# This ONLY works for DHCPv6 on the IPv6 side. Not SLAAC. Netctl doesn't use a dhcp client for
|
||||
# SLAAC.
|
||||
# x = routers, addresses, resolvers
|
||||
# t = ipv4/ipv6 dicts
|
||||
# i = ipv4/ipv6 key
|
||||
# v = boolean of auto
|
||||
# o = each option for given auto type and IP type
|
||||
for x, t in self.auto.items():
|
||||
for i, v in t.items():
|
||||
if not v:
|
||||
for o in opt_map[self.dhcp_client][x][i]:
|
||||
for n in ('requests', 'requires'):
|
||||
if o in opts[n][i]:
|
||||
opts[n][i].remove(o)
|
||||
# We don't want the default route if we're not the default route iface.
|
||||
if not self.is_defroute:
|
||||
# IPv6 uses RA for the default route... We'll probably need to do that via an ExecUpPost?
|
||||
# TODO.
|
||||
for i in ('requests', 'requires'):
|
||||
if 'routers' in opts[i]['ipv4']:
|
||||
opts[i]['ipv4'].remove('routers')
|
||||
if self.dhcp_client == 'dhclient':
|
||||
conf = ['lease {',
|
||||
' interface "{0}";'.format(self.device),
|
||||
'}']
|
||||
for i in ('request', 'require'):
|
||||
k = '{0}s'.format(i)
|
||||
optlist = []
|
||||
for t in ('ipv4', 'ipv6'):
|
||||
optlist.extend(opts[k][t])
|
||||
if optlist:
|
||||
conf.insert(-1, ' {0} {1};'.format(k, ', '.join(optlist)))
|
||||
elif self.dhcp_client == 'dhcpcd':
|
||||
conf = []
|
||||
conf.extend(list(self.dhcp_defaults['dhcpcd']['default_opts']))
|
||||
for i in ('requests', 'requires'):
|
||||
if i == 'requests':
|
||||
k = 'option'
|
||||
else:
|
||||
k = 'require'
|
||||
optlist = []
|
||||
optlist.extend(opts[i]['ipv4'])
|
||||
optlist.extend(opts[i]['ipv6'])
|
||||
# TODO: does require support comma-separated list like option does?
|
||||
conf.append('{0} {1};'.format(k, ','.join(optlist)))
|
||||
with open(custom_cfg, 'w') as fh:
|
||||
fh.write('\n'.join(conf))
|
||||
fh.write('\n')
|
||||
os.chmod(custom_cfg, 0o0644)
|
||||
os.chown(custom_cfg, 0, 0)
|
||||
# And we have to strip out the section from the ini.
|
||||
cfgbuf = io.StringIO()
|
||||
self._cfg.write(cfgbuf, space_around_delimiters = False)
|
||||
cfgbuf.seek(0, 0)
|
||||
with open(netctl_file, 'w') as fh:
|
||||
for line in cfgbuf.readlines():
|
||||
if line.startswith('[BASE]') or line.strip() == '':
|
||||
continue
|
||||
fh.write(line)
|
||||
os.chmod(netctl_file, 0o0600)
|
||||
os.chown(netctl_file, 0, 0)
|
||||
return()
|
||||
|
||||
|
||||
class Ethernet(Connection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.connection_type = 'ethernet'
|
||||
self._initCfg()
|
||||
|
||||
|
||||
class Wireless(Connection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.connection_type = 'wireless'
|
||||
self._initCfg()
|
||||
|
||||
Reference in New Issue
Block a user