Esempio n. 1
0
/*
 * ipsec_common_input() gets called when we receive an IPsec-protected packet
 * in IPv4 or IPv6. All it does is find the right TDB and call the appropriate
 * transform. The callback takes care of further processing (like ingress
 * filtering).
 */
int
ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto,
    int udpencap)
{
#define IPSEC_ISTAT(x,y,z) (sproto == IPPROTO_ESP ? (x)++ : \
			    sproto == IPPROTO_AH ? (y)++ : (z)++)

	union sockaddr_union dst_address;
	struct timeval tv;
	struct tdb *tdbp;
	struct ifnet *encif;
	u_int32_t spi;
	u_int16_t cpi;
	int s, error;

	IPSEC_ISTAT(espstat.esps_input, ahstat.ahs_input,
	    ipcompstat.ipcomps_input);

	if (m == 0) {
		DPRINTF(("ipsec_common_input(): NULL packet received\n"));
		IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops,
		    ipcompstat.ipcomps_hdrops);
		return EINVAL;
	}

	if ((sproto == IPPROTO_ESP && !esp_enable) ||
	    (sproto == IPPROTO_AH && !ah_enable) ||
#if NPF > 0
	    (m->m_pkthdr.pf.flags & PF_TAG_DIVERTED) ||
#endif
	    (sproto == IPPROTO_IPCOMP && !ipcomp_enable)) {
		switch (af) {
#ifdef INET
		case AF_INET:
			rip_input(m, skip, sproto);
			break;
#endif /* INET */
#ifdef INET6
		case AF_INET6:
			rip6_input(&m, &skip, sproto);
			break;
#endif /* INET6 */
		default:
			DPRINTF(("ipsec_common_input(): unsupported protocol "
			    "family %d\n", af));
			m_freem(m);
			IPSEC_ISTAT(espstat.esps_nopf, ahstat.ahs_nopf,
			    ipcompstat.ipcomps_nopf);
			return EPFNOSUPPORT;
		}
		return 0;
	}
	if ((sproto == IPPROTO_IPCOMP) && (m->m_flags & M_COMP)) {
		m_freem(m);
		ipcompstat.ipcomps_pdrops++;
		DPRINTF(("ipsec_common_input(): repeated decompression\n"));
		return EINVAL;
	}

	if (m->m_pkthdr.len - skip < 2 * sizeof(u_int32_t)) {
		m_freem(m);
		IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops,
		    ipcompstat.ipcomps_hdrops);
		DPRINTF(("ipsec_common_input(): packet too small\n"));
		return EINVAL;
	}

	/* Retrieve the SPI from the relevant IPsec header */
	if (sproto == IPPROTO_ESP)
		m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi);
	else if (sproto == IPPROTO_AH)
		m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t),
		    (caddr_t) &spi);
	else if (sproto == IPPROTO_IPCOMP) {
		m_copydata(m, skip + sizeof(u_int16_t), sizeof(u_int16_t),
		    (caddr_t) &cpi);
		spi = ntohl(htons(cpi));
	}

	/*
	 * Find tunnel control block and (indirectly) call the appropriate
	 * kernel crypto routine. The resulting mbuf chain is a valid
	 * IP packet ready to go through input processing.
	 */

	memset(&dst_address, 0, sizeof(dst_address));
	dst_address.sa.sa_family = af;

	switch (af) {
#ifdef INET
	case AF_INET:
		dst_address.sin.sin_len = sizeof(struct sockaddr_in);
		m_copydata(m, offsetof(struct ip, ip_dst),
		    sizeof(struct in_addr),
		    (caddr_t) &(dst_address.sin.sin_addr));
		break;
#endif /* INET */

#ifdef INET6
	case AF_INET6:
		dst_address.sin6.sin6_len = sizeof(struct sockaddr_in6);
		m_copydata(m, offsetof(struct ip6_hdr, ip6_dst),
		    sizeof(struct in6_addr),
		    (caddr_t) &(dst_address.sin6.sin6_addr));
		in6_recoverscope(&dst_address.sin6, &dst_address.sin6.sin6_addr,
		    NULL);
		break;
#endif /* INET6 */

	default:
		DPRINTF(("ipsec_common_input(): unsupported protocol "
		    "family %d\n", af));
		m_freem(m);
		IPSEC_ISTAT(espstat.esps_nopf, ahstat.ahs_nopf,
		    ipcompstat.ipcomps_nopf);
		return EPFNOSUPPORT;
	}

	s = splsoftnet();
	tdbp = gettdb(rtable_l2(m->m_pkthdr.ph_rtableid),
	    spi, &dst_address, sproto);
	if (tdbp == NULL) {
		splx(s);
		DPRINTF(("ipsec_common_input(): could not find SA for "
		    "packet to %s, spi %08x\n",
		    ipsp_address(dst_address), ntohl(spi)));
		m_freem(m);
		IPSEC_ISTAT(espstat.esps_notdb, ahstat.ahs_notdb,
		    ipcompstat.ipcomps_notdb);
		return ENOENT;
	}

	if (tdbp->tdb_flags & TDBF_INVALID) {
		splx(s);
		DPRINTF(("ipsec_common_input(): attempted to use invalid SA %s/%08x/%u\n", ipsp_address(dst_address), ntohl(spi), tdbp->tdb_sproto));
		m_freem(m);
		IPSEC_ISTAT(espstat.esps_invalid, ahstat.ahs_invalid,
		    ipcompstat.ipcomps_invalid);
		return EINVAL;
	}

	if (udpencap && !(tdbp->tdb_flags & TDBF_UDPENCAP)) {
		splx(s);
		DPRINTF(("ipsec_common_input(): attempted to use non-udpencap SA %s/%08x/%u\n", ipsp_address(dst_address), ntohl(spi), tdbp->tdb_sproto));
		m_freem(m);
		espstat.esps_udpinval++;
		return EINVAL;
	}

	if (tdbp->tdb_xform == NULL) {
		splx(s);
		DPRINTF(("ipsec_common_input(): attempted to use uninitialized SA %s/%08x/%u\n", ipsp_address(dst_address), ntohl(spi), tdbp->tdb_sproto));
		m_freem(m);
		IPSEC_ISTAT(espstat.esps_noxform, ahstat.ahs_noxform,
		    ipcompstat.ipcomps_noxform);
		return ENXIO;
	}

	if (sproto != IPPROTO_IPCOMP) {
		if ((encif = enc_getif(tdbp->tdb_rdomain,
		    tdbp->tdb_tap)) == NULL) {
			splx(s);
			DPRINTF(("ipsec_common_input(): "
			    "no enc%u interface for SA %s/%08x/%u\n",
			    tdbp->tdb_tap, ipsp_address(dst_address),
			    ntohl(spi), tdbp->tdb_sproto));
			m_freem(m);

			IPSEC_ISTAT(espstat.esps_pdrops,
			    ahstat.ahs_pdrops,
			    ipcompstat.ipcomps_pdrops);
			return EACCES;
		}

		/* XXX This conflicts with the scoped nature of IPv6 */
		m->m_pkthdr.rcvif = encif;
	}

	/* Register first use, setup expiration timer. */
	if (tdbp->tdb_first_use == 0) {
		tdbp->tdb_first_use = time_second;

		tv.tv_usec = 0;

		tv.tv_sec = tdbp->tdb_exp_first_use + tdbp->tdb_first_use;
		if (tdbp->tdb_flags & TDBF_FIRSTUSE)
			timeout_add(&tdbp->tdb_first_tmo, hzto(&tv));

		tv.tv_sec = tdbp->tdb_first_use + tdbp->tdb_soft_first_use;
		if (tdbp->tdb_flags & TDBF_SOFT_FIRSTUSE)
			timeout_add(&tdbp->tdb_sfirst_tmo, hzto(&tv));
	}

	/*
	 * Call appropriate transform and return -- callback takes care of
	 * everything else.
	 */
	error = (*(tdbp->tdb_xform->xf_input))(m, tdbp, skip, protoff);
	splx(s);
	return error;
}
Esempio n. 2
0
void
ip6_forward(struct mbuf *m, int srcrt)
{
	struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
	struct sockaddr_in6 *dst;
	struct rtentry *rt;
	int error = 0, type = 0, code = 0;
	struct mbuf *mcopy = NULL;
	struct ifnet *origifp;	/* maybe unnecessary */
#ifdef IPSEC
	u_int8_t sproto = 0;
	struct m_tag *mtag;
	union sockaddr_union sdst;
	struct tdb_ident *tdbi;
	u_int32_t sspi;
	struct tdb *tdb;
	int s;
#if NPF > 0
	struct ifnet *encif;
#endif
#endif /* IPSEC */
	u_int rtableid = 0;

	/*
	 * Do not forward packets to multicast destination (should be handled
	 * by ip6_mforward().
	 * Do not forward packets with unspecified source.  It was discussed
	 * in July 2000, on ipngwg mailing list.
	 */
	if ((m->m_flags & (M_BCAST|M_MCAST)) != 0 ||
	    IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
	    IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) {
		ip6stat.ip6s_cantforward++;
		/* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */
		if (ip6_log_time + ip6_log_interval < time_second) {
			ip6_log_time = time_second;
			log(LOG_DEBUG,
			    "cannot forward "
			    "from %s to %s nxt %d received on %s\n",
			    ip6_sprintf(&ip6->ip6_src),
			    ip6_sprintf(&ip6->ip6_dst),
			    ip6->ip6_nxt,
			    m->m_pkthdr.rcvif->if_xname);
		}
		m_freem(m);
		return;
	}

	if (ip6->ip6_hlim <= IPV6_HLIMDEC) {
		/* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */
		icmp6_error(m, ICMP6_TIME_EXCEEDED,
				ICMP6_TIME_EXCEED_TRANSIT, 0);
		return;
	}
	ip6->ip6_hlim -= IPV6_HLIMDEC;

#if NPF > 0
reroute:
#endif

#ifdef IPSEC
	if (!ipsec_in_use)
		goto done_spd;

	s = splnet();

	/*
	 * Check if there was an outgoing SA bound to the flow
	 * from a transport protocol.
	 */

	/* Do we have any pending SAs to apply ? */
	mtag = m_tag_find(m, PACKET_TAG_IPSEC_PENDING_TDB, NULL);
	if (mtag != NULL) {
#ifdef DIAGNOSTIC
		if (mtag->m_tag_len != sizeof (struct tdb_ident))
			panic("ip6_forward: tag of length %d (should be %d",
			    mtag->m_tag_len, sizeof (struct tdb_ident));
#endif
		tdbi = (struct tdb_ident *)(mtag + 1);
		tdb = gettdb(tdbi->rdomain, tdbi->spi, &tdbi->dst,
		    tdbi->proto);
		if (tdb == NULL)
			error = -EINVAL;
		m_tag_delete(m, mtag);
	} else
		tdb = ipsp_spd_lookup(m, AF_INET6, sizeof(struct ip6_hdr),
		    &error, IPSP_DIRECTION_OUT, NULL, NULL);

	if (tdb == NULL) {
	        splx(s);

		if (error == 0) {
		        /*
			 * No IPsec processing required, we'll just send the
			 * packet out.
			 */
		        sproto = 0;

			/* Fall through to routing/multicast handling */
		} else {
		        /*
			 * -EINVAL is used to indicate that the packet should
			 * be silently dropped, typically because we've asked
			 * key management for an SA.
			 */
		        if (error == -EINVAL) /* Should silently drop packet */
				error = 0;

			goto freecopy;
		}
	} else {
		/* Loop detection */
		for (mtag = m_tag_first(m); mtag != NULL;
		    mtag = m_tag_next(m, mtag)) {
			if (mtag->m_tag_id != PACKET_TAG_IPSEC_OUT_DONE &&
			    mtag->m_tag_id !=
			    PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED)
				continue;
			tdbi = (struct tdb_ident *)(mtag + 1);
			if (tdbi->spi == tdb->tdb_spi &&
			    tdbi->proto == tdb->tdb_sproto &&
			    tdbi->rdomain == tdb->tdb_rdomain &&
			    !bcmp(&tdbi->dst, &tdb->tdb_dst,
			    sizeof(union sockaddr_union))) {
				splx(s);
				sproto = 0; /* mark as no-IPsec-needed */
				goto done_spd;
			}
		}

	        /* We need to do IPsec */
	        bcopy(&tdb->tdb_dst, &sdst, sizeof(sdst));
		sspi = tdb->tdb_spi;
		sproto = tdb->tdb_sproto;
	        splx(s);
	}

	/* Fall through to the routing/multicast handling code */
 done_spd:
#endif /* IPSEC */

#if NPF > 0
	rtableid = m->m_pkthdr.rdomain;
#endif

	/*
	 * Save at most ICMPV6_PLD_MAXLEN (= the min IPv6 MTU -
	 * size of IPv6 + ICMPv6 headers) bytes of the packet in case
	 * we need to generate an ICMP6 message to the src.
	 * Thanks to M_EXT, in most cases copy will not occur.
	 *
	 * It is important to save it before IPsec processing as IPsec
	 * processing may modify the mbuf.
	 */
	mcopy = m_copy(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN));

	dst = &ip6_forward_rt.ro_dst;
	if (!srcrt) {
		/*
		 * ip6_forward_rt.ro_dst.sin6_addr is equal to ip6->ip6_dst
		 */
		if (ip6_forward_rt.ro_rt == 0 ||
		    (ip6_forward_rt.ro_rt->rt_flags & RTF_UP) == 0 ||
		    ip6_forward_rt.ro_tableid != rtableid) {
			if (ip6_forward_rt.ro_rt) {
				RTFREE(ip6_forward_rt.ro_rt);
				ip6_forward_rt.ro_rt = 0;
			}
			/* this probably fails but give it a try again */
			ip6_forward_rt.ro_tableid = rtableid;
			rtalloc_mpath((struct route *)&ip6_forward_rt,
			    &ip6->ip6_src.s6_addr32[0]);
		}

		if (ip6_forward_rt.ro_rt == 0) {
			ip6stat.ip6s_noroute++;
			/* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_noroute) */
			if (mcopy) {
				icmp6_error(mcopy, ICMP6_DST_UNREACH,
					    ICMP6_DST_UNREACH_NOROUTE, 0);
			}
			m_freem(m);
			return;
		}
	} else if (ip6_forward_rt.ro_rt == 0 ||
	   (ip6_forward_rt.ro_rt->rt_flags & RTF_UP) == 0 ||
	   !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &dst->sin6_addr) ||
	   ip6_forward_rt.ro_tableid != rtableid) {
		if (ip6_forward_rt.ro_rt) {
			RTFREE(ip6_forward_rt.ro_rt);
			ip6_forward_rt.ro_rt = 0;
		}
		bzero(dst, sizeof(*dst));
		dst->sin6_len = sizeof(struct sockaddr_in6);
		dst->sin6_family = AF_INET6;
		dst->sin6_addr = ip6->ip6_dst;
		ip6_forward_rt.ro_tableid = rtableid;

		rtalloc_mpath((struct route *)&ip6_forward_rt,
		    &ip6->ip6_src.s6_addr32[0]);

		if (ip6_forward_rt.ro_rt == 0) {
			ip6stat.ip6s_noroute++;
			/* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_noroute) */
			if (mcopy) {
				icmp6_error(mcopy, ICMP6_DST_UNREACH,
					    ICMP6_DST_UNREACH_NOROUTE, 0);
			}
			m_freem(m);
			return;
		}
	}
	rt = ip6_forward_rt.ro_rt;

	/*
	 * Scope check: if a packet can't be delivered to its destination
	 * for the reason that the destination is beyond the scope of the
	 * source address, discard the packet and return an icmp6 destination
	 * unreachable error with Code 2 (beyond scope of source address).
	 * [draft-ietf-ipngwg-icmp-v3-00.txt, Section 3.1]
	 */
	if (in6_addr2scopeid(m->m_pkthdr.rcvif, &ip6->ip6_src) !=
	    in6_addr2scopeid(rt->rt_ifp, &ip6->ip6_src)) {
		ip6stat.ip6s_cantforward++;
		ip6stat.ip6s_badscope++;
		in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard);

		if (ip6_log_time + ip6_log_interval < time_second) {
			ip6_log_time = time_second;
			log(LOG_DEBUG,
			    "cannot forward "
			    "src %s, dst %s, nxt %d, rcvif %s, outif %s\n",
			    ip6_sprintf(&ip6->ip6_src),
			    ip6_sprintf(&ip6->ip6_dst),
			    ip6->ip6_nxt,
			    m->m_pkthdr.rcvif->if_xname, rt->rt_ifp->if_xname);
		}
		if (mcopy)
			icmp6_error(mcopy, ICMP6_DST_UNREACH,
				    ICMP6_DST_UNREACH_BEYONDSCOPE, 0);
		m_freem(m);
		goto freert;
	}

