All of lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-11 18:22 Denis Kenzior
  0 siblings, 0 replies; 8+ messages in thread
From: Denis Kenzior @ 2021-11-11 18:22 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 11379 bytes --]

Hi Andrew,

>> I really don't think these belong here.  If we're going in the direction of
>> netconfig delegating the management of applying IP configuration settings, then
>> this is up to the applying entity to store this information.  netconfig would
>> only be responsible for obtaining the settings and notifying the new management
>> entity.
>>
>> The new management entity would either:
>> A) invoke the agent, or
>> B) apply the settings itself.
>>
>> Preferably, A & B would use the same API and be subclasses of some generic
>> abstract interface.  Similarly to how we handle auth_protos, or perhaps modeled
>> after resolv_ops.
> 
> Ok, having these RTNL calls etc., in a separate file from where we
> handle the config methods and policies will make the code a little
> clearer but the API will probably need to include the optional details
> like the MAC addresses.
> 

That is fine.  Every time we logically separate pieces behind a clear API makes 
the code more maintainable and easier to understand.  We pay a bit in code size, 
but it is worth it in my opinion.

>> Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp,
>> netconfig is the one setting routes and addresses.  But with dhcp6, we let the
>> dhcp6_client itself set the route and the address.  So this may need to be
>> refactored to act similarly to how dhcp client is managed.  Otherwise the
>> settings would be applied despite the agent being registered.
> 
> I don't think that's much of an issue but let's do it, one difficulty
> is going to be synchronising the ell and iwd changes though.

So I'm not sure how to interpret the statement that it isn't 'much of an issue'? 
  The contract we have is that if the agent is registered, then iwd doesn't 
apply any settings to the netdevs.  But what I'm pointing out above is that: the 
way l_dhcp6_client is being driven today, this contract is broken.  netconfig 
delegates setting of the address and routes to l_dhcp6_client/l_icmp6_client and 
these settings are always applied if IPv6 support is enabled / detected.

So yes, this is a an issue.  And it should probably be among the first things 
that are refactored as a result of this agent-based approach since these changes 
can be regression-tested rather easily.

>>> +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
>>> +                             unsigned int changed)
>>> +{
>>> +     struct l_rtnl_address *stale_addr;
>>> +
>>> +     if (af == AF_INET)
>>> +             stale_addr = l_steal_ptr(netconfig->stale_v4_address);
>>> +     else
>>> +             stale_addr = l_steal_ptr(netconfig->stale_v6_address);
>>> +
>>> +     if (stale_addr) {
>>> +             L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
>>> +                                             stale_addr,
>>> +                                             netconfig_ifaddr_del_cmd_cb,
>>> +                                             netconfig, NULL));
>>> +             l_rtnl_address_free(stale_addr);
>>> +     }
>>> +
>>> +     if (af == AF_INET) {
>>> +             if (!netconfig->v4_address)
>>> +                     return;
>>> +
>>> +             if ((changed & (NETCONFIG_CHANGED_ADDRESS |
>>> +                                     NETCONFIG_CHANGED_NETMASK)) &&
>>> +                             !netconfig->addr4_add_cmd_id) {
>>> +                     netconfig->addr4_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
>>> +                                     netconfig->ifindex,
>>> +                                     netconfig->v4_address,
>>> +                                     netconfig_ipv4_ifaddr_add_cmd_cb,
>>> +                                     netconfig, NULL);
>>> +                     if (!L_WARN_ON(!netconfig->addr4_add_cmd_id))
>>> +                             changed &= ~(NETCONFIG_CHANGED_ADDRESS |
>>> +                                             NETCONFIG_CHANGED_NETMASK);
>>> +             }
>>> +
>>> +             /*
>>> +              * If we either started the address addition now or one was
>>> +              * already in progress, wait for that operation's callback
>>> +              * and we'll be called again to set the rest of the values
>>> +              * in the changed bitmask.
>>
>> So I'd rather see the ipv4_ifaddr_add_cmd_cb or an intermediate function being
>> invoked than trying to re-invoke netconfig_set in the callback.
> 
> The advantage of this approach is that it handles these async
> operations well, i.e. you look at the bitmask and start the async ops
> to apply the config bits that have changed, clearing the corresponding
> bits from the bitmap.  If during one of those calls a new change
> happens we don't apply the remaining bits and go back to the
> beginning.  Let me see if these can be done differently...
> 

Ok, I can understand the logic behind this, but then again, we set the routes, 
dns and domains once the address is set.  So you can really only 'interrupt' the 
ongoing setting operation after the address has been set.  Explicitly calling 
the address set callback would better reflect this reality.  But I can be 
convinced either way...

>>
>>> +              */
>>> +             if (netconfig->addr4_add_cmd_id) {
>>> +                     netconfig->pending_v4_set |= changed;
>>> +                     return;
>>> +             }
>>> +
>>> +             /*
>>> +              * For IPv6 the address, the subnet route and the gateway
>>> +              * route are handled automatically for DHCP, only cover the
>>> +              * static and FILS-provided cases here.
>>> +              */
>>> +     } else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
>>> +                     (netconfig->fils_override &&
>>> +                      !l_memeqzero(netconfig->fils_override->ipv6_addr,
>>> +                                     16))) {
>>> +             if (!netconfig->v6_address)
>>> +                     return;
>>> +
>>
>> See my comment above about dhcp6_client setting addresses & routes by itself.
>>
>>> +             if ((changed & (NETCONFIG_CHANGED_ADDRESS |
>>> +                                     NETCONFIG_CHANGED_NETMASK)) &&
>>> +                             !netconfig->addr6_add_cmd_id) {
>>> +                     netconfig->addr6_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
>>> +                                     netconfig->ifindex,
>>> +                                     netconfig->v6_address,
>>> +                                     netconfig_ipv6_ifaddr_add_cmd_cb,
>>> +                                     netconfig, NULL);
>>> +                     if (!L_WARN_ON(!netconfig->addr6_add_cmd_id))
>>> +                             changed &= ~(NETCONFIG_CHANGED_ADDRESS |
>>> +                                             NETCONFIG_CHANGED_NETMASK);
>>> +             }
>>> +
>>> +             /*
>>> +              * If we either started the address addition now or one was
>>> +              * already in progress, wait for that operation's callback
>>> +              * and we'll be called again to set the rest of the values
>>> +              * in the changed bitmask.
>>> +              */
>>
>> Is there a reason why we need to wait for the ipv4 address to be set prior to
>> setting the v6 address?
> 
> So within netconfig_set we don't wait, we can be setting them simultaneously.
> 

Ok, cool.

>>
>>> +             if (netconfig->addr6_add_cmd_id) {
>>> +                     netconfig->pending_v4_set |= changed;
>>> +                     return;
>>> +             }
>>> +     }
>>> +
>>> +     if (af == AF_INET) {
>>> +             if (changed & NETCONFIG_CHANGED_GATEWAY) {
>>> +                     netconfig_gateway_to_arp(netconfig);
>>> +
>>> +                     if (netconfig_ipv4_gateway_route_install(netconfig))
>>> +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
>>> +             } else if (netconfig->notify) {
>>
>> This is really quite weird since netconfig_ipv4_gateway_route_install() checks
>> that the gateway is NULL.  What is this trying to take care of?
> 
> You mean what is the if (netconfig_ipv4_gateway_route_install) trying
> to take care of?  Error handling, though errors are not really
> expected to happen there.

You have a special case for gateway being NULL in 
netconfig_ipv4_gateway_route_install().  When lease is lost or cleaning up, you 
set NETCONFIG_CHANGED_GATEWAY flag and a debug message and other special 
processing takes place.  That seems bogus.

> 
>>
>>> +                     /*
>>> +                      * The gateway route addition callback notifies the user
>>> +                      * of netconfig success if a gateway is provided, otherwise
>>> +                      * notify here.
>>> +                      */
>>> +                     netconfig->notify(NETCONFIG_EVENT_CONNECTED,
>>> +                                             netconfig->user_data);
>>> +                     netconfig->notify = NULL;
>>> +             }

Also, it looks like even if a lease is obtained without a gateway, the changed 
flag is still set.  The only time a changed flag is not set is if the lease is 
renewed and the gateway is the same.  Which makes the block above pretty suspect?

> 
> So basically we pass a set of new values to the new
> "netconfig-committer" entity, it checks what's changed, calls the
> agent method first, if no agent is registered it falls back to the
> local implementation.
> 
> Honestly I see no place for an "ops" structure there, it can be forced
> between the "entity" and the agent code but it's adding more problems
> than solving.

I disagree here.  It isn't forced at all.  The new entity just selects a backend 
to be used based on some policy.  Similar to how resolve class works.

Right now we would have 2 backends, the agent one and our native one.  But we 
could add more in the future.  The change tracking wouldn't need to be in the 
entity itself, but the backend.  This is because the agent based backend has no 
need to track changes at all.

>>
>> Gateway should be cleared, not changed.  I believe this is done automagically
>> when removing the address.
> 
> Right, netconfig_set doesn't take the "CHANGED" part literally, it's
> more like a dbus property changed signal ;-)
> 

I think this gets you into all sorts of trouble and should be changed.  See above.

>>
>>> +             }
>>> +
>>> +             if (netconfig_dns_list_update(netconfig, AF_INET))
>>> +                     changed |= NETCONFIG_CHANGED_DNS;
>>> +
>>> +             if (netconfig_domains_update(netconfig, AF_INET))
>>> +                     changed |= NETCONFIG_CHANGED_DOMAINS;
>>> +
>>> +             netconfig_set(netconfig, AF_INET, changed);
>>
>> Perhaps lease expiration, and thus the clearing out of all settings should be a
>> separate operation?  There's not much point setting domains, dns, etc in this case.
> 
> Special-casing it won't make the code clearer but ok.

Again, I disagree completely.  There isn't much point to set empty domains or 
DNS info and cause unnecessary D-Bus traffic.  We do have resolve_revert for a 
reason.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-13  1:15 Andrew Zaborowski
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Zaborowski @ 2021-11-13  1:15 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 7975 bytes --]

Hi Denis,

On Thu, 11 Nov 2021 at 21:10, Denis Kenzior <denkenz(a)gmail.com> wrote:
> On 11/11/21 1:29 PM, Andrew Zaborowski wrote:
> > On Thu, 11 Nov 2021 at 19:35, Denis Kenzior <denkenz(a)gmail.com> wrote:
> >>>> Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp,
> >>>> netconfig is the one setting routes and addresses.  But with dhcp6, we let the
> >>>> dhcp6_client itself set the route and the address.  So this may need to be
> >>>> refactored to act similarly to how dhcp client is managed.  Otherwise the
> >>>> settings would be applied despite the agent being registered.
> >>>
> >>> I don't think that's much of an issue but let's do it, one difficulty
> >>> is going to be synchronising the ell and iwd changes though.
> >>
> >> So I'm not sure how to interpret the statement that it isn't 'much of an issue'?
> >>    The contract we have is that if the agent is registered, then iwd doesn't
> >> apply any settings to the netdevs.
> >
> > Well that's the proposal, not a contract that we have or one that has
> > to be implemented fully from the start (there's hardly any protocol we
>
> Eh, why not?  Especially if you want NM to use it?  Not honoring contracts is
> amateur hour.  I mean why would they even bother if we can't keep our contracts?
>
> > implement 100% and especially in netconfig -- yet it got merged).  In
> > any case there's not much that that agent can do other than the same
> > thing l_dhcp6_client does.
>
> Sure, but it has to be done, not hand-waved away.

Right, but the risk is that it's done twice (less harmful), not that
it doesn't get done.

So looking at l_dhcp6_client it looks like simply skipping
l_dhcp6_client_set_rtnl()/l_icmp6_client_set_rtnl() will make it
behave like l_dhcp_client, no new flag is needed.

