Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pppd/ipv6cp: Add ipv6-pre-up script. #341

Closed
wants to merge 1 commit into from

Conversation

jkroonza
Copy link
Contributor

This is required because either ipcp or ipv6cp can come up first, or as
the only protocol, and once IPv6 parameters has been established, there
are actions that may need to be executed prior to bringing the interface
up.

Prior to this we can have an execution order like:

pppd[26615]: Script /etc/ppp/ipv6-up started (pid 28183)
pppd[26615]: Script /etc/ppp/ip-pre-up started (pid 28186)
pppd[26615]: Script /etc/ppp/ip-pre-up finished (pid 28186), status = 0x0
pppd[26615]: Script /etc/ppp/ip-up started (pid 28238)
pppd[26615]: Script /etc/ppp/ipv6-up finished (pid 28183), status = 0x0
pppd[26615]: Script /etc/ppp/ip-up finished (pid 28238), status = 0x0

ip ad sh shows that during ip-pre-up the interface state was already up:

ppp-ip-pre-up(ppp1)[28208]: 423: ppp1: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast state UNKNOWN group default qlen 3
ppp-ip-pre-up(ppp1)[28208]: link/ppp
ppp-ip-pre-up(ppp1)[28208]: inet 10.1.0.0 peer 192.168.50.0/32 scope global ppp1
ppp-ip-pre-up(ppp1)[28208]: valid_lft forever preferred_lft forever
ulsdns_monitor[28216]: 192.168.50.0 dev ppp1 proto kernel scope link src 10.1.0.0
ppp-ip-pre-up(ppp1)[28208]: inet6 fe80::b4a3:c896:22bc:151f peer fe80::6/128 scope link
ppp-ip-pre-up(ppp1)[28208]: valid_lft forever preferred_lft forever

In order to make this work properly, a system admin would need to take
the same action from ip-up and ipv6-up (probably in a locked manner) if
and only if the interface oper status is down.

As things stand one cannot depende on ip-up being executed whilst
interface is still in down state, and as such, the scription in the man
page is wrong too.

@enaess
Copy link
Contributor

enaess commented May 31, 2022

@pali @jkroonza I don't think this is the right solution. You should be able to configure your iptable/nftables rules to match by a wildcard-interface e.g. -i/-o ppp+ and configure your rules in that regards. This is commonly done for a lot of firewall vendors.

@pali
Copy link
Contributor

pali commented May 31, 2022

I do not like this pre-up script feature. For me it looks like requirement for some buggy behavior. By default firewall should not have "allow all" behavior. If default behavior is "deny all" then configuring per-interface firewall settings could be done in (post-)up script.

There is already ipv4 pre-up script, so probably it could make sense to have also ipv6 pre-up script... As I said I'm not a big fan of it, but I'm not against of it.

Ad wildcard-interface e.g. -i/-o ppp+. This does not work in scenario, if you let pppd/kernel to choose interface name (to choose interface suffix number to some free value) and you want to configure specific per-interface route settings. E.g. if for PPPoE over eth1 (interface ppp<dynamic_assigned2>) you want to have different route settings as for PPPoE over eth2 (interface ppp<dynamic_assigned1>). Such settings are required on PPPoE concentrator server if some connections should be in routed differently than others (matched e.g. by authenticated user or client MAC address). (This argument is about wildcards, not decision if it should be in pre or post script)

@enaess
Copy link
Contributor

enaess commented May 31, 2022

@pali - That's fair. I still don't think (or like) the pre-up scripts either. Your argumentation is probably better than mine. I think @jkroonza will need to provide a compelling reason to make a change, and we can discuss the pre-up scripts and possible alternate solutions. Still don't think we understand the root cause here.

@jkroonza
Copy link
Contributor Author

jkroonza commented Jun 1, 2022

I think the principle is useful. To basically have access to the configured but not yet UP interface. It's not always possible to make firewall and shaping changes using patterns such as ppp+ (we do this ourselves, unfortunately there are cases where this simply doesn't work or doesn't work well and we end up adding interfaces to sets, eg, we have pppoe and/or VPN clients connecting and depending on authentication details needs to be placed in different sets to apply the right rules).

