[備忘録][Linux] setsockopt(NETLINK_LISTEN_ALL_NSID) で Operation not permittedと怒られたとき

すべての Network Namespace からのイベントを Netlink で受信するため, 次のようなプログラムを書いて実行したところ,

#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <stdio.h>

int main()
{
  int soc;
  int on = 1;

  soc = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if (soc < 0)
    {
      perror ("socket");
      return -1;
    }

  if (setsockopt (soc, SOL_NETLINK, NETLINK_LISTEN_ALL_NSID, &on, sizeof(on)) < 0)
    {
      perror("NETLINK_LISTEN_ALL_NSID");
      return -1;
    }
  return 0;
}
kawauso@linux$ ./netlink_test
NETLINK_LISTEN_ALL_NSID: Operation not permitted

というような setsockopt(NETLINK_LISTEN_ALL_NSID) で予測しない権限エラー Operation not permitted が発生したときの対処の備忘録メモです.

setsockopt (NETLINK_LISTEN_ALL_NSID)

man netlink (7) によると NETLINK_LISTEN_ALL_NSID はすべての network namespace の通知を受け取るためのオプションと書かれていますが,

       NETLINK_LISTEN_ALL_NSID (since Linux 4.2)
             When set, this socket will receive netlink notifications
             from all network namespaces that have an nsid assigned
             into the network namespace where the socket has been
             opened.  The nsid is sent to user space via an ancillary
             data.

とくに権限 (capability) について言及されてません.

Operation not permitted メッセージに相当するエラーコード EPERM を返す Linux Kernel Code の箇所を調べてみました.

net/netlink/af_netlink.c

以下のコードが Netlink socket に対して setsockopt() が実行されるLinux Kernel の個所です.

setsockopt の第三引数に相当する optname NETLINK_LISTEN_ALL_NSID の場合の処理(65行目から)に着目します.

static int netlink_setsockopt(struct socket *sock, int level, int optname,
			      sockptr_t optval, unsigned int optlen)
{
	struct sock *sk = sock->sk;
	struct netlink_sock *nlk = nlk_sk(sk);
	unsigned int val = 0;
	int err;

	if (level != SOL_NETLINK)
		return -ENOPROTOOPT;

	if (optlen >= sizeof(int) &&
	    copy_from_sockptr(&val, optval, sizeof(val)))
		return -EFAULT;

	switch (optname) {
	case NETLINK_PKTINFO:
		if (val)
			nlk->flags |= NETLINK_F_RECV_PKTINFO;
		else
			nlk->flags &= ~NETLINK_F_RECV_PKTINFO;
		err = 0;
		break;
	case NETLINK_ADD_MEMBERSHIP:
	case NETLINK_DROP_MEMBERSHIP: {
		if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV))
			return -EPERM;
		err = netlink_realloc_groups(sk);
		if (err)
			return err;
		if (!val || val - 1 >= nlk->ngroups)
			return -EINVAL;
		if (optname == NETLINK_ADD_MEMBERSHIP && nlk->netlink_bind) {
			err = nlk->netlink_bind(sock_net(sk), val);
			if (err)
				return err;
		}
		netlink_table_grab();
		netlink_update_socket_mc(nlk, val,
					 optname == NETLINK_ADD_MEMBERSHIP);
		netlink_table_ungrab();
		if (optname == NETLINK_DROP_MEMBERSHIP && nlk->netlink_unbind)
			nlk->netlink_unbind(sock_net(sk), val);

		err = 0;
		break;
	}
	case NETLINK_BROADCAST_ERROR:
		if (val)
			nlk->flags |= NETLINK_F_BROADCAST_SEND_ERROR;
		else
			nlk->flags &= ~NETLINK_F_BROADCAST_SEND_ERROR;
		err = 0;
		break;
	case NETLINK_NO_ENOBUFS:
		if (val) {
			nlk->flags |= NETLINK_F_RECV_NO_ENOBUFS;
			clear_bit(NETLINK_S_CONGESTED, &nlk->state);
			wake_up_interruptible(&nlk->wait);
		} else {
			nlk->flags &= ~NETLINK_F_RECV_NO_ENOBUFS;
		}
		err = 0;
		break;
	case NETLINK_LISTEN_ALL_NSID:
		if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_BROADCAST))
			return -EPERM;

		if (val)
			nlk->flags |= NETLINK_F_LISTEN_ALL_NSID;
		else
			nlk->flags &= ~NETLINK_F_LISTEN_ALL_NSID;
		err = 0;
		break;
<snip>

ns_capable() で capability CAP_NET_BROADCAST の有無をチェックしていることから, このオプションには CAP_NET_BROADCAST のセットが必要であることが分かります.

setcap / getcap

setcap にてプログラムに capability CAP_NET_BROADCAST を与えてみます.

kawauso@linux$ sudo /sbin/setcap cap_net_broadcast+ep netlink_test

設定された capability は getcap にて確認できます.

kawauso@linux$ /sbin/getcap netlink_test
netlink_test = cap_net_broadcast+ep

この状態で実行したところエラーが発生せず、正常にプログラムは動作しました.

是非とも netlink の man ページの NETLINK_LISTEN_ALL_NSID には capability CAP_NET_BROADCAST が必要、という一文を追加して欲しいです…

nsid

先の man netlink (7) NETLINK_LISTEN_ALL_NSID の箇所に,

       NETLINK_LISTEN_ALL_NSID (since Linux 4.2)
             When set, this socket will receive netlink notifications
             from all network namespaces that have an nsid assigned
             into the network namespace where the socket has been
             opened.  

とあり, NSID が設定されている namespace のイベントは受信されると書かれてます.

すなわち, ip netns set NETNSNAME NETSID でNSID が設定されていないnamespace からのイベントは受け取れない、という点に注意が必要です.

詳しくは ip-netns の man を参照ください.

最後に

Network Namespace や NETLINK 関係の情報は少なくて、ましてや日本語で書かれている文献はほとんど無いに等しいんです…これからも微力ながら分かったことを情報展開していきたいと思います.

今回はこちらを参考にいたしました.