Conversation
- Show IPv4 and IPv6 addresses in IP Address column - Aggregate traffic statistics across all IPs for a session - Add tooltip to display full IP addresses when truncated - Update accounting to include traffic from all associated IPs
…tack support - Resolved conflicts in captiveportal.inc: Use upstream's getValues() method while keeping IPv6 rules - Resolved conflicts in AccessController.php: Merged upstream's hostwatch dump with our IPv6 NDP fallback - Resolved conflicts in clients.volt: Use upstream's zone selection placement while keeping tooltip initialization - Resolved conflicts in pf.py: Preserved IPv6 protocol handling (0x86dd) and accounting methods - Resolved conflicts in db.py: Merged our aggregation logic with upstream's prev_* fields for counter reset detection - Resolved conflicts in cp-background-process.py: Adapted dual-stack MAC handling to use upstream's helper methods All IPv6 dual-stack functionality is preserved while incorporating upstream improvements.
| $backend = new Backend(); | ||
| $backend->configdRun('template reload OPNsense/IPFW'); | ||
| $backend->configdRun("ipfw reload"); |
There was a problem hiding this comment.
is this side effect a good idea? feels a bit too clever :)
There was a problem hiding this comment.
I expected comments here ;)
To be honest this was the quickest win here, the IPFW reload is a requirement though, and missing since the re-introduction of it and I didn't spot any other obvious hooks in the right place.
There was a problem hiding this comment.
for context: the spot where this code resided previously was cleaned up in d8519a0
There was a problem hiding this comment.
Looks duplicate now, as in the following line both dnctl and ipfw are triggered when no argument is specified:
Line 179 in 36b17ad
There was a problem hiding this comment.
@AdSchellevis Coming back to this as it's related to #9902.. the start.sh script does not trigger the template generation for https://github.com/opnsense/core/blob/master/src/opnsense/service/templates/OPNsense/IPFW/rc.conf.d, which means that configuring a zone in a clean install will likely not kickstart ipfw until a reboot.
In this case I'd opt for
$backend->configdRun('template reload OPNsense/IPFW');
To remain in here.
| <label>Allow multiple client IPs</label> | ||
| <help>Allow a connecting client to use multiple IPs (bound to the same MAC). For IPv4, these can be virtual IPs on the client. For IPv6, this option is needed for maximum compatibility because a client may actively use multiple IPv6 addresses.</help> | ||
| <label>Client roaming</label> | ||
| <help>Allow a connecting client to use multiple IPs (bound to the same MAC) over the course of its session. For IPv4, these can be virtual IPs on the client. For IPv6, this option is needed for maximum compatibility because a client may actively use multiple IPv6 addresses.</help> |
There was a problem hiding this comment.
strictly speaking the more common term is "alias" for an extra address (as per ifconfig). Both could still be confusing. Perhaps we can remove the IPv4 explanation or change it to a minor note "This also affects IPv4."
Wouldn't that also allow login from multiple IPs? Didn't we have a setting for that? Concurrent something?
There was a problem hiding this comment.
(if it's strictly tied to the MAC/DUID that's something else and please ignore me)
There was a problem hiding this comment.
"roaming" keys clients (with IP aliases) to a MAC address, if they connect from a different device the "concurrent user logins" setting still applies, different MAC.
|
Thanks for taking this over! |
AdSchellevis
left a comment
There was a problem hiding this comment.
@swhite2 I think we're almost there, missed a couple of spots we discussed last time and added one question about ipv4 on dual stack nets.
|
|
||
| session_ips = {args.ip_address} | ||
| if args.roaming: | ||
| session_ips = db.update_roaming_ips(args.zoneid, response.sessionId, arp.get_all_addresses_by_mac(arp_entry['mac'])) |
There was a problem hiding this comment.
as discussed we likely better only add the connected address and let the background process take care of the rest.
| [allow] | ||
| command:/usr/local/opnsense/scripts/captiveportal/allow.py | ||
| parameters:--zoneid=%s --username=%s --ip_address=%s --authenticated_via=%s | ||
| parameters:--zoneid=%s --username=%s --ip_address=%s --authenticated_via=%s --roaming=%s |
There was a problem hiding this comment.
when roaming isn't relevant, we can ditch it from here as well.
| {% if conf_key == intf_tag and conf_inf.ipaddr and conf_inf.ipaddr != 'dhcp' %} | ||
| {% do item.update({'interface_hostaddr':conf_inf.ipaddr}) %} | ||
| {% if conf_key == intf_tag %} | ||
| {# prefer IPv6 if available, fallback to IPv4 #} |
There was a problem hiding this comment.
what happens with dual stack and an ipv4 only client here?
This makes IPv4 and IPv6 portal entry points behave consistently, fixes proxied client IP detection, and lets roaming sessions discover IPv6 addresses quickly enough to authorize privacy and secondary addresses on the same client.
I had some free time and did a matrix of testing and made a PR that tries to address all of @AdSchellevis comments along with bugs I discovered: #9927 |
This restores best-effort sibling address authorization at login for already-known addresses on the same MAC, while keeping the background reconciliation path as the source of truth for later convergence and cleanup.
…-dual-stack-support
…-dual-stack-support Follow up for dual-stack captive portal authorization in `CaptivePortal`
|
Tested with my setup against 9df582a Setup
Current behaviorThe login address works immediately. Sibling addresses do not always work immediately on current upstream. Addresses that are already visible to the firewall may still need the background reconciliation pass before they are consistently usable. Matrix
Main takeawayCurrent upstream is usable for the core portal flow, but it still has a convergence model rather than an immediate warm-start model. The address used for login works immediately. Other addresses on the same client join later as they become visible to the firewall and the background reconciliation loop processes them. The narrowest remaining rough edge is the DHCPv6 *Sibling addresses usually became usable within about 5 to 12 seconds, which lined up with the 5 second background reconciliation loop plus the time needed for the address to become visible in neighbor discovery. |
| <id>zone.servername</id> | ||
| <label>Hostname</label> | ||
| <type>text</type> | ||
| <help><![CDATA[Hostname (of this machine) to redirect login page to, leave blank to use this interface IP address, otherwise make sure the client can access DNS to resolve this location. When using a SSL certificate, make sure both this name and the cert name are equal.]]></help> |
There was a problem hiding this comment.
Maybe could change to:
Hostname to redirect the login page to. Leave blank to use the interface IP address. When using an SSL certificate, the hostname and certificate name must match. Note: For IPv6-only client compatibility, either set a hostname with both A and AAAA records, or ensure the selected interface has a static IPv6 address configured. If only a dynamic DHCPv6 address is present and no hostname is set, IPv6-only clients may be redirected to an IPv4 address they cannot reach.
| <help><![CDATA[Hostname to redirect the login page to. Leave blank to use the interface IP address. When using an SSL certificate, the hostname and certificate name must match. Note: For IPv6-only client compatibility, either set a hostname with both A and AAAA records, or ensure the selected interface has a static IPv6 address configured. If only a dynamic DHCPv6 address is present and no hostname is set, IPv6-only clients may be redirected to an IPv4 address they cannot reach.]]></help> | |
There was a problem hiding this comment.
I advice to avoid HTML inside language strings as this will break during translation.
There was a problem hiding this comment.
Fair. I took out the HTML formatting in the string suggestion
|
@agoodkind Thanks for your further testing. Your results seem to align with what is to be expected given the constraints. After some further discussion internally, the best strategy to address the rather complicated lighty template seems to be to stop guessing for IPv6 addresses (which are going to be static less often in comparison to IPv4 anyway) and require that a user sets a proper hostname + associated AAAA records (and possibly DNS64 support in Unbound) to make IPv6 work. |
Thanks for the follow-up. I understand the appeal of simplifying the template, but requiring a hostname + AAAA for IPv6 captive portal to function at all would be quite a gap IMO. Although it is true that most network admins should set up some type of hostname to handle this, I still feel it is a gap.
For example, I've encountered two different ISPs where you are required to use DHCP-PD otherwise the traffic won't route (AT&T Fiber Business, and Webpass Business). A captive portal that silently falls back to an unreachable IPv4 address in that configuration is a real bug, not a corner case. The fix itself is fairly contained: runtime address resolution for |
| { | ||
| $clientAddress = $this->request->getClientAddress(); | ||
| $forwardedFor = $this->request->getHeader('X-Forwarded-For'); | ||
| $realIp = $this->request->getHeader('X-Real-Ip'); |
There was a problem hiding this comment.
Can we please just go back to the original situation, I have a very strong feeling we're trying to fix an edge case while introducing new security concerns in the process. It's debatable if by default any of these headers should be processed, but since that's already the case (introduced a very long time ago a7033f2), let's try not to increase the impact so we can discuss improvements on this topic in a separate ticket.
| $userName, | ||
| $clientIp, | ||
| $authServerName | ||
| $authServerName, |
There was a problem hiding this comment.
| $authServerName, | |
| $authServerName |
| # Clear pre-auth states so newly authorized sibling addresses can use the updated table match immediately. | ||
| subprocess.run(['/sbin/pfctl', '-k', f'{address}'], capture_output=True) | ||
| wildcard = '::/0' if PF._is_ipv6(address) else '0.0.0.0/0' | ||
| subprocess.run(['/sbin/pfctl', '-k', wildcard, '-k', f'{address}'], capture_output=True) |
There was a problem hiding this comment.
this looks rather suspicious and may result in unexpected locked sessions to already accepted destinations.
Strip IPv4 runtime resolution (unnecessary since config.xml has the static address) and preserve upstream PR opnsense#9745 template structure. Only two targeted changes on top of swhite2's branch: 1. Runtime IPv6 resolution via get_interface_address.php for track6 interfaces, falling back to conf_inf.ipaddrv6 for static configs 2. Bracketed IPv6 regex fix in [::] socket blocks so redirurl is not truncated at the first colon
…e pf state kill, backend listening op IPv4 only so adjust AccessController logic
The host-match regex ([^:/]+) in the IPv6 lighttpd socket blocks truncates bracketed IPv6 literals at the first colon, producing a broken redirurl (e.g. [2601/ instead of [2606:4700:4700::1111]/). Change the capture group to (\[[^\]]+\]|[^:/]+) so a full bracketed literal is matched first, falling back to the original pattern for IPv4 addresses and hostnames. Missed in: 369630d (Captive portal: IPv6 support, opnsense#9745) See also: opnsense#9973
The host-match regex ([^:/]+) in the IPv6 lighttpd socket blocks truncates bracketed IPv6 literals at the first colon, producing a broken redirurl (e.g. [2601/ instead of [2606:4700:4700::1111]/). Change the capture group to (\[[^\]]+\]|[^:/]+) so a full bracketed literal is matched first, falling back to the original pattern for IPv4 addresses and hostnames. Missed in: 369630d (Captive portal: IPv6 support, #9745) See also: #9973
This reverts commit 497ed54. Revert for the time being since 26.1.5 doesn't force a reboot.
Co-authored-by: Alex Goodkind <alex@goodkind.io> (cherry picked from commit 369630d) (cherry picked from commit 5b07e09) (cherry picked from commit 2ac18ce) (cherry picked from commit cff0e8d) (cherry picked from commit 6f00e1e) (cherry picked from commit da2c0bd) (cherry picked from commit e5effd4)
Closes #8761