#ifdef IPSEC
	/*
	 * Check if the packet needs encapsulation.
	 * ipsp_process_packet will never come back to here.
	 * XXX ipsp_process_packet() calls ip6_output(), and there'll be no
	 * PMTU notification.  is it okay?
	 */
	if (sproto != 0) {
		s = splnet();

		tdb = gettdb(rtable_l2(m->m_pkthdr.rdomain),
		    sspi, &sdst, sproto);
		if (tdb == NULL) {
			splx(s);
			error = EHOSTUNREACH;
			m_freem(m);
			goto senderr;	/*XXX*/
		}

#if NPF > 0
		if ((encif = enc_getif(tdb->tdb_rdomain,
		    tdb->tdb_tap)) == NULL ||
		    pf_test6(PF_FWD, encif, &m, NULL) != PF_PASS) {
			splx(s);
			error = EHOSTUNREACH;
			m_freem(m);
			goto senderr;
		}
		if (m == NULL) {
			splx(s);
			goto senderr;
		}
		ip6 = mtod(m, struct ip6_hdr *);
		/*
		 * PF_TAG_REROUTE handling or not...
		 * Packet is entering IPsec so the routing is
		 * already overruled by the IPsec policy.
		 * Until now the change was not reconsidered.
		 * What's the behaviour?
		 */
#endif

		m->m_flags &= ~(M_BCAST | M_MCAST);	/* just in case */

		/* Callee frees mbuf */
		error = ipsp_process_packet(m, tdb, AF_INET6, 0);
		splx(s);
		m_freem(mcopy);
		goto freert;
	}
