[Toybox] More ifconfig cleanup!

Rob Landley rob at landley.net
Fri Nov 29 22:37:18 PST 2013


So I'm trying to make struct sockaddr_with_len go away. It's a union of  
three data types:

   typedef struct sockaddr_with_len {
     union {
       struct sockaddr sock;
       struct sockaddr_in sock_in;
       struct sockaddr_in6 sock_in6;
     } sock_u;
   } sockaddr_with_len;

(You'll note the actual "len" part was removed in an earlier cleanup.)

This structure is returned by get_sockaddr(), and it's obviously wrong  
because the first thing get_sockaddr does is check for "local:" and if  
so it typecasts sock_u.sock to struct sockaddr_un and starts writing to  
it. That's a 110 byte struct, and the three structs in the union are  
16, 16, and 28 bytes respectively. So we're writing off the edge of our  

get_sockaddr() is only called from two places: One is in set_address(),  
and the other is in the "add/del" ipv6 address manipulation.  
set_address() itself is called from two places, which collectively  
handle "pointopoint", "broadcast", "netmask", "dstaddr", and ifconfig  
without a command (to set the interface address directly). I _think_  
all of those only handle ipv4 addresses. (So what does specifying  
local: mean, anyway? What ifconfig command line would trigger that  
codepath, and what would it mean?)

Let's back up and figure out what this get_sockaddr function does:

1) This local: stuff. The ifconfig man page does not contain the word  
"local", so I have NO CLUE what this is. If this is used, it can  
corrupt memory as described above, because the union doesn't include  
sockaddr_un but we typecast it to that and strncpy up to UNIX_PATH_MATH  
(108 bytes) into a 28 byte structure.

2) Parsing some sort of [ipv6]:port syntax. Neither of the callers use  
the port field of the returned struct, and the port part is not  
currently hooked up because both callers pass in 0 for port, and then  
we set port but it's never used. There's a port_num but it's only ever  
set from the value passed in during variable initialization (I.E.  
always 0). So it looks like I once again broke stuff I couldn't test  
during earlier cleanups. And I still can't test it because the only  
occurrence of the word "port" in the ifconfig man page has to do with  
"media type" which looks deeply obsolete. (10base2?) I'll happily  
accept a patch to put this back, but for right now I can't find an  
example of anything using this syntax.

3) A dns lookup using the function that handles both ipv4 and ipv6.  
Except that if your "hints" are AF_INET it should only return AF_INET  
addresses, and AF_INET6 should only return AF_INET6, correct? If you  
want both, the hint is AF_UNSPEC, which we don't pass. So there's only  
one codepath in play at a time here.

Now I'm curious. In Ubuntu:

   $ ifconfig eth0 inet6 1:2:3:4:5:6:7:8
   Don't know how to set addresses for family 10.

It looks like only "add" and "del" handle ipv6 addresses,

   $ ifconfig eth0 add && ifconfig eth0
   eth0      Link encap:Ethernet  HWaddr dc:0e:a1:52:92:77
             UP BROADCAST MULTICAST  MTU:1500  Metric:1
             RX packets:0 errors:0 dropped:0 overruns:0 frame:0
             TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
             collisions:0 txqueuelen:1000
             RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

and those ONLY handle ipv6 addresses, and instead of an error will  
silently ignore an ipv4 address. (*boggle*).

Ok, let's rename get_sockaddr() to get_addrinfo() and remove all this  
port stuff, and the square bracket syntax I can't find an example of.  
There are two callers: the add/del clause in main(), and set_address().

The reason there are two callers of set_address() is I never quite  
merged all the ifconfig_main() argument parsing into the big table.  
You'll notice the block at the end from "hw" through "default" is  
mis-indented, that's once again me using stale whitespace as a  
note-to-self that this section hasn't been finished yet.

So let's do two things:

1) Copy the misindented if/else staircase before the for(;;) loop  
instead of after it.

2) Merge the isdigit/default case into the table. It's similar to the  
"up" entry except for off being SIOCSIFADDR, special casing the name  
(so it's its' own argument), and this funky behavior with ":" not  
setting the flag.

So in try[] we add {0, IFF_UP|IFF_RUNNING, SIOCSIFADDR}, then in the  

   if (!t->name && (isdigit(**argv) || !strcmp(*argv, "default")))  
   else if (strcmp(t->name, s)) continue;

(The argv-- is so it's treated as its own address argument.) And then  
add the alias check to the set_address call:

   } else if (t->name || !strchr(ifre.ifr_name, ':')) {
     set_address(*argv, &ifre, off);

Now we can zap the old if (isdigit/default) stanza and then block copy  
in the contents of the set_address() function, since this is now the  
only caller. While simplifying, we can eliminate the memset() because  
get_addrinfo() will error_exit() if it can't memset the address with a  
found value.

So that's one of the callers of get_addrinfo() cleared up (and its  
function removed). The other caller is the add/del case of the old  
if/else staircase we just relocated to before the for(;;) loop. And now  
that we've merged "default" into the loop, there are only two clauses  
left in that staircase. The other is "hw".

The reason "hw" is still separate is it takes another argument:

   ifconfig hw ether 11:22:33:44:55:66
   ifconfig hw infiniband \

And of course infiniband is problematic because the ioctl only has room  
for 14 bytes of the 20 byte payload, ala


So you _have_ to much about in /sys to get or set infiniband addresses.  
(Of course the non-table version isn't doing that properly right now  
either, and the display code isn't querying it, but that's a remaining  
todo item for the cleanup.)

So let's see... merging hw into the table probably isn't worth it,  
there's just too much oddball behavior. There are also bugs in the  
current version, because "ifconfig hw ether" with no address will  
probably dereference a null pointer and segfault. Ok, let's add in the  
"write to sysfs" behavior, and while I'm at it throw in a check for "/"  
in the interface name so nobody gets clever ideas about  
"../../../blah\0" as an interface name. (Don't have to check the  
length, we did an xstrncpy that would die if the argument was too long,  
because exiting noisily is better than silently truncating invalid  

The "add/del" ipv6 stuff also has oddball behavior (opening the ipv6  
socket). There's no reason to have the struct definition way far away  
fromits only user. And "ifrinte6_addr" is clearly a typo, yet its only  
user repeated the same typo.

Hmmm, ipv6 mask length should default to 128, not 0.

More information about the Toybox mailing list