Closed marjohn56 closed 2 years ago
@marjohn56 maybe a stupid question, but shouldn't the algoritme zero the configured network size and append the configured digits?
so if my interface has this configured:
inet6 fdf3:2c26:f4e4:0:21c:ffff:fefe:4a31 prefixlen 48
and the alias has these:
1000:1001
1000:1002
The alias will contain:
fdf3:2c26:f4e4:0000:0000:0000:1000:1001
fdf3:2c26:f4e4:0000:0000:0000:1000:1002
which is fdf3:2c26:f4e4:0000:0000:0000:0000:0000
+ 1000:1001
.
One only needs to think if it's desirable to prevent against overflow (mask smaller than provided segment) and if it needs another trigger to expire the alias contents.
Sorry, yes it does do that. If you take a look at the little bit in filter.inc we do exactly that like this: $alias_address = make_ipv6_64_address($ifcfgipv6, $aliased['content']); Where we expand the suffix into a full /64 then pin it on the prefix. It's the same bit of code used in dhcpd6 where we allow override,
Here's the guts of that call:
` $prefix_array = explode(':', Net_IPv6::uncompress($prefix)); $suffix_array = explode(':', Net_IPv6::uncompress($suffix));
/* unconditionally merge right side of /64 */
foreach (array(4, 5, 6, 7) as $index) {
$prefix_array[$index] = $suffix_array[$index];
}
/* XXX correctly merging the left side of /64 requires user-specified prefix length */
foreach (array(0, 1, 2, 3) as $index) {
if ((int)$suffix_array[$index] === 0) {
continue;
}
$prefix_array[$index] = $suffix_array[$index];
}
return implode(':', $prefix_array);
}`
@marjohn56 not exactly, a /48 is treated as a /64 in make_ipv6_64_address()
:)
Yes in that instance.. As I said, the wish is to be able to expand it up, but in the PR it's just a /64.
Looking back at comment, the trigger to remove the old alias should only appear if dhcp6c triggers with a REQUEST rather than a RENEW, If the ISP changes the prefix and dhcp6c tries to renew it will fail and fall through to a new REQUEST, thus newwanipv6 etc is run, The new prefix is in two places, 1) already on the interface and 2) in \temp*_pdinfo. The problem with the latter is you cannot be certain what the user has used as their prefix ID so it gets a bit hairy if we try to pull it from pd_info. There is the option of using a a mask with the address in the alias I suppose, thus if you set the address as ::1000 /64 then it would take the left 64 bits, expand the address and merge, if you set it to 48 then it would take the left 48 bits, expand the address to 80 bits and merge; useful for someone who knows what they are doing but for the average user it might over complicate things, but it would make it very flexible.
Going to ask @maurice-w to comment here, he has use for the variable prefix length so his input may be valuable,
leaving this here, an example of how to calculate the address in python using the logic I've described earlier:
base_address = ipaddress.IPv6Address('fdf3:2c26:f4e4:0:21c:ffff:fefe:4a31')
base_mask = 48
base_size=int((128-base_mask)/16)
offset_address = ipaddress.IPv6Address('0::'+'1000:1001')
calculated_address = ':'.join(base_address.exploded.split(':')[:8-base_size] + offset_address.exploded.split(':')[8-base_size:])
please focus on the content first, reasonable triggers and how to hook them are a different problem.
I've reached an impasse! Still trying to work my way through python, some progress - however. This alias depends on two parameters, the address - which is passed in the content field and the interface name - which should be passed with the tag 'dyninterface'; I changed it's name to prevent any ambiguity. Now I've put a syslog call in the init routine of the Alias class to show me what's there as it iterates through the tags.
for subelem in elem: syslog.syslog(syslog.LOG_ERR, 'dynipv6 subelim %s' % (subelem.tag))
Fine, I see the name, the content etc, what I never see is the 'dyninterface' tag. I've used the 'proto' tag as my guide and gone through the source to try and identify where I may be missing an identifier or something and it all looks the same. I see the 'dyninterface' in the config.xml for the alias so it's being written there ok. I'm missing something that's probably painfully obvious but I am apparently blind to it.. Want to give me a clue? Remember this works if I create the filter in filter.inc.
@AdSchellevis - I've got this working now and the var/db/aliastables entries are being created and the rules.debug shows it's there; it also appears to work with whatever prefix length I enter, so that's good too. I have one issue that you may say is fine or we need to go another route. I need to pull the existing prefix to be merged with the suffix. After looking at what options are available in Python for this there doesn't appear to be a friendly way of finding the primary address of the interface. Knowing the fun that was had making sure the primary address is pulled in for radvd etc I've probably cheated by writing all of the interfaces primary addresses to tmp in filter.inc just before the filter_generate_aliases() is called, picking them up in the Python script when needed. It works well enough, but is there a more elegant solution other than writing a new routine in Python?
@marjohn56 can't it just detach on ifconfig
's output? I don't have an awful lot of time available at the moment, although I don't think this should be very complicated if we try to keep it simple.
Problem with ifconfig's output is you don't know what the primary address is if there is also a ULA on it. Basically because there is no way to order the addresses on the interface, just depends on what came first. :)
So as we already have functions that get the primary address. putting them to /tmp is simple and we just read them as needed when the alias.py needs them. They are also there for anything else that might need them at some time.
we're just not going to glue everything together, there should be a separation of concerns. Maybe best explain what the procedure should do and how one could detect what you're intended to do (just extend the ticket). In order to add this as a feature for the aliases, it should be very easy to explain... if there's magic involved, it's support misery waiting to happen :)
It's already explained what's trying to be done in the opening comment. If this is too complex then I'm really not going to carry on with it. I'll just leave it alone and let you and @fichtner sort it out,
I'm merely trying to help out here finding out the intend to grasp the minimal logic need to create a usable feature which fits within our architecture, but if that's too much explaining, we'll just let this one pass.
I really don't see how the explanation could have been made any simpler.
If a user has a dynamic IPv6 prefixe and is using a static address based on that prefix, for example a webserver, when the prefix changes then the rule the user has created to allow traffic to the server is no loger valid. Therefore the rule must change dynamically as well.
@marjohn56 I'm really trying to help you out here, just remember I'm not the one asking for the feature... Maybe it's easier to explain with an example, I just get the idea that we're trying to overcomplicate stuff, which we should try to prevent (and if I'm right you can still have your feature in the process).
Let's take three different interface examples:
[1] different non link-local addresses on an interface
xxxxx: flags=....
inet6 fe80::20c:29ff:feaf:8196%ipsec6000 prefixlen 64 scopeid 0x1c
inet6 adf1:2c26:f4e4:0:21c:42ff:fefe:4a30 prefixlen 48
inet6 acf1:2c26:f4e4:0:21c:42ff:fefe:4a30 prefixlen 48
[2] a single non link local address
xxxxx: flags=....
inet6 fe80::20c:29ff:feaf:8196%ipsec6000 prefixlen 64 scopeid 0x1c
inet6 aaf1:2c26:f4e4:0:21c:42ff:fefe:4a30 prefixlen 48
[2] different non link local addresses within the same range
xxxxx: flags=....
inet6 fe80::20c:29ff:feaf:8196%ipsec6000 prefixlen 64 scopeid 0x1c
inet6 aaf1:2c26:f4e4:0:21c:42ff:fefe:4a30 prefixlen 48
inet6 aaf1:2c26:f4e4:0:21c:42ff:fefe:4a31 prefixlen 48
What do we expect will be the content of the alias when 1000:1001
and 1000:1002
are provided?
It's not me asking for it either, I've got statics. I've never seen dhcp6c push a /48 address on interface or more than one GUA address. However, if it did then using your example:
aaf1:2c26:f4e4:0:21c:42ff:fefe:4a30 prefixlen 48
His /48 prefix is aaf1:2c26:f4e4:0000:
On that segment, bit of a large segment but we'll take it. the user has a webserver at aaf1:2c26:f4e4:0::0010
His /48 prefix is aaf1:2c26:f4e4:0000::
The ISP changes his prefix to aaf1:2c26:f4e4:0003:
His firewall rule to allow inbound traffic to the server at aaf1:2c26:f4e4:0000::0010 no longer works.
We take his suffix ::0010 we know it's a /48 as it was entered into the alias as ::0010/48
and we take the new prefix aaf1:2c26:f4e4:0003:
and we merge giving aaf1:2c26:f4e4:0003::0010
We update the alias rule and job done as far as the fw is concermed. It's then down to the dyndns to update the address.
All we do is replace the prefix, the user can have 'n' servers on that prefix and the way it works is you could have as many static clients on that segment as you like, the rules get updated for all, I've checked.
/48 is pretty large for most users, /56 is more common. However, most users will just be using a /64 prefix, so we just take the /64 prefix for the interface and append the client suffix. Using a prefix smaller than /64 than would require manual override and is there for the more advanced user. @maurice-w asked that the prefix be variable length.
Oh, your final line, here's my entry:
Here's the alias in aliastables.
2a02:xxxx:xxxx:f501:0000:0000:0000:1002 2a02:xxxx:xxxx:0000:0000:0000:0000:1003
As you can see it's picked up the /64 and the /48. and written them to the aliastable file. However, that's just a test to see if it worked, I would not use a /48 on that interface. Also, if you don't enter a size, it defaults to /64
ok, so if I understand it correctly, the alias should "scan" all non link local addresses and do the "replacement trick", right?
No, it should not do ULA either, only GUA, and I've only ever seen one GUA on a tracking interface. You may have multiple LANs/VLANs that are tracking, but because when creating the rule you are specifying which interface the Alias applies to you only need to find the prefix of that interface. If you have multiple fixed clients on multiple LANs/VLANs you would create an Alias for each interface. On the other hand if you had two fixed clients on the same interface you can enter them both i.e. ::1001:1001 and ::1001:1002 in the contents/address box on the same Alias and select the interface and the prefix change will be applied to both addresses.
I suppose you could look at each v6 address on the interface and check the first ( high ) nibble, if it's 2 or 3 then it's a Unicast GUA address.
The first 3 bits of a GUA address begin with the binary value 001, which results in the first hexadecimal digit becoming a 2 or a 3.
Ok, I think I get it, our alias type should renumber all GUA addresses (2000::/3
according to https://tools.ietf.org/html/rfc3587) using the trick described earlier, that shouldn't be too difficult.
Sorry for all the "dumb" questions, but I don't have a lot of IPv6 setups over here and the abbreviations sometimes distract a bit. It helps making requirements very explicit so we all know what we're building (and why) to avoid going back to the drawing board too often. Eventually requirements are the hard part, the coding is relatively easy :)
You could always create an internal lab for Ipv6. Apart from my test/experiment physical router I have a couple of VM instances too. Looking at what I wrote earlier, yes, should be easy enough to pick the /2 from the v6 addresses on the interface and determine which is the GUA. If I do that then it will simplify the whole shebang as that was the bugbear. However, we still need to determine which interface the friendly name refers to, i.e. 'LAN' is which hardware interface, there do not appear to be any Python routines that are already doing that, so the solution would appear to be do do it in the Alias plugin. It gets saved to config as the friendly name. I can see an issue if we save it as the physical interface name in that changing hardware would possibly break the alias, so therefore it needs to remain the friendly name. This is the routine I have created in alias.py, you'll see where I have pulled the existing address from the tmp files created before the call to Alias. Code doesn't paste nicely for some reason, so just quoted.
def _fetch_dynipv6(self, address): """ take the entered partial address and mask, append to the interface prefix. """ address = address.strip()
split the mask - if no mask it's a /64
if address.find('/') > -1: l_address,l_mask = address.split('/') else: l_mask = '64' l_address = address ipv6addr_filename = "/tmp/%s_ipv6addr" % (self._dyninterface)
addresses are on file for each interface.
try: if os.path.isfile(ipv6addr_filename): iface_address = open(ipv6addr_filename).read().strip() except IOError: syslog.syslog(syslog.LOG_ERR, 'dynipv6 error') base_mask = int(l_mask) base_address = ipaddress.ip_address(iface_address)
base_size=int((128-base_mask)/16) combine_address='0'+l_address ipv6_addr = ipaddress.ip_address(combine_address) calculated_address = ':'.join(base_address.exploded.split(':')[:8-base_size] + ipv6_addr.exploded.split(':')[8-base_size:]) for address in self._parse_address(calculated_address): yield address
The template should take care of the mapping, similar to how the shaper handles this
I'll try to glue something together if I can find I bit of time later this weekend, I think I know what you're looking for now.
Ah the template, yes, that was what held me up earlier in the week with the passing of the dyninterface tag.
Cool. That works, added the macro import of the interfaces macro to the filter_tables.conf and the physical_interfaces call and we get the real interface... :) thank you. Now to parse the v6 addresses.
Any chance this could help using dynamic prefix in NPTv6? In that a LAN tracking WAN, GUA prefix is delegated from ISP, user can use NPT to keep LAN subnet static. I am not sure how exactly alias is implemented but really hope it can be extended to cover entire ULA subnet than fixed hosts. But still having dynamic prefix worked out in few rules is way to go👍🏻
@AdSchellevis - #4941
No doubt will need some tweaking but it works.
@ivwang - This commit does not deal with NPTv6. What it does do is allow for a changing GUA prefix to update the fw rules automatically... that's the theory anyway.
@marjohn56 ok, should I wait for your PR then or propose something from my end?
-- Edit: never mind, missed https://github.com/opnsense/core/pull/4941 ... too much mail ;)
@marjohn56 I'll handle the tweaking, no problem
PR updated with @AdSchellevis's amendments.
re-opening, moved the code to a separate branch (FR_4923) pending the discussion in our docs repo (https://github.com/opnsense/docs/pull/330#issuecomment-839531339)
@AdSchellevis - Just came across something unexpected. If I specified the WAN as the interface then it pulls two addresses, a /128 and a /64, these are the addresses assigned ( in my case by my primary Opnsense router ) by SLAAC and dhdpdv6. Easiest way out of this, as I doubt anyone would use the WAN interface as the address source ( but you never know ), is to just pop a break statement into interface.py at the end of def iter_dynipv6host(), thus it picks up only one IPv6 address and that then works fine.
@marjohn56 I'm not sure to be honest, if there are more addresses in different nets on the selected interfaces it might get a bit obscure, the "right" address isn't necessarily the first (hence some magic in core to try to find it in core).
It's already looks for the GUA address, the break only happens if it's found one, so that's OK. Probably a bit of an oddball as I doubt very much that there are many ISPs that would give out a /128 and /64 on the same interface; We could offer a drop down list of the addresses if the WAN is selected, but that becomes a bit chicken and egg. Generally from my experience the ISP either does not give out a GUA on the WAN or gives out either a single /64 or /128. Multiple addresses on the WAN would suggest either Aliases and statics, not dhcpv6, and in that case you wouldn't be using the dynamic host alias anyway.
Here's a possible solution, tracking interfaces only?
you mean in the alias selection? could work, not sure if it's needed yet
I've put a filter into the alias.xml for the InterfaceField.
`
`
Does it nicely. Hmm, took me a while to work out that you could use filters there, useful! I can think of somewhere else I want to put them now.
@marjohn56 https://github.com/opnsense/core/commit/ce656d076bdebb2d53688f877472b1c0e915c5db should implement what we discussed in https://github.com/opnsense/docs/pull/330
I think it's sensible to implement this for the host alias. Though I was having a think last night and it occurred to me you could also use this same alias in NPTv6. This is where I used the tracked LAN address as one side of the NPT #5284, but it's still a /128 so yes.
Important notices
Before you add a new report, we ask you kindly to acknowledge the following:
Describe the bug
This issue is purely for @AdSchellevis to zero in on.
Users who have use dhcp6 to obtain a prefix from their ISP complain that when the prefix changes any rules they have created become useless, Therefore we need to be able to dynamically change the prefix but leave the suffix intact. @fichtner wants this to be done using an alias containing the suffix and the interface from which to obtain the prefix. So the user would create an alias containing the suffix and the interface. They then create the rule, say on the WAN where they want to allow an ICMPv6 type to an address on the LAN. The destination will be the alias created.
I have created the UI interface for this which creates the alias, and in my PR #4919 it works fine but you want to use the alias.py to handle it.
I have additionally noted in that PR the following:
In the final instance I would like to be able to be flexible with the suffix, in the context that the user must enter the full suffix, so for a /64 suffix they would enter ::0:0:0:1000 , however, if they entered ::0:0:0:0:1000 that would be an 80 bit suffix and we would take the first /48 from the prefix and merge on that. However, that 's a wish, and in the initial case we would just want to merge on the /64.
Is that sufficient info?