xixitalk's snippet

Post Longer Than 140 Characters Tweets

Aug 18, 2016 - Comments

netlink遇到ENOBUFS错误

一个场景:USB插拔的时候内核会通过netlink广播到user层,多个应用接收这个消息。但是出现了errno 105错误,105错误是:No buffer space available

经过内核代码分析,af_netlink.cnetlink_broadcast_deliver函数返回-1才会触发ENOBUFS流程。加printkpanic复现问题(因为嵌入式开发环境抓panic死机现场分析和串口log都太方便了)。

static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
{
	struct netlink_sock *nlk = nlk_sk(sk);

	if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
	    !test_bit(0, &nlk->state)) {
		skb_set_owner_r(skb, sk);
		__netlink_sendskb(sk, skb);
		return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
	}
	printk("[testcode]netlink sk_rmem_alloc:%x sk_rcvbuf:%x \n",atomic_read(&sk->sk_rmem_alloc),sk->sk_rcvbuf);
	panic("[testcode]netlink broadcast panic\n");
	return -1;
}

抓取的串口log如下显示sk_rcvbuf确实小于sk_rmem_alloc了,没有空间了。

[ 18.618473] [testcode]netlink sk_rmem_alloc:b00 sk_rcvbuf:8b8 [
18.623727] Kernel panic - not syncing: [testcode]netlink broadcast panic

对比发现死机现场里的kobject_uevent_env函数里的uevent_sock变量里sk_sndbufsk_rcvbuf都是163840(160K)。而netlink_broadcast_deliver里sock是8b8(2232)。很明显netlink接收socket里的sock比内核驱动的sock接收buf差距太大了。

内核sock.c里sock_init_data函数里进行sock初始化,sk_rcvbufsk_sndbuf初始化成sysctl_rmem_defaultsysctl_rmem_default是个全局变量,导出的panic现场看值就是163840。

sk->sk_rcvbuf = sysctl_rmem_default; sk->sk_sndbuf = sysctl_wmem_default;

看应用netlink的接收,果然用setsockopt修改了RCVBUF

    const int buffersize = 1024;  
    int ret;  

    struct sockaddr_nl snl;  
    bzero(&snl, sizeof(struct sockaddr_nl));  
    snl.nl_family = AF_NETLINK;  
    snl.nl_pid = getpid();  
    snl.nl_groups = 1;  

    int s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);  
    if (s == -1)   
    {  
        perror("socket");  
        return -1;  
    }  
    setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));  

C库的setsockopt函数会通过系统调用进入到内核sock.c文件里的sock_setsockopt函数。SOCK_MIN_RCVBUF就是2232。很明显应用setsockoptbuffersize是1024,乘以2还小于SOCK_MIN_RCVBUF,所以sk_rcvbuf就变成SOCK_MIN_RCVBUF

	case SO_RCVBUF:
	    if (val > sysctl_rmem_max)
			val = sysctl_rmem_max;
		if ((val * 2) < SOCK_MIN_RCVBUF)
			sk->sk_rcvbuf = SOCK_MIN_RCVBUF;
		else
			sk->sk_rcvbuf = val * 2;
		break;

解决办法:删除应用代码里的setsockopt语句,这样sk_rcvbuf默认就是160K,或者用setsockopt设置合适的大小。

通过Google发现,网络上好多netlink实例都用了setsockopt设置了1024的buffer大小。应用这个代码应该是从网上抄来的。


知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。