> >>>>> +             if (netconfig->addr6_add_cmd_id) {
> >>>>> +                     netconfig->pending_v4_set |= changed;
> >>>>> +                     return;
> >>>>> +             }
> >>>>> +     }
> >>>>> +
> >>>>> +     if (af == AF_INET) {
> >>>>> +             if (changed & NETCONFIG_CHANGED_GATEWAY) {
> >>>>> +                     netconfig_gateway_to_arp(netconfig);
> >>>>> +
> >>>>> +                     if (netconfig_ipv4_gateway_route_install(netconfig))
> >>>>> +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
> >>>>> +             } else if (netconfig->notify) {
> >>>>
> >>>> This is really quite weird since netconfig_ipv4_gateway_route_install() checks
> >>>> that the gateway is NULL.  What is this trying to take care of?
> >>>
> >>> You mean what is the if (netconfig_ipv4_gateway_route_install) trying
> >>> to take care of?  Error handling, though errors are not really
> >>> expected to happen there.
> >>
> >> You have a special case for gateway being NULL in
> >> netconfig_ipv4_gateway_route_install().  When lease is lost or cleaning up, you
> >> set NETCONFIG_CHANGED_GATEWAY flag and a debug message and other special
> >> processing takes place.  That seems bogus.
> >>
> >>>
> >>>>
> >>>>> +                     /*
> >>>>> +                      * The gateway route addition callback notifies the user
> >>>>> +                      * of netconfig success if a gateway is provided, otherwise
> >>>>> +                      * notify here.
> >>>>> +                      */
> >>>>> +                     netconfig->notify(NETCONFIG_EVENT_CONNECTED,
> >>>>> +                                             netconfig->user_data);
> >>>>> +                     netconfig->notify = NULL;
> >>>>> +             }
> >>
> >> Also, it looks like even if a lease is obtained without a gateway, the changed
> >> flag is still set.  The only time a changed flag is not set is if the lease is
> >> renewed and the gateway is the same.  Which makes the block above pretty suspect?
> >
> > If there was no gateway and the new lease has no gateway (or the lease
> > is gone), then there's no change in the gateway, right?  We don't set
> > NETCONFIG_CHANGED_GATEWAY in that case.
>
> Sure, but you still try to call the notifier saying that we're 'connected'?  How
> does that make sense?  Yes, I know the notifier is likely NULL by now.  But I
> see no circumstances where this code path would even be hit.  And if it is hit,
> it is probably doing the wrong thing anyway.  So why have it in the first place?

Ok, I think I see what you mean.  It's not really clear why we're
trying to signal NETCONFIG_EVENT_CONNECTED whenever netconfig_set is
called.  It's basically to mirror how the original code works, I could
have probably improved it.

In the original code, when it's time to set the new gateway (i.e.
after we've set the address etc.) we either notify in the
route_add_cmd_cb, or if no gateway address is received, we emit
NETCONFIG_EVENT_CONNECTED directly in
netconfig_ipv4_gateway_route_install.  I mirrored this behaviour for
the sake of review, so it's easy to confirm that this is pure
refactoring and the sequence of RTNL commands, netconfig events and
other interactions is unaffected in the end.

Perhaps we should add a function that is guaranteed to be called after
all the bits from a new lease have been committed to the netdev and
that's where we'd emit the event, if we're not yet in connected state
(in a separate commit).

>
> >
> > When the value has changed then we set NETCONFIG_CHANGED_GATEWAY.  I'm
> > not sure I understand what is bogus about this.  The wording CHANGED
> > is because it's literally changing (new is different than the old --
> > that's how you define a change), now if this is committed at the
> > system level through an erase and re-add doesn't mean that the gateway
> > address hasn't changed...
> >
>
> What you say is absolutely correct.  Yet still wrong ;)  Just look at what
> happens on a 'change'.  You try to call the notifier with 'connected' event.
> You also print a debug message saying there's no gateway in the lease.  Neither
> make sense.

Ok the debug message is indeed wrong if we're handling an
L_DHCP_CLIENT_EVENT_LEASE_EXPIRED.

>
> >>
> >>>
> >>> So basically we pass a set of new values to the new
> >>> "netconfig-committer" entity, it checks what's changed, calls the
> >>> agent method first, if no agent is registered it falls back to the
> >>> local implementation.
> >>>
> >>> Honestly I see no place for an "ops" structure there, it can be forced
> >>> between the "entity" and the agent code but it's adding more problems
> >>> than solving.
> >>
> >> I disagree here.  It isn't forced at all.  The new entity just selects a backend
> >> to be used based on some policy.  Similar to how resolve class works.
> >>
> >> Right now we would have 2 backends, the agent one and our native one.  But we
> >> could add more in the future.
> >
> > Hypothetically we could but in the end each backend basically does the
> > same simple thing in this case.
> >
> > What I suspect that someone will eventually have a usecase for is
> > exposing those config properties on D-Bus, in fact I think there was
> > talk about this since very early on when Tim added netconfig and then
> > again when I was adding P2P (I eventually added the "ConnectedIP"
> > property to start with the bare minimum).
>
> Sure.  But why do you think this is somehow at odds with the proposed architecture?

The "changed" flags logic is helpful for that kind of interface.
Moving that logic into one of those proposed "backends" prevents the
other backends and interfaces from having this information.

> We have to see.  I think an explicit flag stating that v4/v6 info is invalidated
> would be a nice hint to the backend to simply invoke RevertLink or whatever.
> Given this, I'd also generate events and give both ipv4 and ipv6 change sets in
> one operation.  Even if only ipv4 or ipv6 changes were made.

Ok.

Best regards

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-11 20:21 Denis Kenzior
  0 siblings, 0 replies; 8+ messages in thread
From: Denis Kenzior @ 2021-11-11 20:21 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 533 bytes --]

Hi Andrew,

> 
> What you say is absolutely correct.  Yet still wrong ;)  Just look at what 
> happens on a 'change'.  You try to call the notifier with 'connected' event. You 
> also print a debug message saying there's no gateway in the lease.  Neither make 
> sense.
> 

Note, I'm not saying you shouldn't use 'CHANGED'.  Perhaps that is the right 
approach if netconfig would inform the backend only of the 'new' settings.  But 
the way CHANGED is being handled in this patch seems problematic.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-11 20:07 Denis Kenzior
  0 siblings, 0 replies; 8+ messages in thread
From: Denis Kenzior @ 2021-11-11 20:07 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 9804 bytes --]

Hi Andrew,

On 11/11/21 1:29 PM, Andrew Zaborowski wrote:
> On Thu, 11 Nov 2021 at 19:35, Denis Kenzior <denkenz(a)gmail.com> wrote:
>>>> I really don't think these belong here.  If we're going in the direction of
>>>> netconfig delegating the management of applying IP configuration settings, then
>>>> this is up to the applying entity to store this information.  netconfig would
>>>> only be responsible for obtaining the settings and notifying the new management
>>>> entity.
>>>>
>>>> The new management entity would either:
>>>> A) invoke the agent, or
>>>> B) apply the settings itself.
>>>>
>>>> Preferably, A & B would use the same API and be subclasses of some generic
>>>> abstract interface.  Similarly to how we handle auth_protos, or perhaps modeled
>>>> after resolv_ops.
>>>
>>> Ok, having these RTNL calls etc., in a separate file from where we
>>> handle the config methods and policies will make the code a little
>>> clearer but the API will probably need to include the optional details
>>> like the MAC addresses.
>>>
>>
>> That is fine.  Every time we logically separate pieces behind a clear API makes
>> the code more maintainable and easier to understand.  We pay a bit in code size,
>> but it is worth it in my opinion.
> 
> Every time we say "every time" it's probably wrong ;-)
> 
> I mean let's do this, but there's obviously a threshold of complexity
> where this starts to make sense.

Oh I agree.  And so far I would say that we only did such restructuring when it 
made sense.  Of course you can disagree, but I don't recall any situation where 
we did something like this and said: "that was a terrible idea."

> 
> And in general clear APIs are not generally ones that use function
> pointers, or wrap simple things like a memcpy or integer operations in
> functions -- in terms of readability you simply can't beat a language
> feature (I know you disagree, not proposing we spend any more time on
> this topic..).

This is a classic example of polymorphic behavior.  Hiding this behind a virtual 
interface + backend is about as clear as things can get ;)

> 
>>
>>>> Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp,
>>>> netconfig is the one setting routes and addresses.  But with dhcp6, we let the
>>>> dhcp6_client itself set the route and the address.  So this may need to be
>>>> refactored to act similarly to how dhcp client is managed.  Otherwise the
>>>> settings would be applied despite the agent being registered.
>>>
>>> I don't think that's much of an issue but let's do it, one difficulty
>>> is going to be synchronising the ell and iwd changes though.
>>
>> So I'm not sure how to interpret the statement that it isn't 'much of an issue'?
>>    The contract we have is that if the agent is registered, then iwd doesn't
>> apply any settings to the netdevs.
> 
> Well that's the proposal, not a contract that we have or one that has
> to be implemented fully from the start (there's hardly any protocol we

Eh, why not?  Especially if you want NM to use it?  Not honoring contracts is 
amateur hour.  I mean why would they even bother if we can't keep our contracts?

> implement 100% and especially in netconfig -- yet it got merged).  In
> any case there's not much that that agent can do other than the same
> thing l_dhcp6_client does.

Sure, but it has to be done, not hand-waved away.

> 
>>   But what I'm pointing out above is that: the
>> way l_dhcp6_client is being driven today, this contract is broken.  netconfig
>> delegates setting of the address and routes to l_dhcp6_client/l_icmp6_client and
>> these settings are always applied if IPv6 support is enabled / detected.
> 
> Right, the difference between l_dhcp_client and l_dhcp6_client is
> strange and didn't seem to be mentioned anywhere in the netconfig.c
> until the comment I add in this patch.

Well, we tried it one way with dhcpv4.  We tried another approach with dhcpv6. 
Arguably, the dhcpv6 approach made a whole lot more sense until this agent 
proposal came about.  Having ell / dhcp{4|6}_client be able to set settings 
directly still might make sense for certain applications...

>>>>> +             if (netconfig->addr6_add_cmd_id) {
>>>>> +                     netconfig->pending_v4_set |= changed;
>>>>> +                     return;
>>>>> +             }
>>>>> +     }
>>>>> +
>>>>> +     if (af == AF_INET) {
>>>>> +             if (changed & NETCONFIG_CHANGED_GATEWAY) {
>>>>> +                     netconfig_gateway_to_arp(netconfig);
>>>>> +
>>>>> +                     if (netconfig_ipv4_gateway_route_install(netconfig))
>>>>> +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
>>>>> +             } else if (netconfig->notify) {
>>>>
>>>> This is really quite weird since netconfig_ipv4_gateway_route_install() checks
>>>> that the gateway is NULL.  What is this trying to take care of?
>>>
>>> You mean what is the if (netconfig_ipv4_gateway_route_install) trying
>>> to take care of?  Error handling, though errors are not really
>>> expected to happen there.
>>
>> You have a special case for gateway being NULL in
>> netconfig_ipv4_gateway_route_install().  When lease is lost or cleaning up, you
>> set NETCONFIG_CHANGED_GATEWAY flag and a debug message and other special
>> processing takes place.  That seems bogus.
>>
>>>
>>>>
>>>>> +                     /*
>>>>> +                      * The gateway route addition callback notifies the user
>>>>> +                      * of netconfig success if a gateway is provided, otherwise
>>>>> +                      * notify here.
>>>>> +                      */
>>>>> +                     netconfig->notify(NETCONFIG_EVENT_CONNECTED,
>>>>> +                                             netconfig->user_data);
>>>>> +                     netconfig->notify = NULL;
>>>>> +             }
>>
>> Also, it looks like even if a lease is obtained without a gateway, the changed
>> flag is still set.  The only time a changed flag is not set is if the lease is
>> renewed and the gateway is the same.  Which makes the block above pretty suspect?
> 
> If there was no gateway and the new lease has no gateway (or the lease
> is gone), then there's no change in the gateway, right?  We don't set
> NETCONFIG_CHANGED_GATEWAY in that case.

Sure, but you still try to call the notifier saying that we're 'connected'?  How 
does that make sense?  Yes, I know the notifier is likely NULL by now.  But I 
see no circumstances where this code path would even be hit.  And if it is hit, 
it is probably doing the wrong thing anyway.  So why have it in the first place?

> 
> When the value has changed then we set NETCONFIG_CHANGED_GATEWAY.  I'm
> not sure I understand what is bogus about this.  The wording CHANGED
> is because it's literally changing (new is different than the old --
> that's how you define a change), now if this is committed at the
> system level through an erase and re-add doesn't mean that the gateway
> address hasn't changed...
> 

What you say is absolutely correct.  Yet still wrong ;)  Just look at what 
happens on a 'change'.  You try to call the notifier with 'connected' event. 
You also print a debug message saying there's no gateway in the lease.  Neither 
make sense.

>>
>>>
>>> So basically we pass a set of new values to the new
>>> "netconfig-committer" entity, it checks what's changed, calls the
>>> agent method first, if no agent is registered it falls back to the
>>> local implementation.
>>>
>>> Honestly I see no place for an "ops" structure there, it can be forced
>>> between the "entity" and the agent code but it's adding more problems
>>> than solving.
>>
>> I disagree here.  It isn't forced at all.  The new entity just selects a backend
>> to be used based on some policy.  Similar to how resolve class works.
>>
>> Right now we would have 2 backends, the agent one and our native one.  But we
>> could add more in the future.
> 
> Hypothetically we could but in the end each backend basically does the
> same simple thing in this case.
> 
> What I suspect that someone will eventually have a usecase for is
> exposing those config properties on D-Bus, in fact I think there was
> talk about this since very early on when Tim added netconfig and then
> again when I was adding P2P (I eventually added the "ConnectedIP"
> property to start with the bare minimum).

Sure.  But why do you think this is somehow at odds with the proposed architecture?

> 
>>   The change tracking wouldn't need to be in the
>> entity itself, but the backend.  This is because the agent based backend has no
>> need to track changes at all.
> 
> Well we can send all of the parameters to the agent every time, that
> will work too.

We could.

> 
>>>> Perhaps lease expiration, and thus the clearing out of all settings should be a
>>>> separate operation?  There's not much point setting domains, dns, etc in this case.
>>>
>>> Special-casing it won't make the code clearer but ok.
>>
>> Again, I disagree completely.  There isn't much point to set empty domains or
>> DNS info and cause unnecessary D-Bus traffic.  We do have resolve_revert for a
>> reason.
> 
> We don't set them when they're empty, but we make the "committer" part
> aware that the value has changed (is different than it was).
> 