Esempio n. 3
0
/*
 * ESP output callback, called directly by the crypto driver.
 */
int
esp_output_cb(void *op)
{
	struct cryptop *crp = (struct cryptop *) op;
	struct tdb_crypto *tc;
	struct tdb *tdb;
	struct mbuf *m;
	int error, s;

	tc = (struct tdb_crypto *) crp->crp_opaque;

	m = (struct mbuf *) crp->crp_buf;
	if (m == NULL) {
		/* Shouldn't happen... */
		free(tc, M_XDATA);
		crypto_freereq(crp);
		espstat.esps_crypto++;
		DPRINTF(("esp_output_cb(): bogus returned buffer from "
		    "crypto\n"));
		return (EINVAL);
	}


	s = spltdb();

	tdb = gettdb(tc->tc_spi, &tc->tc_dst, tc->tc_proto);
	if (tdb == NULL) {
		free(tc, M_XDATA);
		espstat.esps_notdb++;
		DPRINTF(("esp_output_cb(): TDB is expired while in crypto\n"));
		error = EPERM;
		goto baddone;
	}

	/* Check for crypto errors. */
	if (crp->crp_etype) {
		if (crp->crp_etype == EAGAIN) {
			/* Reset the session ID */
			if (tdb->tdb_cryptoid != 0)
				tdb->tdb_cryptoid = crp->crp_sid;
			splx(s);
			return crypto_dispatch(crp);
		}
		free(tc, M_XDATA);
		espstat.esps_noxform++;
		DPRINTF(("esp_output_cb(): crypto error %d\n",
		    crp->crp_etype));
		error = crp->crp_etype;
		goto baddone;
	}
	free(tc, M_XDATA);

	/* Release crypto descriptors. */
	crypto_freereq(crp);

	/*
	 * If we're doing half-iv, keep a copy of the last few bytes of the
	 * encrypted part, for use as the next IV. Note that HALF-IV is only
	 * supposed to be used without authentication (the old ESP specs).
	 */
	if (tdb->tdb_flags & TDBF_HALFIV)
		m_copydata(m, m->m_pkthdr.len - tdb->tdb_ivlen, tdb->tdb_ivlen,
		    tdb->tdb_iv);

	/* Call the IPsec input callback. */
	error = ipsp_process_done(m, tdb);
	splx(s);
	return error;

 baddone:
	splx(s);

	if (m != NULL)
		m_freem(m);

	crypto_freereq(crp);

	return error;
}
Esempio n. 4
0
/*
 * ESP input callback, called directly by the crypto driver.
 */