One other strategy could be to rename interfaces, but we can't do this using the standard ppp mechanisms, and whilst there are comments in the source about a script changing the name of the interface, we don't believe this is a workable (or at least simple and reliable) solution either, especially with multiple protocols involved, eg, ipv4 and ipv6, does both CPs check for interface name changes? And at what times? So we believe it's simpler to just dispatch ppp+ in nft/iptables to a dedicated chain that then matches more fine-grained based on an interface set).

A wholly better solution compared to ${proto}-pre-up may simply be a consolidated pre-up script, and I'd be all for that. auth-up could in some cases work for that, auth-up doesn't execute for all connections where we need this (eg, to add a dial-out link to a "red" interface set). Granted, the default policy should be "red", but we like being thorough.

@jkroonza
Copy link
Contributor Author

jkroonza commented Jun 1, 2022

Thinking about this a little bit more: I'd be in favour of a single consolidated pre-up script, where it can also be mentioned explicitly that the script it allowed to rename the interface (this makes a whole other PR re interface naming completely moot). Then mark ip-pre-up as deprecated, to be removed in future (issue a warning if it's used kind of thing).

This is required because either ipcp or ipv6cp can come up first, or as
the only protocol, and once IPv6 parameters has been established, there
are actions that may need to be executed prior to bringing the interface
up.

Prior to this we can have an execution order like:

pppd[26615]: Script /etc/ppp/ipv6-up started (pid 28183)
pppd[26615]: Script /etc/ppp/ip-pre-up started (pid 28186)
pppd[26615]: Script /etc/ppp/ip-pre-up finished (pid 28186), status = 0x0
pppd[26615]: Script /etc/ppp/ip-up started (pid 28238)
pppd[26615]: Script /etc/ppp/ipv6-up finished (pid 28183), status = 0x0
pppd[26615]: Script /etc/ppp/ip-up finished (pid 28238), status = 0x0

ip ad sh shows that during ip-pre-up the interface state was already up:

ppp-ip-pre-up(ppp1)[28208]: 423: ppp1: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast state UNKNOWN group default qlen 3
ppp-ip-pre-up(ppp1)[28208]:     link/ppp
ppp-ip-pre-up(ppp1)[28208]:     inet 10.1.0.0 peer 192.168.50.0/32 scope global ppp1
ppp-ip-pre-up(ppp1)[28208]:        valid_lft forever preferred_lft forever
ulsdns_monitor[28216]: 192.168.50.0 dev ppp1 proto kernel scope link src 10.1.0.0
ppp-ip-pre-up(ppp1)[28208]:     inet6 fe80::b4a3:c896:22bc:151f peer fe80::6/128 scope link
ppp-ip-pre-up(ppp1)[28208]:        valid_lft forever preferred_lft forever

In order to make this work properly, a system admin would need to take
the same action from ip-up and ipv6-up (probably in a locked manner) if
and only if the interface oper status is down.

As things stand one cannot depende on ip-up being executed whilst
interface is still in down state, and as such, the scription in the man
page is wrong too.

Signed-off-by: Jaco Kroon <[email protected]>
@jkroonza
Copy link
Contributor Author

@enaess ok, so this is a possible suggestion then:

Deprecate ip-pre-up and just a more generic pre-up?

Related to this, the lcp-up/down, which looking at that again, I suggest a consolidated patch which introduces three scripts:

  1. general init notifier of sorts.
  2. pre-up (which runs PRIOR to bringing up any sub protocols such as IPCP or IPv6CP or IPX or any of these).
  3. post-down (which runs AFTER all sub protocols have been shut down).

motivation for general init:

We need the ability to initialize some state container on our end, this currently happens in the proposed lcp-up script, but it doesn't really matter if this is linked to lcp or not, but we do need to execute stuff very early on, and have certainty in other hooks that the state has been properly reset. There is no other specific hook we can state with 100% certainty that it'll be the first hook, and as such no other script we can rely on to do this.

pre-up:

As per various discussions we've had now, there is debatable need/use for this, but we still believe that certain actions should be performed PRIOR to the interface being brought up. Yes, the defaults should be secure, and who cares if shaping takes a few seconds after link establishment to come up ... but it's still a good thing to be able to do things like set up NAT prior to allowing routing to happen:

  1. Certain UDP based protocols especially are very sensitive to incorrectly established conntrack entries which can happen without this (SIP in particular is a big issue), and if ppp unit numbers/names are assigned dynamically, and we need NAT ons some but not others .... in essense, if a phone uses udp port 5060, and using it's IP as A, the server as S and P for our public, if that initial frame from A:5060 goes out to S:5060 and the (src,dst,reply-src,reply-dst) gets established as (A:5060,S:5060,S:5060,A:5060) you are screwed because the client will retransmit frequently enough to prevent the entry from being purged and re-established correctly as (A:5060,S:5060,S:5060,P:????) such that return traffic can reach the client.

  2. We would still prefer to be able to set up protocol agnostic things (eg, tc) in a single location, eg shaping. Else we're running into issues like which comes up first, IPv4 or IPv6, and now this complicates the interactions between other scripts.

post-down:

Well, shutdown really, ie, link has gone down. Just to perform general state cleanup, again, using ip*-post-down for this for protocol agnostic issues is tricky since how does the script know that all relevant protocols have been brought down and no protocols are running any more?

@jkroonza jkroonza mentioned this pull request Aug 19, 2022
@enaess
Copy link
Contributor

enaess commented Aug 19, 2022

Frankly, this is an area of the code I don't know very well (yet). The introduction of a wait make me a bit eerie, and I too have had to deal with ipv6cp and ipcp coming up independent of each other (it's racy, and it depends on the nature of which protocol completes the message exchanges first).

Having a single ip-pre-up to work for both could also seems reasonable, I don't know.

As far as I know pppd, it was designed to configure the device and apply values at the earliest convenience. For example, LCP does MTU negotiations and it's based on the device capability for the OS. If the peer A receives an MRU option from peer B, it will try to apply it to the adapter; and the LCP ACK, NAK, or failure depends on the result of the ioctl() being issued are replied back to peer B.

Same thing should technically happen to IP(v6) control protocol as well. The protocol is considered UP when both peers agrees on the negotiated parameters. If you refer to RFC1661, what you are looking for in this case is a Network Layer Initialize (Network prior to state UP). Network is UP when all Network based Control Protocols are considered Up. At this point the ppp device is known, the MTU is set, and Authentication is complete. I believe you can write a PPP plugin to act on the state transitions too.

In this ip-pre-up, and ipv6-pre-up, what information does your script require? Also, was ip-pre-up introduced after 2.4.9? In case, it hasn't been released, and a collective change to do a network-up script initialization would be preferable. If it has already been released, then maybe support both ip-pre-up and ip6-pre-up. If a ip-pre-down isn't needed, then maybe have it omitted or do the cleanup in ip6-down or ip-down

Does this make sense?

@jkroonza
Copy link
Contributor Author

Frankly, this is an area of the code I don't know very well (yet). The introduction of a wait make me a bit eerie, and I too have had to deal with ipv6cp and ipcp coming up independent of each other (it's racy, and it depends on the nature of which protocol completes the message exchanges first).

Having a single ip-pre-up to work for both could also seems reasonable, I don't know.

I'd personally just call it pre-up but yes that would actually work, state plainly in docs this is post authentication, but before any sub(?) protocols such as ipcp and/or ipv6cp (ipx has been dropped, but in theory it's possible for others, eg mplscp?), so this isn't prior to IP protocols, coming up, it's before ANY protocols are brought up.

The man page is quite plain about blocking too long in many of these scripts, and some of them even go as far as NOT WAITING, pre-up will HAVE to wait by the nature of it, because pppd should not bring up any protocols before it's finished. In theory we can fork it off, but then have a blocking wait on it's completion prior to actually performing "ip li set up", but given that at least my use-case won't block for more than 10-15ms at most this should be perfectly fine. Hell, based on what I understand from the way pppd works, even a blockage of a second or two should be OK even if it may not be ideal.

As far as I know pppd, it was designed to configure the device and apply values at the earliest convenience. For example, LCP does MTU negotiations and it's based on the device capability for the OS. If the peer A receives an MRU option from peer B, it will try to apply it to the adapter; and the LCP ACK, NAK, or failure depends on the result of the ioctl() being issued are replied back to peer B.

