Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

stanislav-brabec
Copy link
Contributor

@stanislav-brabec stanislav-brabec commented Jul 9, 2025

The current \4 and \6 issue file escapes implementation is inferior. It uses get getifaddrs() to get a list of IP addresses. This function does not provide enough information to discriminate between stable IP addresses and ephemeral addresses. As a result, especially \6 often gives unreliable results. This is a limitation of getifaddrs().

This change set implement netlink base IP address processing in agetty, and creates a library for generic use. It solves the problem and provides a generic framework for the future use.

TODO:

treewide:
There are more places using netlink. It would be nice to use the library in all these places.

agetty:
Two pass processing of issue files. First pass just collects IP protocols and list of interfaces (in future interface patterns). Now it always processes both IPv4 and IPv6 on all interfaces. Not so bad, as \a is smart enough to display just the useful part.
Maybe implement more options and formatting support for \a and \A.

netaddrq:

callback_pre implementing interface filters.

Support batch processing in the monitor mode. Sometimes the processing delays and more messages are ready at once. Now each message is processed separately. It would be nice to support a mode, where only the list update is performed for each message, but all changes are then released at once (e. g. in case of agetty, only one reload can appear, if both IPv4 and IPv6 addresses appear at once).

Configurable limit of interfaces. To prevent screen overflowing, it is currently hardcoded as 12.

There is a small regression related to it. If your machine has more interfaces, then the processing is stopped. As a result, even \4 and \6 could return an empty string, if the requested interface is, say 1000th in order.

FIXME:

The implementation found a problem with inferior I18N support in util-linux. A context needs to be added to words like "unknown", otherwise translation to some languages cannot be done properly, as "unknown" or "(unknown)" may need different translation in particular contexts.

@stanislav-brabec stanislav-brabec force-pushed the master branch 6 times, most recently from 1b5a247 to 1ddc848 Compare July 10, 2025 01:20
else
{
/* There can be race, we do not return error here */
/* FIXME I18N: *"unknown"* is too generic. Use context. */

Check notice

Code scanning / CodeQL

FIXME comment Note

FIXME comment: I18N: *"unknown"* is too generic. Use context.
* That is why again label is here: netlink_groups will be re-evaluated and
* dump will be performed again.
*/
/* netlink_groups = 0; */

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
}

#define DBG_CASE(x) case x: str = #x; break
#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing; the DBG_ prefix seems related to the debug.h stuff, but it's function to convert switch-case to a string. Maybe rename to CASE_TO_STRING() or so.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also suggest using str (and strx) as an argument for the macro rather than hiding any variable use in the macros. The strx+2 looks unnecessary, as you can use "0x" in the snprintf() (just "0x%02hhx").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a part of a debug-only code.

The macro purpose is just a simplification of the repeated pieces of the code that cannot be covered by a function.

str and strx as a parameter would make more sense in case if we want to move this code to debug.h. In this single use case it would again create repeated parts in the code.

strx+2 just saved copying 2 bytes on each run (at cost of worse readability).

#define DBG_CASE(x) case x: str = #x; break
#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break
static char *ip_rating(enum ul_netaddrq_ip_rating q)
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to ip_rating_as_string() to make it more readable..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing.

lib/netaddrq.c Outdated
{
DBG(LIST, ul_debugobj(addrq,
"malloc() 1 failed"));
return -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return -ENOMEM; to be compatible with usual util-linux code patterns/

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and maybe the DBG() is overkill in this rare case :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing.

free(newaddr);
error1:
return NULL;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need all the errorN gotos there? If you use calloc(), then you can use

error:
            if (newaddr) {
                      free(newaddr->ifa_local);
                      free(newaddr->ifa_address);
                      free(newaddr);
            }
            return NULL;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or better:

error:
       ul_nl_addr_free(newaddr);
       return NULL;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing.b

DBG(LIST, ul_debugobj(addrq,
"ul_nl_addr_dup() failed"));
rc = -1;
goto error;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rc = -ENOMEM; as ul_nl_addr_dup() can fail only on memory allocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing and again removing DBG here and in similar cases.

free(addr->ifa_local);
free(addr->ifname);
free(addr);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better when free()-like functions are robust enough to accept NULL as an argument (like libc free()). It means

void ul_nl_addr_free (struct ul_nl_addr *addr)
{
    if (addr) {
	free(addr->ifa_address);
	free(addr->ifa_local);
	free(addr->ifname);
	free(addr);
    }
}

Then you can call the function in an arbitrary situation, including error cases, when you need to clean up anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing.

lib/netaddrq.c Outdated

netaddrq_init_debug();
if (!(nl->data_addr = malloc(sizeof(struct ul_netaddrq_data))))
return -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-ENOMEM; :-)

