diff options
author | ae <ae@FreeBSD.org> | 2015-03-12 09:04:19 +0000 |
---|---|---|
committer | ae <ae@FreeBSD.org> | 2015-03-12 09:04:19 +0000 |
commit | 86de47baf2c972658bde189dbaa24081439df055 (patch) | |
tree | ae85a23781ca4be76eaf454f6f14a23317b17096 /sys/netinet6/ip6_input.c | |
parent | 565f091b88bfa171b05c99de7fc77e5edd26d204 (diff) | |
download | FreeBSD-src-86de47baf2c972658bde189dbaa24081439df055.zip FreeBSD-src-86de47baf2c972658bde189dbaa24081439df055.tar.gz |
MFC r279588:
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.
MFC r279684:
tcp6_ctlinput() doesn't pass MTU value to in6_pcbnotify().
Check cmdarg isn't NULL before dereference, this check was in the
ip6_notify_pmtu() before r279588.
PR: 197059
Sponsored by: Yandex LLC
Diffstat (limited to 'sys/netinet6/ip6_input.c')
-rw-r--r-- | sys/netinet6/ip6_input.c | 29 |
1 files changed, 16 insertions, 13 deletions
diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c index 7d09696..b8a75c6 100644 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -1592,24 +1592,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; @@ -1618,14 +1622,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 |