diff options
author | Angelos D. Keromytis <angelos@cvs.openbsd.org> | 2001-06-01 07:56:47 +0000 |
---|---|---|
committer | Angelos D. Keromytis <angelos@cvs.openbsd.org> | 2001-06-01 07:56:47 +0000 |
commit | 905700f8e336eb14d20da3336698af2a983d072d (patch) | |
tree | 5f2b13629296422278c22aefe8043d5656cfad69 | |
parent | dbb88b7d3a60c5ea16dac96ca0169553d2a36151 (diff) |
ipsp_parse_headers() goes down a list of IPv4/IPv6/AH/ESP headers and
creates a tag for each of the ESP/AH headers. This will be used by
IPsec-aware NIC device drivers that need to notify IPsec that crypto
processing has already been done.
There is an excessive amount of m_copydata() calls used by this
routine, but there's no way around it that I can think of.
-rw-r--r-- | sys/netinet/ip_ipsp.c | 222 | ||||
-rw-r--r-- | sys/netinet/ip_ipsp.h | 3 |
2 files changed, 223 insertions, 2 deletions
diff --git a/sys/netinet/ip_ipsp.c b/sys/netinet/ip_ipsp.c index 0963cd3606d..3e9d0de033e 100644 --- a/sys/netinet/ip_ipsp.c +++ b/sys/netinet/ip_ipsp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ip_ipsp.c,v 1.122 2001/05/30 16:43:11 angelos Exp $ */ +/* $OpenBSD: ip_ipsp.c,v 1.123 2001/06/01 07:56:46 angelos Exp $ */ /* * The authors of this code are John Ioannidis (ji@tla.org), @@ -1313,3 +1313,223 @@ ipsp_skipcrypto_unmark(struct tdb_ident *tdbi) } splx(s); } + +/* + * Go down a chain of IPv4/IPv6/ESP/AH/IPiP chains creating an tag for each + * IPsec header encountered. The offset where the first header, as well + * as its type are given to us. + */ +struct m_tag * +ipsp_parse_headers(struct mbuf *m, int off, u_int8_t proto) +{ + int ipv4sa = 0, s, esphlen = 0, trail = 0, i; + LIST_HEAD(packet_tags, m_tag) tags; + unsigned char lasteight[8]; + struct tdb_ident *tdbi; + struct m_tag *mtag; + struct tdb *tdb; + +#ifdef INET + struct ip iph; +#endif /* INET */ + +#ifdef INET6 + struct in6_addr ip6_dst; +#endif /* INET6 */ + + /* We have to start with a known network protocol */ + if (proto != IPPROTO_IPV4 && proto != IPPROTO_IPV6) + return NULL; + + LIST_INIT(&tags); + + while (1) + { + switch (proto) + { +#ifdef INET + case IPPROTO_IPV4: /* Also IPPROTO_IPIP */ + { + /* Save the IP header (we need both the address and ip_hl) */ + m_copydata(m, off, sizeof(struct ip), (caddr_t) &iph); + ipv4sa = 1; + proto = iph.ip_p; + off += iph.ip_hl << 2; + break; + } +#endif /* INET */ + +#ifdef INET6 + case IPPROTO_IPV6: + { + int nxtp, l; + + /* Copy the IPv6 address */ + m_copydata(m, off + offsetof(struct ip6_hdr, ip6_dst), + sizeof(struct ip6_hdr), (caddr_t) &ip6_dst); + ipv4sa = 0; + + /* + * Go down the chain of headers until we encounter a + * non-option. + */ + for (l = ip6_nexthdr(m, off, proto, &nxtp); l != -1; + l = ip6_nexthdr(m, off, proto, &nxtp)) + { + off += l; + proto = nxtp; + + /* Construct a tag */ + if (nxtp == IPPROTO_AH) + { + mtag = m_tag_get(PACKET_TAG_IPSEC_IN_CRYPTO_DONE, + sizeof(struct tdb_ident), M_NOWAIT); + if (mtag == NULL) + return tags.lh_first; + + tdbi = (struct tdb_ident *) (mtag + 1); + bzero(tdbi, sizeof(struct tdb_ident)); + m_copydata(m, off + sizeof(u_int32_t), + sizeof(u_int32_t), (caddr_t) &tdbi->spi); + tdbi->proto = IPPROTO_AH; + tdbi->dst.sin6.sin6_family = AF_INET6; + tdbi->dst.sin6.sin6_len = sizeof(struct sockaddr_in6); + tdbi->dst.sin6.sin6_addr = ip6_dst; + LIST_INSERT_HEAD(&tags, mtag, m_tag_link); + } + else + if (nxtp == IPPROTO_IPV6) + m_copydata(m, off + offsetof(struct ip6_hdr, ip6_dst), + sizeof(struct ip6_hdr), (caddr_t) &ip6_dst); + } + break; + } +#endif /* INET6 */ + + case IPPROTO_ESP: + /* Verify that this has been decrypted */ + { + union sockaddr_union su; + u_int32_t spi; + + m_copydata(m, off, sizeof(u_int32_t), (caddr_t) &spi); + bzero(&su, sizeof(union sockaddr_union)); + + s = spltdb(); + +#ifdef INET + if (ipv4sa) + { + su.sin.sin_family = AF_INET; + su.sin.sin_len = sizeof(struct sockaddr_in); + su.sin.sin_addr = iph.ip_dst; + } +#endif /* INET */ + +#ifdef INET6 + if (!ipv4sa) + { + su.sin6.sin6_family = AF_INET6; + su.sin6.sin6_len = sizeof(struct sockaddr_in6); + su.sin6.sin6_addr = ip6_dst; + } +#endif /* INET6 */ + + tdb = gettdb(spi, &su, IPPROTO_ESP); + if (tdb == NULL) + { + splx(s); + return tags.lh_first; + } + + /* How large is the ESP header ? We use this later */ + if (tdb->tdb_flags & TDBF_NOREPLAY) + esphlen = sizeof(u_int32_t) + tdb->tdb_ivlen; + else + esphlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; + + /* + * Verify decryption. If the SA is using random padding + * (as the "old" ESP SAs were bound to do, there's nothing + * we can do to see if the payload has been decrypted. + */ + if (tdb->tdb_flags & TDBF_RANDOMPADDING) + { + splx(s); + return tags.lh_first; + } + + /* Update the length of trailing ESP authenticators */ + if (tdb->tdb_authalgxform) + trail += AH_HMAC_HASHLEN; + + splx(s); + + /* Copy the last 10 bytes */ + m_copydata(m, m->m_pkthdr.len - trail - 8, 8, lasteight); + + /* Verify the self-describing padding values */ + for (i = 5; lasteight[i + 1] != 0 && i >= 0; i--) + if (lasteight[i + 1] != lasteight[i] + 1) + return tags.lh_first; + } + /* Fall through */ + case IPPROTO_AH: + mtag = m_tag_get(PACKET_TAG_IPSEC_IN_CRYPTO_DONE, + sizeof(struct tdb_ident), M_NOWAIT); + if (mtag == NULL) + return tags.lh_first; + + tdbi = (struct tdb_ident *) (mtag + 1); + bzero(tdbi, sizeof(struct tdb_ident)); + + if (proto == IPPROTO_AH) + m_copydata(m, off + sizeof(u_int32_t), sizeof(u_int32_t), + (caddr_t) &tdbi->spi); + else /* IPPROTO_ESP */ + m_copydata(m, off, sizeof(u_int32_t), (caddr_t) &tdbi->spi); + + tdbi->proto = proto; /* We can get here for AH or ESP */ + +#ifdef INET + /* Last network header was IPv4 */ + if (ipv4sa) + { + tdbi->dst.sin.sin_family = AF_INET; + tdbi->dst.sin.sin_len = sizeof(struct sockaddr_in); + tdbi->dst.sin.sin_addr = iph.ip_dst; + } +#endif /* INET */ + +#ifdef INET6 + /* Last network header was IPv6 */ + if (!ipv4sa) + { + tdbi->dst.sin6.sin6_family = AF_INET6; + tdbi->dst.sin6.sin6_len = sizeof(struct sockaddr_in6); + tdbi->dst.sin6.sin6_addr = ip6_dst; + } +#endif /* INET6 */ + + LIST_INSERT_HEAD(&tags, mtag, m_tag_link); + + /* Update next protocol/header and header offset */ + if (proto == IPPROTO_AH) + { + m_copydata(m, off, sizeof(u_int8_t), (caddr_t) &proto); + m_copydata(m, off + sizeof(u_int8_t), sizeof(u_int8_t), + (caddr_t) &s); + off += (s + 2) << 2; + } + else /* IPPROTO_ESP */ + { + off += esphlen; + proto = lasteight[7]; + } + break; + + default: + return tags.lh_first; /* done */ + } + } +} diff --git a/sys/netinet/ip_ipsp.h b/sys/netinet/ip_ipsp.h index f7fd4603b03..4a284f628f0 100644 --- a/sys/netinet/ip_ipsp.h +++ b/sys/netinet/ip_ipsp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ip_ipsp.h,v 1.100 2001/06/01 00:09:24 angelos Exp $ */ +/* $OpenBSD: ip_ipsp.h,v 1.101 2001/06/01 07:56:46 angelos Exp $ */ /* * The authors of this code are John Ioannidis (ji@tla.org), @@ -613,5 +613,6 @@ extern int ipsp_is_unspecified(union sockaddr_union); extern void ipsp_reffree(struct ipsec_ref *); extern void ipsp_skipcrypto_unmark(struct tdb_ident *); extern void ipsp_skipcrypto_mark(struct tdb_ident *); +extern struct m_tag *ipsp_parse_headers(struct mbuf *, int, u_int8_t); #endif /* _KERNEL */ #endif /* _NETINET_IPSP_H_ */ |