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
This commit is contained in:
parent
e5a77689c2
commit
a312c1bedf
@ -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
|
||||
|
@ -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
|
||||
|
@ -910,19 +910,12 @@ ip6_output(struct mbuf *m0, struct ip6_pktopts *opt,
|
||||
* 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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user