i officially hate netctl now i think
This commit is contained in:
parent
5e57eb7bc5
commit
3a2eca4b98
147
aif.xsd
147
aif.xsd
@ -169,11 +169,11 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_address_ip4">
|
||||
<xs:simpleType name="t_addr_ip4">
|
||||
<!-- This is a REALLY LAZY regex. Matching IPv4 in regex is ugly as heck, so we do that in-code.
|
||||
This is just a gatekeeper. -->
|
||||
<xs:restriction base="xs:string">
|
||||
<!-- This is a REALLY LAZY regex. Matching IPv4 in regex is ugly as heck, so we do that in-code.
|
||||
This is just a gatekeeper. -->
|
||||
<xs:pattern value="(dhcp|[0-9.]{7,15}/[0-9]{1,2})"/>
|
||||
<xs:pattern value="[0-9.]{7,15}/[0-9]{1,2}"/>
|
||||
<xs:whiteSpace value="collapse"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
@ -187,11 +187,11 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_address_ip6">
|
||||
<xs:simpleType name="t_addr_ip6">
|
||||
<!-- This is a REALLY LAZY regex. Matching IPv6 in regex is ugly as heck, so we do that in-code.
|
||||
This is just a gatekeeper. -->
|
||||
<xs:restriction base="xs:string">
|
||||
<!-- This is a REALLY LAZY regex. Matching IPv6 in regex is ugly as heck, so we do that in-code.
|
||||
This is just a gatekeeper. -->
|
||||
<xs:pattern value="(dhcp6|slaac|([A-Za-z0-9:]+)/[0-9]+)"/>
|
||||
<xs:pattern value="[A-Za-z0-9:]+/[0-9]{1,3}"/>
|
||||
<xs:whiteSpace value="collapse"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
@ -205,12 +205,21 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_resolver_addr">
|
||||
<xs:simpleType name="t_both_addr">
|
||||
<xs:union memberTypes="aif:t_addr_ip4 aif:t_addr_ip6"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_both_gw">
|
||||
<xs:union memberTypes="aif:t_gw_ip4 aif:t_gw_ip6"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_ipv6_auto">
|
||||
<xs:restriction base="xs:string">
|
||||
<!-- This is a REALLY LAZY regex. Matching IPv4/IPv6 in regex is ugly as heck, so we do that in-code.
|
||||
This is just a gatekeeper. -->
|
||||
<xs:pattern value="([0-9.]{7,15}|[A-Za-z0-9:]+)"/>
|
||||
<xs:whiteSpace value="collapse"/>
|
||||
<xs:enumeration value="slaac"/>
|
||||
<xs:enumeration value="dhcp6"/>
|
||||
<xs:enumeration value="false"/>
|
||||
<xs:enumeration value="none"/>
|
||||
<xs:enumeration value="0"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
@ -223,6 +232,14 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_dhcp_clients">
|
||||
<!-- Only valid for netctl. We use the internal daemons for systemd-networkd and NM. -->
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="dhcpcd"/>
|
||||
<xs:enumeration value="dhclient"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="t_netprov">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="netctl"/>
|
||||
@ -245,7 +262,7 @@
|
||||
<xs:element name="address" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="aif:t_address_ip4">
|
||||
<xs:extension base="aif:t_addr_ip4">
|
||||
<xs:attribute name="gateway"
|
||||
type="aif:t_gw_ip4"
|
||||
use="optional"/>
|
||||
@ -254,6 +271,7 @@
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="auto" type="xs:boolean" use="optional" default="true"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv4_addr">
|
||||
<xs:selector xpath="aif:address"/>
|
||||
@ -266,7 +284,7 @@
|
||||
<xs:element name="address" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="aif:t_address_ip6">
|
||||
<xs:extension base="aif:t_addr_ip6">
|
||||
<xs:attribute name="gateway"
|
||||
type="aif:t_gw_ip6"
|
||||
use="optional"/>
|
||||
@ -275,6 +293,8 @@
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="auto" type="aif:t_ipv6_auto"
|
||||
use="optional" default="slaac"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv6_addr">
|
||||
<xs:selector xpath="aif:address"/>
|
||||
@ -288,10 +308,85 @@
|
||||
<xs:element name="resolvers" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:element name="resolver" minOccurs="1" maxOccurs="unbounded"
|
||||
type="aif:t_resolver_addr"/>
|
||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:element name="resolver" minOccurs="1" maxOccurs="unbounded"
|
||||
type="aif:t_both_gw"/>
|
||||
<xs:element name="ipv4" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="auto" type="xs:boolean"
|
||||
use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="ipv6" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="auto" type="xs:boolean"
|
||||
use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="routes" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:element name="ipv4">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="route" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="aif:t_addr_ip4">
|
||||
<xs:attribute name="gateway"
|
||||
type="aif:t_gw_ip4"
|
||||
use="required"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="auto" type="xs:boolean" use="optional" default="true"/>
|
||||
<xs:attribute name="defaultGateway" type="xs:boolean"
|
||||
use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv4_route">
|
||||
<xs:selector xpath="aif:route"/>
|
||||
<xs:field xpath="."/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
<xs:element name="ipv6">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="address" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="aif:t_addr_ip6">
|
||||
<xs:attribute name="gateway"
|
||||
type="aif:t_gw_ip6"
|
||||
use="required"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<!-- DHCPv6 does not have an option to send routes,
|
||||
so you need to use RAs. -->
|
||||
<!-- See https://www.isc.org/blogs/routing-configuration-over-dhcpv6-2/
|
||||
for more information. -->
|
||||
<!-- https://datatracker.ietf.org/doc/draft-ietf-mif-dhcpv6-route-option/
|
||||
expired. Shame, that. -->
|
||||
<xs:attribute name="auto" type="xs:boolean" use="optional" default="true"/>
|
||||
<xs:attribute name="defaultGateway" type="xs:boolean"
|
||||
use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv6_route">
|
||||
<xs:selector xpath="aif:route"/>
|
||||
<xs:field xpath="."/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="noAuto" type="xs:boolean" use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
@ -323,7 +418,7 @@
|
||||
<xs:element name="type" minOccurs="1" maxOccurs="1" default="wpa2">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="wep"/>
|
||||
<!-- <xs:enumeration value="wep"/> -->
|
||||
<xs:enumeration value="wpa"/>
|
||||
<xs:enumeration value="wpa2"/>
|
||||
<!-- <xs:enumeration value="wpa3"/> -->
|
||||
@ -331,14 +426,14 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<!-- "mode" only valid for WPA2 -->
|
||||
<!-- "mode" only valid for WPA/WPA2 (and maybe WPA3 once supported?) -->
|
||||
<xs:element name="mode" minOccurs="0" maxOccurs="1" default="personal">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<!-- PSK -->
|
||||
<xs:enumeration value="personal"/>
|
||||
<!-- RADIUS -->
|
||||
<xs:enumeration value="enterprise"/>
|
||||
<!-- RADIUS, etc. -->
|
||||
<!-- <xs:enumeration value="enterprise"/> -->
|
||||
<xs:whiteSpace value="collapse"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
@ -351,7 +446,7 @@
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="psk"/>
|
||||
<xs:enumeration value="radius"/>
|
||||
<!-- <xs:enumeration value="radius"/> -->
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
@ -369,7 +464,7 @@
|
||||
<xs:element name="encryption" type="aif:t_wifi_crypto" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="essid" type="xs:string" use="required"/>
|
||||
<xs:attribute name="bssid" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="bssid" type="aif:t_mac_addr" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
@ -783,7 +878,6 @@
|
||||
</xs:element>
|
||||
<!-- END STORAGE -->
|
||||
<!-- BEGIN NETWORK -->
|
||||
<!-- TODO: make network optional? -->
|
||||
<xs:element name="network" minOccurs="1" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
||||
@ -793,7 +887,8 @@
|
||||
</xs:choice>
|
||||
<!-- It's nearly impossible to validate FQDNs/hostnames in XSD, so we do it in-code. -->
|
||||
<xs:attribute name="hostname" type="aif:t_nonempty" use="required"/>
|
||||
<xs:attribute name="provider" type="aif:t_netprov" use="optional" default="netctl"/>
|
||||
<xs:attribute name="provider" type="aif:t_netprov" use="optional" default="systemd"/>
|
||||
<xs:attribute name="dhcpClient" type="aif:t_dhcp_clients" use="optional" default="dhcpcd"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_iface_eth">
|
||||
<xs:selector xpath="aif:ethernet|aif:wireless"/>
|
||||
|
@ -15,4 +15,18 @@ from . import pacman
|
||||
|
||||
class AIF(object):
|
||||
def __init__(self):
|
||||
# Process:
|
||||
# 0.) get config (already initialized at this point)
|
||||
# 1.) run pre scripts*
|
||||
# 2.) initialize all objects' classes
|
||||
# 3.) disk ops = partition, mount*
|
||||
# 3.) b.) "pivot" logging here. create <chroot>/root/aif/ and copy log to <chroot>/root/aif/aif.log, use that
|
||||
# as new log file. copy over scripts.
|
||||
# 4.) install base system*
|
||||
# 4.) b.) other system.* tasks. locale(s), etc.*
|
||||
# 5.) run pkg scripts*
|
||||
# 6.) install kernel(?), pkg items*
|
||||
# 6.) b.) remember to install the .packages items for each object
|
||||
# 7.) write out confs and other object application methods*
|
||||
# * = log but don't do anything for dryrun
|
||||
pass
|
||||
|
@ -10,6 +10,7 @@ VERSION = '0.2.0'
|
||||
# blkinfo, mdstat, and pyparted are only needed for the non-gi fallbacks.
|
||||
EXTERNAL_DEPS = ['blkinfo',
|
||||
'gpg',
|
||||
'jinja2',
|
||||
'lxml',
|
||||
'mdstat',
|
||||
'parse',
|
||||
|
@ -1,19 +1,20 @@
|
||||
from . import _common
|
||||
from . import netctl
|
||||
from . import networkd
|
||||
from . import networkmanager
|
||||
from . import net
|
||||
|
||||
# No longer necessary:
|
||||
# try:
|
||||
# from . import _common
|
||||
# except ImportError:
|
||||
# pass # GI isn't supported, so we don't even use a fallback.
|
||||
|
||||
from . import netctl
|
||||
|
||||
# TODO: use DBus interface for systemd but fallback to subprocess?
|
||||
# http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html
|
||||
# https://www.youtube.com/watch?v=ZUX9Fx8Rwzg
|
||||
# https://www.youtube.com/watch?v=lBQgMGPxqNo
|
||||
# https://github.com/facebookincubator/pystemd has some unit/service examples
|
||||
try:
|
||||
from . import networkd
|
||||
except ImportError:
|
||||
from . import networkd_fallback as networkd
|
||||
|
||||
from . import networkmanager
|
||||
from . import net
|
||||
# try:
|
||||
# from . import networkd
|
||||
# except ImportError:
|
||||
# from . import networkd_fallback as networkd
|
||||
|
@ -1,3 +1,213 @@
|
||||
import ipaddress
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
##
|
||||
from pyroute2 import IPDB
|
||||
##
|
||||
import aif.utils
|
||||
|
||||
# Not needed
|
||||
# import gi
|
||||
# gi.require_version('NM', '1.0')
|
||||
# from gi.repository import GObject, NM, GLib
|
||||
|
||||
|
||||
def canonizeEUI(phyaddr):
|
||||
# The easy transformations first.
|
||||
phyaddr = re.sub(r'[.:-]', '', phyaddr.upper().strip())
|
||||
eui = ':'.join(['{0}'.format(phyaddr[i:i+2]) for i in range(0, 12, 2)])
|
||||
return(eui)
|
||||
|
||||
|
||||
def convertIpTuples(addr_xmlobj):
|
||||
# These tuples follow either:
|
||||
# ('dhcp'/'dhcp6'/'slaac', None, None) for auto configuration
|
||||
# (ipaddress.IPv4/6Address(IP), CIDR, ipaddress.IPv4/6Address(GW)) for static configuration
|
||||
if addr_xmlobj.text in ('dhcp', 'dhcp6', 'slaac'):
|
||||
addr = addr_xmlobj.text.strip()
|
||||
net = None
|
||||
gw = None
|
||||
else:
|
||||
components = addr_xmlobj.text.strip().split('/')
|
||||
if len(components) > 2:
|
||||
raise ValueError('Invalid IP/CIDR format: {0}'.format(addr_xmlobj.text))
|
||||
if len(components) == 1:
|
||||
addr = ipaddress.ip_address(components[0])
|
||||
if addr.version == 4:
|
||||
components.append('24')
|
||||
elif addr.version == 6:
|
||||
components.append('64')
|
||||
addr = ipaddress.ip_address(components[0])
|
||||
net = ipaddress.ip_network('/'.join(components), strict = False)
|
||||
try:
|
||||
gw = ipaddress.ip_address(addr_xmlobj.attrib.get('gateway').strip())
|
||||
except (ValueError, AttributeError):
|
||||
gw = next(net.hosts())
|
||||
return((addr, net, gw))
|
||||
|
||||
|
||||
def convertWifiCrypto(crypto_xmlobj):
|
||||
crypto = {'type': crypto_xmlobj.find('type').text.strip()}
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
crypto['mode'] = crypto_xmlobj.find('mode')
|
||||
if not crypto['mode']:
|
||||
crypto['mode'] = 'personal'
|
||||
else:
|
||||
crypto['mode'] = crypto['mode'].text.strip()
|
||||
else:
|
||||
crypto['mode'] = None
|
||||
creds = crypto_xmlobj.find('creds')
|
||||
crypto['auth'] = {'type': creds.attrib.get('type', 'psk').strip()}
|
||||
if crypto['auth']['type'] == 'psk':
|
||||
crypto['auth']['psk'] = creds.text
|
||||
# TODO: enterprise support
|
||||
return(crypto)
|
||||
|
||||
|
||||
def getDefIface(ifacetype):
|
||||
if ifacetype == 'ethernet':
|
||||
if isNotPersistent():
|
||||
prefix = 'eth'
|
||||
else:
|
||||
prefix = 'en'
|
||||
elif ifacetype == 'wireless':
|
||||
prefix = 'wl'
|
||||
else:
|
||||
raise ValueError('ifacetype must be one of "ethernet" or "wireless"')
|
||||
ifname = None
|
||||
with IPDB() as ipdb:
|
||||
for iface in ipdb.interfaces.keys():
|
||||
if iface.startswith(prefix):
|
||||
ifname = iface
|
||||
break
|
||||
if not ifname:
|
||||
return(None)
|
||||
return(ifname)
|
||||
|
||||
|
||||
def isNotPersistent(chroot_base = '/'):
|
||||
chroot_base = pathlib.Path(chroot_base)
|
||||
systemd_override = chroot_base.joinpath('etc',
|
||||
'systemd',
|
||||
'network',
|
||||
'99-default.link')
|
||||
kernel_cmdline = chroot_base.joinpath('proc', 'cmdline')
|
||||
devnull = chroot_base.joinpath('dev', 'null')
|
||||
rootdevnull = pathlib.PosixPath('/dev/null')
|
||||
if os.path.islink(systemd_override) and pathlib.Path(systemd_override).resolve() in (devnull, rootdevnull):
|
||||
return(True)
|
||||
cmds = aif.utils.kernelCmdline(chroot_base)
|
||||
if 'net.ifnames' in cmds.keys() and cmds['net.ifnames'] == '0':
|
||||
return(True)
|
||||
return(False)
|
||||
|
||||
|
||||
class BaseConnection(object):
|
||||
def __init__(self, iface_xml):
|
||||
self.xml = iface_xml
|
||||
self.id = self.xml.attrib['id'].strip()
|
||||
self.device = self.xml.attrib['device'].strip()
|
||||
self.is_defroute = aif.utils.xmlBool(self.xml.attrib.get('defroute', 'false').strip())
|
||||
try:
|
||||
self.domain = self.xml.attrib.get('searchDomain').strip()
|
||||
except AttributeError:
|
||||
self.domain = None
|
||||
self.dhcp_client = self.xml.attrib.get('dhcpClient', 'dhcpcd').strip()
|
||||
self._cfg = None
|
||||
self.connection_type = None
|
||||
self.provider_type = None
|
||||
self.packages = []
|
||||
self.services = {}
|
||||
self.resolvers = []
|
||||
self.addrs = {'ipv4': [],
|
||||
'ipv6': []}
|
||||
self.routes = {'ipv4': [],
|
||||
'ipv6': []}
|
||||
self.auto = {}
|
||||
for x in ('resolvers', 'routes', 'addresses'):
|
||||
self.auto[x] = {}
|
||||
x_xml = self.xml.find(x)
|
||||
for t in ('ipv4', 'ipv6'):
|
||||
if t == 'ipv6' and x == 'addresses':
|
||||
self.auto[x][t] = 'slaac'
|
||||
else:
|
||||
self.auto[x][t] = True
|
||||
if x_xml:
|
||||
t_xml = x_xml.find(t)
|
||||
if t_xml:
|
||||
if t == 'ipv6' and x == 'addresses':
|
||||
a = t_xml.attrib.get('auto', 'slaac').strip()
|
||||
if a.lower() in ('false', '0', 'none'):
|
||||
self.auto[x][t] = False
|
||||
else:
|
||||
self.auto[x][t] = a
|
||||
else:
|
||||
self.auto[x][t] = aif.utils.xmlBool(t_xml.attrib.get('auto', 'true').strip())
|
||||
# These defaults are from the man page. However, we might want to add:
|
||||
# domain-search, netbios-scope, interface-mtu, rfc3442-classless-static-routes, ntp-servers,
|
||||
# dhcp6.fqdn, dhcp6.sntp-servers
|
||||
# under requests and for requires, maybe:
|
||||
# routers, domain-name-servers, domain-name, domain-search, host-name
|
||||
self.dhcp_defaults = {
|
||||
'dhclient': {'requests': {'ipv4': ('subnet-mask', 'broadcast-address', 'time-offset', 'routers',
|
||||
'domain-name', 'domain-name-servers', 'host-name'),
|
||||
'ipv6': ('dhcp6.name-servers',
|
||||
'dhcp6.domain-search')},
|
||||
'requires': {'ipv4': tuple(),
|
||||
'ipv6': tuple()}},
|
||||
'dhcpcd': {'default_opts': ('hostname', 'duid', 'persistent', 'slaac private', 'noipv4ll'),
|
||||
# dhcpcd -V to display variables.
|
||||
# "option <foo>", prepend "dhcp6_" for ipv6. if no ipv6 opts present, same are mapped to ipv6.
|
||||
# But we explicitly add them for munging downstream.
|
||||
'requests': {'ipv4': ('rapid_commit', 'domain_name_servers', 'domain_name', 'domain_search',
|
||||
'host_name', 'classless_static_routes', 'interface_mtu'),
|
||||
'ipv6': ('dhcp6_rapid_commit', 'dhcp6_domain_name_servers', 'dhcp6_domain_name',
|
||||
'dhcp6_domain_search', 'dhcp6_host_name', 'dhcp6_classless_static_routes',
|
||||
'dhcp6_interface_mtu')},
|
||||
# "require <foo>"
|
||||
'requires': {'ipv4': ('dhcp_server_identifier', ),
|
||||
'ipv6': tuple()}}}
|
||||
self._initAddrs()
|
||||
self._initResolvers()
|
||||
self._initRoutes()
|
||||
|
||||
def _initAddrs(self):
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
for a in self.xml.findall('addresses/{0}/address'.format(addrtype)):
|
||||
addrset = convertIpTuples(a)
|
||||
if addrset not in self.addrs[addrtype]:
|
||||
self.addrs[addrtype].append(addrset)
|
||||
return()
|
||||
|
||||
def _initCfg(self):
|
||||
# A dummy method; this is overridden by the subclasses.
|
||||
# It's honestly here to make my IDE stop complaining. :)
|
||||
pass
|
||||
return()
|
||||
|
||||
def _initConnCfg(self):
|
||||
# A dummy method; this is overridden by the subclasses.
|
||||
# It's honestly here to make my IDE stop complaining. :)
|
||||
pass
|
||||
return()
|
||||
|
||||
def _initResolvers(self):
|
||||
resolvers_xml = self.xml.find('resolvers')
|
||||
if resolvers_xml:
|
||||
for r in resolvers_xml.findall('resolver'):
|
||||
resolver = ipaddress.ip_address(r.text.strip())
|
||||
if resolver not in self.resolvers:
|
||||
self.resolvers.append(resolver)
|
||||
return()
|
||||
|
||||
def _initRoutes(self):
|
||||
routes_xml = self.xml.find('routes')
|
||||
if routes_xml:
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
for a in self.xml.findall('routes/{0}/route'.format(addrtype)):
|
||||
addrset = convertIpTuples(a)
|
||||
if addrset not in self.routes[addrtype]:
|
||||
self.routes[addrtype].append(addrset)
|
||||
return()
|
||||
|
@ -1,8 +1,11 @@
|
||||
import os
|
||||
|
||||
|
||||
class Network(object):
|
||||
def __init__(self, network_xml):
|
||||
self.xml = network_xml
|
||||
self.hostname = self.xml.attrib['hostname']
|
||||
self.provider = self.xml.attrib.get('provider', 'netctl')
|
||||
self.hostname = self.xml.attrib['hostname'].strip()
|
||||
self.provider = self.xml.attrib.get('provider', 'systemd').strip()
|
||||
handler = None
|
||||
if self.provider == 'netctl':
|
||||
import aif.network.netctl as handler
|
||||
@ -24,3 +27,14 @@ class Network(object):
|
||||
elif e.tag == 'wireless':
|
||||
conn = self.provider.Wireless(e)
|
||||
self.connections.append(conn)
|
||||
|
||||
def apply(self, chroot_base):
|
||||
cfg = os.path.join(chroot_base, 'etc', 'hostname')
|
||||
with open(cfg, 'w') as fh:
|
||||
fh.write('{0}\n'.format(self.hostname))
|
||||
os.chown(cfg, 0, 0)
|
||||
os.chmod(cfg, 0o0644)
|
||||
# TODO: symlinks for systemd for provider
|
||||
# TODO: writeConf for provider
|
||||
|
||||
return()
|
||||
|
@ -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()
|
@ -1,4 +1,7 @@
|
||||
import ipaddress
|
||||
import socket
|
||||
|
||||
|
||||
##
|
||||
# We have to use Jinja2 because while there are ways to *parse* an INI with duplicate keys
|
||||
# (https://stackoverflow.com/a/38286559/733214), there's no way to *write* an INI with them using configparser.
|
||||
# So we use Jinja2 logic.
|
||||
import jinja2
|
||||
|
@ -1,59 +1,33 @@
|
||||
import configparser
|
||||
import datetime
|
||||
import ipaddress
|
||||
import os
|
||||
import uuid
|
||||
##
|
||||
import aif.utils
|
||||
|
||||
# TODO: auto dev assignment
|
||||
import aif.network._common
|
||||
|
||||
|
||||
class Connection(object):
|
||||
class Connection(aif.network._common.BaseConnection):
|
||||
def __init__(self, iface_xml):
|
||||
self.xml = iface_xml
|
||||
self.id = self.xml.attrib['id']
|
||||
self.device = self.xml.attrib['device']
|
||||
self.is_defroute = aif.utils.xmlBool(self.xml.attrib.get('defroute', 'false'))
|
||||
self.domain = self.xml.attrib.get('searchDomain', None)
|
||||
self._cfg = None
|
||||
self.connection_type = None
|
||||
super().__init__(iface_xml)
|
||||
self.provider_type = 'NetworkManager'
|
||||
self.addrs = {'ipv4': set(),
|
||||
'ipv6': set()}
|
||||
self.resolvers = []
|
||||
self.packages = set('networkmanager')
|
||||
self.services = {
|
||||
('/usr/lib/systemd/system/NetworkManager.service'): ('etc/systemd/system/'
|
||||
'multi-user.target.wants/'
|
||||
'NetworkManager.service'),
|
||||
('/usr/lib/systemd/system/NetworkManager-dispatcher.service'): ('etc/systemd/system/'
|
||||
'dbus-org.freedesktop.'
|
||||
'nm-dispatcher.service'),
|
||||
('/usr/lib/systemd/system/NetworkManager-wait-online.service'): ('etc/systemd/'
|
||||
'system/'
|
||||
'network-online.target.wants/'
|
||||
'NetworkManager-wait-online.service')}
|
||||
self.uuid = uuid.uuid4()
|
||||
self._initAddrs()
|
||||
self._initResolvers()
|
||||
|
||||
def _initAddrs(self):
|
||||
# These tuples follow either:
|
||||
# ('dhcp'/'dhcp6'/'slaac', None, None) for auto configuration
|
||||
# (ipaddress.IPv4/6Address(IP), CIDR, ipaddress.IPv4/6Address(GW)) for static configuration
|
||||
for addrtype in ('ipv4', 'ipv6'):
|
||||
for a in self.xml.findall('addresses/{0}/address'.format(addrtype)):
|
||||
if a.text in ('dhcp', 'dhcp6', 'slaac'):
|
||||
addr = a.text
|
||||
net = None
|
||||
gw = None
|
||||
else:
|
||||
components = a.text.split('/')
|
||||
if len(components) > 2:
|
||||
raise ValueError('Invalid IP/CIDR format: {0}'.format(a.text))
|
||||
if len(components) == 1:
|
||||
addr = components[0]
|
||||
if addrtype == 'ipv4':
|
||||
components.append('24')
|
||||
elif addrtype == 'ipv6':
|
||||
components.append('64')
|
||||
addr = ipaddress.ip_address(components[0])
|
||||
net = ipaddress.ip_network('/'.join(components), strict = False)
|
||||
gw = ipaddress.ip_address(a.attrib.get('gateway'))
|
||||
self.addrs[addrtype].add((addr, net, gw))
|
||||
self.addrs[addrtype] = list(self.addrs[addrtype])
|
||||
return()
|
||||
|
||||
def _initCfg(self):
|
||||
if self.device == 'auto':
|
||||
self.device = aif.network._common.getDefIface(self.connection_type)
|
||||
self._cfg = configparser.ConfigParser()
|
||||
self._cfg.optionxform = str
|
||||
self._cfg['connection'] = {'id': self.id,
|
||||
@ -63,61 +37,60 @@ class Connection(object):
|
||||
'permissions': '',
|
||||
'timestamp': datetime.datetime.utcnow().timestamp()}
|
||||
# We *theoretically* could do this in _initAddrs() but we do it separately so we can trim out duplicates.
|
||||
# TODO: rework this? we technically don't need to split in ipv4/ipv6 since ipaddress does that for us.
|
||||
for addrtype, addrs in self.addrs.items():
|
||||
self._cfg[addrtype] = {}
|
||||
cidr_gws = {}
|
||||
# Routing
|
||||
if not self.is_defroute:
|
||||
self._cfg[addrtype]['never-default'] = 'true'
|
||||
if not self.auto['routes'][addrtype]:
|
||||
self._cfg[addrtype]['ignore-auto-routes'] = 'true'
|
||||
# DNS
|
||||
self._cfg[addrtype]['dns-search'] = (self.domain if self.domain else '')
|
||||
if not self.auto['resolvers'][addrtype]:
|
||||
self._cfg[addrtype]['ignore-auto-dns'] = 'true'
|
||||
# Address handling
|
||||
if addrtype == 'ipv6':
|
||||
self._cfg[addrtype]['addr-gen-mode'] = 'stable-privacy'
|
||||
if not addrs:
|
||||
if not addrs and not self.auto['addresses'][addrtype]:
|
||||
self._cfg[addrtype]['method'] = 'ignore'
|
||||
elif self.auto['addresses'][addrtype]:
|
||||
if addrtype == 'ipv4':
|
||||
self._cfg[addrtype]['method'] = 'auto'
|
||||
else:
|
||||
self._cfg[addrtype]['method'] = ('auto' if self.auto['addresses'][addrtype] == 'slaac'
|
||||
else 'dhcp6')
|
||||
else:
|
||||
self._cfg[addrtype]['method'] = 'manual'
|
||||
for idx, (ip, cidr, gw) in enumerate(addrs):
|
||||
if cidr not in cidr_gws.keys():
|
||||
cidr_gws[cidr] = gw
|
||||
new_cidr = True
|
||||
else:
|
||||
new_cidr = False
|
||||
if addrtype == 'ipv4':
|
||||
if ip == 'dhcp':
|
||||
self._cfg[addrtype]['method'] = 'auto'
|
||||
continue
|
||||
elif addrtype == 'ipv6':
|
||||
if ip == 'dhcp6':
|
||||
self._cfg[addrtype]['method'] = 'dhcp'
|
||||
continue
|
||||
elif ip == 'slaac':
|
||||
self._cfg[addrtype]['method'] = 'auto'
|
||||
continue
|
||||
addrnum = idx + 1
|
||||
addr_str = '{0}/{1}'.format(str(ip), str(cidr.prefixlen))
|
||||
if new_cidr:
|
||||
addr_str = '{0},{1}'.format(addr_str, str(gw))
|
||||
self._cfg[addrtype]['address{0}'.format(addrnum)] = addr_str
|
||||
for r in self.resolvers:
|
||||
if addrtype == 'ipv{0}'.format(r.version):
|
||||
for idx, (ip, cidr, gw) in enumerate(addrs):
|
||||
if cidr not in cidr_gws.keys():
|
||||
cidr_gws[cidr] = gw
|
||||
new_cidr = True
|
||||
else:
|
||||
new_cidr = False
|
||||
addrnum = idx + 1
|
||||
addr_str = '{0}/{1}'.format(str(ip), str(cidr.prefixlen))
|
||||
if new_cidr:
|
||||
addr_str = '{0},{1}'.format(addr_str, str(gw))
|
||||
self._cfg[addrtype]['address{0}'.format(addrnum)] = addr_str
|
||||
# Resolvers
|
||||
for resolver in self.resolvers:
|
||||
if addrtype == 'ipv{0}'.format(resolver.version):
|
||||
if 'dns' not in self._cfg[addrtype]:
|
||||
self._cfg[addrtype]['dns'] = []
|
||||
self._cfg[addrtype]['dns'].append(str(r))
|
||||
self._cfg[addrtype]['dns'].append(str(resolver))
|
||||
if 'dns' in self._cfg[addrtype].keys():
|
||||
self._cfg[addrtype]['dns'] = '{0};'.format(';'.join(self._cfg[addrtype]['dns']))
|
||||
# Routes
|
||||
for idx, (dest, net, gw) in self.routes[addrtype]:
|
||||
routenum = idx + 1
|
||||
self._cfg[addrtype]['route{0}'.format(routenum)] = '{0}/{1},{2}'.format(str(dest),
|
||||
str(net.prefixlen),
|
||||
str(gw))
|
||||
self._initConnCfg()
|
||||
return()
|
||||
|
||||
def _initConnCfg(self):
|
||||
# A dummy method; this is overridden by the subclasses.
|
||||
# It's honestly here to make my IDE stop complaining. :)
|
||||
pass
|
||||
return()
|
||||
|
||||
def _initResolvers(self):
|
||||
for r in self.xml.findall('resolvers/resolver'):
|
||||
resolver = ipaddress.ip_address(r.text)
|
||||
if resolver not in self.resolvers:
|
||||
self.resolvers.append(resolver)
|
||||
return()
|
||||
|
||||
def writeConf(self, chroot_base):
|
||||
cfgroot = os.path.join(chroot_base, 'etc', 'NetworkManager')
|
||||
cfgdir = os.path.join(cfgroot, 'system-connections')
|
||||
@ -146,7 +119,8 @@ class Ethernet(Connection):
|
||||
self._initCfg()
|
||||
|
||||
def _initConnCfg(self):
|
||||
pass
|
||||
self._cfg[self.connection_type] = {'mac-address-blacklist': ''}
|
||||
return()
|
||||
|
||||
|
||||
class Wireless(Connection):
|
||||
@ -156,4 +130,27 @@ class Wireless(Connection):
|
||||
self._initCfg()
|
||||
|
||||
def _initConnCfg(self):
|
||||
pass
|
||||
self._cfg['wifi'] = {'mac-address-blacklist': '',
|
||||
'mode': 'infrastructure',
|
||||
'ssid': self.xml.attrib['essid']}
|
||||
try:
|
||||
bssid = self.xml.attrib.get('bssid').strip()
|
||||
except AttributeError:
|
||||
bssid = None
|
||||
if bssid:
|
||||
bssid = aif.network._common.canonizeEUI(bssid)
|
||||
self._cfg['wifi']['bssid'] = bssid
|
||||
self._cfg['wifi']['seen-bssids'] = '{0};'.format(bssid)
|
||||
crypto = self.xml.find('encryption')
|
||||
if crypto:
|
||||
self.packages.add('wpa_supplicant')
|
||||
self._cfg['wifi-security'] = {}
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto)
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
# TODO: WPA2 enterprise
|
||||
self._cfg['wifi-security']['key-mgmt'] = 'wpa-psk'
|
||||
# if crypto['type'] in ('wep', 'wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
self._cfg['wifi-security']['psk'] = crypto['auth']['psk']
|
||||
return()
|
||||
|
18
aif/utils.py
18
aif/utils.py
@ -1,6 +1,8 @@
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
##
|
||||
import psutil
|
||||
@ -55,6 +57,22 @@ def isPowerofTwo(n):
|
||||
return(isPowerOf2)
|
||||
|
||||
|
||||
def kernelCmdline(chroot_base = '/'):
|
||||
cmds = {}
|
||||
chroot_base = pathlib.PosixPath(chroot_base)
|
||||
cmdline = chroot_base.joinpath('proc', 'cmdline')
|
||||
if not os.path.isfile(cmdline):
|
||||
return(cmds)
|
||||
with open(cmdline, 'r') as fh:
|
||||
raw_cmds = fh.read().strip()
|
||||
for c in shlex.split(raw_cmds):
|
||||
l = c.split('=', 1)
|
||||
if len(l) < 2:
|
||||
l.append(None)
|
||||
cmds[l[0]] = l[1]
|
||||
return(cmds)
|
||||
|
||||
|
||||
def kernelFilesystems():
|
||||
# I wish there was a better way of doing this.
|
||||
# https://unix.stackexchange.com/a/98680
|
||||
|
@ -571,6 +571,9 @@ Using `start`/`stop` attributes makes sense for disk partitions because they ope
|
||||
|
||||
LVM (LVs, in particular), however, aren't consecutive. There *is* no concept of a "start" and "stop" for an LV; LVM uses chunks called "(physical) extents" rather than sectors, and VGs don't have geometry since they're essentially a pool of blocks. This is also why the modifiers like `-` and `+` aren't allowed for LV sizes - they're position-based.
|
||||
|
||||
=== "How do I specify packages from the AUR?"
|
||||
You'd have to https://wiki.archlinux.org/index.php/Makepkg[build the package(s)^], https://wiki.archlinux.org/index.php/Pacman/Tips_and_tricks#Custom_local_repository[set up a repository^], serve it via e.g. https://www.nginx.com/[nginx^], and add it as a repo (`/aif/pacman/repos/repo`) first. Then you can specify the package as normal as a `/aif/pacman/software/package` item.
|
||||
|
||||
=== "Why aren't the network settings in <network> being applied during install?"
|
||||
Simply put, a logical race condition. In order for probably 90+% of AIF-NG deploys to bootstrap, they fetch their XML configuration via a network URI (rather than a file URI). This means it needs a network connection that pre-exists in the *install environment* (LiveCD, LiveUSB, PXE/iPXE, etc.) before it even knows what network configuration you want the *persistent environment* to have.
|
||||
|
||||
@ -580,6 +583,23 @@ If you desire the configuration to be applied *during* the install, you can do i
|
||||
|
||||
If you wish to SSH into the install environment to check the status/progress of the install, it is recommended that you set up a static lease (if using DHCP) or use SLAAC (if using IPv6) beforehand and configure your install environment beforehand. Remember, AIF-NG only *installs* Arch Linux; it tries very hard to *not* interact with the install environment.
|
||||
|
||||
=== "Why isn't enabling/disabling automatic DNS resolvers working?"
|
||||
This is going to be highly unpredictable based on the networking provider you choose. This is a limitation of underlying network provider intercompatibility, resolver libraries, and technology architecture. This may be changed in the future, but because of how DNS servers are handled via DHCP/RDNSS and glibc (and the fact that IPv4 resolver addresses can serve IPv6 -- e.g. AAAA -- records and vice versa) and inherent limitations in some network providers like netctl, I wouldn't hold your breath.
|
||||
|
||||
=== "I'm using netctl as my network provider, and-"
|
||||
I'ma let you finish, but netctl is a *really* simple network provider. I mean REALLY simple. As such, a lot of things (like mixing auto DNS and non-auto addressing) don't work at all feasibly, and probably might not ever. It's great for simple and flat configurations (i.e. all static everything, all automatic everything, etc.) and I even use it on my own machines where I can, but it just simply doesn't make allowances for more complex setups. (This is why init scripts were replaced by systemd for init, remember? Script-and-shell-based utilities, such as netctl -- seriously, the entire thing's written in Bash -- just can't handle more complex jobs reliably.)
|
||||
|
||||
If you need more advanced functionality but don't want a lot of cruft or bloat, I recommend `systemd` as your network provider. It requires no extra packages (other than wpa_supplicant, if you're using wireless) because it's part of the systemd package (which is part of the most basic install of Arch) and handles more advanced configurations a lot more reliably.
|
||||
|
||||
=== "How do I specify WEP for a wireless network?"
|
||||
You can't. WEP's pretty broken. I understand some legacy networks may still use it, but I'm incredibly uncomfortable supporting it.
|
||||
|
||||
If absolutely necessary, you can manually configure it yourself via a `/aif/scripts/post/script` script.
|
||||
|
||||
=== "How do I connect to a WPA2 Enterprise network?"
|
||||
You can't, currently; support is only stubbed out for now. If absolutely necessary, you can manually configure it yourself via a `/aif/scripts/post/script` script.
|
||||
|
||||
This hopefully will be changed in the future, however, as I'm interested in adding support. For now, WPA/WPA2 PSK only are considered supported.
|
||||
|
||||
== Bug Reports/Feature Requests
|
||||
NOTE: It is possible to submit a bug or feature request without registering in my bugtracker. One of my pet peeves is needing to create an account/register on a bugtracker simply to report a bug! The following links only require an email address to file a bug (which is necessary in case I need any further clarification from you or to keep you updated on the status of the bug/feature request -- so please be sure to use a valid email address).
|
||||
|
@ -122,16 +122,23 @@
|
||||
<network hostname="aiftest.square-r00t.net" provider="netctl">
|
||||
<ethernet id="lan" device="auto" defroute="true" searchDomain="domain.tld">
|
||||
<addresses>
|
||||
<ipv4>
|
||||
<address>dhcp</address>
|
||||
<ipv4 auto="true">
|
||||
<address gateway="192.168.1.1">192.168.1.5/24</address>
|
||||
</ipv4>
|
||||
<ipv6>
|
||||
<address>slaac</address>
|
||||
<ipv6 auto="slaac">
|
||||
<address>fde4:16b9:654b:bbfa::15/64</address>
|
||||
</ipv6>
|
||||
</addresses>
|
||||
<resolvers noAuto="true">
|
||||
<routes>
|
||||
<ipv4 defaultGateway="true" auto="true">
|
||||
<route gateway="192.168.1.1">10.1.1.0/24</route>
|
||||
<route gateway="10.1.1.4">172.16.1.20/32</route>
|
||||
</ipv4>
|
||||
<ipv6 defaultGateway="false" auto="true"/>
|
||||
</routes>
|
||||
<resolvers>
|
||||
<ipv4 auto="false"/>
|
||||
<ipv6 auto="false"/>
|
||||
<resolver>64.6.64.6</resolver>
|
||||
<resolver>4.2.2.1</resolver>
|
||||
<resolver>8.8.8.8</resolver>
|
||||
@ -140,10 +147,11 @@
|
||||
<wireless id="wlan" device="wlp2s0" essid="MyWirelessLan"
|
||||
bssid="00-00-5E-00-53-00" defroute="false" searchDomain="wifi.lan">
|
||||
<addresses>
|
||||
<ipv4>
|
||||
<address>dhcp</address>
|
||||
</ipv4>
|
||||
<ipv4 auto="true"/>
|
||||
</addresses>
|
||||
<routes>
|
||||
<ipv6 defaultGateway="true" auto="true"/>
|
||||
</routes>
|
||||
<encryption>
|
||||
<type>wpa2</type>
|
||||
<mode>personal</mode>
|
||||
|
Loading…
Reference in New Issue
Block a user