arp表溢出导致发送报文报“No buffer space available”异常

近日项目组上线了一个业务特性后,日志被异常日志刷了屏,这个频繁出现的日志内容为:

2018-05-30 14:27:46,672 | WARN | monPool-worker-6 | AsyncGetHandler | 361 - org.opendaylight.snmp - 2.1.0-SNAPSHOT | Exception when sending GET request
org.snmp4j.MessageException: No buffer space available (sendto failed)
at org.snmp4j.MessageDispatcherImpl.sendPdu(MessageDispatcherImpl.java:526)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.Snmp.sendMessage(Snmp.java:1004)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.Snmp.send(Snmp.java:974)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.Snmp.send(Snmp.java:958)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.opendaylight.snmp.plugin.internal.AsyncGetHandler.sendRequest(AsyncGetHandler.java:178)[361:org.opendaylight.snmp:2.1.0-SNAPSHOT]
at org.opendaylight.snmp.plugin.internal.AsyncGetHandler.getRpcResponse(AsyncGetHandler.java:186)[361:org.opendaylight.snmp:2.1.0-SNAPSHOT]
at org.opendaylight.snmp.plugin.internal.SNMPImpl.snmpGet(SNMPImpl.java:128)[361:org.opendaylight.snmp:2.1.0-SNAPSHOT]
at org.opendaylight.yangtools.yang.binding.util.RpcMethodInvokerWithInput.invokeOn(RpcMethodInvokerWithInput.java:31)[124:org.opendaylight.mdsal.yang-binding:0.10.3.Carbon]
at org.opendaylight.yangtools.yang.binding.util.AbstractMappedRpcInvoker.invokeRpc(AbstractMappedRpcInvoker.java:52)[124:org.opendaylight.mdsal.yang-binding:0.10.3.Carbon]
at org.opendaylight.controller.md.sal.binding.impl.BindingDOMRpcImplementationAdapter.invoke(BindingDOMRpcImplementationAdapter.java:83)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.binding.impl.BindingDOMRpcImplementationAdapter.invokeRpc(BindingDOMRpcImplementationAdapter.java:70)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.dom.broker.impl.GlobalDOMRpcRoutingTableEntry.invokeRpc(GlobalDOMRpcRoutingTableEntry.java:39)[203:org.opendaylight.controller.sal-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRoutingTable.invokeRpc(DOMRpcRoutingTable.java:177)[203:org.opendaylight.controller.sal-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRouter.invokeRpc(DOMRpcRouter.java:102)[203:org.opendaylight.controller.sal-broker-impl:1.5.3.Carbon]
at Proxydb85dcd1_4bc5_4f96_b771_e4d4fd957676.invokeRpc(Unknown Source)[:]
at Proxy630aa470_14a8_4d02_96f2_cd4207c27a7a.invokeRpc(Unknown Source)[:]
at org.opendaylight.controller.md.sal.binding.impl.RpcServiceAdapter.invoke0(RpcServiceAdapter.java:65)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.binding.impl.RpcServiceAdapter.access$000(RpcServiceAdapter.java:43)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.binding.impl.RpcServiceAdapter$RpcInvocationStrategy.invoke(RpcServiceAdapter.java:159)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at org.opendaylight.controller.md.sal.binding.impl.RpcServiceAdapter.invoke(RpcServiceAdapter.java:96)[205:org.opendaylight.controller.sal-binding-broker-impl:1.5.3.Carbon]
at com.sun.proxy.$Proxy170.snmpGet(Unknown Source)[361:org.opendaylight.snmp:2.1.0-SNAPSHOT]
at com.sdn.service.devdiscvry.impl.SnmpTask.getSnmpFuture(SnmpTask.java:249)[362:com.sdn.service.devdiscvry.impl:2.0.0.SNAPSHOT]
at com.sdn.service.devdiscvry.impl.SnmpTask.run(SnmpTask.java:95)[362:com.sdn.service.devdiscvry.impl:2.0.0.SNAPSHOT]
at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1626)[:1.8.0_152]
at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1618)[:1.8.0_152]
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)[:1.8.0_152]
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)[:1.8.0_152]
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)[:1.8.0_152]
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)[:1.8.0_152]
Caused by: java.io.IOException: No buffer space available (sendto failed)
at java.net.PlainDatagramSocketImpl.send(Native Method)[:1.8.0_152]
at java.net.DatagramSocket.send(DatagramSocket.java:693)[:1.8.0_152]
at org.snmp4j.transport.DefaultUdpTransportMapping.sendMessage(DefaultUdpTransportMapping.java:117)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.transport.DefaultUdpTransportMapping.sendMessage(DefaultUdpTransportMapping.java:42)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.MessageDispatcherImpl.sendMessage(MessageDispatcherImpl.java:202)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
at org.snmp4j.MessageDispatcherImpl.sendPdu(MessageDispatcherImpl.java:503)[359:org.apache.servicemix.bundles.snmp4j:2.3.1.1]
... 28 more

2018-05-30 14:27:46,672 | INFO | monPool-worker-6 | SnmpTask | 362 - com.sdn.service.devdiscvry.impl - 2.0.0.SNAPSHOT | Devdiscvry: snmp 1.3.6.1.2.1.1.5 getwalk hostname nw_YQ01_192.168.151.171

2018-05-30 14:27:46,672 | ERROR | monPool-worker-6 | ExecutionList | 104 - com.google.guava - 18.0.0 | RuntimeException while executing runnable com.google.common.util.concurrent.Futures$6@4fa08f52 with executor INSTANCE
java.lang.NullPointerException