We have to see.  I think an explicit flag stating that v4/v6 info is invalidated 
would be a nice hint to the backend to simply invoke RevertLink or whatever. 
Given this, I'd also generate events and give both ipv4 and ipv6 change sets in 
one operation.  Even if only ipv4 or ipv6 changes were made.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-11 19:29 Andrew Zaborowski
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Zaborowski @ 2021-11-11 19:29 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 11046 bytes --]

On Thu, 11 Nov 2021 at 19:35, Denis Kenzior <denkenz(a)gmail.com> wrote:
> >> I really don't think these belong here.  If we're going in the direction of
> >> netconfig delegating the management of applying IP configuration settings, then
> >> this is up to the applying entity to store this information.  netconfig would
> >> only be responsible for obtaining the settings and notifying the new management
> >> entity.
> >>
> >> The new management entity would either:
> >> A) invoke the agent, or
> >> B) apply the settings itself.
> >>
> >> Preferably, A & B would use the same API and be subclasses of some generic
> >> abstract interface.  Similarly to how we handle auth_protos, or perhaps modeled
> >> after resolv_ops.
> >
> > Ok, having these RTNL calls etc., in a separate file from where we
> > handle the config methods and policies will make the code a little
> > clearer but the API will probably need to include the optional details
> > like the MAC addresses.
> >
>
> That is fine.  Every time we logically separate pieces behind a clear API makes
> the code more maintainable and easier to understand.  We pay a bit in code size,
> but it is worth it in my opinion.

Every time we say "every time" it's probably wrong ;-)

I mean let's do this, but there's obviously a threshold of complexity
where this starts to make sense.

And in general clear APIs are not generally ones that use function
pointers, or wrap simple things like a memcpy or integer operations in
functions -- in terms of readability you simply can't beat a language
feature (I know you disagree, not proposing we spend any more time on
this topic..).

>
> >> Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp,
> >> netconfig is the one setting routes and addresses.  But with dhcp6, we let the
> >> dhcp6_client itself set the route and the address.  So this may need to be
> >> refactored to act similarly to how dhcp client is managed.  Otherwise the
> >> settings would be applied despite the agent being registered.
> >
> > I don't think that's much of an issue but let's do it, one difficulty
> > is going to be synchronising the ell and iwd changes though.
>
> So I'm not sure how to interpret the statement that it isn't 'much of an issue'?
>   The contract we have is that if the agent is registered, then iwd doesn't
> apply any settings to the netdevs.

Well that's the proposal, not a contract that we have or one that has
to be implemented fully from the start (there's hardly any protocol we
implement 100% and especially in netconfig -- yet it got merged).  In
any case there's not much that that agent can do other than the same
thing l_dhcp6_client does.

>  But what I'm pointing out above is that: the
> way l_dhcp6_client is being driven today, this contract is broken.  netconfig
> delegates setting of the address and routes to l_dhcp6_client/l_icmp6_client and
> these settings are always applied if IPv6 support is enabled / detected.

Right, the difference between l_dhcp_client and l_dhcp6_client is
strange and didn't seem to be mentioned anywhere in the netconfig.c
until the comment I add in this patch.

>
> So yes, this is a an issue.  And it should probably be among the first things
> that are refactored as a result of this agent-based approach since these changes
> can be regression-tested rather easily.
>
> >>> +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
> >>> +                             unsigned int changed)
> >>> +{
> >>> +     struct l_rtnl_address *stale_addr;
> >>> +
> >>> +     if (af == AF_INET)
> >>> +             stale_addr = l_steal_ptr(netconfig->stale_v4_address);
> >>> +     else
> >>> +             stale_addr = l_steal_ptr(netconfig->stale_v6_address);
> >>> +
> >>> +     if (stale_addr) {
> >>> +             L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> >>> +                                             stale_addr,
> >>> +                                             netconfig_ifaddr_del_cmd_cb,
> >>> +                                             netconfig, NULL));
> >>> +             l_rtnl_address_free(stale_addr);
> >>> +     }
> >>> +
> >>> +     if (af == AF_INET) {
> >>> +             if (!netconfig->v4_address)
> >>> +                     return;
> >>> +
> >>> +             if ((changed & (NETCONFIG_CHANGED_ADDRESS |
> >>> +                                     NETCONFIG_CHANGED_NETMASK)) &&
> >>> +                             !netconfig->addr4_add_cmd_id) {
> >>> +                     netconfig->addr4_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
> >>> +                                     netconfig->ifindex,
> >>> +                                     netconfig->v4_address,
> >>> +                                     netconfig_ipv4_ifaddr_add_cmd_cb,
> >>> +                                     netconfig, NULL);
> >>> +                     if (!L_WARN_ON(!netconfig->addr4_add_cmd_id))
> >>> +                             changed &= ~(NETCONFIG_CHANGED_ADDRESS |
> >>> +                                             NETCONFIG_CHANGED_NETMASK);
> >>> +             }
> >>> +
> >>> +             /*
> >>> +              * If we either started the address addition now or one was
> >>> +              * already in progress, wait for that operation's callback
> >>> +              * and we'll be called again to set the rest of the values
> >>> +              * in the changed bitmask.
> >>
> >> So I'd rather see the ipv4_ifaddr_add_cmd_cb or an intermediate function being
> >> invoked than trying to re-invoke netconfig_set in the callback.
> >
> > The advantage of this approach is that it handles these async
> > operations well, i.e. you look at the bitmask and start the async ops
> > to apply the config bits that have changed, clearing the corresponding
> > bits from the bitmap.  If during one of those calls a new change
> > happens we don't apply the remaining bits and go back to the
> > beginning.  Let me see if these can be done differently...
> >
>
> Ok, I can understand the logic behind this, but then again, we set the routes,
> dns and domains once the address is set.  So you can really only 'interrupt' the
> ongoing setting operation after the address has been set.  Explicitly calling
> the address set callback would better reflect this reality.  But I can be
> convinced either way...

(we do call ipv4_ifaddr_add_cmd_cb, and from *there* is where we look
at what's the next step)

>
> >>> +             if (netconfig->addr6_add_cmd_id) {
> >>> +                     netconfig->pending_v4_set |= changed;
> >>> +                     return;
> >>> +             }
> >>> +     }
> >>> +
> >>> +     if (af == AF_INET) {
> >>> +             if (changed & NETCONFIG_CHANGED_GATEWAY) {
> >>> +                     netconfig_gateway_to_arp(netconfig);
> >>> +
> >>> +                     if (netconfig_ipv4_gateway_route_install(netconfig))
> >>> +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
> >>> +             } else if (netconfig->notify) {
> >>
> >> This is really quite weird since netconfig_ipv4_gateway_route_install() checks
> >> that the gateway is NULL.  What is this trying to take care of?
> >
> > You mean what is the if (netconfig_ipv4_gateway_route_install) trying
> > to take care of?  Error handling, though errors are not really
> > expected to happen there.
>
> You have a special case for gateway being NULL in
> netconfig_ipv4_gateway_route_install().  When lease is lost or cleaning up, you
> set NETCONFIG_CHANGED_GATEWAY flag and a debug message and other special
> processing takes place.  That seems bogus.
>
> >
> >>
> >>> +                     /*
> >>> +                      * The gateway route addition callback notifies the user
> >>> +                      * of netconfig success if a gateway is provided, otherwise
> >>> +                      * notify here.
> >>> +                      */
> >>> +                     netconfig->notify(NETCONFIG_EVENT_CONNECTED,
> >>> +                                             netconfig->user_data);
> >>> +                     netconfig->notify = NULL;
> >>> +             }
>
> Also, it looks like even if a lease is obtained without a gateway, the changed
> flag is still set.  The only time a changed flag is not set is if the lease is
> renewed and the gateway is the same.  Which makes the block above pretty suspect?

If there was no gateway and the new lease has no gateway (or the lease
is gone), then there's no change in the gateway, right?  We don't set
NETCONFIG_CHANGED_GATEWAY in that case.

When the value has changed then we set NETCONFIG_CHANGED_GATEWAY.  I'm
not sure I understand what is bogus about this.  The wording CHANGED
is because it's literally changing (new is different than the old --
that's how you define a change), now if this is committed at the
system level through an erase and re-add doesn't mean that the gateway
address hasn't changed...

>
> >
> > So basically we pass a set of new values to the new
> > "netconfig-committer" entity, it checks what's changed, calls the
> > agent method first, if no agent is registered it falls back to the
> > local implementation.
> >
> > Honestly I see no place for an "ops" structure there, it can be forced
> > between the "entity" and the agent code but it's adding more problems
> > than solving.
>
> I disagree here.  It isn't forced at all.  The new entity just selects a backend
> to be used based on some policy.  Similar to how resolve class works.
>
> Right now we would have 2 backends, the agent one and our native one.  But we
> could add more in the future.

Hypothetically we could but in the end each backend basically does the
same simple thing in this case.

What I suspect that someone will eventually have a usecase for is
exposing those config properties on D-Bus, in fact I think there was
talk about this since very early on when Tim added netconfig and then
again when I was adding P2P (I eventually added the "ConnectedIP"
property to start with the bare minimum).

>  The change tracking wouldn't need to be in the
> entity itself, but the backend.  This is because the agent based backend has no
> need to track changes at all.

Well we can send all of the parameters to the agent every time, that
will work too.

> >> Perhaps lease expiration, and thus the clearing out of all settings should be a
> >> separate operation?  There's not much point setting domains, dns, etc in this case.
> >
> > Special-casing it won't make the code clearer but ok.
>
> Again, I disagree completely.  There isn't much point to set empty domains or
> DNS info and cause unnecessary D-Bus traffic.  We do have resolve_revert for a
> reason.

We don't set them when they're empty, but we make the "committer" part
aware that the value has changed (is different than it was).

Best regards

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-11 16:54 Andrew Zaborowski
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Zaborowski @ 2021-11-11 16:54 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 22694 bytes --]

Hi Denis,

On Wed, 10 Nov 2021 at 19:05, Denis Kenzior <denkenz(a)gmail.com> wrote:
> On 11/8/21 5:28 AM, Andrew Zaborowski wrote:
> >   src/netconfig.c | 541 +++++++++++++++++++++++++++++++++++-------------
> >   1 file changed, 398 insertions(+), 143 deletions(-)
> >
>
> It would be far easier if this was broken down into more digestible chunks.  I
> could even apply some right away since many make sense on their own.

Right, it's grown some again but it was already a result of splitting
the previous version.

>
> > diff --git a/src/netconfig.c b/src/netconfig.c
> > index e7fd4855..ea77e818 100644
> > --- a/src/netconfig.c
> > +++ b/src/netconfig.c
> > @@ -52,6 +52,15 @@
> >   #include "src/netconfig.h"
> >   #include "src/sysfs.h"
> >
> > +enum netconfig_changed_flags {
> > +     NETCONFIG_CHANGED_ADDRESS       = 1 << 0,
> > +     NETCONFIG_CHANGED_NETMASK       = 1 << 1,
>
> Isn't netmask effectively part of the address?

It's more closely related to the gateway since it's part of talking to
outside subnets (unlike address).. but you can treat it in different
ways.  I tried to make this granular to give us flexibility in how
this gets exposed to user but ok, let's skip the netmask.

>  If netmask changes, then you
> need to change the connected route and the address on the interface anyway.
>
> > +     NETCONFIG_CHANGED_GATEWAY       = 1 << 2,
> > +     NETCONFIG_CHANGED_DNS           = 1 << 3,
> > +     NETCONFIG_CHANGED_DOMAINS       = 1 << 4,
> > +     NETCONFIG_CHANGED_ALL           = 0x1f,
> > +};
> > +
> >   struct netconfig {
> >       uint32_t ifindex;
> >       struct l_dhcp_client *dhcp_client;
> > @@ -59,7 +68,9 @@ struct netconfig {
> >       uint8_t rtm_protocol;
> >       uint8_t rtm_v6_protocol;
> >       struct l_rtnl_address *v4_address;
> > +     struct l_rtnl_address *stale_v4_address;
> >       struct l_rtnl_address *v6_address;
> > +     struct l_rtnl_address *stale_v6_address;
>
> I really don't think these belong here.  If we're going in the direction of
> netconfig delegating the management of applying IP configuration settings, then
> this is up to the applying entity to store this information.  netconfig would
> only be responsible for obtaining the settings and notifying the new management
> entity.
>
> The new management entity would either:
> A) invoke the agent, or
> B) apply the settings itself.
>
> Preferably, A & B would use the same API and be subclasses of some generic
> abstract interface.  Similarly to how we handle auth_protos, or perhaps modeled
> after resolv_ops.

Ok, having these RTNL calls etc., in a separate file from where we
handle the config methods and policies will make the code a little
clearer but the API will probably need to include the optional details
like the MAC addresses.