Correct. lcp-up and lcp-down was probably not the right concept, I suspect init and shutdown are probably better, and we invoke those prior to even starting LCP etc ... and we invoke shutdown last thing before exit kind of thing. Yes, in theory this can be handled by the process invoking pppd, but that's not always viable either, and we do want the unit number during out init stuff, so we can invoke init the moment after the unit number is available, and just prior to nuking the interface again would be good.

Same thing should technically happen to IP(v6) control protocol as well. The protocol is considered UP when both peers agrees on the negotiated parameters. If you refer to RFC1661, what you are looking for in this case is a Network Layer Initialize (Network prior to state UP). Network is UP when all Network based Control Protocols are considered Up. At this point the ppp device is known, the MTU is set, and Authentication is complete. I believe you can write a PPP plugin to act on the state transitions too.

Yea, so ip{,v6}-{up,down} executes just after bringing IPCP and IPv6CP up, and just after deconfiguring the specific protocol (if ondemand is set returning to the state wherein ondemand can happen is set up first).

In this ip-pre-up, and ipv6-pre-up, what information does your script require? Also, was ip-pre-up introduced after 2.4.9? In case, it hasn't been released, and a collective change to do a network-up script initialization would be preferable. If it has already been released, then maybe support both ip-pre-up and ip6-pre-up. If a ip-pre-down isn't needed, then maybe have it omitted or do the cleanup in ip6-down or ip-down

ip-pre-up has been available for as long as I can remember. Looking at the git logs @paulusmack added it back in 2005, even though I could have sworn debian had it already in 2003 when I first set up pppd.

No ipv6-pre-up, I suggested it here, but I do tend to agree with you that a single consolidate pre-up is a better approach. Yes, I do want some kind of script that executes after auth before protocols are brought up. The work that should happen here should all be protocol agnostic since without a consolidated pre-up we have no guarantee that the interface will be down. At this stage we care about things like shaping parameters from radius. I would also like to suggest that interface renaming should be possible from this script via things like "ip il set dev pppX name ppp-whatever" (which suggests that auth details should be available to the script). This also reduces the need for the other (seeminly) complicated ifname patch that's being developed on another PR.

Does this make sense?

Perfect. And I thing we're (mostly?) in agreement.

Thus my suggestion of a general init (pppd unit created), pre-up (pre protocol up, ie, up to auth completed) and post-down (all protos down, but unit still available).

@enaess
Copy link
Contributor

enaess commented Aug 23, 2022

@jkroonza

I think you are on the right track here. Instead of introducing the ip6cp_pre_up script (with possible race conditions), the entire ip-pre-up should really be renamed to pre-up, or net_init. Though, since this feature has existed for quite some time, and despite the name 'pre-up' may not be the best name, maybe the simplest thing is to accept the name as it is, but move the logic to spawn the script to the right place of the code which make it protocol agnostic? Alternatively, add another hook 'net-init' in addition to 'ip-pre-up' if we feel it would break existing scripts? I tend to favor the former as it eliminates ambiguity (and if this is going to be version 2.5, we already have broken backwards compatibility).

Sounds like you and @EasyNetDev should have a conversation of interface naming and how that is done. That could possibly help him accomplish what he needs without an extensive patch set?

@pali
Copy link
Contributor

pali commented Aug 23, 2022

Could you contact NetworkManager people? I think they use those scripts and hook a lot, so maybe they could have inputs if changing spawn logic could break something or not.

@enaess
Copy link
Contributor

enaess commented Aug 23, 2022

@pali, I can have a look and reach out to them. Which particular project did you have in mind? The network-manager stuff also include all the vpn plugins.

@jkroonza
Copy link
Contributor Author

I think you are on the right track here. Instead of introducing the ip6cp_pre_up script (with possible race conditions), the entire ip-pre-up should really be renamed to pre-up, or net_init.

Thank you. The race already exists, the man page has this to say about ip-pre-up:

       /etc/ppp/ip-pre-up
              A program or script which is executed just before  the  ppp  network  interface  is
              brought  up.   It is executed with the same parameters as the ip-up script (below).
              At this point the interface exists and has IP addresses assigned but is still down.
              This  can  be used to add firewall rules before any IP traffic can pass through the
              interface.  Pppd will wait for this script to finish before bringing the  interface
              up, so this script should run quickly.

