/*
* Copyright (c) 2017 Internet Initiative Japan Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
static int
ipsecif4_needfrag(struct mbuf *m, struct ipsecrequest *isr)
{
struct ip ip0;
struct ip *ip;
int mtu;
struct secasvar *sav;
sav = key_lookup_sa_bysaidx(&isr->saidx);
if (sav == NULL)
return 0;
if (!(sav->natt_type & UDP_ENCAP_ESPINUDP)) {
mtu = 0;
goto out;
}
if (m->m_len < sizeof(struct ip)) {
m_copydata(m, 0, sizeof(ip0), &ip0);
ip = &ip0;
} else {
ip = mtod(m, struct ip *);
}
mtu = sav->esp_frag;
if (ntohs(ip->ip_len) <= mtu)
mtu = 0;
out:
KEY_SA_UNREF(&sav);
return mtu;
}
static struct mbuf *
ipsecif4_flowinfo(struct mbuf *m, int family, int *proto0, u_int8_t *tos0)
{
const struct ip *ip;
int proto;
int tos;
KASSERT(proto0 != NULL);
KASSERT(tos0 != NULL);
switch (family) {
case AF_INET:
proto = IPPROTO_IPV4;
if (m->m_len < sizeof(*ip)) {
m = m_pullup(m, sizeof(*ip));
if (m == NULL) {
*tos0 = 0;
*proto0 = 0;
return NULL;
}
}
ip = mtod(m, const struct ip *);
tos = ip->ip_tos;
/* TODO: support ALTQ for inner packet */
break;
#ifdef INET6
case AF_INET6: {
const struct ip6_hdr *ip6;
proto = IPPROTO_IPV6;
if (m->m_len < sizeof(*ip6)) {
m = m_pullup(m, sizeof(*ip6));
if (m == NULL) {
*tos0 = 0;
*proto0 = 0;
return NULL;
}
}
ip6 = mtod(m, const struct ip6_hdr *);
tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff;
/* TODO: support ALTQ for inner packet */
break;
}
#endif /* INET6 */
default:
*tos0 = 0;
*proto0 = 0;
return NULL;
}
*proto0 = proto;
*tos0 = tos;
return m;
}
static int
ipsecif4_fragout(struct ipsec_variant *var, int family, struct mbuf *m, int mtu)
{
struct ifnet *ifp = &var->iv_softc->ipsec_if;
struct mbuf *next;
struct m_tag *mtag;
int error;
KASSERT(if_ipsec_heldref_variant(var));
mtag = m_tag_find(m, PACKET_TAG_IPSEC_NAT_T_PORTS);
if (mtag)
m_tag_delete(m, mtag);
/* consider new IP header prepended in ipsecif4_output() */
if (mtu <= sizeof(struct ip)) {
m_freem(m);
return ENETUNREACH;
}
m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
error = ip_fragment(m, ifp, mtu - sizeof(struct ip));
if (error)
return error;
for (error = 0; m; m = next) {
next = m->m_nextpkt;
m->m_nextpkt = NULL;
if (error) {
m_freem(m);
continue;
}
/* port match */
if (src_port != var->iv_dport ||
dst_port != var->iv_sport) {
#ifdef DEBUG
printf("%s: port mismatch: pkt(%u, %u), if(%u, %u)\n",
__func__, ntohs(src_port), ntohs(dst_port),
ntohs(var->iv_sport), ntohs(var->iv_dport));
#endif
return 0;
}
match:
/*
* hide NAT-T information from encapsulated traffics.
* they don't know about IPsec.
*/
if (mtag)
m_tag_delete(m, mtag);
return sizeof(src->sin_addr) + sizeof(dst->sin_addr);
}
static int
ipsecif4_output(struct ipsec_variant *var, int family, struct mbuf *m)
{
struct secpolicy *sp = NULL;
u_int8_t tos;
int proto;
int error;
int mtu;
u_long sa_mtu = 0;
switch (family) {
case AF_INET:
sp = IV_SP_OUT(var);
break;
case AF_INET6:
sp = IV_SP_OUT6(var);
break;
default:
m_freem(m);
return EAFNOSUPPORT;
}
KASSERT(sp != NULL);
/*
* The SPs in ipsec_variant are prevented from freed by
* ipsec_variant->iv_psref. So, KEY_SP_REF() is unnecessary here.
*
* However, lastused should be updated.
*/
key_sp_touch(sp);
/* get flowinfo */
m = ipsecif4_flowinfo(m, family, &proto, &tos);
if (m == NULL) {
error = ENETUNREACH;
goto done;
}
/* prepend new IP header */
m = ipsecif4_prepend_hdr(var, m, proto, tos);
if (m == NULL) {
error = ENETUNREACH;
goto done;
}
/*
* Normal netipsec's NAT-T fragmentation is done in ip_output().
* See "natt_frag" processing.
* However, ipsec(4) interface's one is not done in the same way,
* so we must do NAT-T fragmentation by own code.
*/
/* NAT-T ESP fragmentation */
mtu = ipsecif4_needfrag(m, sp->req);
if (mtu > 0)
return ipsecif4_fragout(var, family, m, mtu);
/* set NAT-T ports */
error = ipsecif_set_natt_ports(var, m);
if (error) {
m_freem(m);
goto done;
}
/* IPsec output */
IP_STATINC(IP_STAT_LOCALOUT);
error = ipsec4_process_packet(m, sp->req, &sa_mtu);
if (error == ENOENT)
error = 0;
/*
* fragmentation is already done in ipsecif4_fragout(),
* so ipsec4_process_packet() must not do fragmentation here.
*/
KASSERT(sa_mtu == 0);
/* port match */
if (src_port != var->iv_dport ||
dst_port != var->iv_sport) {
#ifdef DEBUG
printf("%s: port mismatch: pkt(%u, %u), if(%u, %u)\n",
__func__, ntohs(src_port), ntohs(dst_port),
ntohs(var->iv_sport), ntohs(var->iv_dport));
#endif
return 0;
}
match:
/*
* hide NAT-T information from encapsulated traffics.
* they don't know about IPsec.
*/
if (mtag)
m_tag_delete(m, mtag);
return sizeof(src->sin6_addr) + sizeof(dst->sin6_addr);
}