>
> >       char **dns4_overrides;
> >       char **dns6_overrides;
> >       char **dns4_list;
> > @@ -70,6 +81,8 @@ struct netconfig {
> >       char *v6_gateway_str;
> >       char *v4_domain;
> >       char **v6_domains;
> > +     unsigned int pending_v4_set;
> > +     unsigned int pending_v6_set;
>
> These probably belong in the new management entity as well.
>
> >
> >       const struct l_settings *active_settings;
> >
> > @@ -813,10 +826,8 @@ static void netconfig_route6_add_cb(int error, uint16_t type,
> >       }
> >   }
> >
> > -static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
> > +static bool netconfig_ipv4_subnet_route_install(struct netconfig *netconfig)
> >   {
> > -     L_AUTO_FREE_VAR(char *, gateway) = NULL;
> > -     const uint8_t *gateway_mac = NULL;
> >       struct in_addr in_addr;
> >       char ip[INET_ADDRSTRLEN];
> >       char network[INET_ADDRSTRLEN];
> > @@ -839,10 +850,19 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
> >                                               netconfig_route_generic_cb,
> >                                               netconfig, NULL)) {
> >               l_error("netconfig: Failed to add subnet route.");
> > -
> >               return false;
> >       }
> >
> > +     return true;
> > +}
> > +
> > +static bool netconfig_ipv4_gateway_route_install(struct netconfig *netconfig)
> > +{
> > +     L_AUTO_FREE_VAR(char *, gateway) = NULL;
> > +     const uint8_t *gateway_mac = NULL;
> > +     struct in_addr in_addr;
> > +     char ip[INET_ADDRSTRLEN];
> > +
> >       gateway = netconfig_ipv4_get_gateway(netconfig, &gateway_mac);
> >       if (!gateway) {
> >               l_debug("No gateway obtained from %s.",
> > @@ -858,6 +878,10 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
> >               return true;
> >       }
> >
> > +     if (!l_rtnl_address_get_address(netconfig->v4_address, ip) ||
> > +                     inet_pton(AF_INET, ip, &in_addr) < 1)
> > +             return false;
> > +
> >       netconfig->route4_add_gateway_cmd_id =
> >               l_rtnl_route4_add_gateway(rtnl, netconfig->ifindex, gateway, ip,
> >                                               ROUTE_PRIORITY_OFFSET,
> > @@ -871,50 +895,77 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
> >               return false;
> >       }
> >
> > -     if (gateway_mac) {
> > -             /*
> > -              * Attempt to use the gateway MAC address received from the AP
> > -              * by writing the mapping directly into the netdev's ARP table
> > -              * so as to save one data frame roundtrip before first IP
> > -              * connections are established.  This is very low-priority but
> > -              * print error messages just because they may indicate bigger
> > -              * problems.
> > -              */
> > -             if (!l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
> > +     /*
> > +      * Attempt to use the gateway MAC address received from the AP by
> > +      * writing the mapping directly into the netdev's ARP table so as
> > +      * to save one data frame roundtrip before first IP connections
> > +      * are established.  This is very low-priority but print error
> > +      * messages just because they may indicate bigger problems.
> > +      */
> > +     if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
> >                                       AF_INET,
> >                                       &netconfig->fils_override->ipv4_gateway,
> >                                       gateway_mac, 6,
> >                                       netconfig_set_neighbor_entry_cb, NULL,
> >                                       NULL))
> > -                     l_debug("l_rtnl_neighbor_set_hwaddr failed");
> > +             l_debug("l_rtnl_neighbor_set_hwaddr failed");
> > +
> > +     return true;
> > +}
>
> I cherry-picked these changes into a separate commit, please review.

Looks good.

>
> > +
> > +static bool netconfig_ipv6_static_gateway_route_install(
> > +                                             struct netconfig *netconfig)
> > +{
> > +     _auto_(l_rtnl_route_free) struct l_rtnl_route *gateway = NULL;
> > +     const uint8_t *gateway_mac;
> > +
> > +     gateway = netconfig_get_static6_gateway(netconfig,
> > +                                             &netconfig->v6_gateway_str,
> > +                                             &gateway_mac);
> > +     if (!gateway) {
> > +             l_debug("No IPv6 gateway set");
> > +             return true;
> >       }
> >
> > +     netconfig->route6_add_cmd_id = l_rtnl_route_add(rtnl,
> > +                                                     netconfig->ifindex,
> > +                                                     gateway,
> > +                                                     netconfig_route6_add_cb,
> > +                                                     netconfig, NULL);
> > +     if (!L_WARN_ON(unlikely(!netconfig->route6_add_cmd_id)))
> > +             return false;
> > +
> > +     if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
> > +                                     AF_INET6,
> > +                                     netconfig->fils_override->ipv6_gateway,
> > +                                     gateway_mac, 6,
> > +                                     netconfig_set_neighbor_entry_cb, NULL,
> > +                                     NULL))
> > +             l_debug("l_rtnl_neighbor_set_hwaddr failed");
> > +
> >       return true;
> >   }
>
> Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp,
> netconfig is the one setting routes and addresses.  But with dhcp6, we let the
> dhcp6_client itself set the route and the address.  So this may need to be
> refactored to act similarly to how dhcp client is managed.  Otherwise the
> settings would be applied despite the agent being registered.

I don't think that's much of an issue but let's do it, one difficulty
is going to be synchronising the ell and iwd changes though.

>
> >
> > +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
> > +                             unsigned int changed);
> > +
>
> doc/coding-style.txt item M17

It's the exception M17 talks about.

>
> <snip>
>
> > @@ -978,21 +1005,169 @@ static void netconfig_ifaddr_del_cmd_cb(int error, uint16_t type,
> >                               "Error %d: %s", error, strerror(-error));
> >   }
> >
> > +static bool netconfig_address_cmp_address(const struct l_rtnl_address *a,
> > +                                             const struct l_rtnl_address *b)
> > +{
> > +     char str_a[INET6_ADDRSTRLEN];
> > +     char str_b[INET6_ADDRSTRLEN];
> > +
> > +     if (a == b)
> > +             return true;
> > +
> > +     if (!a || !b)
> > +             return false;
> > +
> > +     if (!l_rtnl_address_get_address(a, str_a) ||
> > +                     !l_rtnl_address_get_address(b, str_b))
> > +             return false;
> > +
> > +     return !strcmp(str_a, str_b);
> > +}
> > +
>
> This probably belongs in ell.

Ok.

>
> > +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
> > +                             unsigned int changed)
> > +{
> > +     struct l_rtnl_address *stale_addr;
> > +
> > +     if (af == AF_INET)
> > +             stale_addr = l_steal_ptr(netconfig->stale_v4_address);
> > +     else
> > +             stale_addr = l_steal_ptr(netconfig->stale_v6_address);
> > +
> > +     if (stale_addr) {
> > +             L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> > +                                             stale_addr,
> > +                                             netconfig_ifaddr_del_cmd_cb,
> > +                                             netconfig, NULL));
> > +             l_rtnl_address_free(stale_addr);
> > +     }
> > +
> > +     if (af == AF_INET) {
> > +             if (!netconfig->v4_address)
> > +                     return;
> > +
> > +             if ((changed & (NETCONFIG_CHANGED_ADDRESS |
> > +                                     NETCONFIG_CHANGED_NETMASK)) &&
> > +                             !netconfig->addr4_add_cmd_id) {
> > +                     netconfig->addr4_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
> > +                                     netconfig->ifindex,
> > +                                     netconfig->v4_address,
> > +                                     netconfig_ipv4_ifaddr_add_cmd_cb,
> > +                                     netconfig, NULL);
> > +                     if (!L_WARN_ON(!netconfig->addr4_add_cmd_id))
> > +                             changed &= ~(NETCONFIG_CHANGED_ADDRESS |
> > +                                             NETCONFIG_CHANGED_NETMASK);
> > +             }
> > +
> > +             /*
> > +              * If we either started the address addition now or one was
> > +              * already in progress, wait for that operation's callback
> > +              * and we'll be called again to set the rest of the values
> > +              * in the changed bitmask.
>
> So I'd rather see the ipv4_ifaddr_add_cmd_cb or an intermediate function being
> invoked than trying to re-invoke netconfig_set in the callback.

The advantage of this approach is that it handles these async
operations well, i.e. you look at the bitmask and start the async ops
to apply the config bits that have changed, clearing the corresponding
bits from the bitmap.  If during one of those calls a new change
happens we don't apply the remaining bits and go back to the
beginning.  Let me see if these can be done differently...

>
> > +              */
> > +             if (netconfig->addr4_add_cmd_id) {
> > +                     netconfig->pending_v4_set |= changed;
> > +                     return;
> > +             }
> > +
> > +             /*
> > +              * For IPv6 the address, the subnet route and the gateway
> > +              * route are handled automatically for DHCP, only cover the
> > +              * static and FILS-provided cases here.
> > +              */
> > +     } else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
> > +                     (netconfig->fils_override &&
> > +                      !l_memeqzero(netconfig->fils_override->ipv6_addr,
> > +                                     16))) {
> > +             if (!netconfig->v6_address)
> > +                     return;
> > +
>
> See my comment above about dhcp6_client setting addresses & routes by itself.
>
> > +             if ((changed & (NETCONFIG_CHANGED_ADDRESS |
> > +                                     NETCONFIG_CHANGED_NETMASK)) &&
> > +                             !netconfig->addr6_add_cmd_id) {
> > +                     netconfig->addr6_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
> > +                                     netconfig->ifindex,
> > +                                     netconfig->v6_address,
> > +                                     netconfig_ipv6_ifaddr_add_cmd_cb,
> > +                                     netconfig, NULL);
> > +                     if (!L_WARN_ON(!netconfig->addr6_add_cmd_id))
> > +                             changed &= ~(NETCONFIG_CHANGED_ADDRESS |
> > +                                             NETCONFIG_CHANGED_NETMASK);
> > +             }
> > +
> > +             /*
> > +              * If we either started the address addition now or one was
> > +              * already in progress, wait for that operation's callback
> > +              * and we'll be called again to set the rest of the values
> > +              * in the changed bitmask.
> > +              */
>
> Is there a reason why we need to wait for the ipv4 address to be set prior to
> setting the v6 address?

So within netconfig_set we don't wait, we can be setting them simultaneously.