从异常堆栈信息可知,在发送snmp get消息时,程序报了IOException: No buffer space available (sendto failed) WARN,并在随后的代码中引发了NullPointerException ERROR。而抛出异常的原始函数为java.net.PlainDatagramSocketImpl.send(),这是一个udp报文的发送函数,我们看一下函数的内容:

protected native void send(DatagramPacket p) throws IOException;

这是一个native函数,说明异常是从jdk中抛出来的。

由于udp发送报文最终会通过sendwritesendto等函数通过linux内核网络协议栈进行发送,查看这几个函数的返回错误码,与buffer空间相关的两个错误码为:

   ENOBUFS
          The output queue for a network interface was full.  This generally indicates that the interface has stopped sending, but may be caused by  tran-
          sient congestion.  (Normally, this does not occur in Linux.  Packets are just silently dropped when a device queue overflows.)

   ENOMEM No memory available.

ENOBUFS说明网络接口的输出队列满了,ENOMEM说明内核某个地方需要分配内存资源时,但是内存资源(物理的或逻辑限额)用完了。

同时查看内核日志/var/log/messages,发现内核打印了大量的kernel: Neighbour table overflow信息

# tail -f /var/log/messages

Jun 6 16:37:29 sdn-dcn-controller03 kernel: __ratelimit: 1013 callbacks suppressed
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:29 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:34 sdn-dcn-controller03 kernel: __ratelimit: 101 callbacks suppressed
Jun 6 16:37:34 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:34 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:34 sdn-dcn-controller03 kernel: Neighbour table overflow.
Jun 6 16:37:34 sdn-dcn-controller03 kernel: Neighbour table overflow.

说明邻居表溢出了。
查看一下打印kernel: Neighbour table overflow信息的内核源代码,可知arp表满了,但是又没有旧的表项可以释放:

            /* Neighbour tables are full and nothing
               can be released. Try to shrink route cache,
               it is most likely it holds some neighbour records.
             */
            if (attempts-- > 0) {
                int saved_elasticity = ip_rt_gc_elasticity;
                int saved_int = ip_rt_gc_min_interval;
                ip_rt_gc_elasticity = 1;
                ip_rt_gc_min_interval   = 0;
                rt_garbage_collect(&ipv4_dst_ops);
                ip_rt_gc_min_interval   = saved_int;
                ip_rt_gc_elasticity = saved_elasticity;
                goto restart;
            }

            if (net_ratelimit())
                printk(KERN_WARNING "Neighbour table overflow.\n");
            rt_drop(rt);
            return -ENOBUFS;

多次查看arp表的内容,发现表的大小经常在1000多,

# arp -an | wc
1023 6238 61876

查看跟arp表项大小相关的内核参数

# cat /proc/sys/net/ipv4/neigh/default/gc_interval
30
# cat /proc/sys/net/ipv4/neigh/default/gc_stale_time
60
# cat /proc/sys/net/ipv4/neigh/default/gc_thresh1
128
# cat /proc/sys/net/ipv4/neigh/default/gc_thresh2
512
# cat /proc/sys/net/ipv4/neigh/default/gc_thresh3
1024

gc_interval表示arp表垃圾收集的时间间隔,gc_stale_time表示arp表项老化时间,如果某个arp表项在gc_stale_time时间内没有被使用,在垃圾收集时会把它删除掉,gc_thresh1表示arp表项数小于该值时,不会进行垃圾收集,gc_thresh2表示如果arp表项数大于该值时,5秒内会执行垃圾收集,而gc_thresh3表示arp表的最大限额,如果发现arp表达到了最大限额,会执行垃圾收集,尝试释放失效的arp表项,如果没有表项可以释放,则会报Neighbour table overflow异常。

从当前内核参数值来看,arp表项最大值为1024,表项老化时间为60s。跟模块开发人员交流并结合代码分析,了解到出现异常的背景为:有一个模块会周期性的多线程并发向同一个局域网内的几千台机器发送snmp get请求时。这种情况下1024条arp表项显然太小了,导致内核报Neighbour table overflow,应用程序报No buffer space available (sendto failed)异常

临时修改arp表最大限额参数gc_thresh3的值,

# echo "10240" > /proc/sys/net/ipv4/neigh/default/gc_thresh3

或永久修改:

# vim /etc/sysctl.conf
net.ipv4.neigh.default.gc_thresh3 = 10240
# sysctl -p

查看日志,没有继续打印开头显示的异常日志了,查看arp表大小,峰值为:

# ip nei | wc
5202 27946 273768

参考修改方法
1 扩大arp表项大小
2 多个设备负载均衡,每个控制器只向部分设备发送snmg get,降低arp表项资源需求
3 周期内分时间间隔发送snmp get请求,每个时间间隔只发送部分请求,同时间隔要大于表项老化时间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 摘要 OpenFlow 简史(SDN != OpenFlow ; 版本问题) OpenFlow 工作原理 Open...
    RiboseYim阅读 1,455评论 0 6
  • NAME dnsmasq - A lightweight DHCP and caching DNS server....
    ximitc阅读 2,805评论 0 0
  • 我们总是喜欢关于天长地久的奇迹,也喜欢一场电影下来结局是美好的。我们总是许诺他人要彼此不忘记,此生永远在一起。我们...
    沐府墓主阅读 737评论 0 0
  • 一、理性和非理性,是不是经济学的基础? 经济学研究的规律本身,不以人是理性的还是不理性的为转移。如果一个人不理性所...
    爷有蔓草阅读 230评论 0 0
  • 叶清苒不知道昨天的寝室卧谈会具体开到几点,只知道后来大家的话题就从叶清苒和时沥清渐渐变成了慧慧和陈浩,然后变成了最...
    滚圆滚圆阅读 676评论 2 1