Design SIOCGIFADDR on IPv6 socket

As some of you already know SIOCGIFADDR isn't supported on IPv6 socket although SIOCSIFADDR is. For sure there is a reason linux kernel doesn't do so but it's worth to build up by our hands, then we get to know much more in detail. This is good for us as a studying purpose rather than use in product.

We define simple ideas with some requirements, then look at kernel patch and a demo.

Ideas

A significant one is that an interface could have multiple IPv6 addresses, so our prototype would be the following result in user program perspective ...

  1. Take an IPv6 address and an interface name via args to set
  2. Get and print an IPv6 address mapped to an index of the required interface via its args. Assume the index would be 0 if the args isn't passed.

Assigning an address isn't a big deal but trying to get one is a bit complicated. Because we have to pass an index to kernel. For the requirement, use a member, struct in6_addr of struct in6_ifreq which the size of memory is 128 bits. Split the space into two parts and put the index value into the second space. We could even split it into 4 spaces of 32 bits based though, make it simple.

1.-------------------------------------------.
2|                 128 bits                  |  <-- struct in6_addr
3'-------------------------------------------'
4
5^--------------------^ ^--------------------^
6       64 bits                 64 bits

We use the following struct on both kernel and user program to serialize between them. It's originally from kernel, but manually port to the user program. Remember ifr6_addr is very important.

1struct in6_ifreq {
2  struct in6_addr ifr6_addr;
3  u_int32_t ifr6_prefixlen;
4  unsigned int ifr6_ifindex;
5};

Here is a part of our test code, cmd_get function is the main routine implemented with SIOCGIFADDR but you could see the full code here.

 1static int cmd_get(int fd, int argc, char *argv[])
 2{
 3  char buf[256] = { 0, };
 4  struct in6_ifreq req;
 5  struct ifreq ifr;
 6  uint64_t *index = &((uint64_t*)&req.ifr6_addr)[1];
 7
 8  init_ifindex(ifr);
 9
10  memset(&req, 0, sizeof(struct in6_ifreq));
11  /* Set index of nth address on the interface */
12  *index = (argc == 1) ? 0 : atoi(argv[1]);
13  req.ifr6_prefixlen = 64;
14  req.ifr6_ifindex = ifr.ifr_ifindex;
15  retval_if( ioctl(fd, SIOCGIFADDR, &req), -5 /* 251 */, NULL );
16
17  inet_ntop(AF_INET6, &req.ifr6_addr, buf, sizeof(buf));
18  printf("interface (%s) addr (%s)\n", argv[0], buf);
19
20  return 0;
21}
22
23int main(int argc, char *argv[])
24{
25  /* Skip useless code */
26  retval_if( ((fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)), fd < 0),
27             -2 /* 254 */, NULL );
28  /* Skip useless code */
29    ret = cmd_get(fd, argc-2, argv+2);
30}

As our ideas, a pointer named index points to the second space of struct in6_addr which sets on demand and assume our requirement is always with 64 bits prefix length.

Kernel patch