>
> > +             if (netconfig->addr6_add_cmd_id) {
> > +                     netconfig->pending_v4_set |= changed;
> > +                     return;
> > +             }
> > +     }
> > +
> > +     if (af == AF_INET) {
> > +             if (changed & NETCONFIG_CHANGED_GATEWAY) {
> > +                     netconfig_gateway_to_arp(netconfig);
> > +
> > +                     if (netconfig_ipv4_gateway_route_install(netconfig))
> > +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
> > +             } else if (netconfig->notify) {
>
> This is really quite weird since netconfig_ipv4_gateway_route_install() checks
> that the gateway is NULL.  What is this trying to take care of?

You mean what is the if (netconfig_ipv4_gateway_route_install) trying
to take care of?  Error handling, though errors are not really
expected to happen there.

>
> > +                     /*
> > +                      * The gateway route addition callback notifies the user
> > +                      * of netconfig success if a gateway is provided, otherwise
> > +                      * notify here.
> > +                      */
> > +                     netconfig->notify(NETCONFIG_EVENT_CONNECTED,
> > +                                             netconfig->user_data);
> > +                     netconfig->notify = NULL;
> > +             }
> > +     } else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
> > +                     (netconfig->fils_override &&
> > +                      !l_memeqzero(netconfig->fils_override->ipv6_gateway,
> > +                                     16))) {
> > +             if (changed & (NETCONFIG_CHANGED_GATEWAY))
> > +                     if (netconfig_ipv6_static_gateway_route_install(
> > +                                                             netconfig))
> > +                             changed &= ~NETCONFIG_CHANGED_GATEWAY;
> > +     }
> > +
> > +     if (changed & NETCONFIG_CHANGED_DNS) {
> > +             netconfig_set_dns(netconfig);
> > +             changed &= ~NETCONFIG_CHANGED_DNS;
> > +     }
> > +
> > +     if (changed & NETCONFIG_CHANGED_DOMAINS) {
> > +             netconfig_set_domains(netconfig);
> > +             changed &= ~NETCONFIG_CHANGED_DOMAINS;
> > +     }
> > +}
> > +
> >   static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
> >                                               enum l_dhcp_client_event event,
> >                                               void *userdata)
> >   {
> >       struct netconfig *netconfig = userdata;
> > +     unsigned int changed = 0;
> >
> >       l_debug("DHCPv4 event %d", event);
> >
> >       switch (event) {
> >       case L_DHCP_CLIENT_EVENT_IP_CHANGED:
> > -             L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> > -                                     netconfig->v4_address,
> > -                                     netconfig_ifaddr_del_cmd_cb,
> > -                                     netconfig, NULL));
> > -             /* Fall through. */
> >       case L_DHCP_CLIENT_EVENT_LEASE_OBTAINED:
> >       {
> >               char *gateway_str;
> > @@ -1004,9 +1179,19 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
> >               else {
> >                       l_free(netconfig->v4_gateway_str);
> >                       netconfig->v4_gateway_str = gateway_str;
> > +                     changed |= NETCONFIG_CHANGED_GATEWAY;
> >               }
> >
> >               address = netconfig_get_dhcp4_address(netconfig);
> > +
> > +             if (!netconfig_address_cmp_prefix_len(netconfig->v4_address,
> > +                                                     address))
> > +                     changed |= NETCONFIG_CHANGED_NETMASK;
> > +
> > +             if (!netconfig_address_cmp_address(netconfig->v4_address,
> > +                                                     address))
> > +                     changed |= NETCONFIG_CHANGED_ADDRESS;
> > +
>
> My impression is that the comparison of new vs old settings should be given over
> to the new management entity.

Ok.

So basically we pass a set of new values to the new
"netconfig-committer" entity, it checks what's changed, calls the
agent method first, if no agent is registered it falls back to the
local implementation.

Honestly I see no place for an "ops" structure there, it can be forced
between the "entity" and the agent code but it's adding more problems
than solving.

>
> >               l_rtnl_address_free(netconfig->v4_address);
> >               netconfig->v4_address = address;
> >
> > @@ -1016,26 +1201,36 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
> >                       return;
> >               }
> >
> > -             netconfig_dns_list_update(netconfig, AF_INET);
> > -             netconfig_domains_update(netconfig, AF_INET);
> > +             if (netconfig_dns_list_update(netconfig, AF_INET))
> > +                     changed |= NETCONFIG_CHANGED_DNS;
> >
> > -             L_WARN_ON(!(netconfig->addr4_add_cmd_id =
> > -                             l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
> > -                                     netconfig->v4_address,
> > -                                     netconfig_ipv4_ifaddr_add_cmd_cb,
> > -                                     netconfig, NULL)));
> > +             if (netconfig_domains_update(netconfig, AF_INET))
> > +                     changed |= NETCONFIG_CHANGED_DOMAINS;
> > +
> > +             netconfig_set(netconfig, AF_INET, changed);
> >               break;
> >       }
> >       case L_DHCP_CLIENT_EVENT_LEASE_RENEWED:
> >               break;
> >       case L_DHCP_CLIENT_EVENT_LEASE_EXPIRED:
> > -             L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> > -                                     netconfig->v4_address,
> > -                                     netconfig_ifaddr_del_cmd_cb,
> > -                                     netconfig, NULL));
> > -             l_rtnl_address_free(netconfig->v4_address);
> > -             netconfig->v4_address = NULL;
> > -             l_free(l_steal_ptr(netconfig->v4_gateway_str));
> > +             l_debug("Lease for interface %u expired", netconfig->ifindex);
> > +             changed = NETCONFIG_CHANGED_ADDRESS | NETCONFIG_CHANGED_NETMASK;
> > +             l_rtnl_address_free(netconfig->stale_v4_address);
> > +             netconfig->stale_v4_address =
> > +                     l_steal_ptr(netconfig->v4_address);
> > +
> > +             if (netconfig->v4_gateway_str) {
> > +                     changed |= NETCONFIG_CHANGED_GATEWAY;
> > +                     l_free(l_steal_ptr(netconfig->v4_gateway_str));
>
> Gateway should be cleared, not changed.  I believe this is done automagically
> when removing the address.

Right, netconfig_set doesn't take the "CHANGED" part literally, it's
more like a dbus property changed signal ;-)

>
> > +             }
> > +
> > +             if (netconfig_dns_list_update(netconfig, AF_INET))
> > +                     changed |= NETCONFIG_CHANGED_DNS;
> > +
> > +             if (netconfig_domains_update(netconfig, AF_INET))
> > +                     changed |= NETCONFIG_CHANGED_DOMAINS;
> > +
> > +             netconfig_set(netconfig, AF_INET, changed);
>
> Perhaps lease expiration, and thus the clearing out of all settings should be a
> separate operation?  There's not much point setting domains, dns, etc in this case.

Special-casing it won't make the code clearer but ok.

Best regards

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-10 17:51 Denis Kenzior
  0 siblings, 0 replies; 8+ messages in thread
From: Denis Kenzior @ 2021-11-10 17:51 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 16342 bytes --]

Hi Andrew,

On 11/8/21 5:28 AM, Andrew Zaborowski wrote:
> Refactor and unify how the netconfig values, that we obtain from DHCP or
> the settings or otherwise, are written/committed to the system netdevs.
> We basically concentrate all of those actions in a new function
> netconfig_set() that is called whenever any value changes.  The only
> exceptions are the sysfs writes and some of the ARP table writes.
> 
> This will allow us to more easily delegate committing the values to an
> (optional) D-Bus agent, or expose them as D-Bus properties if we ever
> choose to do that in the future.  For that I'm also passing a bitmask
> telling netconfig_set() which values may have actually changed, so we
> don't bother looking at those that definitely haven't changed.
> ---
>   src/netconfig.c | 541 +++++++++++++++++++++++++++++++++++-------------
>   1 file changed, 398 insertions(+), 143 deletions(-)
> 

It would be far easier if this was broken down into more digestible chunks.  I 
could even apply some right away since many make sense on their own.

> diff --git a/src/netconfig.c b/src/netconfig.c
> index e7fd4855..ea77e818 100644
> --- a/src/netconfig.c
> +++ b/src/netconfig.c
> @@ -52,6 +52,15 @@
>   #include "src/netconfig.h"
>   #include "src/sysfs.h"
>   
> +enum netconfig_changed_flags {
> +	NETCONFIG_CHANGED_ADDRESS	= 1 << 0,
> +	NETCONFIG_CHANGED_NETMASK	= 1 << 1,

Isn't netmask effectively part of the address?  If netmask changes, then you 
need to change the connected route and the address on the interface anyway.

> +	NETCONFIG_CHANGED_GATEWAY	= 1 << 2,
> +	NETCONFIG_CHANGED_DNS		= 1 << 3,
> +	NETCONFIG_CHANGED_DOMAINS	= 1 << 4,
> +	NETCONFIG_CHANGED_ALL		= 0x1f,
> +};
> +
>   struct netconfig {
>   	uint32_t ifindex;
>   	struct l_dhcp_client *dhcp_client;
> @@ -59,7 +68,9 @@ struct netconfig {
>   	uint8_t rtm_protocol;
>   	uint8_t rtm_v6_protocol;
>   	struct l_rtnl_address *v4_address;
> +	struct l_rtnl_address *stale_v4_address;
>   	struct l_rtnl_address *v6_address;
> +	struct l_rtnl_address *stale_v6_address;

I really don't think these belong here.  If we're going in the direction of 
netconfig delegating the management of applying IP configuration settings, then 
this is up to the applying entity to store this information.  netconfig would 
only be responsible for obtaining the settings and notifying the new management 
entity.

The new management entity would either:
A) invoke the agent, or
B) apply the settings itself.

Preferably, A & B would use the same API and be subclasses of some generic 
abstract interface.  Similarly to how we handle auth_protos, or perhaps modeled 
after resolv_ops.

>   	char **dns4_overrides;
>   	char **dns6_overrides;
>   	char **dns4_list;
> @@ -70,6 +81,8 @@ struct netconfig {
>   	char *v6_gateway_str;
>   	char *v4_domain;
>   	char **v6_domains;
> +	unsigned int pending_v4_set;
> +	unsigned int pending_v6_set;

These probably belong in the new management entity as well.

>   
>   	const struct l_settings *active_settings;
>   
> @@ -813,10 +826,8 @@ static void netconfig_route6_add_cb(int error, uint16_t type,
>   	}
>   }
>   
> -static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
> +static bool netconfig_ipv4_subnet_route_install(struct netconfig *netconfig)
>   {
> -	L_AUTO_FREE_VAR(char *, gateway) = NULL;
> -	const uint8_t *gateway_mac = NULL;
>   	struct in_addr in_addr;
>   	char ip[INET_ADDRSTRLEN];
>   	char network[INET_ADDRSTRLEN];
> @@ -839,10 +850,19 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
>   						netconfig_route_generic_cb,
>   						netconfig, NULL)) {
>   		l_error("netconfig: Failed to add subnet route.");
> -
>   		return false;
>   	}
>   
> +	return true;
> +}
> +
> +static bool netconfig_ipv4_gateway_route_install(struct netconfig *netconfig)
> +{
> +	L_AUTO_FREE_VAR(char *, gateway) = NULL;
> +	const uint8_t *gateway_mac = NULL;
> +	struct in_addr in_addr;
> +	char ip[INET_ADDRSTRLEN];
> +
>   	gateway = netconfig_ipv4_get_gateway(netconfig, &gateway_mac);
>   	if (!gateway) {
>   		l_debug("No gateway obtained from %s.",
> @@ -858,6 +878,10 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
>   		return true;
>   	}
>   
> +	if (!l_rtnl_address_get_address(netconfig->v4_address, ip) ||
> +			inet_pton(AF_INET, ip, &in_addr) < 1)
> +		return false;
> +
>   	netconfig->route4_add_gateway_cmd_id =
>   		l_rtnl_route4_add_gateway(rtnl, netconfig->ifindex, gateway, ip,
>   						ROUTE_PRIORITY_OFFSET,
> @@ -871,50 +895,77 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
>   		return false;
>   	}
>   
> -	if (gateway_mac) {
> -		/*
> -		 * Attempt to use the gateway MAC address received from the AP
> -		 * by writing the mapping directly into the netdev's ARP table
> -		 * so as to save one data frame roundtrip before first IP
> -		 * connections are established.  This is very low-priority but
> -		 * print error messages just because they may indicate bigger
> -		 * problems.
> -		 */
> -		if (!l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
> +	/*
> +	 * Attempt to use the gateway MAC address received from the AP by
> +	 * writing the mapping directly into the netdev's ARP table so as
> +	 * to save one data frame roundtrip before first IP connections
> +	 * are established.  This is very low-priority but print error
> +	 * messages just because they may indicate bigger problems.
> +	 */
> +	if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
>   					AF_INET,
>   					&netconfig->fils_override->ipv4_gateway,
>   					gateway_mac, 6,
>   					netconfig_set_neighbor_entry_cb, NULL,
>   					NULL))
> -			l_debug("l_rtnl_neighbor_set_hwaddr failed");
> +		l_debug("l_rtnl_neighbor_set_hwaddr failed");
> +
> +	return true;
> +}

I cherry-picked these changes into a separate commit, please review.

> +
> +static bool netconfig_ipv6_static_gateway_route_install(
> +						struct netconfig *netconfig)
> +{
> +	_auto_(l_rtnl_route_free) struct l_rtnl_route *gateway = NULL;
> +	const uint8_t *gateway_mac;
> +
> +	gateway = netconfig_get_static6_gateway(netconfig,
> +						&netconfig->v6_gateway_str,
> +						&gateway_mac);
> +	if (!gateway) {
> +		l_debug("No IPv6 gateway set");
> +		return true;
>   	}
>   
> +	netconfig->route6_add_cmd_id = l_rtnl_route_add(rtnl,
> +							netconfig->ifindex,
> +							gateway,
> +							netconfig_route6_add_cb,
> +							netconfig, NULL);
> +	if (!L_WARN_ON(unlikely(!netconfig->route6_add_cmd_id)))
> +		return false;
> +
> +	if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
> +					AF_INET6,
> +					netconfig->fils_override->ipv6_gateway,
> +					gateway_mac, 6,
> +					netconfig_set_neighbor_entry_cb, NULL,
> +					NULL))
> +		l_debug("l_rtnl_neighbor_set_hwaddr failed");
> +
>   	return true;
>   }

Note that netconfig behaves differently with dhcp6 client vs dhcp.  With dhcp, 
netconfig is the one setting routes and addresses.  But with dhcp6, we let the 
dhcp6_client itself set the route and the address.  So this may need to be 
refactored to act similarly to how dhcp client is managed.  Otherwise the 
settings would be applied despite the agent being registered.

>   
> +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
> +				unsigned int changed);
> +

doc/coding-style.txt item M17

<snip>

> @@ -978,21 +1005,169 @@ static void netconfig_ifaddr_del_cmd_cb(int error, uint16_t type,
>   				"Error %d: %s", error, strerror(-error));
>   }
>   
> +static bool netconfig_address_cmp_address(const struct l_rtnl_address *a,
> +						const struct l_rtnl_address *b)
> +{
> +	char str_a[INET6_ADDRSTRLEN];
> +	char str_b[INET6_ADDRSTRLEN];
> +
> +	if (a == b)
> +		return true;
> +
> +	if (!a || !b)
> +		return false;
> +
> +	if (!l_rtnl_address_get_address(a, str_a) ||
> +			!l_rtnl_address_get_address(b, str_b))
> +		return false;
> +
> +	return !strcmp(str_a, str_b);
> +}
> +

This probably belongs in ell.

