okay. let's give this a shot.
This commit is contained in:
parent
4df9287abd
commit
80765e58ed
@ -1,4 +1,3 @@
|
||||
* fix creds
|
||||
** needs user/password, and the updateKey is unique per-tunnel so move it into an element in there.
|
||||
** need to get user/password into HEConf somehow. if i can get ?tid= working for the URL, that'd be perfect.
|
||||
^ need to use updateKey for tunnel-specific xml
|
||||
DHCPv6:
|
||||
* NTP server in <ra>? (dnsmasq: option6:ntpserver,...)
|
||||
* bootfile-(url|param) in <ra>? (dnsmasq: option6:*)
|
||||
|
@ -233,6 +233,12 @@ class Config(BaseConfig):
|
||||
tun_creds_id = tun_xml.attrib['creds']
|
||||
creds = self.creds[tun_creds_id]
|
||||
update_key = tun_xml.find('updateKey').text.strip()
|
||||
# TODO: do I instead want to use HEConfig() and fetch the single unified config?
|
||||
# Pros:
|
||||
# * I wouldn't completely die on a misconfigured tunnel in the user config.
|
||||
# Cons:
|
||||
# * We'd have to skip missing tunnels (bad auth at HE, etc.)
|
||||
# * We would use more memory and take more time during init.
|
||||
he_conf = HETunnelConfig(tun_id, creds, update_key)
|
||||
tun = tunnel.Tunnel(tun_xml, he_conf, self.creds[tun_creds_id])
|
||||
self.tunnels[tun_id] = tun
|
||||
|
@ -48,6 +48,8 @@
|
||||
the "ra" child element under <assign> for further details.
|
||||
If you are using dnsmasq, you will want to edit dnsmasq.conf to *include* the generated file, most likely, as it
|
||||
only generates configuration for IPv6 options.
|
||||
If this is not specified, NO RA/DHCPv6 management will be done *regardless* of any "re" child elements for below
|
||||
"assign" objects.
|
||||
-->
|
||||
<assignments raProvider="dnsmasq">
|
||||
<!--
|
||||
@ -76,27 +78,50 @@
|
||||
resolver via RDNSS.
|
||||
It takes one (optional) attribute, "domains", which is a space-separated list of search domains, referred
|
||||
to in IPv6 as DNSSL (RFC 6106).
|
||||
Note that Windows does not support DNSSL, and as such you must use dhcpv6's "domains" attribute if you wish
|
||||
to do that.
|
||||
Note that Windows does not support DNSSL properly, and as such you must use dnsmasq as your RA provider if
|
||||
you wish to send search domains.
|
||||
If "domains" is specified but the element is false, the configuration will only advertise DNSSL and not
|
||||
RDNSS.
|
||||
If you also specify dhcpv6 below and are using dnsmasq as your raProvider, then:
|
||||
* the same domains will be sent via DHCPv6 option 24
|
||||
* the same RDNSS resolver will be passed via DHCPv6 option 23
|
||||
-->
|
||||
<dns domains="foo.com bar.com">true</dns>
|
||||
<!--
|
||||
Enable DHCPv6 for this assignment. Only used for dnsmasq, has no effect for radvd. As mentioned above, you
|
||||
can also specify the "domains" attribute here as well, which will pass them via a regular DHCPv6 option.
|
||||
If "domains" is specified but the element is false, only the domains will be passed.
|
||||
Again, this only pertains to dnsmasq since radvd offers no DHCPv6 capabilities whatsoever.
|
||||
Enable DHCPv6 for this assignment.
|
||||
|
||||
RADVD:
|
||||
If you're using radvd, this will only enable the "AdvManagedFlag" and/or "AdvOtherConfigFlag" flags
|
||||
(the "MO" bits). *No actual DHCPv6 address assignment will, or can, occur via radvd, only SLAAC.*
|
||||
|
||||
DNSMASQ:
|
||||
To ensure maximum compatability with SLAAC, addresses will be served in the fixed range of:
|
||||
<PREFIX>:dead:beef:cafe:[0000-FFFF]
|
||||
(65535 addresses per prefix assignment, a.k.a. a /112).
|
||||
Obviously your assignment's prefix length *must* be smaller than /112 (but should be at LEAST a /64 anyways
|
||||
per RFC specification). Regardless of settings below, SLAAC *will* be offered if an "ra" element is defined.
|
||||
|
||||
It has an optional attribute, "advOther", which controls the "Other Configuration" bit.
|
||||
The default is "false".
|
||||
The "MO" bits (RFC 4861 § 4.2) are set accordingly:
|
||||
===================================================================================================
|
||||
| Condition | M | O | Will addresses be assigned via DHCPv6 (if dnsmasq)? |
|
||||
===================================================================================================
|
||||
| advOther="true", dhcpv6 is true | 1 | 1 | Yes |
|
||||
| advOther="true", dhcpv6 is false | 0 | 1 | No |
|
||||
| advOther="false", dhcpv6 is false | 0 | 0 | No |
|
||||
| advOther="false", dhcpv6 is true | 1 | 0 | Yes |
|
||||
===================================================================================================
|
||||
-->
|
||||
<dhcpv6 domains="foo.com bar.com">true</dhcpv6>
|
||||
<dhcpv6 advOther="true">true</dhcpv6>
|
||||
</ra>
|
||||
</assign>
|
||||
<!-- Disable RA for this set (no "ra" chiled specified). -->
|
||||
<!-- Disable RA for this set (no "ra" child specified). -->
|
||||
<assign prefix="64" alloc="48" iface="eth0"/>
|
||||
<assign prefix="64" alloc="48" iface="eth1">
|
||||
<ra tag="vmlan">
|
||||
<!-- This will use strictly SLAAC (if using dnsmasq, obviously - radvd only does SLAAC). -->
|
||||
<dhcpv6>false</dhcpv6>
|
||||
<dhcpv6 advOther="false">false</dhcpv6>
|
||||
<!-- And let clients choose their own resolver. -->
|
||||
<dns>false</dns>
|
||||
</ra>
|
||||
@ -105,7 +130,7 @@
|
||||
<ra tag="wlan">
|
||||
<!-- Only pass RDNSS resolvers. -->
|
||||
<dns>true</dns>
|
||||
<dhcpv6>false</dhcpv6>
|
||||
<dhcpv6 advOther="false">false</dhcpv6>
|
||||
</ra>
|
||||
</assign>
|
||||
</assignments>
|
||||
@ -116,7 +141,8 @@
|
||||
<assignments>
|
||||
<!--
|
||||
Uses the default prefix of /64 from your standard /64 allocation from Hurricane Electric.
|
||||
Most users probably want this unless they're running an IPv6 router.
|
||||
Most users probably want this if they just want IPv6 for their local computer unless they're running an IPv6
|
||||
router.
|
||||
-->
|
||||
<assign iface="eth0"/>
|
||||
</assignments>
|
||||
|
@ -43,14 +43,8 @@ class RAConf(object):
|
||||
return(None)
|
||||
|
||||
def generate(self, assignments):
|
||||
ns = {}
|
||||
for a in assignments:
|
||||
if len(a.iface_addrs) > 3:
|
||||
ns_addrs = a.iface_addrs[:3]
|
||||
else:
|
||||
ns_addrs = a.iface_addrs
|
||||
ns[a.iface] = ns_addrs
|
||||
self.cfgstr = self.tpl.render(assignments = assignments, nameservers = ns)
|
||||
self.cfgstr = self.tpl.render(assignments = assignments)
|
||||
return(None)
|
||||
|
||||
def write(self):
|
||||
if not self.cfgstr:
|
||||
|
@ -32,3 +32,10 @@
|
||||
# https://tools.ietf.org/html/rfc5175
|
||||
# https://tools.ietf.org/html/rfc6104
|
||||
# https://tools.ietf.org/html/rfc7772
|
||||
# DHCPv6
|
||||
# https://tools.ietf.org/html/rfc3315
|
||||
# https://tools.ietf.org/html/rfc3646
|
||||
# https://tools.ietf.org/html/rfc4649
|
||||
# https://tools.ietf.org/html/rfc8415
|
||||
##
|
||||
# https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/ipv6_fhsec/configuration/xe-3s/ip6f-xe-3s-book/ip6-rfcs.pdf
|
||||
|
@ -5,12 +5,19 @@
|
||||
{%- set mtu = 1480 -%}
|
||||
{#- Minimum seconds allowed between sending unsolicited multicast RAs. 3 < x < (0.75 * max_inter) -#}
|
||||
{#- If using Mobile Extensions, 0.33 < x (0.75 * max_inter) -#}
|
||||
{%- set min_inter = 60 -%}
|
||||
{%- set min_inter = 10 -%}
|
||||
{#- Maximum seconds allowed between sending unsolicited multicast RAs. 4 < x < 1800 -#}
|
||||
{#- If using Mobile Extensions, 0.07 < x 1800 -#}
|
||||
{%- set max_inter = 600 -%}
|
||||
{%- set max_inter = 60 -%}
|
||||
{#- Minimum seconds between sending multicast RAs (solicited and unsolicited). -#}
|
||||
{#- If using Mobile Extensions, 0.03 < x -#}
|
||||
{%- set min_delay = 3 -%}
|
||||
{#- The lifetime associated with the default router in units of seconds. 0 OR max_inter < x < 9000 -#}
|
||||
{%- set lifetime = 9000 -%}
|
||||
{#- ## DHCPv6 OPTIONS ## -#}
|
||||
{#- Obviously, these only works for DNSMasq. -#}
|
||||
{#- Obviously, these only work for DNSMasq. -#}
|
||||
{#- How long the lease should last until a new one is requested. -#}
|
||||
{#- This is also used for *SLAAC addresses* in radvd. -#}
|
||||
{%- set lease_life = 21600 -%}{#- 6 hours -#}
|
||||
{#- How long should the options be valid for. -#}
|
||||
{%- set opts_life = lease_life -%}
|
||||
|
@ -1,2 +1,49 @@
|
||||
{%- import '_common.j2' as common_opts with context -%}
|
||||
# This file should be *included* in your dnsmasq configuration.
|
||||
# Generated by he_ipv6.
|
||||
# See "dnsmasq --help dhcp6" for matching option identifers ("dhcp-option = ..., option6: <option>").
|
||||
enable-ra
|
||||
{% for assignment in assignments %}
|
||||
{%- set assign_loop = loop -%}
|
||||
{%- set ra_opts = [] -%}
|
||||
{%- if assignment.ra_tag -%}
|
||||
{%- set id_set = 'tag:' + assignment.ra_tag -%}
|
||||
{%- set identifier = id_set -%}
|
||||
{%- set do_listen = false -%}
|
||||
{%- else -%}
|
||||
{%- set id_set = 'set:' + assignment.iface -%}
|
||||
{%- set identifier = 'tag:' + assignment.iface -%}
|
||||
{%- set do_listen = true -%}
|
||||
{%- endif -%}
|
||||
{%- if assignment.ra_dns -%}
|
||||
{%- do ra_opts.append('sa-names') -%}
|
||||
{%- endif -%}
|
||||
{%- if assignment.ra_dhcp is false -%}
|
||||
{%- do ra_opts.append('ra-only') -%}
|
||||
{%- if assignment.ra_other is true -%}
|
||||
{%- do ra_opts.append('ra-stateless') -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- do ra_opts.append('slaac') -%}
|
||||
{%- do ra_opts.append('ra-names') -%}
|
||||
# {{ assignment.iface }} assignment
|
||||
{%- if do_listen %}
|
||||
listen = {{ assignment.iface_ll }}
|
||||
{%- endif %}
|
||||
ra-param = {{ assignment.iface }}, mtu:{{ common_opts.mtu }}, high, {{ common_opts.min_delay }}, {{ common_opts.lifetime }}
|
||||
{%- if assignment.ra_dhcp %}
|
||||
{%- for block in assignment.iface_blocks %}
|
||||
dhcp-range = {{ id_set }}, {{ assignment.dhcp6_ranges[assign_loop.index0]|join(', ') }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
dhcp-range = {{ id_set }}, ::, constructor={{ assignment.iface }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}{#- TODO: check this. #}
|
||||
{%- endif %}
|
||||
dhcp-option = {{ identifier }}, option6:information-refresh-time, {{ common_opts.opts_life }}
|
||||
{%- if assignment.ra_dns %}
|
||||
dhcp-option = {{ identifier }}, option6:dns-server, [{{ assignment.iface_ll }}]
|
||||
{%- endif %}
|
||||
{%- if assignment.ra_domains %}
|
||||
dhcp-option = {{ identifier }}, option6:domain-search, {{ assignment.ra_domains|join(',') }}
|
||||
{%- endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
@ -1,30 +1,44 @@
|
||||
# Generated by he_ipv6
|
||||
{% for assign in assignments %}
|
||||
{% for assignment in assign.iface_blocks %}
|
||||
{%- import '_common.j2' as common_opts with context -%}
|
||||
# Generated by he_ipv6.
|
||||
# This may go wonky with multiple assignments on the same iface.
|
||||
{% for assignment in assignments %}
|
||||
interface {{ assignment.iface }} {
|
||||
AdvSendAdvert on;
|
||||
# Is it 1480 or 1280? Arch wiki says 1480, but everything else (older) says 1280.
|
||||
# AdvLinkMTU 1280;
|
||||
AdvLinkMTU 1480;
|
||||
MinRtrAdvInterval 60;
|
||||
MaxRtrAdvInterval 600;
|
||||
AdvDefaultLifetime 9000;
|
||||
AdvLinkMTU {{ common_opts.mtu }};
|
||||
MinRtrAdvInterval {{ common_opts.min_inter }};
|
||||
MaxRtrAdvInterval {{ common_opts.max_inter }};
|
||||
MinDelayBetweenRAs {{ common_opts.min_delay }};
|
||||
AdvDefaultLifetime {{ common_opts.lifetime }};
|
||||
{%- if assignment.ra_dhcp is true -%}
|
||||
AdvManagedFlag on;
|
||||
{%- endif %}
|
||||
{%- if assignment.ra_other is true -%}
|
||||
AdvOtherConfigFlag on;
|
||||
{%- endif %}
|
||||
|
||||
{%- for block in assignment.iface_blocks %}
|
||||
prefix {{ block|string }} {
|
||||
AdvOnLink on;
|
||||
{%- if block.prefixlen == 64 %}
|
||||
{%- if block.prefixlen <= 64 %}
|
||||
AdvAutonomous on;
|
||||
{%- endif %}
|
||||
AdvValidLifetime {{ common_opts.lease_life }};
|
||||
AdvPreferredLifetime {{ common_opts.lease_life }};
|
||||
AdvRouterAddr off;
|
||||
};
|
||||
{%- endfor %}
|
||||
|
||||
{%- if ra.dns is true %}
|
||||
RDNSS {{ nameservers[assignment.iface]|join(' ') }} {
|
||||
{%- if assign.ra_dns is true %}
|
||||
RDNSS {{ assignment.iface_ll }} {
|
||||
AdvRDNSSLifetime {{ common_opts.opts_life }};
|
||||
};
|
||||
{%- endif %}
|
||||
|
||||
{%- if assign.ra_domains %}
|
||||
DNSSL {{ assignment.ra_domains|join(' ') }} {
|
||||
AdvDNSSLLifetime {{ common_opts.opts_life }};
|
||||
};
|
||||
{%- endif %}
|
||||
};
|
||||
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import ipaddress
|
||||
import socket
|
||||
##
|
||||
import netaddr
|
||||
from pyroute2 import IPRoute
|
||||
##
|
||||
from . import utils
|
||||
from . import ra
|
||||
from . import utils
|
||||
|
||||
|
||||
class IP(object):
|
||||
@ -51,14 +52,28 @@ class IP6(IP):
|
||||
|
||||
|
||||
class Assignment(object):
|
||||
def __init__(self, assign_xml, ra = False, dns = False, ra_provider = 'dnsmasq'):
|
||||
def __init__(self,
|
||||
assign_xml,
|
||||
ra_dns = False,
|
||||
ra_dhcp = False,
|
||||
ra_other = False,
|
||||
ra_tag = None,
|
||||
ra_domains = None):
|
||||
self.xml = assign_xml
|
||||
self.ra = ra
|
||||
self.dns = dns
|
||||
self.ra_dns = ra_dns
|
||||
self.ra_tag = ra_tag
|
||||
self.ra_other = ra_other
|
||||
self.ra_domains = set()
|
||||
if isinstance(ra_domains, list):
|
||||
self.ra_domains.update(ra_domains)
|
||||
elif isinstance(ra_domains, str):
|
||||
self.ra_domains.update([i.lower().strip() for i in ra_domains if i.strip() != ''])
|
||||
self.ra_dhcp = ra_dhcp
|
||||
self.iface = None
|
||||
self.iface_ll = None
|
||||
self.iface_idx = None
|
||||
self.iface_addrs = []
|
||||
self.iface_blocks = []
|
||||
self.dhcp6_ranges = []
|
||||
self.alloc = None # This must be set externally to a mapped Allocation instance
|
||||
self.alloc_id = None
|
||||
self.prefix = None
|
||||
@ -74,6 +89,16 @@ class Assignment(object):
|
||||
self.iface = _iface_txt.strip()
|
||||
ipr = IPRoute()
|
||||
self.iface_idx = ipr.link_lookup(ifname = self.iface)[0]
|
||||
# Link-Local address
|
||||
ll = ipr.get_addr(index = self.iface_idx,
|
||||
family = socket.AF_INET6,
|
||||
scope = 253)[0]['attrs']
|
||||
addrs = dict(ll)['IFA_ADDRESS']
|
||||
if isinstance(addrs, (list, tuple)):
|
||||
addr = addrs[0]
|
||||
else:
|
||||
addr = addrs
|
||||
self.iface_ll = addr
|
||||
ipr.close()
|
||||
return(None)
|
||||
|
||||
@ -92,7 +117,11 @@ class Assignment(object):
|
||||
# NOT AN IP6 OBJECT!
|
||||
self.iface_blocks = self.alloc_block.extract_subnet(self.prefix, count = 1)
|
||||
for i in self.iface_blocks:
|
||||
self.iface_addrs.append(IP6(str(next(i.iter_hosts())), 128))
|
||||
# DHCPv6 range.
|
||||
_base = str(i.ip).rstrip(':')
|
||||
start = '{0}:dead:beef:cafe:0'.format(_base)
|
||||
stop = '{0}:dead:beef:cafe:ffff'.format(_base)
|
||||
self.dhcp6_ranges.append((start, stop))
|
||||
return(None)
|
||||
|
||||
|
||||
@ -114,11 +143,8 @@ class Tunnel(object):
|
||||
self.client = None
|
||||
self.server = None
|
||||
self.endpoint = None
|
||||
self.ra = False
|
||||
self.ra_provider = None
|
||||
self.ra_dns = False
|
||||
self.ra_dhcp = False
|
||||
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj
|
||||
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj (as provided by HE)
|
||||
self.assignments = [] # This is a list of Assignment objs
|
||||
self.parse()
|
||||
|
||||
@ -128,11 +154,27 @@ class Tunnel(object):
|
||||
|
||||
def _assignments(self):
|
||||
_assigns_xml = self.xml.find('assignments')
|
||||
|
||||
self.enable_ra = utils.xml2bool(_assigns_xml.attrib.get('radvd', 'false'))
|
||||
self.ra_dns = utils.xml2bool(_assigns_xml.attrib.get('radvdDns', 'false'))
|
||||
self.ra_provider = _assigns_xml.attrib.get('raProvider')
|
||||
for _assign_xml in _assigns_xml.findall('assign'):
|
||||
assign = Assignment(_assign_xml, ra = self.enable_ra, dns = self.ra_dns)
|
||||
do_dns = False
|
||||
domains = []
|
||||
do_dhcp = False
|
||||
ra_other = False
|
||||
tag = _assign_xml.attrib.get('tag', None)
|
||||
dns = _assign_xml.find('dns')
|
||||
if dns and self.ra_provider:
|
||||
do_dns = utils.xml2bool(dns.text.strip())
|
||||
domains = [i.strip() for i in dns.attrib.get('domains', '').split() if i.strip() != '']
|
||||
dhcp = _assign_xml.find('dhcpv6')
|
||||
if dhcp and self.ra_provider:
|
||||
do_dhcp = utils.xml2bool(dhcp.text.strip())
|
||||
ra_other = utils.xml2bool(dhcp.attrib.get('advOther', 'false').strip())
|
||||
assign = Assignment(_assign_xml,
|
||||
ra_dns = do_dns,
|
||||
ra_dhcp = do_dhcp,
|
||||
ra_other = ra_other,
|
||||
ra_tag = tag,
|
||||
ra_domains = domains)
|
||||
assign.alloc = self.allocations[assign.alloc_id]
|
||||
assign.parse_alloc()
|
||||
self.assignments.append(assign)
|
||||
@ -154,9 +196,13 @@ class Tunnel(object):
|
||||
self.id = int(self.xml.attrib['id'].strip())
|
||||
return(None)
|
||||
|
||||
def _radvd(self):
|
||||
|
||||
self.radvd.conf.generate(self.assignments)
|
||||
def _ra(self):
|
||||
# TODO: support conf path override via config XML?
|
||||
if self.ra_provider.strip().lower() == 'dnsmasq':
|
||||
self.ra = ra.DNSMasq()
|
||||
elif self.ra_provider.strip().lower() == 'radvd':
|
||||
self.ra = ra.RADVD()
|
||||
self.ra.conf.generate(self.assignments)
|
||||
return(None)
|
||||
|
||||
def _server(self):
|
||||
@ -171,5 +217,5 @@ class Tunnel(object):
|
||||
self._endpoint()
|
||||
self._allocations()
|
||||
self._assignments()
|
||||
self._radvd()
|
||||
self._ra()
|
||||
return(None)
|
||||
|
@ -143,30 +143,6 @@ class TunnelBroker(object):
|
||||
ipr.close()
|
||||
raise e
|
||||
for assignment in self.tun.assignments:
|
||||
for a in assignment.iface_addrs:
|
||||
# The interface-specific ":1" addrs.
|
||||
# Try to remove first in case it's already assigned.
|
||||
try:
|
||||
ipr.addr('del',
|
||||
index = assignment.iface_idx,
|
||||
address = a.str,
|
||||
mask = a.prefix,
|
||||
family = socket.AF_INET6)
|
||||
logger.debug('Removed {0} with prefix {1} from {2}.'.format(a.str, a.prefixlen, assignment.iface))
|
||||
except Exception as e:
|
||||
pass
|
||||
try:
|
||||
ipr.addr('add',
|
||||
index = assignment.iface_idx,
|
||||
address = a.str,
|
||||
mask = a.prefix,
|
||||
family = socket.AF_INET6)
|
||||
logger.debug('Added {0} with prefix {1} to {2}.'.format(a.str, a.prefix, assignment.iface))
|
||||
except Exception as e:
|
||||
logger.error(('Could not add address {0} on {1}: '
|
||||
'{2}').format(a.str, assignment.iface, e))
|
||||
ipr.close()
|
||||
raise e
|
||||
# The SLAAC prefixes.
|
||||
for b in assignment.iface_blocks:
|
||||
# Try to remove first in case it's already assigned.
|
||||
|
Loading…
Reference in New Issue
Block a user