Code is a bit simple. Look up and compare the index on the specified interface. See the line 44, kernel knows what number of addresses a user asks by parsing the index from user buffer and compare on 48 line. If the number is existed, copy the address to the same buffer and return back on the line, 84.

  1diff --git a/include/net/addrconf.h b/include/net/addrconf.h
  2index ab8b3eb53d..97f8de4c09 100644
  3--- a/include/net/addrconf.h
  4+++ b/include/net/addrconf.h
  5@@ -77,6 +77,7 @@ int addrconf_init(void);
  6 void addrconf_cleanup(void);
  7 
  8 int addrconf_add_ifaddr(struct net *net, void __user *arg);
  9+int addrconf_get_ifaddr(struct net *net, void __user *arg);
 10 int addrconf_del_ifaddr(struct net *net, void __user *arg);
 11 int addrconf_set_dstaddr(struct net *net, void __user *arg);
 12 
 13diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
 14index 635b2482fa..46e9a80e7a 100644
 15--- a/net/ipv6/addrconf.c
 16+++ b/net/ipv6/addrconf.c
 17@@ -2985,6 +2985,46 @@ static int inet6_addr_add(struct net *net, int ifindex,
 18 	return PTR_ERR(ifp);
 19 }
 20 
 21+static int inet6_addr_get(struct net *net,
 22+                          int ifindex,
 23+                          u32 ifa_flags,
 24+                          const struct in6_addr *pfx,
 25+                          unsigned int plen)
 26+{
 27+	struct inet6_ifaddr *ifp;
 28+	struct inet6_dev *idev;
 29+	struct net_device *dev;
 30+	u64 upper, lower, i = 0;
 31+
 32+	if (plen > 128)
 33+		return -EINVAL;
 34+
 35+	dev = __dev_get_by_index(net, ifindex);
 36+	if (!dev)
 37+		return -ENODEV;
 38+
 39+	idev = __in6_dev_get(dev);
 40+	if (!idev)
 41+		return -ENXIO;
 42+
 43+	upper = ((u64*)pfx)[0];
 44+	lower = ((u64*)pfx)[1];
 45+
 46+	read_lock_bh(&idev->lock);
 47+	list_for_each_entry(ifp, &idev->addr_list, if_list) {
 48+		if (ifp->prefix_len == plen && lower == i++) {
 49+			read_unlock_bh(&idev->lock);
 50+
 51+			memcpy(pfx, &(ifp->addr), sizeof(struct in6_addr));
 52+			return 0;
 53+		}
 54+		if (i > lower)
 55+			break;
 56+	}
 57+	read_unlock_bh(&idev->lock);
 58+	return -EINVAL;
 59+}
 60+
 61 static int inet6_addr_del(struct net *net, int ifindex, u32 ifa_flags,
 62 			  const struct in6_addr *pfx, unsigned int plen)
 63 {
 64@@ -3053,6 +3093,28 @@ int addrconf_add_ifaddr(struct net *net, void __user *arg)
 65 	return err;
 66 }
 67 
 68+int addrconf_get_ifaddr(struct net *net, void __user *arg)
 69+{
 70+	struct in6_ifreq ireq;
 71+	int err;
 72+
 73+	if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
 74+		return -EPERM;
 75+
 76+	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
 77+		return -EFAULT;
 78+
 79+	rtnl_lock();
 80+	err = inet6_addr_get(net, ireq.ifr6_ifindex, 0, &ireq.ifr6_addr,
 81+			ireq.ifr6_prefixlen);
 82+	rtnl_unlock();
 83+
 84+	if (!err && copy_to_user(arg, &ireq, sizeof(struct in6_ifreq)))
 85+		err = -EFAULT;
 86+
 87+	return err;
 88+}
 89+
 90 int addrconf_del_ifaddr(struct net *net, void __user *arg)
 91 {
 92 	struct in6_ifreq ireq;
 93diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
 94index 14ac1d9112..6bb0da9fd5 100644
 95--- a/net/ipv6/af_inet6.c
 96+++ b/net/ipv6/af_inet6.c
 97@@ -550,6 +550,8 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 98 
 99 	case SIOCSIFADDR:
100 		return addrconf_add_ifaddr(net, (void __user *) arg);
101+	case SIOCGIFADDR:
102+		return addrconf_get_ifaddr(net, (void __user *) arg);
103 	case SIOCDIFADDR:
104 		return addrconf_del_ifaddr(net, (void __user *) arg);
105 	case SIOCSIFDSTADDR:

Demo time!

Now make a test interface named br-demo on bridge layer, then assign an IPv6 address to it. Of course, try to get each addresses assigned to the interface.

 1root@OpenWrt:/# brctl addbr br-demo && ifconfig br-demo up
 2root@OpenWrt:/# ifconfig
 3br-demo   Link encap:Ethernet  HWaddr 06:FA:DC:0E:5F:84  
 4          inet6 addr: fe80::4fa:dcff:fe0e:5f84/64 Scope:Link
 5          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 6          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 7          TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
 8          collisions:0 txqueuelen:1000 
 9          RX bytes:0 (0.0 B)  TX bytes:454 (454.0 B)
10
11root@OpenWrt:/# demo_ipv6 
12usage: demo_ipv6 <command>
13
14- command
15    add : <ifname> <ipv6 address>
16    get : <ifname> [index]
17
18root@OpenWrt:/# demo_ipv6 add br-demo 2001:0db8:85a3::8a82
19root@OpenWrt:/# ifconfig br-demo
20br-demo   Link encap:Ethernet  HWaddr 06:FA:DC:0E:5F:84  
21          inet6 addr: fe80::6ce4:38ff:fe99:74d2/64 Scope:Link
22          inet6 addr: 2001:db8:85a3::8a82/64 Scope:Global
23          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
24          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
25          TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
26          collisions:0 txqueuelen:1000 
27          RX bytes:0 (0.0 B)  TX bytes:1360 (1.3 KiB)
28
29root@OpenWrt:/# demo_ipv6 get br-demo 
30interface (br-demo) addr (2001:db8:85a3::8a82)
31root@OpenWrt:/# demo_ipv6 get br-demo 1
32interface (br-demo) addr (fe80::6ce4:38ff:fe99:74d2)
33root@OpenWrt:/# demo_ipv6 get br-demo 2
34root@OpenWrt:/# echo $?
35251
36root@OpenWrt:/# demo_ipv6 get br-demo 0
37interface (br-demo) addr (2001:db8:85a3::8a82)

Conclusion

This were a simple tutorial though, we could see that IOCTL programming is a way to communicate between kernel and user programs and give lots of opportunities for hacking the kernel.

Stay Safe!