There are a few things I'd like to note here:

"just before the ppp network interface is brought up" - this statement is no longer true, if it ever truly was, I do think it was "mostly true" due to only ipv4 being typically used (at least, for a portion of the two decades I've been using pppd prior to IPv6 becoming more mainstream). So currently, if IPv6CP (or another CP other than ipv4) causes the interface to become "up" then this statement is false.

"At this point the interface exists and has IP addresses assigned but is still down." - the latter portion of this statement (but is still down) accentuates the discussion above, but the portion before that is the important snippet here, in that it also means this logic cannot trivially move and retain the same name since certain scripts may depend on IPv4 information being available already. This part of the documentation is also accurate based on the codebase.

"pppd will wait for this script to finish" ... not if IPv6CP comes up first.

I like net-init here - any objections? Would suggest we implement net-init, and then flag ip-pre-up as DEPRECATED with the above explanation, just better phrased in some way?

Though, since this feature has existed for quite some time, and despite the name 'pre-up' may not be the best name, maybe the simplest thing is to accept the name as it is, but move the logic to spawn the script to the right place of the code which make it protocol agnostic?

Refer above.

Alternatively, add another hook 'net-init' in addition to 'ip-pre-up' if we feel it would break existing scripts? I tend to favor the former as it eliminates ambiguity (and if this is going to be version 2.5, we already have broken backwards compatibility).

Well, then why not make the breakage spectacular and completely bomb out with an error message about ip-pre-up being broken and should convert to net-init if ip-pre-up exists unless a parameter like ignore-ip-pre-up is passed (aka, "i know what I'm doing")

Sounds like you and @EasyNetDev should have a conversation of interface naming and how that is done. That could possibly help him accomplish what he needs without an extensive patch set?

@EasyNetDev if you look at pppd/ipcp.c, around lines 1938 through 1943, there is logic currently to pick up if the interface name has been changed in the ip-pre-up script - if this moves along to the net-init concept - would you be able to utilize the net-init hook script to perform your interface renaming in an adequate manner? Not that I'm against your conceptual patch (in fact, I like it), it just that maybe this might be "simpler"? So the ppp starts out as pppX, and then in net-init phase you can rename however you like, and pppd will just pick up on that?

@enaess there is already ref counts in sifup and sifdown - as such, I'm thinking the following set of scripts are best suited:

net-pre-up - executes just prior to the first setifstate call. If setifstate fails there is a possible problem in that a future call to sifup will trigger another invocation. Therefor a better location to add this hook may be just prior to initiating the IP*CP protocols. To accomodate @EasyNetDev and possibly easier conversion of ip-pre-up scripts, accomodate interface renaming here.

net-down - similar. Do we invoke it PRIOR to sifstate of after?

Also, there are separate logic for counting the refs w.r.t. if_is_up and if6_is_up (simple boolean flag) which seems suspect in terms of logic. What happens if MPLSCP gets added? So pure ref counting here would be best, but I'd pass a string to indicate which protocol (ipcp, ipv6cp, mplscp etc ...) triggered the ups and downs, along with a number of warn/error triggers to hopefully find mismatches or at least enable mismatch detection (or we could even keep a list of "up" protocols so that we can indicate which protocol double-up'ed or down'ed without being up?)

Not sure if these are the best places to hook, possibly a more general net-init and net-down script when auth phase is done, and before starting IP*CP, and net-down just prior to tearing down the ppp interface unit.

init - general init script, to be invoked directly once the ppp iface has been created.

This would then consolidate this PR and #342 into a single PR, presumably with improved structure.

Got some time hopefully over the weekend early next week to cook this, and happy to do so. Just don't want to go down a path that will end up not getting the required support here.

@enaess
Copy link
Contributor

enaess commented Aug 23, 2022

@jkroonza

I won't consider my opinion an authoritative voice on the matter. It maybe good to solicit feedback from others here as well, like @paulusmack as he will eventually be the deciding factor if a PR gets merged.

I didn't realize this earlier, but the ip-pre-up script provides the IPv4 parameters just like the ip-up script does, but before the adapter is set in state IFF_UP. I would imagine that an ipv6 version of the same script would do the same thing (or mplscp for that sake). A net-pre-up would not be able to pass protocol specific data indicating what the resulting negotiated IP-address would be. However, the net-up script could be made into querying the interface for those values.

Also, the action of calling sifup, sifdown, or sif6up of sif6down is done directly from the ipcp.c or ip6cp.c which make it tricky as there is no central point to coordinate that. It's like the code needs to be refactored to indicate that the protocol came up and that when all network protocols has come up, there is a final commit message being sent to them where the sifup/sif6up/etc would set the interface in state up. Almost like pppd should have another state that says PHASE_NETWORK_UP after PHASE_NETWORK then "commit" the interface to "IFF_UP"

This would 'violate' the original design, and likely require yet another callback into the ipcp/ip6cp (and other/future) protocols. It may very well be the right change, and right now; the interface will be in state IFF_UP when either ip6cp or ipcp transition to up.

@pali as far as NetworkManager projects go, it is not using the ip-up/down scripts, but rather a plugin and dbus messages between it and the modem manager, vpn plugins, etc. This would impact maybe the ppp-scripts package on debian, and/or systemd integrations?

@jkroonza
Copy link
Contributor Author

I won't consider my opinion an authoritative voice on the matter. It maybe good to solicit feedback from others here as well, like @paulusmack as he will eventually be the deciding factor if a PR gets merged.

ACK

I didn't realize this earlier, but the ip-pre-up script provides the IPv4 parameters just like the ip-up script does, but before the adapter is set in state IFF_UP. I would imagine that an ipv6 version of the same script would do the same thing (or mplscp for that sake). A net-pre-up would not be able to pass protocol specific data indicating what the resulting negotiated IP-address would be. However, the net-up script could be made into querying the interface for those values.

Correct. The problem currently is the order in which various protocols calls sifup (which is a pppd function, which wraps the system specific mechanisms to up/down an interface), so the constraint that the "interface is configured but not brought up" is violated, in that if IPv6CP brings up the interface first, then once IPCP configures the interface it's not still down, so when ip-pre-up runs, the interface could already be up.

One possible change might be to just move ip-pre-up to run before the interface is configured for IPv4 but pass the information that WILL BE USED to the ip-pre-up (or ipv6-pre-up script- although, given the way IPv6 works we really won't have useful information at this stage, link-local addresses only, once these are brought up the normal IPv6 configuration mechanisms (by way of RA and optionally DHCPv6) takes over. So from that perspective I'm not convinced keeping these are useful, and I'm in favour of deprecating the ip

Also, the action of calling sifup, sifdown, or sif6up of sif6down is done directly from the ipcp.c or ip6cp.c which make it tricky as there is no central point to coordinate that. It's like the code needs to be refactored to indicate that the protocol came up and that when all network protocols has come up, there is a final commit message being sent to them where the sifup/sif6up/etc would set the interface in state up. Almost like pppd should have another state that says PHASE_NETWORK_UP after PHASE_NETWORK then "commit" the interface to "IFF_UP"

OUCH. OK, so this should be reworked, if there is an up/down for each protocol that does the same thing (ditto for sys-solaris.c) then perhaps we should consolidate that logic into shared code for linux and solaris (in main.c?) which then calls simplified OS specific ifup and ifdown interfaces when the interfaces actually needs to be set up/down (as determined by the OS in use as per sys-*.c). I'll submit a separate PR for this quickly with what I recommend we do. This would also serve as a base then for net-pre-up and net-down as these hooks can then be run from same locations. So once we're happy with that, I can update this PR to be based off of that introducing:

net-init (which runs post ppp unit creation, but prior to actually doing anything else)
net-pre-up (which runs just prior to requesting the OS to set the interface as UP - possibly with some config already done, but no guaranees).
net-down (run directly after bringing the interface DOWN).

@jkroonza
Copy link
Contributor Author

Scripts as discussed implemented in #367

@jkroonza jkroonza closed this Aug 24, 2022
@jkroonza jkroonza deleted the ipv6-pre-up branch December 4, 2023 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants