Further simplify nd6_output_lle.

Currently we have 3 usage patterns:
1) nd6_output (most traffic flow, no lle supplied, lle RLOCK sufficient)
2) corner cases for output (no lle, STALE lle, so on). lle WLOCK needed.
3) nd* iunternal machinery (WLOCK'ed lle provided, perform packet queing).

We separate case 1 and implement it inside its only customer - nd6_output.
This leads to some code duplication (especialy SEND stuff, which should be
hooked to output in a different way), but simplifies locking and control
flow logic fir nd6_output_lle.

Reviewed by:	ae
MFC after:	3 weeks
Sponsored by:	Yandex LLC
This commit is contained in:
melifaro 2014-02-13 19:09:04 +00:00
parent ab3f62ff43
commit dfdcfd9e83

View File

@ -1832,34 +1832,114 @@ nd6_slowtimo(void *arg)
CURVNET_RESTORE();
}
/*
* IPv6 packet output - light version.
* Checks if destination LLE exists and is in proper state
* (e.g no modification required). If not true, fall back to
* "heavy" version.
*/
int
nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
struct sockaddr_in6 *dst, struct rtentry *rt0)
{
struct llentry *ln = NULL;
int error = 0;
return (nd6_output_lle(ifp, origifp, m0, dst, rt0, NULL, NULL));
/* discard the packet if IPv6 operation is disabled on the interface */
if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) {
m_freem(m);
return (ENETDOWN); /* better error? */
}
if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr))
goto sendpkt;
if (nd6_need_cache(ifp) == 0)
goto sendpkt;
IF_AFDATA_RLOCK(ifp);
ln = nd6_lookup(&dst->sin6_addr, 0, ifp);
IF_AFDATA_RUNLOCK(ifp);
/*
* Perform fast path for the following cases:
* 1) lle state is REACHABLE
* 2) lle state is DELAY (NS message sentNS message sent)
*
* Every other case involves lle modification, so we handle
* them separately.
*/
if (ln == NULL || (ln->ln_state != ND6_LLINFO_REACHABLE &&
ln->ln_state != ND6_LLINFO_DELAY)) {
/* Fall back to slow processing path */
if (ln != NULL)
LLE_RUNLOCK(ln);
return (nd6_output_lle(ifp, origifp, m, dst, rt0, NULL, NULL));
}
sendpkt:
if (ln != NULL)
LLE_RUNLOCK(ln);
#ifdef MAC
mac_netinet6_nd6_send(ifp, m);
#endif
/*
* If called from nd6_ns_output() (NS), nd6_na_output() (NA),
* icmp6_redirect_output() (REDIRECT) or from rip6_output() (RS, RA
* as handled by rtsol and rtadvd), mbufs will be tagged for SeND
* to be diverted to user space. When re-injected into the kernel,
* send_output() will directly dispatch them to the outgoing interface.
*/
if (send_sendso_input_hook != NULL) {
struct m_tag *mtag;
struct ip6_hdr *ip6;
int ip6len;
mtag = m_tag_find(m, PACKET_TAG_ND_OUTGOING, NULL);
if (mtag != NULL) {
ip6 = mtod(m, struct ip6_hdr *);
ip6len = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen);
/* Use the SEND socket */
error = send_sendso_input_hook(m, ifp, SND_OUT,
ip6len);
/* -1 == no app on SEND socket */
if (error == 0 || error != -1)
return (error);
}
}
m_clrprotoflags(m); /* Avoid confusing lower layers. */
IP_PROBE(send, NULL, NULL, mtod(m, struct ip6_hdr *), ifp, NULL,
mtod(m, struct ip6_hdr *));
if ((ifp->if_flags & IFF_LOOPBACK) == 0)
origifp = ifp;
error = (*ifp->if_output)(origifp, m, (struct sockaddr *)dst, NULL);
return (error);
}
/*
* Note that I'm not enforcing any global serialization
* lle state or asked changes here as the logic is too
* complicated to avoid having to always acquire an exclusive
* lock
* KMM
* Output IPv6 packet - heavy version.
* Function assume that either
* 1) destination LLE does not exist, is invalid or stale, so
* ND6_EXCLUSIVE lock needs to be acquired
* 2) destination lle is provided (with ND6_EXCLUSIVE lock),
* in that case packets are queued in &chain.
*
*/
int
nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
struct sockaddr_in6 *dst, struct rtentry *rt0, struct llentry *lle,
struct mbuf **chain)
{
struct mbuf *m = m0;
struct m_tag *mtag;
struct llentry *ln = lle;
struct ip6_hdr *ip6;
int error = 0;
int flags = 0;
int has_lle = 0;
int ip6len;
#ifdef INVARIANTS
@ -1877,30 +1957,26 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
return (ENETDOWN); /* better error? */
}
if (lle != NULL)
has_lle = 1;
if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr))
goto sendpkt;
if (nd6_need_cache(ifp) == 0)
goto sendpkt;
/*
* next hop determination. This routine is derived from ether_output.
*/
/*
* Address resolution or Neighbor Unreachability Detection
* for the next hop.
* At this point, the destination of the packet must be a unicast
* or an anycast address(i.e. not a multicast).
*/
flags = (lle != NULL) ? LLE_EXCLUSIVE : 0;
if (ln == NULL) {
retry:
if (lle == NULL) {
IF_AFDATA_RLOCK(ifp);
ln = lla_lookup(LLTABLE6(ifp), flags, (struct sockaddr *)dst);
lle = nd6_lookup(&dst->sin6_addr, ND6_EXCLUSIVE, ifp);
IF_AFDATA_RUNLOCK(ifp);
if ((ln == NULL) && nd6_is_addr_neighbor(dst, ifp)) {
if ((lle == NULL) && nd6_is_addr_neighbor(dst, ifp)) {
/*
* Since nd6_is_addr_neighbor() internally calls nd6_lookup(),
* the condition below is not very efficient. But we believe
@ -1908,34 +1984,31 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
*/
flags = ND6_CREATE | ND6_EXCLUSIVE;
IF_AFDATA_LOCK(ifp);
ln = nd6_lookup(&dst->sin6_addr, flags, ifp);
lle = nd6_lookup(&dst->sin6_addr, flags, ifp);
IF_AFDATA_UNLOCK(ifp);
}
}
if (ln == NULL) {
if (lle == NULL) {
if ((ifp->if_flags & IFF_POINTOPOINT) == 0 &&
!(ND_IFINFO(ifp)->flags & ND6_IFF_PERFORMNUD)) {
char ip6buf[INET6_ADDRSTRLEN];
log(LOG_DEBUG,
"nd6_output: can't allocate llinfo for %s "
"(ln=%p)\n",
ip6_sprintf(ip6buf, &dst->sin6_addr), ln);
ip6_sprintf(ip6buf, &dst->sin6_addr), lle);
m_freem(m);
return (ENOBUFS);
}
goto sendpkt; /* send anyway */
}
LLE_WLOCK_ASSERT(lle);
/* We don't have to do link-layer address resolution on a p2p link. */
if ((ifp->if_flags & IFF_POINTOPOINT) != 0 &&
ln->ln_state < ND6_LLINFO_REACHABLE) {
if ((flags & LLE_EXCLUSIVE) == 0) {
flags |= LLE_EXCLUSIVE;
LLE_RUNLOCK(ln);
goto retry;
}
ln->ln_state = ND6_LLINFO_STALE;
nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
lle->ln_state < ND6_LLINFO_REACHABLE) {
lle->ln_state = ND6_LLINFO_STALE;
nd6_llinfo_settimer_locked(lle, (long)V_nd6_gctimer * hz);
}
/*
@ -1945,15 +2018,10 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* neighbor unreachability detection on expiration.
* (RFC 2461 7.3.3)
*/
if (ln->ln_state == ND6_LLINFO_STALE) {
if ((flags & LLE_EXCLUSIVE) == 0) {
flags |= LLE_EXCLUSIVE;
LLE_RUNLOCK(ln);
goto retry;
}
ln->la_asked = 0;
ln->ln_state = ND6_LLINFO_DELAY;
nd6_llinfo_settimer_locked(ln, (long)V_nd6_delay * hz);
if (lle->ln_state == ND6_LLINFO_STALE) {
lle->la_asked = 0;
lle->ln_state = ND6_LLINFO_DELAY;
nd6_llinfo_settimer_locked(lle, (long)V_nd6_delay * hz);
}
/*
@ -1961,7 +2029,7 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* (i.e. its link-layer address is already resolved), just
* send the packet.
*/
if (ln->ln_state > ND6_LLINFO_INCOMPLETE)
if (lle->ln_state > ND6_LLINFO_INCOMPLETE)
goto sendpkt;
/*
@ -1971,23 +2039,15 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* does not exceed nd6_maxqueuelen. When it exceeds nd6_maxqueuelen,
* the oldest packet in the queue will be removed.
*/
if (ln->ln_state == ND6_LLINFO_NOSTATE)
ln->ln_state = ND6_LLINFO_INCOMPLETE;
if (lle->ln_state == ND6_LLINFO_NOSTATE)
lle->ln_state = ND6_LLINFO_INCOMPLETE;
if ((flags & LLE_EXCLUSIVE) == 0) {
flags |= LLE_EXCLUSIVE;
LLE_RUNLOCK(ln);
goto retry;
}
LLE_WLOCK_ASSERT(ln);
if (ln->la_hold) {
if (lle->la_hold != NULL) {
struct mbuf *m_hold;
int i;
i = 0;
for (m_hold = ln->la_hold; m_hold; m_hold = m_hold->m_nextpkt) {
for (m_hold = lle->la_hold; m_hold; m_hold = m_hold->m_nextpkt){
i++;
if (m_hold->m_nextpkt == NULL) {
m_hold->m_nextpkt = m;
@ -1995,35 +2055,34 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
}
}
while (i >= V_nd6_maxqueuelen) {
m_hold = ln->la_hold;
ln->la_hold = ln->la_hold->m_nextpkt;
m_hold = lle->la_hold;
lle->la_hold = lle->la_hold->m_nextpkt;
m_freem(m_hold);
i--;
}
} else {
ln->la_hold = m;
lle->la_hold = m;
}
/*
* If there has been no NS for the neighbor after entering the
* INCOMPLETE state, send the first solicitation.
*/
if (!ND6_LLINFO_PERMANENT(ln) && ln->la_asked == 0) {
ln->la_asked++;
if (!ND6_LLINFO_PERMANENT(lle) && lle->la_asked == 0) {
lle->la_asked++;
nd6_llinfo_settimer_locked(ln,
nd6_llinfo_settimer_locked(lle,
(long)ND_IFINFO(ifp)->retrans * hz / 1000);
LLE_WUNLOCK(ln);
nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
if (lle != NULL && ln == lle)
LLE_WUNLOCK(lle);
nd6_ns_output(ifp, NULL, &dst->sin6_addr, lle, 0);
if (has_lle != 0)
LLE_WLOCK(lle);
} else if (lle == NULL || ln != lle) {
} else if (has_lle == 0) {
/*
* We did the lookup (no lle arg) so we
* need to do the unlock here.
*/
LLE_WUNLOCK(ln);
LLE_WUNLOCK(lle);
}
return (0);
@ -2033,12 +2092,8 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* ln is valid and the caller did not pass in
* an llentry
*/
if ((ln != NULL) && (lle == NULL)) {
if (flags & LLE_EXCLUSIVE)
LLE_WUNLOCK(ln);
else
LLE_RUNLOCK(ln);
}
if (lle != NULL && has_lle == 0)
LLE_WUNLOCK(lle);
#ifdef MAC
mac_netinet6_nd6_send(ifp, m);
@ -2072,7 +2127,7 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* a list of mbufs to send and transmit them in the caller
* after the lock is dropped
*/
if (lle != NULL) {
if (has_lle != 0) {
if (*chain == NULL)
*chain = m;
else {