diff options
author | ae <ae@FreeBSD.org> | 2015-03-04 11:20:01 +0000 |
---|---|---|
committer | ae <ae@FreeBSD.org> | 2015-03-04 11:20:01 +0000 |
commit | a312c1bedf1e3323e1828af19ab3339c39019f36 (patch) | |
tree | f7d4640ef0da161c128f53c5a19e9768fc664ff0 /sys/netinet6 | |
parent | e5a77689c2f3d6165cb5a1078df04c9936ab9060 (diff) | |
download | FreeBSD-src-a312c1bedf1e3323e1828af19ab3339c39019f36.zip FreeBSD-src-a312c1bedf1e3323e1828af19ab3339c39019f36.tar.gz |
Fix deadlock in IPv6 PCB code.
When several threads are trying to send datagram to the same destination,
but fragmentation is disabled and datagram size exceeds link MTU,
ip6_output() calls pfctlinput2(PRC_MSGSIZE). It does notify all
sockets wanted to know MTU to this destination. And since all threads
hold PCB lock while sending, taking the lock for each PCB in the
in6_pcbnotify() leads to deadlock.
RFC 3542 p.11.3 suggests notify all application wanted to receive
IPV6_PATHMTU ancillary data for each ICMPv6 packet too big message.
But it doesn't require this, when we don't receive ICMPv6 message.
Change ip6_notify_pmtu() function to be able use it directly from
ip6_output() to notify only one socket, and to notify all sockets
when ICMPv6 packet too big message received.
PR: 197059
Differential Revision: https://reviews.freebsd.org/D1949
Reviewed by: no objection from #network
Obtained from: Yandex LLC
MFC after: 1 week
Sponsored by: Yandex LLC
Diffstat (limited to 'sys/netinet6')
-rw-r--r-- | sys/netinet6/in6_pcb.c | 12 | ||||
-rw-r--r-- | sys/netinet6/ip6_input.c | 29 | ||||
-rw-r--r-- | sys/netinet6/ip6_output.c | 17 | ||||
-rw-r--r-- | sys/netinet6/ip6_var.h | 3 |
4 files changed, 25 insertions, 36 deletions
diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c index 3b61849..7296ae6 100644 --- a/sys/netinet6/in6_pcb.c +++ b/sys/netinet6/in6_pcb.c @@ -642,18 +642,12 @@ in6_pcbnotify(struct inpcbinfo *pcbinfo, struct sockaddr *dst, /* * If the error designates a new path MTU for a destination * and the application (associated with this socket) wanted to - * know the value, notify. Note that we notify for all - * disconnected sockets if the corresponding application - * wanted. This is because some UDP applications keep sending - * sockets disconnected. + * know the value, notify. * XXX: should we avoid to notify the value to TCP sockets? */ - if (cmd == PRC_MSGSIZE && (inp->inp_flags & IN6P_MTU) != 0 && - (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) || - IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, &sa6_dst->sin6_addr))) { + if (cmd == PRC_MSGSIZE) ip6_notify_pmtu(inp, (struct sockaddr_in6 *)dst, - (u_int32_t *)cmdarg); - } + *(u_int32_t *)cmdarg); /* * Detect if we should notify the error. If no source and diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c index 64d6cdb..78e8ef3 100644 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -1343,24 +1343,28 @@ ip6_savecontrol(struct inpcb *in6p, struct mbuf *m, struct mbuf **mp) #undef IS2292 void -ip6_notify_pmtu(struct inpcb *in6p, struct sockaddr_in6 *dst, u_int32_t *mtu) +ip6_notify_pmtu(struct inpcb *inp, struct sockaddr_in6 *dst, u_int32_t mtu) { struct socket *so; struct mbuf *m_mtu; struct ip6_mtuinfo mtuctl; - so = in6p->inp_socket; - - if (mtu == NULL) + KASSERT(inp != NULL, ("%s: inp == NULL", __func__)); + /* + * Notify the error by sending IPV6_PATHMTU ancillary data if + * application wanted to know the MTU value. + * NOTE: we notify disconnected sockets, because some udp + * applications keep sending sockets disconnected. + * NOTE: our implementation doesn't notify connected sockets that has + * foreign address that is different than given destination addresses + * (this is permitted by RFC 3542). + */ + if ((inp->inp_flags & IN6P_MTU) == 0 || ( + !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) && + !IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, &dst->sin6_addr))) return; -#ifdef DIAGNOSTIC - if (so == NULL) /* I believe this is impossible */ - panic("ip6_notify_pmtu: socket is NULL"); -#endif - - bzero(&mtuctl, sizeof(mtuctl)); /* zero-clear for safety */ - mtuctl.ip6m_mtu = *mtu; + mtuctl.ip6m_mtu = mtu; mtuctl.ip6m_addr = *dst; if (sa6_recoverscope(&mtuctl.ip6m_addr)) return; @@ -1369,14 +1373,13 @@ ip6_notify_pmtu(struct inpcb *in6p, struct sockaddr_in6 *dst, u_int32_t *mtu) IPV6_PATHMTU, IPPROTO_IPV6)) == NULL) return; + so = inp->inp_socket; if (sbappendaddr(&so->so_rcv, (struct sockaddr *)dst, NULL, m_mtu) == 0) { m_freem(m_mtu); /* XXX: should count statistics */ } else sorwakeup(so); - - return; } #ifdef PULLDOWN_TEST diff --git a/sys/netinet6/ip6_output.c b/sys/netinet6/ip6_output.c index a94d797..9213d8f 100644 --- a/sys/netinet6/ip6_output.c +++ b/sys/netinet6/ip6_output.c @@ -910,19 +910,12 @@ passout: * Even if the DONTFRAG option is specified, we cannot send the * packet when the data length is larger than the MTU of the * outgoing interface. - * Notify the error by sending IPV6_PATHMTU ancillary data as - * well as returning an error code (the latter is not described - * in the API spec.) + * Notify the error by sending IPV6_PATHMTU ancillary data if + * application wanted to know the MTU value. Also return an + * error code (this is not described in the API spec). */ - u_int32_t mtu32; - struct ip6ctlparam ip6cp; - - mtu32 = (u_int32_t)mtu; - bzero(&ip6cp, sizeof(ip6cp)); - ip6cp.ip6c_cmdarg = (void *)&mtu32; - pfctlinput2(PRC_MSGSIZE, (struct sockaddr *)&ro_pmtu->ro_dst, - (void *)&ip6cp); - + if (inp != NULL) + ip6_notify_pmtu(inp, &dst_sa, (u_int32_t)mtu); error = EMSGSIZE; goto bad; } diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h index 54c5c66..50188ee 100644 --- a/sys/netinet6/ip6_var.h +++ b/sys/netinet6/ip6_var.h @@ -368,8 +368,7 @@ int ip6_process_hopopts(struct mbuf *, u_int8_t *, int, u_int32_t *, struct mbuf **ip6_savecontrol_v4(struct inpcb *, struct mbuf *, struct mbuf **, int *); void ip6_savecontrol(struct inpcb *, struct mbuf *, struct mbuf **); -void ip6_notify_pmtu(struct inpcb *, struct sockaddr_in6 *, - u_int32_t *); +void ip6_notify_pmtu(struct inpcb *, struct sockaddr_in6 *, u_int32_t); int ip6_sysctl(int *, u_int, void *, size_t *, void *, size_t); void ip6_forward(struct mbuf *, int); |