Copy link
Collaborator

@karelzak karelzak Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest using calloc() to ensure greater robustness for future changes to the struct. This is a generic recommendation when working with structs in an interface, especially if you do not plan to use a function like memcpy() after malloc().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing and removing explicit zeroing.

lib/netaddrq.c Outdated
*threshold = ul_netaddrq_bestaddr(nl, best_ifaceq, &best, ifa_family);
if (best[*threshold])
return ul_nl_addr_ntop_address(best[*threshold]->addr);
else
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, no else-after-return ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

@stanislav-brabec stanislav-brabec force-pushed the master branch 2 times, most recently from abbc025 to a007eab Compare July 25, 2025 02:06
addrq = UL_NETADDRQ_DATA(nl);
addrq->callback_pre = callback_pre;
addrq->callback_post = callback_post;
addrq->callback_data = data;

Check warning

Code scanning / CodeQL

Local variable address stored in non-local memory Warning

A stack address which arrived via a
parameter
may be assigned to a non-local variable.
lib/netlink.c Outdated

int ul_nl_process(struct ul_nl_data *nl, bool async, bool loop)
{
char buf[4096];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4096 -> BUFSIZ ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is no exact size defined. If we don't want to handle MSG_TRUNC, it should cover sizeof (struct nlmsghdr) plus the expected message payload size. BUFSIZ (8192) is surely more safe than 4096.

/* In case of any error, the addrq list is just empty, and we can use
* the code without any error checking. */
ul_nl_close(&(ie->nl));
ie->nl.fd = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be there ie->nl.fd = -1; ?

netlink_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;

/* Already initialized? */
if (ie->nl.fd)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if (ie->nl.fd >= 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code was indeed intended to represent 0 as no fd opened, supposing that fd channel 0 is never used for the netlink socket. But not fully, select() uses 0 as an active value. -1 is for sure safer and the more convenient value.

Updating code in the comments as well.

And actually, it needs to update the initialization in show_issue to:
struct issue ie = { .output = NULL, .nl.fd = -1 };

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, actually, also another struct issue initialization needs update as well:
struct issue issue = {
.mem = NULL,
.nl.fd = -1
};

To support netlink and IP address processing, two new library files were
added:

netlink: Generic netlink message processing code converting netlink
messages to calls of callbacks with a pre-processed data.

netaddrq: A code that gets and maintains linked list of the current
interfaces and assigned IP addresses. It also provides a rating of IP
addresses based on its "quality", i. e. type of address, validity, lifetime
etc.

Signed-off-by: Stanislav Brabec <[email protected]>
The current \4 and \6 issue file escapes implementation is inferior. It
uses get getifaddrs() to get a list of IP addresses. This function does not
provide enough information to discriminate between stable IP addresses and
ephemeral addresses. As a result, especially \6 often gives unreliable
results.

The code is actually unable to get list of all interfaces, so a proper out
of the box IP address reporting depends on external tools that generate
issue file with the interfaces list.

The netlink messages are already used, but only as a change notifier. The
contents is not used, even if it contains exact information about the
change. As a result, change processing is triggered even for unrelated
network changes like IPv6 router advertisement.

The new implementation uses the new netaddrq library. It reports more
reliable results especially for IPv6.

Additionally, two new escapes are implemented:

\a Report all interfaces and assigned addresses that are considered as
reliable.

\A Report all interfaces and all assigned addresses.

TODO:

To prevent overflooding of the console, the list is currently limited to 12
interfaces. It would be nice to make it configurable.

Two pass processing of issue files. First pass just collects IP protocols
and list of interfaces (in future interface patterns). Now it always
processes both IPv4 and IPv6 on all interfaces. Not so bad, as \a is smart
enough to display just the useful part.

Maybe implement more options and formatting support for \a and \A.

Maybe implement interface filter globs or regexps for \a and \A. Still not
so bad, as \a automatically skips interfaces without reliable addresses
(e. g. lo or TUN).

Signed-off-by: Stanislav Brabec <[email protected]>
@karelzak
Copy link
Collaborator

Looks good to me (with minimal netlink experience). I think we can merge it and continue with possible improvements within the tree.

The following significant change will be to modify agetty to keep parsed issue files in memory (two-pass processing).

@t-8ch do you want to review?

bmwiedemann pushed a commit to bmwiedemann/openSUSE that referenced this pull request Sep 10, 2025
https://build.opensuse.org/request/show/1303156
by user sbrabec + anag_factory
- Implement escape code for printing of ssh host keys in agetty
  issue file (util-linux-agetty-ssh-host-keys.patch.
- Include fixes from
  util-linux/util-linux#3649 (jsc#PED-8734,
  util-linux-lib-netlink.patch, util-linux-agetty-netlink.patch).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants