概述

neigh_update函数用来更新指定的邻居项,更新内容是硬件地址和状态,更新之后,会根据新状态设置其输出函数,CONNECTED状态则使用快速输出,否则使用慢速输出;如果是由原来的无效状态变为现在的有效状态,则需要将数据包缓存队列中的数据包发送出去;

该函数在邻居子系统中被频繁调用;arp模块再收到邻居应答,收到邻居的情况,转发单播代理请求后,会调用该函数更新地址和状态;netlink或者ioctl模块添加或者删除邻居项,也会调用该函数更新地址和状态;

源码分析
  1 /* 更新指定的邻居项,更新内容为硬件地址和状态,新状态有效,并且有缓存包,则发送 */
  2 int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
  3          u32 flags, u32 nlmsg_pid)
  4 {
  5     u8 old;
  6     int err;
  7     int notify = 0;
  8     struct net_device *dev;
  9     int update_isrouter = 0;
 10 
 11     write_lock_bh(&neigh->lock);
 12 
 13     dev    = neigh->dev;
 14     old    = neigh->nud_state;
 15     err    = -EPERM;
 16 
 17     /* 原状态是NOARP或者PERMANENT,必须要求是用户管理员发生的更新 */
 18     if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
 19         (old & (NUD_NOARP | NUD_PERMANENT)))
 20         goto out;
 21 
 22     /* 已经在销毁状态 */
 23     if (neigh->dead)
 24         goto out;
 25 
 26     /* 新状态不是有效状态 */
 27     if (!(new & NUD_VALID)) {
 28 
 29         /* 删除定时器 */
 30         neigh_del_timer(neigh);
 31 
 32         /* 原状态是已连接状态,更新输出函数 */
 33         if (old & NUD_CONNECTED)
 34             neigh_suspect(neigh);
 35         /* 设置状态 */
 36         neigh->nud_state = new;
 37         err = 0;
 38         notify = old & NUD_VALID;
 39 
 40         /* 原状态为INCOMPLETE或者PROBE,新状态为失败状态 */
 41         if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
 42             (new & NUD_FAILED)) {
 43             /* 清空缓存包队列 */
 44             neigh_invalidate(neigh);
 45             notify = 1;
 46         }
 47         goto out;
 48     }
 49 
 50     /* Compare new lladdr with cached one */
 51     if (!dev->addr_len) {
 52         /* First case: device needs no address. */
 53         lladdr = neigh->ha;
 54     } else if (lladdr) {
 55         /* The second case: if something is already cached
 56            and a new address is proposed:
 57            - compare new & old
 58            - if they are different, check override flag
 59          */
 60         if ((old & NUD_VALID) &&
 61             !memcmp(lladdr, neigh->ha, dev->addr_len))
 62             lladdr = neigh->ha;
 63     } else {
 64         /* No address is supplied; if we know something,
 65            use it, otherwise discard the request.
 66          */
 67         err = -EINVAL;
 68         if (!(old & NUD_VALID))
 69             goto out;
 70         lladdr = neigh->ha;
 71     }
 72 
 73     /* If entry was valid and address is not changed,
 74        do not change entry state, if new one is STALE.
 75      */
 76     err = 0;
 77     update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
 78 
 79     /* 原状态有效 */
 80     if (old & NUD_VALID) {
 81         /* 地址不同 && 无UPDATE_F_OVERRIDE标记 */
 82         if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
 83             update_isrouter = 0;
 84             /* 有UPDATE_F_WEAK_OVERRIDE状态 && 原状态是连接状态 */
 85             if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
 86                 (old & NUD_CONNECTED)) {
 87                 /* 更新硬件地址为邻居项地址 */
 88                 lladdr = neigh->ha;
 89                 /* 更新状态为STALE */
 90                 new = NUD_STALE;
 91             } else
 92                 goto out;
 93         } 
 94         /* 地址相同或者有UPDATE_F_OVERRIDE标记 */
 95         else {
 96             /* 地址相同&&新状态为STALE&&不是管理员更新,新状态设置为原状态 */
 97             if (lladdr == neigh->ha && new == NUD_STALE &&
 98                 !(flags & NEIGH_UPDATE_F_ADMIN))
 99                 new = old;
100         }
101     }
102 
103     /* Update timestamps only once we know we will make a change to the
104      * neighbour entry. Otherwise we risk to move the locktime window with
105      * noop updates and ignore relevant ARP updates.
106      */
107     /* 新旧状态不同或新旧地址不同 */
108     if (new != old || lladdr != neigh->ha) {
109         /* 新状态是连接状态,更新确认时间 */
110         if (new & NUD_CONNECTED)
111             neigh->confirmed = jiffies;
112         /* 更新更新时间 */
113         neigh->updated = jiffies;
114     }
115 
116     /* 新旧状态不同 */
117     if (new != old) {
118         /* 删除定时器 */
119         neigh_del_timer(neigh);
120 
121         /* 新状态为PROBE,设置未接受到应答计数为0 */
122         if (new & NUD_PROBE)
123             atomic_set(&neigh->probes, 0);
124 
125         /* 新状态需要定时器,则添加 */
126         if (new & NUD_IN_TIMER)
127             neigh_add_timer(neigh, (jiffies +
128                         ((new & NUD_REACHABLE) ?
129                          neigh->parms->reachable_time :
130                          0)));
131         /* 设置新状态 */
132         neigh->nud_state = new;
133         notify = 1;
134     }
135 
136     /* 新旧地址不同 */
137     if (lladdr != neigh->ha) {
138         write_seqlock(&neigh->ha_lock);
139         /* 拷贝新地址 */
140         memcpy(&neigh->ha, lladdr, dev->addr_len);
141         write_sequnlock(&neigh->ha_lock);
142         /* 更新二层头缓存 */
143         neigh_update_hhs(neigh);
144 
145         /* 新状态不是连接状态,更新确认时间 */
146         if (!(new & NUD_CONNECTED))
147             neigh->confirmed = jiffies -
148                       (NEIGH_VAR(neigh->parms, BASE_REACHABLE_TIME) << 1);
149         notify = 1;
150     }
151 
152     /* 新旧状态相同 */
153     if (new == old)
154         goto out;
155 
156     /* 新状态为CONNECTED,更新输出函数为connected_out */
157     if (new & NUD_CONNECTED)
158         neigh_connect(neigh);
159     /* 否则,输出函数为output */
160     else
161         neigh_suspect(neigh);
162 
163     /* 原状态无效,新状态有效 */
164     if (!(old & NUD_VALID)) {
165         struct sk_buff *skb;
166 
167         /* Again: avoid dead loop if something went wrong */
168 
169         /* 新状态有效,缓存队列不为空 */
170         while (neigh->nud_state & NUD_VALID &&
171                (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
172             struct dst_entry *dst = skb_dst(skb);
173             struct neighbour *n2, *n1 = neigh;
174             write_unlock_bh(&neigh->lock);
175 
176             rcu_read_lock();
177 
178             /* Why not just use 'neigh' as-is?  The problem is that
179              * things such as shaper, eql, and sch_teql can end up
180              * using alternative, different, neigh objects to output
181              * the packet in the output path.  So what we need to do
182              * here is re-lookup the top-level neigh in the path so
183              * we can reinject the packet there.
184              */
185             n2 = NULL;
186             /* 有路由缓存,则根据路由缓存获取邻居项,有则替换 */
187             if (dst) {
188                 n2 = dst_neigh_lookup_skb(dst, skb);
189                 if (n2)
190                     n1 = n2;
191             }
192 
193             /* 输出数据包 */
194             n1->output(n1, skb);
195 
196             /* 是否引用的邻居项 */
197             if (n2)
198                 neigh_release(n2);
199             rcu_read_unlock();
200 
201             write_lock_bh(&neigh->lock);
202         }
203 
204         /* 清空数据包缓存队列 */
205         __skb_queue_purge(&neigh->arp_queue);
206         neigh->arp_queue_len_bytes = 0;
207     }
208 out:
209     if (update_isrouter) {
210         neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
211             (neigh->flags | NTF_ROUTER) :
212             (neigh->flags & ~NTF_ROUTER);
213     }
214     write_unlock_bh(&neigh->lock);
215 
216     /* 通知其他关心的模块 */
217     if (notify)
218         neigh_update_notify(neigh, nlmsg_pid);
219 
220     return err;
221 }