> +static void netconfig_set(struct netconfig *netconfig, uint8_t af,
> +				unsigned int changed)
> +{
> +	struct l_rtnl_address *stale_addr;
> +
> +	if (af == AF_INET)
> +		stale_addr = l_steal_ptr(netconfig->stale_v4_address);
> +	else
> +		stale_addr = l_steal_ptr(netconfig->stale_v6_address);
> +
> +	if (stale_addr) {
> +		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> +						stale_addr,
> +						netconfig_ifaddr_del_cmd_cb,
> +						netconfig, NULL));
> +		l_rtnl_address_free(stale_addr);
> +	}
> +
> +	if (af == AF_INET) {
> +		if (!netconfig->v4_address)
> +			return;
> +
> +		if ((changed & (NETCONFIG_CHANGED_ADDRESS |
> +					NETCONFIG_CHANGED_NETMASK)) &&
> +				!netconfig->addr4_add_cmd_id) {
> +			netconfig->addr4_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
> +					netconfig->ifindex,
> +					netconfig->v4_address,
> +					netconfig_ipv4_ifaddr_add_cmd_cb,
> +					netconfig, NULL);
> +			if (!L_WARN_ON(!netconfig->addr4_add_cmd_id))
> +				changed &= ~(NETCONFIG_CHANGED_ADDRESS |
> +						NETCONFIG_CHANGED_NETMASK);
> +		}
> +
> +		/*
> +		 * If we either started the address addition now or one was
> +		 * already in progress, wait for that operation's callback
> +		 * and we'll be called again to set the rest of the values
> +		 * in the changed bitmask.

So I'd rather see the ipv4_ifaddr_add_cmd_cb or an intermediate function being 
invoked than trying to re-invoke netconfig_set in the callback.

> +		 */
> +		if (netconfig->addr4_add_cmd_id) {
> +			netconfig->pending_v4_set |= changed;
> +			return;
> +		}
> +
> +		/*
> +		 * For IPv6 the address, the subnet route and the gateway
> +		 * route are handled automatically for DHCP, only cover the
> +		 * static and FILS-provided cases here.
> +		 */
> +	} else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
> +			(netconfig->fils_override &&
> +			 !l_memeqzero(netconfig->fils_override->ipv6_addr,
> +					16))) {
> +		if (!netconfig->v6_address)
> +			return;
> +

See my comment above about dhcp6_client setting addresses & routes by itself.

> +		if ((changed & (NETCONFIG_CHANGED_ADDRESS |
> +					NETCONFIG_CHANGED_NETMASK)) &&
> +				!netconfig->addr6_add_cmd_id) {
> +			netconfig->addr6_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
> +					netconfig->ifindex,
> +					netconfig->v6_address,
> +					netconfig_ipv6_ifaddr_add_cmd_cb,
> +					netconfig, NULL);
> +			if (!L_WARN_ON(!netconfig->addr6_add_cmd_id))
> +				changed &= ~(NETCONFIG_CHANGED_ADDRESS |
> +						NETCONFIG_CHANGED_NETMASK);
> +		}
> +
> +		/*
> +		 * If we either started the address addition now or one was
> +		 * already in progress, wait for that operation's callback
> +		 * and we'll be called again to set the rest of the values
> +		 * in the changed bitmask.
> +		 */

Is there a reason why we need to wait for the ipv4 address to be set prior to 
setting the v6 address?

> +		if (netconfig->addr6_add_cmd_id) {
> +			netconfig->pending_v4_set |= changed;
> +			return;
> +		}
> +	}
> +
> +	if (af == AF_INET) {
> +		if (changed & NETCONFIG_CHANGED_GATEWAY) {
> +			netconfig_gateway_to_arp(netconfig);
> +
> +			if (netconfig_ipv4_gateway_route_install(netconfig))
> +				changed &= ~NETCONFIG_CHANGED_GATEWAY;
> +		} else if (netconfig->notify) {

This is really quite weird since netconfig_ipv4_gateway_route_install() checks 
that the gateway is NULL.  What is this trying to take care of?

> +			/*
> +			 * The gateway route addition callback notifies the user
> +			 * of netconfig success if a gateway is provided, otherwise
> +			 * notify here.
> +			 */
> +			netconfig->notify(NETCONFIG_EVENT_CONNECTED,
> +						netconfig->user_data);
> +			netconfig->notify = NULL;
> +		}
> +	} else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
> +			(netconfig->fils_override &&
> +			 !l_memeqzero(netconfig->fils_override->ipv6_gateway,
> +					16))) {
> +		if (changed & (NETCONFIG_CHANGED_GATEWAY))
> +			if (netconfig_ipv6_static_gateway_route_install(
> +								netconfig))
> +				changed &= ~NETCONFIG_CHANGED_GATEWAY;
> +	}
> +
> +	if (changed & NETCONFIG_CHANGED_DNS) {
> +		netconfig_set_dns(netconfig);
> +		changed &= ~NETCONFIG_CHANGED_DNS;
> +	}
> +
> +	if (changed & NETCONFIG_CHANGED_DOMAINS) {
> +		netconfig_set_domains(netconfig);
> +		changed &= ~NETCONFIG_CHANGED_DOMAINS;
> +	}
> +}
> +
>   static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
>   						enum l_dhcp_client_event event,
>   						void *userdata)
>   {
>   	struct netconfig *netconfig = userdata;
> +	unsigned int changed = 0;
>   
>   	l_debug("DHCPv4 event %d", event);
>   
>   	switch (event) {
>   	case L_DHCP_CLIENT_EVENT_IP_CHANGED:
> -		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> -					netconfig->v4_address,
> -					netconfig_ifaddr_del_cmd_cb,
> -					netconfig, NULL));
> -		/* Fall through. */
>   	case L_DHCP_CLIENT_EVENT_LEASE_OBTAINED:
>   	{
>   		char *gateway_str;
> @@ -1004,9 +1179,19 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
>   		else {
>   			l_free(netconfig->v4_gateway_str);
>   			netconfig->v4_gateway_str = gateway_str;
> +			changed |= NETCONFIG_CHANGED_GATEWAY;
>   		}
>   
>   		address = netconfig_get_dhcp4_address(netconfig);
> +
> +		if (!netconfig_address_cmp_prefix_len(netconfig->v4_address,
> +							address))
> +			changed |= NETCONFIG_CHANGED_NETMASK;
> +
> +		if (!netconfig_address_cmp_address(netconfig->v4_address,
> +							address))
> +			changed |= NETCONFIG_CHANGED_ADDRESS;
> +

My impression is that the comparison of new vs old settings should be given over 
to the new management entity.

>   		l_rtnl_address_free(netconfig->v4_address);
>   		netconfig->v4_address = address;
>   
> @@ -1016,26 +1201,36 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
>   			return;
>   		}
>   
> -		netconfig_dns_list_update(netconfig, AF_INET);
> -		netconfig_domains_update(netconfig, AF_INET);
> +		if (netconfig_dns_list_update(netconfig, AF_INET))
> +			changed |= NETCONFIG_CHANGED_DNS;
>   
> -		L_WARN_ON(!(netconfig->addr4_add_cmd_id =
> -				l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
> -					netconfig->v4_address,
> -					netconfig_ipv4_ifaddr_add_cmd_cb,
> -					netconfig, NULL)));
> +		if (netconfig_domains_update(netconfig, AF_INET))
> +			changed |= NETCONFIG_CHANGED_DOMAINS;
> +
> +		netconfig_set(netconfig, AF_INET, changed);
>   		break;
>   	}
>   	case L_DHCP_CLIENT_EVENT_LEASE_RENEWED:
>   		break;
>   	case L_DHCP_CLIENT_EVENT_LEASE_EXPIRED:
> -		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
> -					netconfig->v4_address,
> -					netconfig_ifaddr_del_cmd_cb,
> -					netconfig, NULL));
> -		l_rtnl_address_free(netconfig->v4_address);
> -		netconfig->v4_address = NULL;
> -		l_free(l_steal_ptr(netconfig->v4_gateway_str));
> +		l_debug("Lease for interface %u expired", netconfig->ifindex);
> +		changed = NETCONFIG_CHANGED_ADDRESS | NETCONFIG_CHANGED_NETMASK;
> +		l_rtnl_address_free(netconfig->stale_v4_address);
> +		netconfig->stale_v4_address =
> +			l_steal_ptr(netconfig->v4_address);
> +
> +		if (netconfig->v4_gateway_str) {
> +			changed |= NETCONFIG_CHANGED_GATEWAY;
> +			l_free(l_steal_ptr(netconfig->v4_gateway_str));

Gateway should be cleared, not changed.  I believe this is done automagically 
when removing the address.

> +		}
> +
> +		if (netconfig_dns_list_update(netconfig, AF_INET))
> +			changed |= NETCONFIG_CHANGED_DNS;
> +
> +		if (netconfig_domains_update(netconfig, AF_INET))
> +			changed |= NETCONFIG_CHANGED_DOMAINS;
> +
> +		netconfig_set(netconfig, AF_INET, changed);

Perhaps lease expiration, and thus the clearing out of all settings should be a 
separate operation?  There's not much point setting domains, dns, etc in this case.

>   
>   		/* Fall through. */
>   	case L_DHCP_CLIENT_EVENT_NO_LEASE:

<snip>

Regards,
-Denis

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/9] netconfig: Refactor setting new values to system
@ 2021-11-08 11:28 Andrew Zaborowski
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Zaborowski @ 2021-11-08 11:28 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 25258 bytes --]

Refactor and unify how the netconfig values, that we obtain from DHCP or
the settings or otherwise, are written/committed to the system netdevs.
We basically concentrate all of those actions in a new function
netconfig_set() that is called whenever any value changes.  The only
exceptions are the sysfs writes and some of the ARP table writes.

This will allow us to more easily delegate committing the values to an
(optional) D-Bus agent, or expose them as D-Bus properties if we ever
choose to do that in the future.  For that I'm also passing a bitmask
telling netconfig_set() which values may have actually changed, so we
don't bother looking at those that definitely haven't changed.
---
 src/netconfig.c | 541 +++++++++++++++++++++++++++++++++++-------------
 1 file changed, 398 insertions(+), 143 deletions(-)

diff --git a/src/netconfig.c b/src/netconfig.c
index e7fd4855..ea77e818 100644
--- a/src/netconfig.c
+++ b/src/netconfig.c
@@ -52,6 +52,15 @@
 #include "src/netconfig.h"
 #include "src/sysfs.h"
 
+enum netconfig_changed_flags {
+	NETCONFIG_CHANGED_ADDRESS	= 1 << 0,
+	NETCONFIG_CHANGED_NETMASK	= 1 << 1,
+	NETCONFIG_CHANGED_GATEWAY	= 1 << 2,
+	NETCONFIG_CHANGED_DNS		= 1 << 3,
+	NETCONFIG_CHANGED_DOMAINS	= 1 << 4,
+	NETCONFIG_CHANGED_ALL		= 0x1f,
+};
+
 struct netconfig {
 	uint32_t ifindex;
 	struct l_dhcp_client *dhcp_client;
@@ -59,7 +68,9 @@ struct netconfig {
 	uint8_t rtm_protocol;
 	uint8_t rtm_v6_protocol;
 	struct l_rtnl_address *v4_address;
+	struct l_rtnl_address *stale_v4_address;
 	struct l_rtnl_address *v6_address;
+	struct l_rtnl_address *stale_v6_address;
 	char **dns4_overrides;
 	char **dns6_overrides;
 	char **dns4_list;
@@ -70,6 +81,8 @@ struct netconfig {
 	char *v6_gateway_str;
 	char *v4_domain;
 	char **v6_domains;
+	unsigned int pending_v4_set;
+	unsigned int pending_v6_set;
 
 	const struct l_settings *active_settings;
 
@@ -813,10 +826,8 @@ static void netconfig_route6_add_cb(int error, uint16_t type,
 	}
 }
 
-static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
+static bool netconfig_ipv4_subnet_route_install(struct netconfig *netconfig)
 {
-	L_AUTO_FREE_VAR(char *, gateway) = NULL;
-	const uint8_t *gateway_mac = NULL;
 	struct in_addr in_addr;
 	char ip[INET_ADDRSTRLEN];
 	char network[INET_ADDRSTRLEN];
@@ -839,10 +850,19 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
 						netconfig_route_generic_cb,
 						netconfig, NULL)) {
 		l_error("netconfig: Failed to add subnet route.");
-
 		return false;
 	}
 
+	return true;
+}
+
+static bool netconfig_ipv4_gateway_route_install(struct netconfig *netconfig)
+{
+	L_AUTO_FREE_VAR(char *, gateway) = NULL;
+	const uint8_t *gateway_mac = NULL;
+	struct in_addr in_addr;
+	char ip[INET_ADDRSTRLEN];
+
 	gateway = netconfig_ipv4_get_gateway(netconfig, &gateway_mac);
 	if (!gateway) {
 		l_debug("No gateway obtained from %s.",
@@ -858,6 +878,10 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
 		return true;
 	}
 
+	if (!l_rtnl_address_get_address(netconfig->v4_address, ip) ||
+			inet_pton(AF_INET, ip, &in_addr) < 1)
+		return false;
+
 	netconfig->route4_add_gateway_cmd_id =
 		l_rtnl_route4_add_gateway(rtnl, netconfig->ifindex, gateway, ip,
 						ROUTE_PRIORITY_OFFSET,
@@ -871,50 +895,77 @@ static bool netconfig_ipv4_routes_install(struct netconfig *netconfig)
 		return false;
 	}
 
-	if (gateway_mac) {
-		/*
-		 * Attempt to use the gateway MAC address received from the AP
-		 * by writing the mapping directly into the netdev's ARP table
-		 * so as to save one data frame roundtrip before first IP
-		 * connections are established.  This is very low-priority but
-		 * print error messages just because they may indicate bigger
-		 * problems.
-		 */
-		if (!l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
+	/*
+	 * Attempt to use the gateway MAC address received from the AP by
+	 * writing the mapping directly into the netdev's ARP table so as
+	 * to save one data frame roundtrip before first IP connections
+	 * are established.  This is very low-priority but print error
+	 * messages just because they may indicate bigger problems.
+	 */
+	if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
 					AF_INET,
 					&netconfig->fils_override->ipv4_gateway,
 					gateway_mac, 6,
 					netconfig_set_neighbor_entry_cb, NULL,
 					NULL))
-			l_debug("l_rtnl_neighbor_set_hwaddr failed");
+		l_debug("l_rtnl_neighbor_set_hwaddr failed");
+
+	return true;
+}
+
+static bool netconfig_ipv6_static_gateway_route_install(
+						struct netconfig *netconfig)
+{
+	_auto_(l_rtnl_route_free) struct l_rtnl_route *gateway = NULL;
+	const uint8_t *gateway_mac;
+
+	gateway = netconfig_get_static6_gateway(netconfig,
+						&netconfig->v6_gateway_str,
+						&gateway_mac);
+	if (!gateway) {
+		l_debug("No IPv6 gateway set");
+		return true;
 	}
 
+	netconfig->route6_add_cmd_id = l_rtnl_route_add(rtnl,
+							netconfig->ifindex,
+							gateway,
+							netconfig_route6_add_cb,
+							netconfig, NULL);
+	if (!L_WARN_ON(unlikely(!netconfig->route6_add_cmd_id)))
+		return false;
+
+	if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl, netconfig->ifindex,
+					AF_INET6,
+					netconfig->fils_override->ipv6_gateway,
+					gateway_mac, 6,
+					netconfig_set_neighbor_entry_cb, NULL,
+					NULL))
+		l_debug("l_rtnl_neighbor_set_hwaddr failed");
+
 	return true;
 }
 