int
esp_input_cb(void *op)
{
	u_int8_t lastthree[3], aalg[AH_HMAC_HASHLEN];
	int s, hlen, roff, skip, protoff, error;
	struct mbuf *m1, *mo, *m;
	struct auth_hash *esph;
	struct tdb_crypto *tc;
	struct cryptop *crp;
	struct m_tag *mtag;
	struct tdb *tdb;
	u_int32_t btsx;
	caddr_t ptr;

	crp = (struct cryptop *) op;

	tc = (struct tdb_crypto *) crp->crp_opaque;
	skip = tc->tc_skip;
	protoff = tc->tc_protoff;
	mtag = (struct m_tag *) tc->tc_ptr;

	m = (struct mbuf *) crp->crp_buf;
	if (m == NULL) {
		/* Shouldn't happen... */
		free(tc, M_XDATA);
		crypto_freereq(crp);
		espstat.esps_crypto++;
		DPRINTF(("esp_input_cb(): bogus returned buffer from crypto\n"));
		return (EINVAL);
	}

	s = spltdb();

	tdb = gettdb(tc->tc_spi, &tc->tc_dst, tc->tc_proto);
	if (tdb == NULL) {
		free(tc, M_XDATA);
		espstat.esps_notdb++;
		DPRINTF(("esp_input_cb(): TDB is expired while in crypto"));
		error = EPERM;
		goto baddone;
	}

	esph = (struct auth_hash *) tdb->tdb_authalgxform;

	/* Check for crypto errors */
	if (crp->crp_etype) {
		if (crp->crp_etype == EAGAIN) {
			/* Reset the session ID */
			if (tdb->tdb_cryptoid != 0)
				tdb->tdb_cryptoid = crp->crp_sid;
			splx(s);
			return crypto_dispatch(crp);
		}
		free(tc, M_XDATA);
		espstat.esps_noxform++;
		DPRINTF(("esp_input_cb(): crypto error %d\n", crp->crp_etype));
		error = crp->crp_etype;
		goto baddone;
	}

	/* If authentication was performed, check now. */
	if (esph != NULL) {
		/*
		 * If we have a tag, it means an IPsec-aware NIC did the verification
		 * for us.
		 */
		if (mtag == NULL) {
			/* Copy the authenticator from the packet */
			m_copydata(m, m->m_pkthdr.len - esph->authsize,
			    esph->authsize, aalg);

			ptr = (caddr_t) (tc + 1);

			/* Verify authenticator */
			if (bcmp(ptr, aalg, esph->authsize)) {
				free(tc, M_XDATA);
				DPRINTF(("esp_input_cb(): authentication failed for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
				espstat.esps_badauth++;
				error = EACCES;
				goto baddone;
			}
		}

		/* Remove trailing authenticator */
		m_adj(m, -(esph->authsize));
	}
	free(tc, M_XDATA);

	/* Replay window checking, if appropriate */
	if ((tdb->tdb_wnd > 0) && (!(tdb->tdb_flags & TDBF_NOREPLAY))) {
		m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t),
		    (unsigned char *) &btsx);
		btsx = ntohl(btsx);

		switch (checkreplaywindow32(btsx, 0, &(tdb->tdb_rpl),
		    tdb->tdb_wnd, &(tdb->tdb_bitmap), 1)) {
		case 0: /* All's well */
#if NPFSYNC > 0
			pfsync_update_tdb(tdb,0);
#endif
			break;

		case 1:
			DPRINTF(("esp_input_cb(): replay counter wrapped for SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
			espstat.esps_wrap++;
			error = EACCES;
			goto baddone;

		case 2:
		case 3:
			DPRINTF(("esp_input_cb(): duplicate packet received in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
			error = EACCES;
			goto baddone;

		default:
			DPRINTF(("esp_input_cb(): bogus value from checkreplaywindow32() in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
			espstat.esps_replay++;
			error = EACCES;
			goto baddone;
		}
	}

	/* Release the crypto descriptors */
	crypto_freereq(crp);

	/* Determine the ESP header length */
	if (tdb->tdb_flags & TDBF_NOREPLAY)
		hlen = sizeof(u_int32_t) + tdb->tdb_ivlen; /* "old" ESP */
	else
		hlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; /* "new" ESP */

	/* Find beginning of ESP header */
	m1 = m_getptr(m, skip, &roff);
	if (m1 == NULL)	{
		espstat.esps_hdrops++;
		splx(s);
		DPRINTF(("esp_input_cb(): bad mbuf chain, SA %s/%08x\n",
		    ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
		m_freem(m);
		return EINVAL;
	}

	/* Remove the ESP header and IV from the mbuf. */
	if (roff == 0) {
		/* The ESP header was conveniently at the beginning of the mbuf */
		m_adj(m1, hlen);
		if (!(m1->m_flags & M_PKTHDR))
			m->m_pkthdr.len -= hlen;
	} else if (roff + hlen >= m1->m_len) {
		/*
		 * Part or all of the ESP header is at the end of this mbuf, so
		 * first let's remove the remainder of the ESP header from the
		 * beginning of the remainder of the mbuf chain, if any.
		 */
		if (roff + hlen > m1->m_len) {
			/* Adjust the next mbuf by the remainder */
			m_adj(m1->m_next, roff + hlen - m1->m_len);

			/* The second mbuf is guaranteed not to have a pkthdr... */
			m->m_pkthdr.len -= (roff + hlen - m1->m_len);
		}

		/* Now, let's unlink the mbuf chain for a second...*/
		mo = m1->m_next;
		m1->m_next = NULL;

		/* ...and trim the end of the first part of the chain...sick */
		m_adj(m1, -(m1->m_len - roff));
		if (!(m1->m_flags & M_PKTHDR))
			m->m_pkthdr.len -= (m1->m_len - roff);

		/* Finally, let's relink */
		m1->m_next = mo;
	} else {
		/*
		 * The ESP header lies in the "middle" of the mbuf...do an
		 * overlapping copy of the remainder of the mbuf over the ESP
		 * header.
		 */
		bcopy(mtod(m1, u_char *) + roff + hlen,
		    mtod(m1, u_char *) + roff, m1->m_len - (roff + hlen));
		m1->m_len -= hlen;
		m->m_pkthdr.len -= hlen;
	}

	/* Save the last three bytes of decrypted data */
	m_copydata(m, m->m_pkthdr.len - 3, 3, lastthree);

	/* Verify pad length */
	if (lastthree[1] + 2 > m->m_pkthdr.len - skip) {
		espstat.esps_badilen++;
		splx(s);
		DPRINTF(("esp_input_cb(): invalid padding length %d for packet in SA %s/%08x\n", lastthree[1], ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
		m_freem(m);
		return EINVAL;
	}

	/* Verify correct decryption by checking the last padding bytes */
	if (!(tdb->tdb_flags & TDBF_RANDOMPADDING)) {
		if ((lastthree[1] != lastthree[0]) && (lastthree[1] != 0)) {
			espstat.esps_badenc++;
			splx(s);
			DPRINTF(("esp_input(): decryption failed for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
			m_freem(m);
			return EINVAL;
		}
	}

	/* Trim the mbuf chain to remove the trailing authenticator and padding */
	m_adj(m, -(lastthree[1] + 2));

	/* Restore the Next Protocol field */
	m_copyback(m, protoff, sizeof(u_int8_t), lastthree + 2);

	/* Back to generic IPsec input processing */
	error = ipsec_common_input_cb(m, tdb, skip, protoff, mtag);
	splx(s);
	return (error);

 baddone:
	splx(s);

	if (m != NULL)
		m_freem(m);

	crypto_freereq(crp);

	return (error);
}
Esempio n. 5
0
/*
 * IPSEC <> netlink interface
 * Copyright (C) 1996, 1997  John Ioannidis.
 * Copyright (C) 1998, 1999, 2000, 2001  Richard Guy Briggs.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

char ipsec_netlink_c_version[] = "RCSID $Id: ipsec_netlink.c,v 1.56 2002/01/29 17:17:55 mcr Exp $";

#include <linux/config.h>
#include <linux/version.h>
#include <linux/kernel.h> /* printk() */

#include "ipsec_param.h"

#ifdef MALLOC_SLAB
# include <linux/slab.h> /* kmalloc() */
#else /* MALLOC_SLAB */
# include <linux/malloc.h> /* kmalloc() */
#endif /* MALLOC_SLAB */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/interrupt.h> /* mark_bh */

#include <linux/netdevice.h>   /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h>          /* struct iphdr */
#include <linux/skbuff.h>
#include <freeswan.h>
#ifdef SPINLOCK
# ifdef SPINLOCK_23
#  include <linux/spinlock.h> /* *lock* */
# else /* 23_SPINLOCK */
#  include <asm/spinlock.h> /* *lock* */
# endif /* 23_SPINLOCK */
#endif /* SPINLOCK */
#ifdef NET_21
# include <asm/uaccess.h>
# include <linux/in6.h>
# define ip_chk_addr inet_addr_type
# define IS_MYADDR RTN_LOCAL
#endif
#include <asm/checksum.h>
#include <net/ip.h>
#ifdef NETLINK_SOCK
# include <linux/netlink.h>
#else
# include <net/netlink.h>
#endif

#include "radij.h"
#include "ipsec_encap.h"
#include "ipsec_radij.h"
#include "ipsec_netlink.h"
#include "ipsec_xform.h"

#include "ipsec_rcv.h"
#include "ipsec_ah.h"
#include "ipsec_esp.h"

#ifdef CONFIG_IPSEC_DEBUG
# include "ipsec_tunnel.h"
#endif /* CONFIG_IPSEC_DEBUG */

#include <pfkeyv2.h>
#include <pfkey.h>

#ifdef CONFIG_IPSEC_DEBUG
 int debug_netlink = 0;
#endif /* CONFIG_IPSEC_DEBUG */

#define SENDERR(_x) do { len = -(_x); goto errlab; } while (0)

#if 0
int 
#ifdef NETLINK_SOCK
ipsec_callback(int proto, struct sk_buff *skb)
#else /* NETLINK_SOCK */
ipsec_callback(struct sk_buff *skb)
#endif /* NETLINK_SOCK */
{
	/*
	 * this happens when we write to /dev/ipsec (c 36 10)
	 */
	int len = skb->len;
	u_char *dat = (u_char *)skb->data;
	struct encap_msghdr *em	= (struct encap_msghdr *)dat;
	struct tdb *tdbp, *tprev;
	int i, nspis, error = 0;
#ifdef CONFIG_IPSEC_DEBUG
	struct eroute *eret;
	char sa[SATOA_BUF];
	size_t sa_len;
	struct sk_buff *first, *last;


	sa_len = satoa(em->em_said, 0, sa, SATOA_BUF);

	if(debug_netlink) {
		printk("klips_debug:ipsec_callback: "
		       "skb=0x%p skblen=%ld em_magic=%d em_type=%d\n",
		       skb,
		       (unsigned long int)skb->len,
		       em->em_magic,
		       em->em_type);
		switch(em->em_type) {
		case EMT_SETDEBUG:
			printk("klips_debug:ipsec_callback: "
			       "set ipsec_debug level\n");
			break;
		case EMT_DELEROUTE:
		case EMT_CLREROUTE:
		case EMT_CLRSPIS:
			break;
		default:
			printk("klips_debug:ipsec_callback: "
			       "called for SA:%s\n",
			       sa_len ? sa : " (error)");
		}
	}
#endif /* CONFIG_IPSEC_DEBUG */

	/* XXXX Temporarily disable netlink I/F code until it gets permanantly
	   ripped out in favour of PF_KEYv2 I/F. */
	SENDERR(EPROTONOSUPPORT);

	/*	em = (struct encap_msghdr *)dat; */
	if (em->em_magic != EM_MAGIC) {
		printk("klips_debug:ipsec_callback: "
		       "bad magic=%d failed, should be %d\n",
		       em->em_magic,
		       EM_MAGIC);
		SENDERR(EINVAL);
	}
	switch (em->em_type) {
	case EMT_SETDEBUG:
#ifdef CONFIG_IPSEC_DEBUG
		if(em->em_db_nl >> (sizeof(em->em_db_nl) * 8 - 1)) {
			em->em_db_nl &= ~(1 << (sizeof(em->em_db_nl) * 8 -1));
			debug_tunnel  |= em->em_db_tn;
			debug_netlink |= em->em_db_nl;
			debug_xform   |= em->em_db_xf;
			debug_eroute  |= em->em_db_er;
			debug_spi     |= em->em_db_sp;
			debug_radij   |= em->em_db_rj;
			debug_esp     |= em->em_db_es;
			debug_ah      |= em->em_db_ah;
			debug_rcv     |= em->em_db_rx;
			debug_pfkey   |= em->em_db_ky;
			if(debug_netlink)
				printk("klips_debug:ipsec_callback: set\n");
		} else {
			if(debug_netlink)
				printk("klips_debug:ipsec_callback: unset\n");
			debug_tunnel  &= em->em_db_tn;
			debug_netlink &= em->em_db_nl;
			debug_xform   &= em->em_db_xf;
			debug_eroute  &= em->em_db_er;
			debug_spi     &= em->em_db_sp;
			debug_radij   &= em->em_db_rj;
			debug_esp     &= em->em_db_es;
			debug_ah      &= em->em_db_ah;
			debug_rcv     &= em->em_db_rx;
			debug_pfkey   &= em->em_db_ky;
		}
#else /* CONFIG_IPSEC_DEBUG */
		printk("klips_debug:ipsec_callback: "
		       "debugging not enabled\n");
		SENDERR(EINVAL);
#endif /* CONFIG_IPSEC_DEBUG */
		break;

	case EMT_SETEROUTE:
		if ((error = ipsec_makeroute(&(em->em_eaddr), &(em->em_emask), em->em_ersaid, 0, NULL, NULL, NULL)))
			SENDERR(-error);
		break;

	case EMT_REPLACEROUTE:
		if ((error = ipsec_breakroute(&(em->em_eaddr), &(em->em_emask), &first, &last)) == EINVAL) {
			kfree_skb(first);
			kfree_skb(last);
			SENDERR(-error);
		}
		if ((error = ipsec_makeroute(&(em->em_eaddr), &(em->em_emask), em->em_ersaid, NULL, NULL)))
			SENDERR(-error);
		break;

	case EMT_DELEROUTE:
		if ((error = ipsec_breakroute(&(em->em_eaddr), &(em->em_emask), &first, &last)))
			kfree_skb(first);
			kfree_skb(last);
			SENDERR(-error);
		break;

	case EMT_CLREROUTE:
		if ((error = ipsec_cleareroutes()))
			SENDERR(-error);
		break;

	case EMT_SETSPI:
		if (em->em_if >= 5)	/* XXX -- why 5? */
			SENDERR(ENODEV);
		
		tdbp = gettdb(&(em->em_said));
		if (tdbp == NULL) {
			tdbp = (struct tdb *)kmalloc(sizeof (*tdbp), GFP_ATOMIC);

			if (tdbp == NULL)
				SENDERR(ENOBUFS);
			
			memset((caddr_t)tdbp, 0, sizeof(*tdbp));
			
			tdbp->tdb_said = em->em_said;
			tdbp->tdb_flags = em->em_flags;
			
			if(ip_chk_addr((unsigned long)em->em_said.dst.s_addr) == IS_MYADDR) {
				tdbp->tdb_flags |= EMT_INBOUND;
			}
			KLIPS_PRINT(debug_netlink & DB_NL_TDBCB,
				    "klips_debug:ipsec_callback: "
				    "existing Tunnel Descriptor Block not found (this is good) for SA: %s, %s-bound, allocating.\n",
				    sa_len ? sa : " (error)",
				    (tdbp->tdb_flags & EMT_INBOUND) ? "in" : "out");

/* XXX  		tdbp->tdb_rcvif = &(enc_softc[em->em_if].enc_if);*/
			tdbp->tdb_rcvif = NULL;
		} else {
			KLIPS_PRINT(debug_netlink & DB_NL_TDBCB,
				    "klips_debug:ipsec_callback: "
				    "EMT_SETSPI found an old Tunnel Descriptor Block for SA: %s, delete it first.\n",
				    sa_len ? sa : " (error)");
			SENDERR(EEXIST);
		}
		
		if ((error = tdb_init(tdbp, em))) {
			KLIPS_PRINT(debug_netlink & DB_NL_TDBCB,
				    "klips_debug:ipsec_callback: "
				    "EMT_SETSPI not successful for SA: %s, deleting.\n",
				    sa_len ? sa : " (error)");
			ipsec_tdbwipe(tdbp);
			
			SENDERR(-error);
		}

		tdbp->tdb_lifetime_addtime_c = jiffies/HZ;
		tdbp->tdb_state = 1;
		if(!tdbp->tdb_lifetime_allocations_c) {
			tdbp->tdb_lifetime_allocations_c += 1;
		}

		puttdb(tdbp);
		KLIPS_PRINT(debug_netlink & DB_NL_TDBCB,
			    "klips_debug:ipsec_callback: "
			    "EMT_SETSPI successful for SA: %s\n",
			    sa_len ? sa : " (error)");
		break;
		
	case EMT_DELSPI:
		if (em->em_if >= 5)	/* XXX -- why 5? */
			SENDERR(ENODEV);
		
		spin_lock_bh(&tdb_lock);
		
		tdbp = gettdb(&(em->em_said));
		if (tdbp == NULL) {
			KLIPS_PRINT(debug_netlink & DB_NL_TDBCB,
				    "klips_debug:ipsec_callback: "
				    "EMT_DELSPI Tunnel Descriptor Block not found for SA%s, could not delete.\n",
				    sa_len ? sa : " (error)");
			spin_unlock_bh(&tdb_lock);
			SENDERR(ENXIO);  /* XXX -- wrong error message... */
		} else {
			if((error = deltdbchain(tdbp))) {
				spin_unlock_bh(&tdb_lock);
				SENDERR(-error);
			}
		}
		spin_unlock_bh(&tdb_lock);

		break;
		
	case EMT_GRPSPIS:
		nspis = (len - EMT_GRPSPIS_FLEN) / sizeof(em->em_rel[0]);
		if ((nspis * (sizeof(em->em_rel[0]))) != (len - EMT_GRPSPIS_FLEN)) {
			printk("klips_debug:ipsec_callback: "
			       "EMT_GRPSPI message size incorrect, expected nspis(%d)*%d, got %d.\n",
			       nspis,
			       sizeof(em->em_rel[0]),
			       (len - EMT_GRPSPIS_FLEN));
			SENDERR(EINVAL);
			break;
		}
		
		spin_lock_bh(&tdb_lock);

		for (i = 0; i < nspis; i++) {
			KLIPS_PRINT(debug_netlink,
				    "klips_debug:ipsec_callback: "
				    "EMT_GRPSPI for SA(%d) %s,\n",
				    i,
				    sa_len ? sa : " (error)");
			if ((tdbp = gettdb(&(em->em_rel[i].emr_said))) == NULL) {
				KLIPS_PRINT(debug_netlink,
					    "klips_debug:ipsec_callback: "
					    "EMT_GRPSPI Tunnel Descriptor Block not found for SA%s, could not group.\n",
					    sa_len ? sa : " (error)");
				spin_unlock_bh(&tdb_lock);
				SENDERR(ENXIO);
			} else {
				if(tdbp->tdb_inext || tdbp->tdb_onext) {
					KLIPS_PRINT(debug_netlink,
						    "klips_debug:ipsec_callback: "
						    "EMT_GRPSPI Tunnel Descriptor Block already grouped for SA: %s, can't regroup.\n",
						    sa_len ? sa : " (error)");
					spin_unlock_bh(&tdb_lock);
					SENDERR(EBUSY);
				}
				em->em_rel[i].emr_tdb = tdbp;
			}
		}
		tprev = em->em_rel[0].emr_tdb;
		tprev->tdb_inext = NULL;
		for (i = 1; i < nspis; i++) {
			tdbp = em->em_rel[i].emr_tdb;
			tprev->tdb_onext = tdbp;
			tdbp->tdb_inext = tprev;
			tprev = tdbp;
		}
		tprev->tdb_onext = NULL;

		spin_unlock_bh(&tdb_lock);

		error = 0;
		break;
		
	case EMT_UNGRPSPIS:
		if (len != (8 + (sizeof(struct sa_id) + sizeof(struct tdb *)) /* 12 */) ) {
			printk("klips_debug:ipsec_callback: "
			       "EMT_UNGRPSPIS message size incorrect, expected %d, got %d.\n",
			       8 + (sizeof(struct sa_id) + sizeof(struct tdb *)),
				    len);
			SENDERR(EINVAL);
			break;
		}
		
		spin_lock_bh(&tdb_lock);

		if ((tdbp = gettdb(&(em->em_rel[0].emr_said))) == NULL) {
			KLIPS_PRINT(debug_netlink,
				    "klips_debug:ipsec_callback: "
				    "EMT_UGRPSPI Tunnel Descriptor Block not found for SA%s, could not ungroup.\n",
				    sa_len ? sa : " (error)");
			spin_unlock_bh(&tdb_lock);
			SENDERR(ENXIO);
		}
		while(tdbp->tdb_onext) {
			tdbp = tdbp->tdb_onext;
		}
		while(tdbp->tdb_inext) {
			tprev = tdbp;
			tdbp = tdbp->tdb_inext;
			tprev->tdb_inext = NULL;
			tdbp->tdb_onext = NULL;
		}

		spin_unlock_bh(&tdb_lock);

		break;
		
	case EMT_CLRSPIS:
		KLIPS_PRINT(debug_netlink,
			    "klips_debug:ipsec_callback: "
			    "spi clear called.\n");
		if (em->em_if >= 5)	/* XXX -- why 5? */
			SENDERR(ENODEV);
		ipsec_tdbcleanup(0);
		break;
	default:
		KLIPS_PRINT(debug_netlink,
			    "klips_debug:ipsec_callback: "
			    "unknown message type\n");
		SENDERR(EINVAL);
	}