git: fcb3f813f379 - main - netinet*: remove PRC_ constants and streamline ICMP processing

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Tue, 04 Oct 2022 03:57:37 UTC
The branch main has been updated by glebius:

URL: https://cgit.FreeBSD.org/src/commit/?id=fcb3f813f379f544f9cd2a10d18045588da0e132

commit fcb3f813f379f544f9cd2a10d18045588da0e132
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2022-10-04 03:53:04 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2022-10-04 03:53:04 +0000

    netinet*: remove PRC_ constants and streamline ICMP processing
    
    In the original design of the network stack from the protocol control
    input method pr_ctlinput was used notify the protocols about two very
    different kinds of events: internal system events and receival of an
    ICMP messages from outside.  These events were coded with PRC_ codes.
    Today these methods are removed from the protosw(9) and are isolated
    to IPv4 and IPv6 stacks and are called only from icmp*_input().  The
    PRC_ codes now just create a shim layer between ICMP codes and errors
    or actions taken by protocols.
    
    - Change ipproto_ctlinput_t to pass just pointer to ICMP header.  This
      allows protocols to not deduct it from the internal IP header.
    - Change ip6proto_ctlinput_t to pass just struct ip6ctlparam pointer.
      It has all the information needed to the protocols.  In the structure,
      change ip6c_finaldst fields to sockaddr_in6.  The reason is that
      icmp6_input() already has this address wrapped in sockaddr, and the
      protocols want this address as sockaddr.
    - For UDP tunneling control input, as well as for IPSEC control input,
      change the prototypes to accept a transparent union of either ICMP
      header pointer or struct ip6ctlparam pointer.
    - In icmp_input() and icmp6_input() do only validation of ICMP header and
      count bad packets.  The translation of ICMP codes to errors/actions is
      done by protocols.
    - Provide icmp_errmap() and icmp6_errmap() as substitute to inetctlerrmap,
      inet6ctlerrmap arrays.
    - In protocol ctlinput methods either trust what icmp_errmap() recommend,
      or do our own logic based on the ICMP header.
    
    Differential revision:  https://reviews.freebsd.org/D36731
---
 sys/netinet/icmp6.h          |   1 +
 sys/netinet/in_var.h         |   2 -
 sys/netinet/ip_icmp.c        | 106 +++++++++++++++++++----------------
 sys/netinet/ip_icmp.h        |   1 +
 sys/netinet/ip_input.c       |  17 ------
 sys/netinet/ip_var.h         |   3 +-
 sys/netinet/raw_ip.c         |  11 +---
 sys/netinet/sctp_usrreq.c    |  10 ++--
 sys/netinet/sctp_var.h       |   2 +-
 sys/netinet/sctputil.c       |  13 ++---
 sys/netinet/tcp_subr.c       | 128 +++++++++++++++++++++++++------------------
 sys/netinet/udp_usrreq.c     |  24 ++++----
 sys/netinet/udp_var.h        |   6 +-
 sys/netinet6/icmp6.c         | 100 ++++++++++++++++++---------------
 sys/netinet6/in6_pcb.c       |  26 +--------
 sys/netinet6/in6_var.h       |   1 -
 sys/netinet6/ip6_input.c     |  20 -------
 sys/netinet6/ip6_var.h       |  11 ++--
 sys/netinet6/raw_ip6.c       |  31 ++---------
 sys/netinet6/sctp6_usrreq.c  |   8 +--
 sys/netinet6/udp6_usrreq.c   |  26 ++++-----
 sys/netipsec/ipsec_input.c   |  24 ++++----
 sys/netipsec/ipsec_support.h |  17 ++++--
 sys/sys/protosw.h            |  43 ---------------
 24 files changed, 274 insertions(+), 357 deletions(-)

diff --git a/sys/netinet/icmp6.h b/sys/netinet/icmp6.h
index 9628c0957c4a..7429b8173b6a 100644
--- a/sys/netinet/icmp6.h
+++ b/sys/netinet/icmp6.h
@@ -701,6 +701,7 @@ struct	rttimer;
 struct	in6_multi;
 # endif
 void	icmp6_paramerror(struct mbuf *, int);
+int	icmp6_errmap(const struct icmp6_hdr *);
 void	icmp6_error(struct mbuf *, int, int, int);
 void	icmp6_error2(struct mbuf *, int, int, int, struct ifnet *);
 int	icmp6_input(struct mbuf **, int *, int);