+static void netconfig_set(struct netconfig *netconfig, uint8_t af,
+				unsigned int changed);
+
 static void netconfig_ipv4_ifaddr_add_cmd_cb(int error, uint16_t type,
 						const void *data, uint32_t len,
 						void *user_data)
 {
 	struct netconfig *netconfig = user_data;
+	unsigned int changed = netconfig->pending_v4_set;
 
 	netconfig->addr4_add_cmd_id = 0;
 
-	if (error && error != -EEXIST) {
+	if (error && error != -EEXIST)
 		l_error("netconfig: Failed to add IP address. "
 				"Error %d: %s", error, strerror(-error));
-		return;
-	}
-
-	netconfig_gateway_to_arp(netconfig);
-
-	if (!netconfig_ipv4_routes_install(netconfig)) {
-		l_error("netconfig: Failed to install IPv4 routes.");
-		return;
-	}
+	else
+		netconfig_ipv4_subnet_route_install(netconfig);
 
-	netconfig_set_dns(netconfig);
-	netconfig_set_domains(netconfig);
+	netconfig->pending_v4_set = 0;
+	netconfig_set(netconfig, AF_INET, changed);
 }
 
 static void netconfig_ipv6_ifaddr_add_cmd_cb(int error, uint16_t type,
@@ -922,40 +973,16 @@ static void netconfig_ipv6_ifaddr_add_cmd_cb(int error, uint16_t type,
 						void *user_data)
 {
 	struct netconfig *netconfig = user_data;
-	struct l_rtnl_route *gateway;
-	const uint8_t *gateway_mac;
+	unsigned int changed = netconfig->pending_v4_set;
 
 	netconfig->addr6_add_cmd_id = 0;
 
-	if (error && error != -EEXIST) {
+	if (error && error != -EEXIST)
 		l_error("netconfig: Failed to add IPv6 address. "
 				"Error %d: %s", error, strerror(-error));
-		return;
-	}
-
-	gateway = netconfig_get_static6_gateway(netconfig,
-						&netconfig->v6_gateway_str,
-						&gateway_mac);
-	if (gateway) {
-		netconfig->route6_add_cmd_id = l_rtnl_route_add(rtnl,
-							netconfig->ifindex,
-							gateway,
-							netconfig_route6_add_cb,
-							netconfig, NULL);
-		L_WARN_ON(unlikely(!netconfig->route6_add_cmd_id));
-		l_rtnl_route_free(gateway);
-
-		if (gateway_mac && !l_rtnl_neighbor_set_hwaddr(rtnl,
-					netconfig->ifindex, AF_INET6,
-					netconfig->fils_override->ipv6_gateway,
-					gateway_mac, 6,
-					netconfig_set_neighbor_entry_cb, NULL,
-					NULL))
-			l_debug("l_rtnl_neighbor_set_hwaddr failed");
-	}
 
-	netconfig_set_dns(netconfig);
-	netconfig_set_domains(netconfig);
+	netconfig->pending_v6_set = 0;
+	netconfig_set(netconfig, AF_INET6, changed);
 }
 
 static void netconfig_ifaddr_del_cmd_cb(int error, uint16_t type,
@@ -978,21 +1005,169 @@ static void netconfig_ifaddr_del_cmd_cb(int error, uint16_t type,
 				"Error %d: %s", error, strerror(-error));
 }
 
+static bool netconfig_address_cmp_address(const struct l_rtnl_address *a,
+						const struct l_rtnl_address *b)
+{
+	char str_a[INET6_ADDRSTRLEN];
+	char str_b[INET6_ADDRSTRLEN];
+
+	if (a == b)
+		return true;
+
+	if (!a || !b)
+		return false;
+
+	if (!l_rtnl_address_get_address(a, str_a) ||
+			!l_rtnl_address_get_address(b, str_b))
+		return false;
+
+	return !strcmp(str_a, str_b);
+}
+
+static bool netconfig_address_cmp_prefix_len(const struct l_rtnl_address *a,
+						const struct l_rtnl_address *b)
+{
+	if (a == b)
+		return true;
+
+	if (!a || !b)
+		return false;
+
+	return l_rtnl_address_get_prefix_length(a) ==
+		l_rtnl_address_get_prefix_length(b);
+}
+
+static void netconfig_set(struct netconfig *netconfig, uint8_t af,
+				unsigned int changed)
+{
+	struct l_rtnl_address *stale_addr;
+
+	if (af == AF_INET)
+		stale_addr = l_steal_ptr(netconfig->stale_v4_address);
+	else
+		stale_addr = l_steal_ptr(netconfig->stale_v6_address);
+
+	if (stale_addr) {
+		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
+						stale_addr,
+						netconfig_ifaddr_del_cmd_cb,
+						netconfig, NULL));
+		l_rtnl_address_free(stale_addr);
+	}
+
+	if (af == AF_INET) {
+		if (!netconfig->v4_address)
+			return;
+
+		if ((changed & (NETCONFIG_CHANGED_ADDRESS |
+					NETCONFIG_CHANGED_NETMASK)) &&
+				!netconfig->addr4_add_cmd_id) {
+			netconfig->addr4_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
+					netconfig->ifindex,
+					netconfig->v4_address,
+					netconfig_ipv4_ifaddr_add_cmd_cb,
+					netconfig, NULL);
+			if (!L_WARN_ON(!netconfig->addr4_add_cmd_id))
+				changed &= ~(NETCONFIG_CHANGED_ADDRESS |
+						NETCONFIG_CHANGED_NETMASK);
+		}
+
+		/*
+		 * If we either started the address addition now or one was
+		 * already in progress, wait for that operation's callback
+		 * and we'll be called again to set the rest of the values
+		 * in the changed bitmask.
+		 */
+		if (netconfig->addr4_add_cmd_id) {
+			netconfig->pending_v4_set |= changed;
+			return;
+		}
+
+		/*
+		 * For IPv6 the address, the subnet route and the gateway
+		 * route are handled automatically for DHCP, only cover the
+		 * static and FILS-provided cases here.
+		 */
+	} else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
+			(netconfig->fils_override &&
+			 !l_memeqzero(netconfig->fils_override->ipv6_addr,
+					16))) {
+		if (!netconfig->v6_address)
+			return;
+
+		if ((changed & (NETCONFIG_CHANGED_ADDRESS |
+					NETCONFIG_CHANGED_NETMASK)) &&
+				!netconfig->addr6_add_cmd_id) {
+			netconfig->addr6_add_cmd_id = l_rtnl_ifaddr_add(rtnl,
+					netconfig->ifindex,
+					netconfig->v6_address,
+					netconfig_ipv6_ifaddr_add_cmd_cb,
+					netconfig, NULL);
+			if (!L_WARN_ON(!netconfig->addr6_add_cmd_id))
+				changed &= ~(NETCONFIG_CHANGED_ADDRESS |
+						NETCONFIG_CHANGED_NETMASK);
+		}
+
+		/*
+		 * If we either started the address addition now or one was
+		 * already in progress, wait for that operation's callback
+		 * and we'll be called again to set the rest of the values
+		 * in the changed bitmask.
+		 */
+		if (netconfig->addr6_add_cmd_id) {
+			netconfig->pending_v4_set |= changed;
+			return;
+		}
+	}
+
+	if (af == AF_INET) {
+		if (changed & NETCONFIG_CHANGED_GATEWAY) {
+			netconfig_gateway_to_arp(netconfig);
+
+			if (netconfig_ipv4_gateway_route_install(netconfig))
+				changed &= ~NETCONFIG_CHANGED_GATEWAY;
+		} else if (netconfig->notify) {
+			/*
+			 * The gateway route addition callback notifies the user
+			 * of netconfig success if a gateway is provided, otherwise
+			 * notify here.
+			 */
+			netconfig->notify(NETCONFIG_EVENT_CONNECTED,
+						netconfig->user_data);
+			netconfig->notify = NULL;
+		}
+	} else if (netconfig->rtm_v6_protocol == RTPROT_STATIC ||
+			(netconfig->fils_override &&
+			 !l_memeqzero(netconfig->fils_override->ipv6_gateway,
+					16))) {
+		if (changed & (NETCONFIG_CHANGED_GATEWAY))
+			if (netconfig_ipv6_static_gateway_route_install(
+								netconfig))
+				changed &= ~NETCONFIG_CHANGED_GATEWAY;
+	}
+
+	if (changed & NETCONFIG_CHANGED_DNS) {
+		netconfig_set_dns(netconfig);
+		changed &= ~NETCONFIG_CHANGED_DNS;
+	}
+
+	if (changed & NETCONFIG_CHANGED_DOMAINS) {
+		netconfig_set_domains(netconfig);
+		changed &= ~NETCONFIG_CHANGED_DOMAINS;
+	}
+}
+
 static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
 						enum l_dhcp_client_event event,
 						void *userdata)
 {
 	struct netconfig *netconfig = userdata;
+	unsigned int changed = 0;
 
 	l_debug("DHCPv4 event %d", event);
 
 	switch (event) {
 	case L_DHCP_CLIENT_EVENT_IP_CHANGED:
-		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ifaddr_del_cmd_cb,
-					netconfig, NULL));
-		/* Fall through. */
 	case L_DHCP_CLIENT_EVENT_LEASE_OBTAINED:
 	{
 		char *gateway_str;
@@ -1004,9 +1179,19 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
 		else {
 			l_free(netconfig->v4_gateway_str);
 			netconfig->v4_gateway_str = gateway_str;
+			changed |= NETCONFIG_CHANGED_GATEWAY;
 		}
 
 		address = netconfig_get_dhcp4_address(netconfig);
+
+		if (!netconfig_address_cmp_prefix_len(netconfig->v4_address,
+							address))
+			changed |= NETCONFIG_CHANGED_NETMASK;
+
+		if (!netconfig_address_cmp_address(netconfig->v4_address,
+							address))
+			changed |= NETCONFIG_CHANGED_ADDRESS;
+
 		l_rtnl_address_free(netconfig->v4_address);
 		netconfig->v4_address = address;
 
@@ -1016,26 +1201,36 @@ static void netconfig_ipv4_dhcp_event_handler(struct l_dhcp_client *client,
 			return;
 		}
 
-		netconfig_dns_list_update(netconfig, AF_INET);
-		netconfig_domains_update(netconfig, AF_INET);
+		if (netconfig_dns_list_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DNS;
 
-		L_WARN_ON(!(netconfig->addr4_add_cmd_id =
-				l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ipv4_ifaddr_add_cmd_cb,
-					netconfig, NULL)));
+		if (netconfig_domains_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		netconfig_set(netconfig, AF_INET, changed);
 		break;
 	}
 	case L_DHCP_CLIENT_EVENT_LEASE_RENEWED:
 		break;
 	case L_DHCP_CLIENT_EVENT_LEASE_EXPIRED:
-		L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ifaddr_del_cmd_cb,
-					netconfig, NULL));
-		l_rtnl_address_free(netconfig->v4_address);
-		netconfig->v4_address = NULL;
-		l_free(l_steal_ptr(netconfig->v4_gateway_str));
+		l_debug("Lease for interface %u expired", netconfig->ifindex);
+		changed = NETCONFIG_CHANGED_ADDRESS | NETCONFIG_CHANGED_NETMASK;
+		l_rtnl_address_free(netconfig->stale_v4_address);
+		netconfig->stale_v4_address =
+			l_steal_ptr(netconfig->v4_address);
+
+		if (netconfig->v4_gateway_str) {
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+			l_free(l_steal_ptr(netconfig->v4_gateway_str));
+		}
+
+		if (netconfig_dns_list_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		if (netconfig_domains_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		netconfig_set(netconfig, AF_INET, changed);
 
 		/* Fall through. */
 	case L_DHCP_CLIENT_EVENT_NO_LEASE:
@@ -1059,6 +1254,7 @@ static void netconfig_dhcp6_event_handler(struct l_dhcp6_client *client,
 						void *userdata)
 {
 	struct netconfig *netconfig = userdata;
+	unsigned int changed = 0;
 
 	switch (event) {
 	case L_DHCP6_CLIENT_EVENT_IP_CHANGED:
@@ -1081,28 +1277,52 @@ static void netconfig_dhcp6_event_handler(struct l_dhcp6_client *client,
 		else {
 			l_free(netconfig->v6_gateway_str);
 			netconfig->v6_gateway_str = gateway_str;
+			changed |= NETCONFIG_CHANGED_GATEWAY;
 		}
 
 		address = l_rtnl_address_new(addr_str,
 					l_dhcp6_lease_get_prefix_length(lease));
+
+		if (!netconfig_address_cmp_prefix_len(netconfig->v6_address,
+							address))
+			changed |= NETCONFIG_CHANGED_NETMASK;
+
+		if (!netconfig_address_cmp_address(netconfig->v6_address,
+							address))
+			changed |= NETCONFIG_CHANGED_ADDRESS;
+
 		l_rtnl_address_free(netconfig->v6_address);
 		netconfig->v6_address = address;
 
-		netconfig_dns_list_update(netconfig, AF_INET6);
-		netconfig_domains_update(netconfig, AF_INET6);
-		netconfig_set_dns(netconfig);
-		netconfig_set_domains(netconfig);
+		if (netconfig_dns_list_update(netconfig, AF_INET6))
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		if (netconfig_domains_update(netconfig, AF_INET6))
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		netconfig_set(netconfig, AF_INET6, changed);
 		break;
 	}
 	case L_DHCP6_CLIENT_EVENT_LEASE_EXPIRED:
-		l_debug("Lease for interface %u expired", netconfig->ifindex);
-		netconfig_dns_list_update(netconfig, AF_INET6);
-		netconfig_domains_update(netconfig, AF_INET6);
-		netconfig_set_dns(netconfig);
-		netconfig_set_domains(netconfig);
-		l_rtnl_address_free(netconfig->v6_address);
-		netconfig->v6_address = NULL;
-		l_free(l_steal_ptr(netconfig->v6_gateway_str));
+		l_debug("IPv6 lease for interface %u expired",
+			netconfig->ifindex);
+		changed = NETCONFIG_CHANGED_ADDRESS | NETCONFIG_CHANGED_NETMASK;
+		l_rtnl_address_free(netconfig->stale_v6_address);
+		netconfig->stale_v6_address =
+			l_steal_ptr(netconfig->v6_address);
+
+		if (netconfig->v6_gateway_str) {
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+			l_free(l_steal_ptr(netconfig->v6_gateway_str));
+		}
+
+		if (netconfig_dns_list_update(netconfig, AF_INET6))
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		if (netconfig_domains_update(netconfig, AF_INET6))
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		netconfig_set(netconfig, AF_INET6, changed);
 
 		/* Fall through */
 	case L_DHCP6_CLIENT_EVENT_NO_LEASE:
@@ -1113,50 +1333,93 @@ static void netconfig_dhcp6_event_handler(struct l_dhcp6_client *client,
 	}
 }
 
-static void netconfig_remove_v4_address(struct netconfig *netconfig)
-{
-	if (!netconfig->v4_address)
-		return;
-
-	L_WARN_ON(!l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ifaddr_del_cmd_cb,
-					netconfig, NULL));
-	l_rtnl_address_free(netconfig->v4_address);
-	netconfig->v4_address = NULL;
-}
-
 static void netconfig_reset_v4(struct netconfig *netconfig)
 {
 	if (netconfig->rtm_protocol) {
-		netconfig_remove_v4_address(netconfig);
+		unsigned int changed = 0;
+
+		if (netconfig->v4_address)
+			changed |= NETCONFIG_CHANGED_ADDRESS |
+				NETCONFIG_CHANGED_NETMASK;
+
+		l_rtnl_address_free(netconfig->stale_v4_address);
+		netconfig->stale_v4_address = l_steal_ptr(netconfig->v4_address);
+
+		if (netconfig->v4_gateway_str)
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+
+		l_free(l_steal_ptr(netconfig->v4_gateway_str));
+
+		if (netconfig->dns4_list)
+			changed |= NETCONFIG_CHANGED_DNS;
 
 		l_strv_free(l_steal_ptr(netconfig->dns4_overrides));
 		l_strv_free(l_steal_ptr(netconfig->dns4_list));
 
+		if (netconfig->v4_domain)
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		l_free(l_steal_ptr(netconfig->v4_domain));
+
+		netconfig_set(netconfig, AF_INET, changed);
+
 		l_dhcp_client_stop(netconfig->dhcp_client);
 		netconfig->rtm_protocol = 0;
 
 		l_acd_destroy(netconfig->acd);
 		netconfig->acd = NULL;
+	}
+}
 
-		l_free(l_steal_ptr(netconfig->v4_gateway_str));
+static void netconfig_reset_v6(struct netconfig *netconfig)
+{
+	if (netconfig->rtm_v6_protocol) {
+		unsigned int changed = 0;
+		struct netdev *netdev = netdev_find(netconfig->ifindex);
 
-		l_free(l_steal_ptr(netconfig->v4_domain));
+		if (netconfig->v6_address)
+			changed |= NETCONFIG_CHANGED_ADDRESS |
+				NETCONFIG_CHANGED_NETMASK;
+
+		l_rtnl_address_free(netconfig->stale_v6_address);
+		netconfig->stale_v6_address = l_steal_ptr(netconfig->v6_address);
+
+		if (netconfig->v6_gateway_str)
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+
+		l_free(l_steal_ptr(netconfig->v6_gateway_str));
+
+		if (netconfig->dns6_list)
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		l_strv_free(l_steal_ptr(netconfig->dns6_overrides));
+		l_strv_free(l_steal_ptr(netconfig->dns6_list));
+
+		if (netconfig->v6_domains)
+			changed |= NETCONFIG_CHANGED_DOMAINS;
+
+		l_free(l_steal_ptr(netconfig->v6_domains));
+
+		netconfig_set(netconfig, AF_INET6, changed);
+
+		l_dhcp6_client_stop(netconfig->dhcp6_client);
+		netconfig->rtm_v6_protocol = 0;
+
+		sysfs_write_ipv6_setting(netdev_get_name(netdev),
+						"disable_ipv6", "1");
 	}
 }
 
 static void netconfig_ipv4_acd_event(enum l_acd_event event, void *user_data)
 {
 	struct netconfig *netconfig = user_data;
+	unsigned int changed;
 
 	switch (event) {
 	case L_ACD_EVENT_AVAILABLE:
-		L_WARN_ON(!(netconfig->addr4_add_cmd_id =
-				l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ipv4_ifaddr_add_cmd_cb,
-					netconfig, NULL)));
+		changed = netconfig->pending_v4_set;
+		netconfig->pending_v4_set = 0;
+		netconfig_set(netconfig, AF_INET, changed);
 		return;
 	case L_ACD_EVENT_CONFLICT:
 		/*
@@ -1174,7 +1437,10 @@ static void netconfig_ipv4_acd_event(enum l_acd_event event, void *user_data)
 		 * case.
 		 */
 		l_error("netconfig: statically configured address was lost");
-		netconfig_remove_v4_address(netconfig);
+
+		l_rtnl_address_free(netconfig->stale_v4_address);
+		netconfig->stale_v4_address = l_steal_ptr(netconfig->v4_address);
+		netconfig_set(netconfig, AF_INET, NETCONFIG_CHANGED_ALL);
 		break;
 	}
 }
@@ -1212,6 +1478,17 @@ static bool netconfig_ipv4_select_and_install(struct netconfig *netconfig)
 
 	if (set_address) {
 		char ip[INET6_ADDRSTRLEN];
+		unsigned int changed = NETCONFIG_CHANGED_ADDRESS |
+			NETCONFIG_CHANGED_NETMASK;
+
+		if (netconfig->v4_gateway_str)
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+
+		if (netconfig_dns_list_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		if (netconfig_domains_update(netconfig, AF_INET))
+			changed |= NETCONFIG_CHANGED_DOMAINS;
 
 		if (L_WARN_ON(!netconfig->v4_address ||
 					!l_rtnl_address_get_address(
@@ -1219,9 +1496,6 @@ static bool netconfig_ipv4_select_and_install(struct netconfig *netconfig)
 							ip)))
 			return false;
 
-		netconfig_dns_list_update(netconfig, AF_INET);
-		netconfig_domains_update(netconfig, AF_INET);
-
 		netconfig->acd = l_acd_new(netconfig->ifindex);
 		l_acd_set_event_handler(netconfig->acd,
 					netconfig_ipv4_acd_event, netconfig,
@@ -1235,12 +1509,9 @@ static bool netconfig_ipv4_select_and_install(struct netconfig *netconfig)
 			l_acd_destroy(netconfig->acd);
 			netconfig->acd = NULL;
 
-			L_WARN_ON(!(netconfig->addr4_add_cmd_id =
-				l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
-					netconfig->v4_address,
-					netconfig_ipv4_ifaddr_add_cmd_cb,
-					netconfig, NULL)));
-		}
+			netconfig_set(netconfig, AF_INET, changed);
+		} else
+			netconfig->pending_v4_set |= changed;
 
 		return true;
 	}
