如果内核在locktime时间内接收到多个ARP报文,仅首个报文生效。对于arp协议,内核默认的locktime时长为1秒钟,可通过PROC文件或者ip命令进行修改。
struct neigh_table arp_tbl = {
.family = AF_INET,
.key_len = 4,
.protocol = cpu_to_be16(ETH_P_IP),
.hash = arp_hash,
.key_eq = arp_key_eq,
.constructor = arp_constructor,
.proxy_redo = parp_redo,
.id = "arp_cache",
.parms = {
.tbl = &arp_tbl,
.reachable_time = 30 * HZ,
.data = {
...
[NEIGH_VAR_LOCKTIME] = 1 * HZ,
通过PROC文件locktime可查看和修改此值,如下,默认为100,对应于1秒钟。
$ cat /proc/sys/net/ipv4/neigh/eth0/locktime
100
内核中静态变量neigh_sysctl_table定义了locktime的PROC文件信息。
static struct neigh_sysctl_table {
struct ctl_table_header *sysctl_header;
struct ctl_table neigh_vars[NEIGH_VAR_MAX + 1];
} neigh_sysctl_template __read_mostly = {
.neigh_vars = {
...
NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(LOCKTIME, "locktime"),
如下定义,locktime的处理有neigh_proc_dointvec_userhz_jiffies,其负责将内核使用的jiffies值转换为用户层面的HZ(值为100),即内核locktime初始化为1000,用户层面显示为100。
#define NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(attr, name) \
NEIGH_SYSCTL_ENTRY(attr, attr, name, 0644, neigh_proc_dointvec_userhz_jiffies)
netlink接口
除了以上的PROC文件外,还可使用ip ntable命令查看和修改设备的邻居表参数。
# ip ntable show dev eth0
inet arp_cache
dev eth0
refcnt 12 reachable 28884 base_reachable 30000 retrans 1000
gc_stale 60000 delay_probe 5000 queue 31
app_probes 0 ucast_probes 3 mcast_probes 3
anycast_delay 1000 proxy_delay 800 proxy_queue 64 locktime 1000
与PROC文件不同,这里显示的locktime单位为毫秒值。如下将设备eth0的邻居表参数locktime修改为2秒钟。
# ip ntable change name arp_cache dev eth0 base_reachable 2000
内核函数neigh_init初始化了以上ip ntable change命令的处理函数neightbl_set。
static int __init neigh_init(void)
{
...
rtnl_register(PF_UNSPEC, RTM_SETNEIGHTBL, neightbl_set, NULL, 0);
如下为neightbl_set的实现,函数nla_get_msecs读取IP命令行设置的locktime的毫秒值参数,转换为内核使用的jiffies值,设置到邻居表的参数中。对于arp协议,将设置到arp_tbl成员parms的子成员data数组中,索引位置为NEIGH_VAR_LOCKTIME。
static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
{
struct neigh_table *tbl;
struct nlattr *tb[NDTA_MAX+1];
if (tb[NDTA_PARMS]) {
struct neigh_parms *p;
p = lookup_neigh_parms(tbl, net, ifindex);
...
for (i = 1; i <= NDTPA_MAX; i++) {
switch (i) {
...
case NDTPA_LOCKTIME:
NEIGH_VAR_SET(p, LOCKTIME, nla_get_msecs(tbp[i]));
显示命令ip ntable show由内核中的函数neightbl_fill_parms填充值,对于locktime的值,由nla_put_msecs填充。
static int neightbl_fill_parms(struct sk_buff *skb, struct neigh_parms *parms)
{
...
if ((parms->dev &&
...
nla_put_msecs(skb, NDTPA_LOCKTIME,
NEIGH_VAR(parms, LOCKTIME), NDTPA_PAD)
如下函数nla_put_msecs,其需要将内核使用locktime的jiffies值转换为ip ntable show显示时的毫秒值,通过jiffies_to_msecs实现。
static inline int nla_put_msecs(struct sk_buff *skb, int attrtype,
unsigned long njiffies, int padattr)
{
u64 tmp = jiffies_to_msecs(njiffies);
return nla_put_64bit(skb, attrtype, sizeof(u64), &tmp, padattr);
}
locktime处理
在arp报文处理函数arp_process中,对于连续(back-to-back)接收到的ARP回复,内核选择使用首个回复报文,因为,如果链路上存在多个活动的ARP代理,这样就选择了其中速度较快的一个,也可屏蔽一些无用的ARP回复。
内核判断连续报文的依据为locktime时长,在此段时间内接收到的ARP报文,进行更新时不设置覆盖标志位NEIGH_UPDATE_F_OVERRIDE(garp的情况除外)。
static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
/* Update our ARP tables */
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
...
if (n) {
int state = NUD_REACHABLE;
int override;
/* If several different ARP replies follows back-to-back,
use the FIRST one. It is possible, if several proxy
agents are active. Taking the first reply prevents
arp trashing and chooses the fastest router.
*/
override = time_after(jiffies,
n->updated +
NEIGH_VAR(n->parms, LOCKTIME)) || is_garp;
/* Broadcast replies and request packets
do not assert neighbour reachability.
*/
if (arp->ar_op != htons(ARPOP_REPLY) || skb->pkt_type != PACKET_HOST)
state = NUD_STALE;
neigh_update(n, sha, state,
override ? NEIGH_UPDATE_F_OVERRIDE : 0, 0);
内核版本 5.0