diff --git a/sys/netinet/in_var.h b/sys/netinet/in_var.h
index 40e1c1a23c40..c4cfeea66ba8 100644
--- a/sys/netinet/in_var.h
+++ b/sys/netinet/in_var.h
@@ -100,8 +100,6 @@ struct in_ifaddr {
 #define IN_LNAOF(in, ifa) \
 	((ntohl((in).s_addr) & ~((struct in_ifaddr *)(ifa)->ia_subnetmask))
 
-extern	u_char	inetctlerrmap[];
-
 #define LLTABLE(ifp)	\
 	((struct in_ifinfo *)(ifp)->if_afdata[AF_INET])->ii_llt
 /*
diff --git a/sys/netinet/ip_icmp.c b/sys/netinet/ip_icmp.c
index f0cc703c2757..fdde24fd94be 100644
--- a/sys/netinet/ip_icmp.c
+++ b/sys/netinet/ip_icmp.c
@@ -403,6 +403,55 @@ freeit:
 	m_freem(n);
 }
 
+int
+icmp_errmap(const struct icmp *icp)
+{
+
+	switch (icp->icmp_type) {
+	case ICMP_UNREACH:
+		switch (icp->icmp_code) {
+		case ICMP_UNREACH_NET:
+		case ICMP_UNREACH_HOST:
+		case ICMP_UNREACH_SRCFAIL:
+		case ICMP_UNREACH_NET_UNKNOWN:
+		case ICMP_UNREACH_HOST_UNKNOWN:
+		case ICMP_UNREACH_ISOLATED:
+		case ICMP_UNREACH_TOSNET:
+		case ICMP_UNREACH_TOSHOST:
+		case ICMP_UNREACH_HOST_PRECEDENCE:
+		case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+			return (EHOSTUNREACH);
+		case ICMP_UNREACH_NEEDFRAG:
+			return (EMSGSIZE);
+		case ICMP_UNREACH_PROTOCOL:
+		case ICMP_UNREACH_PORT:
+		case ICMP_UNREACH_NET_PROHIB:
+		case ICMP_UNREACH_HOST_PROHIB:
+		case ICMP_UNREACH_FILTER_PROHIB:
+			return (ECONNREFUSED);
+		default:
+			return (0);
+		}
+	case ICMP_TIMXCEED:
+		switch (icp->icmp_code) {
+		case ICMP_TIMXCEED_INTRANS:
+			return (EHOSTUNREACH);
+		default:
+			return (0);
+		}
+	case ICMP_PARAMPROB:
+		switch (icp->icmp_code) {
+		case ICMP_PARAMPROB_ERRATPTR:
+		case ICMP_PARAMPROB_OPTABSENT:
+			return (ENOPROTOOPT);
+		default:
+			return (0);
+		}
+	default:
+		return (0);
+	}
+}
+
 /*
  * Process a received ICMP message.
  */
@@ -484,56 +533,21 @@ icmp_input(struct mbuf **mp, int *offp, int proto)
 	code = icp->icmp_code;
 	switch (icp->icmp_type) {
 	case ICMP_UNREACH:
-		switch (code) {
-			case ICMP_UNREACH_NET:
-			case ICMP_UNREACH_HOST:
-			case ICMP_UNREACH_SRCFAIL:
-			case ICMP_UNREACH_NET_UNKNOWN:
-			case ICMP_UNREACH_HOST_UNKNOWN:
-			case ICMP_UNREACH_ISOLATED:
-			case ICMP_UNREACH_TOSNET:
-			case ICMP_UNREACH_TOSHOST:
-			case ICMP_UNREACH_HOST_PRECEDENCE:
-			case ICMP_UNREACH_PRECEDENCE_CUTOFF:
-				code = PRC_UNREACH_NET;
-				break;
-
-			case ICMP_UNREACH_NEEDFRAG:
-				code = PRC_MSGSIZE;
-				break;
-
-			/*
-			 * RFC 1122, Sections 3.2.2.1 and 4.2.3.9.
-			 * Treat subcodes 2,3 as immediate RST
-			 */
-			case ICMP_UNREACH_PROTOCOL:
-				code = PRC_UNREACH_PROTOCOL;
-				break;
-			case ICMP_UNREACH_PORT:
-				code = PRC_UNREACH_PORT;
-				break;
-
-			case ICMP_UNREACH_NET_PROHIB:
-			case ICMP_UNREACH_HOST_PROHIB:
-			case ICMP_UNREACH_FILTER_PROHIB:
-				code = PRC_UNREACH_ADMIN_PROHIB;
-				break;
-
-			default:
-				goto badcode;
-		}
-		goto deliver;
+		if (code > ICMP_UNREACH_PRECEDENCE_CUTOFF)
+			goto badcode;
+		else
+			goto deliver;
 
 	case ICMP_TIMXCEED:
-		if (code > 1)
+		if (code > ICMP_TIMXCEED_REASS)
 			goto badcode;
-		code += PRC_TIMXCEED_INTRANS;
-		goto deliver;
+		else
+			goto deliver;
 
 	case ICMP_PARAMPROB:
-		if (code > 1)
+		if (code > ICMP_PARAMPROB_LENGTH)
 			goto badcode;
-		code = PRC_PARAMPROB;
+
 	deliver:
 		/*
 		 * Problem with datagram; advise higher level routines.
@@ -553,7 +567,6 @@ icmp_input(struct mbuf **mp, int *offp, int proto)
 		if (icmpprintfs)
 			printf("deliver to protocol %d\n", icp->icmp_ip.ip_p);
 #endif
-		icmpsrc.sin_addr = icp->icmp_ip.ip_dst;
 		/*
 		 * XXX if the packet contains [IPv4 AH TCP], we can't make a
 		 * notification to TCP layer.
@@ -576,8 +589,7 @@ icmp_input(struct mbuf **mp, int *offp, int proto)
 		 *   ICMP_ADVLENPREF. See its definition in ip_icmp.h.
 		 */
 		if (ip_ctlprotox[icp->icmp_ip.ip_p] != NULL)
-			ip_ctlprotox[icp->icmp_ip.ip_p](code, &icmpsrc,
-			    &icp->icmp_ip);
+			ip_ctlprotox[icp->icmp_ip.ip_p](icp);
 		break;
 
 	badcode:
diff --git a/sys/netinet/ip_icmp.h b/sys/netinet/ip_icmp.h
index 0303a09509c7..fefece665a00 100644
--- a/sys/netinet/ip_icmp.h
+++ b/sys/netinet/ip_icmp.h
@@ -216,6 +216,7 @@ struct icmp {
 	(type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY)
 
 #ifdef _KERNEL
+int	icmp_errmap(const struct icmp *);
 void	icmp_error(struct mbuf *, int, int, uint32_t, int);
 int	icmp_input(struct mbuf **, int *, int);
 int	ip_next_mtu(int, int);
diff --git a/sys/netinet/ip_input.c b/sys/netinet/ip_input.c
index 145c4464b855..88fd4f5e4def 100644
--- a/sys/netinet/ip_input.c
+++ b/sys/netinet/ip_input.c
@@ -873,23 +873,6 @@ ipproto_unregister(uint8_t proto)
 		return (ENOENT);
 }
 
-/* (x) - issued by icmp_input() */
-u_char inetctlerrmap[PRC_NCMDS] = {
-	[PRC_MSGSIZE] = EMSGSIZE,			/* (x) */
-	[PRC_HOSTDEAD] = EHOSTDOWN,
-	[PRC_HOSTUNREACH] = EHOSTUNREACH,
-	[PRC_UNREACH_NET] = EHOSTUNREACH,		/* (x) */
-	[PRC_UNREACH_HOST] = EHOSTUNREACH,
-	[PRC_UNREACH_PROTOCOL] = ECONNREFUSED,		/* (x) */
-	[PRC_UNREACH_PORT] = ECONNREFUSED,		/* (x) */
-	[12] = EMSGSIZE,
-	[PRC_UNREACH_SRCFAIL] = EHOSTUNREACH,
-	[PRC_TIMXCEED_INTRANS] = EHOSTUNREACH,		/* (x) */
-	[PRC_TIMXCEED_REASS] = 0,			/* (x) */
-	[PRC_PARAMPROB] = ENOPROTOOPT,			/* (x) */
-	[PRC_UNREACH_ADMIN_PROHIB] = ECONNREFUSED,	/* (x) */
-};
-
 /*
  * Forward a packet.  If some error occurs return the sender
  * an icmp packet.  Note we can't always generate a meaningful
diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h
index 070c82677150..0a2d915b12b3 100644
--- a/sys/netinet/ip_var.h
+++ b/sys/netinet/ip_var.h
@@ -238,7 +238,8 @@ extern void	(*ip_rsvp_force_done)(struct socket *);
 extern int	(*rsvp_input_p)(struct mbuf **, int *, int);
 
 typedef int	ipproto_input_t(struct mbuf **, int *, int);
-typedef void	ipproto_ctlinput_t(int, struct sockaddr_in *, struct ip *);
+struct icmp;
+typedef void	ipproto_ctlinput_t(struct icmp *);
 int	ipproto_register(uint8_t, ipproto_input_t, ipproto_ctlinput_t);
 int	ipproto_unregister(uint8_t);
 #define	IPPROTO_REGISTER(prot, input, ctl)	do {			\
diff --git a/sys/netinet/raw_ip.c b/sys/netinet/raw_ip.c
index fb692e0822cf..2065b47883bb 100644
--- a/sys/netinet/raw_ip.c
+++ b/sys/netinet/raw_ip.c
@@ -804,17 +804,12 @@ rip_ctloutput(struct socket *so, struct sockopt *sopt)
 }
 
 void
-rip_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *ip)
+rip_ctlinput(struct icmp *icmp)
 {
-
-	switch (cmd) {
 #if defined(IPSEC) || defined(IPSEC_SUPPORT)
-	case PRC_MSGSIZE:
-		if (IPSEC_ENABLED(ipv4))
-			IPSEC_CTLINPUT(ipv4, cmd, (struct sockaddr *)sin, ip);
-		break;
+	if (IPSEC_ENABLED(ipv4))
+		IPSEC_CTLINPUT(ipv4, icmp);
 #endif
-	}
 }
 
 static int
diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c
index 4c70b618bf41..b06920529f44 100644
--- a/sys/netinet/sctp_usrreq.c
+++ b/sys/netinet/sctp_usrreq.c
@@ -260,23 +260,21 @@ sctp_notify(struct sctp_inpcb *inp,
 }
 
 void
-sctp_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *inner_ip)
+sctp_ctlinput(struct icmp *icmp)
 {
-	struct ip *outer_ip;
+	struct ip *inner_ip, *outer_ip;
 	struct sctphdr *sh;
-	struct icmp *icmp;
 	struct sctp_inpcb *inp;
 	struct sctp_tcb *stcb;
 	struct sctp_nets *net;
 	struct sctp_init_chunk *ch;
 	struct sockaddr_in src, dst;
 
-	if (inetctlerrmap[cmd] == 0)
+	if (icmp_errmap(icmp) == 0)
 		return;
 
-	icmp = (struct icmp *)((caddr_t)inner_ip -
-	    (sizeof(struct icmp) - sizeof(struct ip)));
 	outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
+	inner_ip = &icmp->icmp_ip;
 	sh = (struct sctphdr *)((caddr_t)inner_ip + (inner_ip->ip_hl << 2));
 	memset(&src, 0, sizeof(struct sockaddr_in));
 	src.sin_family = AF_INET;
diff --git a/sys/netinet/sctp_var.h b/sys/netinet/sctp_var.h
index 77516db37773..fc0b491bf4bb 100644
--- a/sys/netinet/sctp_var.h
+++ b/sys/netinet/sctp_var.h
@@ -322,7 +322,7 @@ struct sctphdr;
 
 void sctp_close(struct socket *so);
 int sctp_disconnect(struct socket *so);
-void sctp_ctlinput(int, struct sockaddr_in *, struct ip *);
+ipproto_ctlinput_t sctp_ctlinput;
 int sctp_ctloutput(struct socket *, struct sockopt *);
 void sctp_input_with_port(struct mbuf *, int, uint16_t);
 int sctp_input(struct mbuf **, int *, int);
diff --git a/sys/netinet/sctputil.c b/sys/netinet/sctputil.c
index bdb35b988ae6..677040cd1d31 100644
--- a/sys/netinet/sctputil.c
+++ b/sys/netinet/sctputil.c
@@ -7190,11 +7190,11 @@ out:
 
 #ifdef INET
 static void
-sctp_recv_icmp_tunneled_packet(int cmd, struct sockaddr *sa, void *vip, void *ctx SCTP_UNUSED)
+sctp_recv_icmp_tunneled_packet(udp_tun_icmp_param_t param)
 {
+	struct icmp *icmp = param.icmp;
 	struct ip *outer_ip, *inner_ip;
 	struct sctphdr *sh;
-	struct icmp *icmp;
 	struct udphdr *udp;
 	struct sctp_inpcb *inp;
 	struct sctp_tcb *stcb;
@@ -7203,9 +7203,7 @@ sctp_recv_icmp_tunneled_packet(int cmd, struct sockaddr *sa, void *vip, void *ct
 	struct sockaddr_in src, dst;
 	uint8_t type, code;
 
-	inner_ip = (struct ip *)vip;
-	icmp = (struct icmp *)((caddr_t)inner_ip -
-	    (sizeof(struct icmp) - sizeof(struct ip)));
+	inner_ip = &icmp->icmp_ip;
 	outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
 	if (ntohs(outer_ip->ip_len) <
 	    sizeof(struct ip) + 8 + (inner_ip->ip_hl << 2) + sizeof(struct udphdr) + 8) {
@@ -7300,9 +7298,9 @@ sctp_recv_icmp_tunneled_packet(int cmd, struct sockaddr *sa, void *vip, void *ct
 
 #ifdef INET6
 static void
-sctp_recv_icmp6_tunneled_packet(int cmd, struct sockaddr *sa, void *d, void *ctx SCTP_UNUSED)
+sctp_recv_icmp6_tunneled_packet(udp_tun_icmp_param_t param)
 {
-	struct ip6ctlparam *ip6cp;
+	struct ip6ctlparam *ip6cp = param.ip6cp;
 	struct sctp_inpcb *inp;
 	struct sctp_tcb *stcb;
 	struct sctp_nets *net;
@@ -7311,7 +7309,6 @@ sctp_recv_icmp6_tunneled_packet(int cmd, struct sockaddr *sa, void *d, void *ctx
 	struct sockaddr_in6 src, dst;
 	uint8_t type, code;
 
-	ip6cp = (struct ip6ctlparam *)d;
 	/*
 	 * XXX: We assume that when IPV6 is non NULL, M and OFF are valid.
 	 */
diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c
index 7e9fe9e4ff5f..5bbea0176303 100644
--- a/sys/netinet/tcp_subr.c
+++ b/sys/netinet/tcp_subr.c
@@ -2854,38 +2854,44 @@ tcp_next_pmtu(const struct icmp *icp, const struct ip *ip)
 }
 
 static void
-tcp_ctlinput_with_port(int cmd, struct sockaddr_in *sin, struct ip *ip,
-    uint16_t port)
+tcp_ctlinput_with_port(struct icmp *icp, uint16_t port)
 {
+	struct ip *ip;
 	struct tcphdr *th;
 	struct inpcb *inp;
 	struct tcpcb *tp;
-	struct inpcb *(*notify)(struct inpcb *, int) = tcp_notify;
-	struct icmp *icp;
+	struct inpcb *(*notify)(struct inpcb *, int);
 	struct in_conninfo inc;
 	tcp_seq icmp_tcp_seq;
-	int mtu;
+	int errno, mtu;
 
-	switch (cmd) {
-	case PRC_MSGSIZE:
+	errno = icmp_errmap(icp);
+	switch (errno) {
+	case 0:
+		return;
+	case EMSGSIZE:
 		notify = tcp_mtudisc_notify;
 		break;
-	case PRC_UNREACH_PORT:
-	case PRC_UNREACH_PROTOCOL:
-	case PRC_TIMXCEED_INTRANS:
-	case PRC_UNREACH_ADMIN_PROHIB:
+	case ECONNREFUSED:
 		if (V_icmp_may_rst)
 			notify = tcp_drop_syn_sent;
+		else
+			notify = tcp_notify;
 		break;
+	case EHOSTUNREACH:
+		if (V_icmp_may_rst && icp->icmp_type == ICMP_TIMXCEED)
+			notify = tcp_drop_syn_sent;
+		else
+			notify = tcp_notify;
+		break;
+	default:
+		notify = tcp_notify;
 	}
 
-	if (inetctlerrmap[cmd] == 0)
-		return;
-
-	icp = (struct icmp *)((caddr_t)ip - offsetof(struct icmp, icmp_ip));
+	ip = &icp->icmp_ip;
 	th = (struct tcphdr *)((caddr_t)ip + (ip->ip_hl << 2));
 	icmp_tcp_seq = th->th_seq;
-	inp = in_pcblookup(&V_tcbinfo, sin->sin_addr, th->th_dport, ip->ip_src,
+	inp = in_pcblookup(&V_tcbinfo, ip->ip_dst, th->th_dport, ip->ip_src,
 	    th->th_sport, INPLOOKUP_WLOCKPCB, NULL);
 	if (inp != NULL)  {
 		if (!(inp->inp_flags & INP_TIMEWAIT) &&
@@ -2893,7 +2899,7 @@ tcp_ctlinput_with_port(int cmd, struct sockaddr_in *sin, struct ip *ip,
 		    !(inp->inp_socket == NULL)) {
 			tp = intotcpcb(inp);
 #ifdef TCP_OFFLOAD
-			if (tp->t_flags & TF_TOE && cmd == PRC_MSGSIZE) {
+			if (tp->t_flags & TF_TOE && errno == EMSGSIZE) {
 				/*
 				 * MTU discovery for offloaded connections.  Let
 				 * the TOE driver verify seq# and process it.
@@ -2908,7 +2914,7 @@ tcp_ctlinput_with_port(int cmd, struct sockaddr_in *sin, struct ip *ip,
 			}
 			if (SEQ_GEQ(ntohl(icmp_tcp_seq), tp->snd_una) &&
 			    SEQ_LT(ntohl(icmp_tcp_seq), tp->snd_max)) {
-				if (cmd == PRC_MSGSIZE) {
+				if (errno == EMSGSIZE) {
 					/*
 					 * MTU discovery: we got a needfrag and
 					 * will potentially try a lower MTU.
@@ -2922,22 +2928,21 @@ tcp_ctlinput_with_port(int cmd, struct sockaddr_in *sin, struct ip *ip,
 					if (mtu < tp->t_maxseg +
 					    sizeof(struct tcpiphdr)) {
 						bzero(&inc, sizeof(inc));
-						inc.inc_faddr = sin->sin_addr;
+						inc.inc_faddr = ip->ip_dst;
 						inc.inc_fibnum =
 						    inp->inp_inc.inc_fibnum;
 						tcp_hc_updatemtu(&inc, mtu);
 						inp = tcp_mtudisc(inp, mtu);
 					}
 				} else
-					inp = (*notify)(inp,
-					    inetctlerrmap[cmd]);
+					inp = (*notify)(inp, errno);
 			}
 		}
 	} else {
 		bzero(&inc, sizeof(inc));
 		inc.inc_fport = th->th_dport;
 		inc.inc_lport = th->th_sport;
-		inc.inc_faddr = sin->sin_addr;
+		inc.inc_faddr = ip->ip_dst;
 		inc.inc_laddr = ip->ip_src;
 		syncache_unreach(&inc, icmp_tcp_seq, port);
 	}
@@ -2947,26 +2952,24 @@ out:
 }
 
 static void
-tcp_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *ip)
+tcp_ctlinput(struct icmp *icmp)
 {
-	tcp_ctlinput_with_port(cmd, sin, ip, htons(0));
+	tcp_ctlinput_with_port(icmp, htons(0));
 }
 
 static void
-tcp_ctlinput_viaudp(int cmd, struct sockaddr *sa, void *vip, void *unused)
+tcp_ctlinput_viaudp(udp_tun_icmp_param_t param)
 {
 	/* Its a tunneled TCP over UDP icmp */
+	struct icmp *icmp = param.icmp;
 	struct ip *outer_ip, *inner_ip;
-	struct icmp *icmp;
 	struct udphdr *udp;
 	struct tcphdr *th, ttemp;
 	int i_hlen, o_len;
 	uint16_t port;
 
-	inner_ip = (struct ip *)vip;
-	icmp = (struct icmp *)((caddr_t)inner_ip -
-	    (sizeof(struct icmp) - sizeof(struct ip)));
 	outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
+	inner_ip = &icmp->icmp_ip;
 	i_hlen = inner_ip->ip_hl << 2;
 	o_len = ntohs(outer_ip->ip_len);
 	if (o_len <
@@ -2987,7 +2990,7 @@ tcp_ctlinput_viaudp(int cmd, struct sockaddr *sa, void *vip, void *unused)
 	o_len -= sizeof(struct udphdr);
 	outer_ip->ip_len = htons(o_len);
 	/* Now call in to the normal handling code */
-	tcp_ctlinput_with_port(cmd, (struct sockaddr_in *)sa, vip, port);
+	tcp_ctlinput_with_port(icmp, port);
 }
 #endif /* INET */
 
@@ -3007,11 +3010,10 @@ tcp6_next_pmtu(const struct icmp6_hdr *icmp6)
 }
 
 static void
-tcp6_ctlinput_with_port(int cmd, struct sockaddr_in6 *sin6,
-    struct ip6ctlparam *ip6cp, uint16_t port)
+tcp6_ctlinput_with_port(struct ip6ctlparam *ip6cp, uint16_t port)
 {
 	struct in6_addr *dst;
-	struct inpcb *(*notify)(struct inpcb *, int) = tcp_notify;
+	struct inpcb *(*notify)(struct inpcb *, int);
 	struct ip6_hdr *ip6;
 	struct mbuf *m;
 	struct inpcb *inp;
@@ -3025,29 +3027,51 @@ tcp6_ctlinput_with_port(int cmd, struct sockaddr_in6 *sin6,
 	tcp_seq icmp_tcp_seq;
 	unsigned int mtu;
 	unsigned int off;
+	int errno;
 
 	icmp6 = ip6cp->ip6c_icmp6;
 	m = ip6cp->ip6c_m;
 	ip6 = ip6cp->ip6c_ip6;
 	off = ip6cp->ip6c_off;
-	dst = ip6cp->ip6c_finaldst;
+	dst = &ip6cp->ip6c_finaldst->sin6_addr;
 
-	switch (cmd) {
-	case PRC_MSGSIZE:
+	errno = icmp6_errmap(icmp6);
+	switch (errno) {
+	case 0:
+		return;
+	case EMSGSIZE:
 		notify = tcp_mtudisc_notify;
 		break;
-	case PRC_UNREACH_ADMIN_PROHIB:
-	case PRC_UNREACH_PORT:
-	case PRC_UNREACH_PROTOCOL:
-	case PRC_TIMXCEED_INTRANS:
+	case ECONNREFUSED:
 		if (V_icmp_may_rst)
 			notify = tcp_drop_syn_sent;
+		else
+			notify = tcp_notify;
+		break;
+	case EHOSTUNREACH:
+		/*
+		 * There are only four ICMPs that may reset connection:
+		 * - administratively prohibited
+		 * - port unreachable
+		 * - time exceeded in transit
+		 * - unknown next header
+		 */
+		if (V_icmp_may_rst &&
+		    ((icmp6->icmp6_type == ICMP6_DST_UNREACH &&
+		     (icmp6->icmp6_code == ICMP6_DST_UNREACH_ADMIN ||
+		      icmp6->icmp6_code == ICMP6_DST_UNREACH_NOPORT)) ||
+		    (icmp6->icmp6_type == ICMP6_TIME_EXCEEDED &&
+		      icmp6->icmp6_code == ICMP6_TIME_EXCEED_TRANSIT) ||
+		    (icmp6->icmp6_type == ICMP6_PARAM_PROB &&
+		      icmp6->icmp6_code == ICMP6_PARAMPROB_NEXTHEADER)))
+			notify = tcp_drop_syn_sent;
+		else
+			notify = tcp_notify;
 		break;
+	default:
+		notify = tcp_notify;
 	}
 
-	if (inet6ctlerrmap[cmd] == 0)
-		return;
-
 	/* Check if we can safely get the ports from the tcp hdr */
 	if (m == NULL ||
 	    (m->m_pkthdr.len <
@@ -3069,7 +3093,7 @@ tcp6_ctlinput_with_port(int cmd, struct sockaddr_in6 *sin6,
 		    !(inp->inp_socket == NULL)) {
 			tp = intotcpcb(inp);
 #ifdef TCP_OFFLOAD
-			if (tp->t_flags & TF_TOE && cmd == PRC_MSGSIZE) {
+			if (tp->t_flags & TF_TOE && errno == EMSGSIZE) {
 				/* MTU discovery for offloaded connections. */
 				mtu = tcp6_next_pmtu(icmp6);
 				tcp_offload_pmtu_update(tp, icmp_tcp_seq, mtu);
@@ -3081,7 +3105,7 @@ tcp6_ctlinput_with_port(int cmd, struct sockaddr_in6 *sin6,
 			}
 			if (SEQ_GEQ(ntohl(icmp_tcp_seq), tp->snd_una) &&
 			    SEQ_LT(ntohl(icmp_tcp_seq), tp->snd_max)) {
-				if (cmd == PRC_MSGSIZE) {
+				if (errno == EMSGSIZE) {
 					/*
 					 * MTU discovery:
 					 * If we got a needfrag set the MTU
@@ -3109,8 +3133,7 @@ tcp6_ctlinput_with_port(int cmd, struct sockaddr_in6 *sin6,
 						ICMP6STAT_INC(icp6s_pmtuchg);
 					}
 				} else
-					inp = (*notify)(inp,
-					    inet6ctlerrmap[cmd]);
+					inp = (*notify)(inp, errno);
 			}
 		}
 	} else {
@@ -3129,20 +3152,19 @@ out:
 }
 
 static void
-tcp6_ctlinput(int cmd, struct sockaddr_in6 *sin6, struct ip6ctlparam *ctl)
+tcp6_ctlinput(struct ip6ctlparam *ctl)
 {
-	tcp6_ctlinput_with_port(cmd, sin6, ctl, htons(0));
+	tcp6_ctlinput_with_port(ctl, htons(0));
 }
 
 static void
-tcp6_ctlinput_viaudp(int cmd, struct sockaddr *sa, void *d, void *unused)
+tcp6_ctlinput_viaudp(udp_tun_icmp_param_t param)
 {
-	struct ip6ctlparam *ip6cp;
+	struct ip6ctlparam *ip6cp = param.ip6cp;
 	struct mbuf *m;
 	struct udphdr *udp;
 	uint16_t port;
 
-	ip6cp = (struct ip6ctlparam *)d;
 	m = m_pulldown(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(struct udphdr), NULL);
 	if (m == NULL) {
 		return;
@@ -3157,7 +3179,7 @@ tcp6_ctlinput_viaudp(int cmd, struct sockaddr *sa, void *d, void *unused)
 		ip6cp->ip6c_m->m_pkthdr.len -= sizeof(struct udphdr);
 	}
 	/* Now call in to the normal handling code */
-	tcp6_ctlinput_with_port(cmd, (struct sockaddr_in6 *)sa, ip6cp, port);
+	tcp6_ctlinput_with_port(ip6cp, port);
 }
 
 #endif /* INET6 */
diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c
index 70474fa18f92..8e8547a42922 100644
--- a/sys/netinet/udp_usrreq.c
+++ b/sys/netinet/udp_usrreq.c
@@ -740,54 +740,52 @@ udp_notify(struct inpcb *inp, int errno)
 
 #ifdef INET
 static void
-udp_common_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *ip,
-    struct inpcbinfo *pcbinfo)
+udp_common_ctlinput(struct icmp *icmp, struct inpcbinfo *pcbinfo)
 {
+	struct ip *ip = &icmp->icmp_ip;
 	struct udphdr *uh;
 	struct inpcb *inp;
 
-	if (inetctlerrmap[cmd] == 0)
+	if (icmp_errmap(icmp) == 0)
 		return;
 
 	uh = (struct udphdr *)((caddr_t)ip + (ip->ip_hl << 2));
-	inp = in_pcblookup(pcbinfo, sin->sin_addr, uh->uh_dport, ip->ip_src,
+	inp = in_pcblookup(pcbinfo, ip->ip_dst, uh->uh_dport, ip->ip_src,
 	    uh->uh_sport, INPLOOKUP_WLOCKPCB, NULL);
 	if (inp != NULL) {
 		INP_WLOCK_ASSERT(inp);
 		if (inp->inp_socket != NULL)
-			udp_notify(inp, inetctlerrmap[cmd]);
+			udp_notify(inp, icmp_errmap(icmp));
 		INP_WUNLOCK(inp);
 	} else {
-		inp = in_pcblookup(pcbinfo, sin->sin_addr, uh->uh_dport,
+		inp = in_pcblookup(pcbinfo, ip->ip_dst, uh->uh_dport,
 		    ip->ip_src, uh->uh_sport,
 		    INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, NULL);
 		if (inp != NULL) {
 			struct udpcb *up;
-			void *ctx;
 			udp_tun_icmp_t *func;
 
 			up = intoudpcb(inp);
-			ctx = up->u_tun_ctx;
 			func = up->u_icmp_func;
 			INP_RUNLOCK(inp);
 			if (func != NULL)
-				(*func)(cmd, (struct sockaddr *)sin, ip, ctx);
+				func(icmp);
 		}
 	}
 }
 
 static void
-udp_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *ip)
+udp_ctlinput(struct icmp *icmp)
 {
 
-	return (udp_common_ctlinput(cmd, sin, ip, &V_udbinfo));
+	return (udp_common_ctlinput(icmp, &V_udbinfo));
 }
 
 static void
-udplite_ctlinput(int cmd, struct sockaddr_in *sin, struct ip *ip)
+udplite_ctlinput(struct icmp *icmp)
 {
 
-	return (udp_common_ctlinput(cmd, sin, ip, &V_ulitecbinfo));
+	return (udp_common_ctlinput(icmp, &V_ulitecbinfo));
 }
 #endif /* INET */
 
diff --git a/sys/netinet/udp_var.h b/sys/netinet/udp_var.h
index 2f3db518b26a..84ed16aa32f7 100644
--- a/sys/netinet/udp_var.h
+++ b/sys/netinet/udp_var.h
@@ -64,7 +64,11 @@ struct mbuf;
 #ifdef _KERNEL
 typedef bool	udp_tun_func_t(struct mbuf *, int, struct inpcb *,
 		    const struct sockaddr *, void *);
-typedef void	udp_tun_icmp_t(int, struct sockaddr *, void *, void *);
+typedef union {
+	struct icmp *icmp;
+	struct ip6ctlparam *ip6cp;
+} udp_tun_icmp_param_t __attribute__((__transparent_union__));
+typedef void	udp_tun_icmp_t(udp_tun_icmp_param_t);
 
 /*
  * UDP control block; one per udp.
diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c
index 5f49b2215cde..4497041f0330 100644
--- a/sys/netinet6/icmp6.c
+++ b/sys/netinet6/icmp6.c
@@ -147,7 +147,7 @@ static int ni6_addrs(struct icmp6_nodeinfo *, struct mbuf *,
 			  struct ifnet **, struct in6_addr *);
 static int ni6_store_addrs(struct icmp6_nodeinfo *, struct icmp6_nodeinfo *,
 				struct ifnet *, int);
-static int icmp6_notify_error(struct mbuf **, int, int, int);
+static int icmp6_notify_error(struct mbuf **, int, int);
 
 /*
  * Kernel module interface for updating icmp6stat.  The argument is an index
@@ -390,6 +390,50 @@ icmp6_error(struct mbuf *m, int type, int code, int param)
 	m_freem(m);
 }
 
+int
+icmp6_errmap(const struct icmp6_hdr *icmp6)
+{
+
+	switch (icmp6->icmp6_type) {
+	case ICMP6_DST_UNREACH:
+		switch (icmp6->icmp6_code) {
+		case ICMP6_DST_UNREACH_NOROUTE:
+		case ICMP6_DST_UNREACH_ADDR:
+			return (EHOSTUNREACH);
+		case ICMP6_DST_UNREACH_NOPORT:
+		case ICMP6_DST_UNREACH_ADMIN:
+			return (ECONNREFUSED);
+		case ICMP6_DST_UNREACH_BEYONDSCOPE:
+			return (ENOPROTOOPT);
+		default:
+			return (0);	/* Shouldn't happen. */
+		}
+	case ICMP6_PACKET_TOO_BIG:
+		return (EMSGSIZE);
+	case ICMP6_TIME_EXCEEDED:
+		switch (icmp6->icmp6_code) {
+		case ICMP6_TIME_EXCEED_TRANSIT:
+			return (EHOSTUNREACH);
+		case ICMP6_TIME_EXCEED_REASSEMBLY:
+			return (0);
+		default:
+			return (0);	/* Shouldn't happen. */
+		}
+	case ICMP6_PARAM_PROB:
+		switch (icmp6->icmp6_code) {
+		case ICMP6_PARAMPROB_NEXTHEADER:
+			return (ECONNREFUSED);
+		case ICMP6_PARAMPROB_HEADER:
+		case ICMP6_PARAMPROB_OPTION:
+			return (ENOPROTOOPT);
+		default:
+			return (0);	/* Shouldn't happen. */
+		}
+	default:
+		return (0);
+	}
+}
+
 /*
  * Process a received ICMP6 message.
  */
@@ -467,72 +511,43 @@ icmp6_input(struct mbuf **mp, int *offp, int proto)
 	case ICMP6_DST_UNREACH:
 		icmp6_ifstat_inc(ifp, ifs6_in_dstunreach);
 		switch (code) {
-		case ICMP6_DST_UNREACH_NOROUTE:
-		case ICMP6_DST_UNREACH_ADDR:	/* PRC_HOSTDEAD is a DOS */
-			code = PRC_UNREACH_NET;
-			break;
 		case ICMP6_DST_UNREACH_ADMIN:
 			icmp6_ifstat_inc(ifp, ifs6_in_adminprohib);
-			code = PRC_UNREACH_ADMIN_PROHIB;
-			break;
+		case ICMP6_DST_UNREACH_NOROUTE:
+		case ICMP6_DST_UNREACH_ADDR:
 		case ICMP6_DST_UNREACH_BEYONDSCOPE:
-			/* I mean "source address was incorrect." */
-			code = PRC_PARAMPROB;
-			break;
 		case ICMP6_DST_UNREACH_NOPORT:
-			code = PRC_UNREACH_PORT;
-			break;
+			goto deliver;
 		default:
 			goto badcode;
 		}
-		goto deliver;
-		break;
-
 	case ICMP6_PACKET_TOO_BIG:
 		icmp6_ifstat_inc(ifp, ifs6_in_pkttoobig);
-
-		/* validation is made in icmp6_mtudisc_update */
-
-		code = PRC_MSGSIZE;
-
 		/*
+		 * Validation is made in icmp6_mtudisc_update.
 		 * Updating the path MTU will be done after examining
 		 * intermediate extension headers.
 		 */
 		goto deliver;
-		break;
-
 	case ICMP6_TIME_EXCEEDED:
 		icmp6_ifstat_inc(ifp, ifs6_in_timeexceed);
 		switch (code) {
 		case ICMP6_TIME_EXCEED_TRANSIT:
-			code = PRC_TIMXCEED_INTRANS;
-			break;
 		case ICMP6_TIME_EXCEED_REASSEMBLY:
-			code = PRC_TIMXCEED_REASS;
-			break;
+			goto deliver;
 		default:
 			goto badcode;
 		}
-		goto deliver;
-		break;
-
 	case ICMP6_PARAM_PROB:
 		icmp6_ifstat_inc(ifp, ifs6_in_paramprob);
 		switch (code) {
 		case ICMP6_PARAMPROB_NEXTHEADER:
-			code = PRC_UNREACH_PROTOCOL;
-			break;
 		case ICMP6_PARAMPROB_HEADER:
 		case ICMP6_PARAMPROB_OPTION:
-			code = PRC_PARAMPROB;
-			break;
+			goto deliver;
 		default:
 			goto badcode;
 		}
-		goto deliver;
-		break;
-
 	case ICMP6_ECHO_REQUEST:
 		icmp6_ifstat_inc(ifp, ifs6_in_echo);
 		if (code != 0)
@@ -856,14 +871,13 @@ icmp6_input(struct mbuf **mp, int *offp, int proto)
 		    ifp ? ifp->if_index : 0));
 		if (icmp6->icmp6_type < ICMP6_ECHO_REQUEST) {
 			/* ICMPv6 error: MUST deliver it by spec... */
-			code = PRC_NCMDS;
-			/* deliver */
+			goto deliver;
 		} else {
 			/* ICMPv6 informational: MUST not deliver */
 			break;
 		}
 	deliver:
-		if (icmp6_notify_error(&m, off, icmp6len, code) != 0) {
+		if (icmp6_notify_error(&m, off, icmp6len) != 0) {
 			/* In this case, m should've been freed. */
 			*mp = NULL;
 			return (IPPROTO_DONE);
@@ -892,7 +906,7 @@ icmp6_input(struct mbuf **mp, int *offp, int proto)
 }
 
 static int
-icmp6_notify_error(struct mbuf **mp, int off, int icmp6len, int code)
+icmp6_notify_error(struct mbuf **mp, int off, int icmp6len)
 {
 	struct mbuf *m;
 	struct icmp6_hdr *icmp6;
@@ -1075,7 +1089,7 @@ icmp6_notify_error(struct mbuf **mp, int off, int icmp6len, int code)
 		ip6cp.ip6c_icmp6 = icmp6;
 		ip6cp.ip6c_ip6 = (struct ip6_hdr *)(icmp6 + 1);
 		ip6cp.ip6c_off = eoff;
-		ip6cp.ip6c_finaldst = &icmp6dst.sin6_addr;
+		ip6cp.ip6c_finaldst = &icmp6dst;
 		ip6cp.ip6c_src = &icmp6src;
 		ip6cp.ip6c_nxt = nxt;
 
@@ -1086,7 +1100,7 @@ icmp6_notify_error(struct mbuf **mp, int off, int icmp6len, int code)
 		}
 
 		if (ip6_ctlprotox[nxt] != NULL)
-			ip6_ctlprotox[nxt](code, &icmp6dst, &ip6cp);
+			ip6_ctlprotox[nxt](&ip6cp);
 	}
 	*mp = m;
 	return (0);
@@ -1100,7 +1114,7 @@ icmp6_notify_error(struct mbuf **mp, int off, int icmp6len, int code)
 void
 icmp6_mtudisc_update(struct ip6ctlparam *ip6cp, int validated)
 {
-	struct in6_addr *dst = ip6cp->ip6c_finaldst;
+	struct in6_addr *dst = &ip6cp->ip6c_finaldst->sin6_addr;
 	struct icmp6_hdr *icmp6 = ip6cp->ip6c_icmp6;
 	struct mbuf *m = ip6cp->ip6c_m;	/* will be necessary for scope issue */
 	u_int mtu = ntohl(icmp6->icmp6_mtu);
diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c
index 9a055dcb0563..40aaff592a6c 100644
--- a/sys/netinet6/in6_pcb.c
+++ b/sys/netinet6/in6_pcb.c
@@ -680,10 +680,11 @@ inp_match6(const struct inpcb *inp, void *v __unused)
 
 	return ((inp->inp_vflag & INP_IPV6) != 0);
*** 442 LINES SKIPPED ***