@@ -1293,13 +1564,16 @@ static bool netconfig_ipv6_select_and_install(struct netconfig *netconfig)
 	}
 
 	if (netconfig->v6_address) {
-		netconfig_dns_list_update(netconfig, AF_INET6);
+		unsigned int changed = NETCONFIG_CHANGED_ADDRESS |
+			NETCONFIG_CHANGED_NETMASK;
 
-		L_WARN_ON(!(netconfig->addr6_add_cmd_id =
-			l_rtnl_ifaddr_add(rtnl, netconfig->ifindex,
-					netconfig->v6_address,
-					netconfig_ipv6_ifaddr_add_cmd_cb,
-					netconfig, NULL)));
+		if (netconfig->v6_gateway_str)
+			changed |= NETCONFIG_CHANGED_GATEWAY;
+
+		if (netconfig_dns_list_update(netconfig, AF_INET6))
+			changed |= NETCONFIG_CHANGED_DNS;
+
+		netconfig_set(netconfig, AF_INET6, changed);
 		return true;
 	}
 
@@ -1493,8 +1767,6 @@ bool netconfig_reconfigure(struct netconfig *netconfig, bool set_arp_gw)
 
 bool netconfig_reset(struct netconfig *netconfig)
 {
-	struct netdev *netdev = netdev_find(netconfig->ifindex);
-
 	if (netconfig->route4_add_gateway_cmd_id) {
 		l_netlink_cancel(rtnl, netconfig->route4_add_gateway_cmd_id);
 		netconfig->route4_add_gateway_cmd_id = 0;
@@ -1519,24 +1791,7 @@ bool netconfig_reset(struct netconfig *netconfig)
 		resolve_revert(netconfig->resolve);
 
 	netconfig_reset_v4(netconfig);
-
-	if (netconfig->rtm_v6_protocol) {
-		l_rtnl_address_free(netconfig->v6_address);
-		netconfig->v6_address = NULL;
-
-		l_strv_free(l_steal_ptr(netconfig->dns6_overrides));
-		l_strv_free(l_steal_ptr(netconfig->dns6_list));
-
-		l_dhcp6_client_stop(netconfig->dhcp6_client);
-		netconfig->rtm_v6_protocol = 0;
-
-		sysfs_write_ipv6_setting(netdev_get_name(netdev),
-						"disable_ipv6", "1");
-
-		l_free(l_steal_ptr(netconfig->v6_gateway_str));
-
-		l_strv_free(l_steal_ptr(netconfig->v6_domains));
-	}
+	netconfig_reset_v6(netconfig);
 
 	l_free(l_steal_ptr(netconfig->fils_override));
 
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-11-13  1:15 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-11 18:22 [PATCH 1/9] netconfig: Refactor setting new values to system Denis Kenzior
  -- strict thread matches above, loose matches on Subject: below --
2021-11-13  1:15 Andrew Zaborowski
2021-11-11 20:21 Denis Kenzior
2021-11-11 20:07 Denis Kenzior
2021-11-11 19:29 Andrew Zaborowski
2021-11-11 16:54 Andrew Zaborowski
2021-11-10 17:51 Denis Kenzior
2021-11-08 11:28 Andrew Zaborowski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.