Add support for IPv6 to ipfw fwd:

Distinguish IPv4 and IPv6 addresses and optional port numbers in
user space to set the option for the correct protocol family.
Add support in the kernel for carrying the new IPv6 destination
address and port.
Add support to TCP and UDP for IPv6 and fix UDP IPv4 to not change
the address in the IP header.
Add support for IPv6 forwarding to a non-local destination.
Add a regession test uitilizing VIMAGE to check all 20 possible
combinations I could think of.

Obtained from:	David Dolson at Sandvine Incorporated
		(original version for ipfw fwd IPv6 support)
Sponsored by:	Sandvine Incorporated
PR:		bin/117214
MFC after:	4 weeks
Approved by:	re (kib)
This commit is contained in:
Bjoern A. Zeeb 2011-08-20 17:05:11 +00:00
parent 90bc35de38
commit 8a006adb24
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=225044
15 changed files with 800 additions and 85 deletions

View File

@ -1,7 +1,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 29, 2011
.Dd August 20, 2011
.Dt IPFW 8
.Os
.Sh NAME
@ -726,7 +726,7 @@ The search terminates.
Change the next-hop on matching packets to
.Ar ipaddr ,
which can be an IP address or a host name.
The next hop can also be supplied by the last table
For IPv4, the next hop can also be supplied by the last table
looked up for the packet by using the
.Cm tablearg
keyword instead of an explicit address.

View File

@ -1111,6 +1111,18 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
}
break;
case O_FORWARD_IP6:
{
char buf[4 + INET6_ADDRSTRLEN + 1];
ipfw_insn_sa6 *s = (ipfw_insn_sa6 *)cmd;
printf("fwd %s", inet_ntop(AF_INET6, &s->sa.sin6_addr,
buf, sizeof(buf)));
if (s->sa.sin6_port)
printf(",%d", s->sa.sin6_port);
}
break;
case O_LOG: /* O_LOG is printed last */
logptr = (ipfw_insn_log *)cmd;
break;
@ -2809,40 +2821,96 @@ ipfw_add(char *av[])
break;
case TOK_FORWARD: {
ipfw_insn_sa *p = (ipfw_insn_sa *)action;
/*
* Locate the address-port separator (':' or ',').
* Could be one of the following:
* hostname:port
* IPv4 a.b.c.d,port
* IPv4 a.b.c.d:port
* IPv6 w:x:y::z,port
* The ':' can only be used with hostname and IPv4 address.
* XXX-BZ Should we also support [w:x:y::z]:port?
*/
struct sockaddr_storage result;
struct addrinfo *res;
char *s, *end;
int family;
u_short port_number;
NEED1("missing forward address[:port]");
action->opcode = O_FORWARD_IP;
action->len = F_INSN_SIZE(ipfw_insn_sa);
/*
* In the kernel we assume AF_INET and use only
* sin_port and sin_addr. Remember to set sin_len as
* the routing code seems to use it too.
*/
p->sa.sin_family = AF_INET;
p->sa.sin_len = sizeof(struct sockaddr_in);
p->sa.sin_port = 0;
/*
* locate the address-port separator (':' or ',')
*/
s = strchr(*av, ':');
if (s == NULL)
s = strchr(*av, ',');
s = strchr(*av, ',');
if (s == NULL) {
/* Distinguish between IPv4:port and IPv6 cases. */
s = strchr(*av, ':');
if (s && strchr(s+1, ':'))
s = NULL; /* no port */
}
port_number = 0;
if (s != NULL) {
/* Terminate host portion and set s to start of port. */
*(s++) = '\0';
i = strtoport(s, &end, 0 /* base */, 0 /* proto */);
if (s == end)
errx(EX_DATAERR,
"illegal forwarding port ``%s''", s);
p->sa.sin_port = (u_short)i;
port_number = (u_short)i;
}
if (_substrcmp(*av, "tablearg") == 0) {
family = PF_INET;
((struct sockaddr_in*)&result)->sin_addr.s_addr =
INADDR_ANY;
} else {
/*
* Resolve the host name or address to a family and a
* network representation of the addres.
*/
if (getaddrinfo(*av, NULL, NULL, &res))
errx(EX_DATAERR, NULL);
/* Just use the first host in the answer. */
family = res->ai_family;
memcpy(&result, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
}
if (family == PF_INET) {
ipfw_insn_sa *p = (ipfw_insn_sa *)action;
action->opcode = O_FORWARD_IP;
action->len = F_INSN_SIZE(ipfw_insn_sa);
/*
* In the kernel we assume AF_INET and use only
* sin_port and sin_addr. Remember to set sin_len as
* the routing code seems to use it too.
*/
p->sa.sin_len = sizeof(struct sockaddr_in);
p->sa.sin_family = AF_INET;
p->sa.sin_port = port_number;
p->sa.sin_addr.s_addr =
((struct sockaddr_in *)&result)->sin_addr.s_addr;
} else if (family == PF_INET6) {
ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action;
action->opcode = O_FORWARD_IP6;
action->len = F_INSN_SIZE(ipfw_insn_sa6);
p->sa.sin6_len = sizeof(struct sockaddr_in6);
p->sa.sin6_family = AF_INET6;
p->sa.sin6_port = port_number;
p->sa.sin6_flowinfo = 0;
p->sa.sin6_scope_id = 0;
/* No table support for v6 yet. */
bcopy(&((struct sockaddr_in6*)&result)->sin6_addr,
&p->sa.sin6_addr, sizeof(p->sa.sin6_addr));
} else {
errx(EX_DATAERR, "Invalid address family in forward action");
}
if (_substrcmp(*av, "tablearg") == 0)
p->sa.sin_addr.s_addr = INADDR_ANY;
else
lookup_host(*av, &(p->sa.sin_addr));
av++;
break;
}

View File

@ -203,6 +203,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_CALLRETURN, /* arg1=called rule number */
O_FORWARD_IP6, /* fwd sockaddr_in6 */
O_LAST_OPCODE /* not an opcode! */
};
@ -298,6 +300,14 @@ typedef struct _ipfw_insn_sa {
struct sockaddr_in sa;
} ipfw_insn_sa;
/*
* This is used to forward to a given address (ipv6).
*/
typedef struct _ipfw_insn_sa6 {
ipfw_insn o;
struct sockaddr_in6 sa;
} ipfw_insn_sa6;
/*
* This is used for MAC addr-mask pairs.
*/

View File

@ -796,6 +796,7 @@ set_match(struct ip_fw_args *args, int slot,
*
* args->rule Pointer to the last matching rule (in/out)
* args->next_hop Socket we are forwarding to (out).
* args->next_hop6 IPv6 next hop we are forwarding to (out).
* args->f_id Addresses grabbed from the packet (out)
* args->rule.info a cookie depending on rule action
*
@ -2281,6 +2282,23 @@ do { \
done = 1; /* exit outer loop */
break;
#ifdef INET6
case O_FORWARD_IP6:
if (args->eh) /* not valid on layer2 pkts */
break;
if (q == NULL || q->rule != f ||
dyn_dir == MATCH_FORWARD) {
struct sockaddr_in6 *sin6;
sin6 = &(((ipfw_insn_sa6 *)cmd)->sa);
args->next_hop6 = sin6;
}
retval = IP_FW_PASS;
l = 0; /* exit inner loop */
done = 1; /* exit outer loop */
break;
#endif
case O_NETGRAPH:
case O_NGTEE:
set_match(args, f_pos, chain);

View File

@ -167,7 +167,7 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
{
char *action;
int limit_reached = 0;
char action2[40], proto[128], fragment[32];
char action2[92], proto[128], fragment[32];
if (V_fw_verbose == 0) {
#ifndef WITHOUT_BPF
@ -290,6 +290,21 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
sa->sa.sin_port);
}
break;
#ifdef INET6
case O_FORWARD_IP6: {
char buf[INET6_ADDRSTRLEN];
ipfw_insn_sa6 *sa = (ipfw_insn_sa6 *)cmd;
int len;
len = snprintf(SNPARGS(action2, 0), "Forward to [%s]",
ip6_sprintf(buf, &sa->sa.sin6_addr));
if (sa->sa.sin6_port)
snprintf(SNPARGS(action2, len), ":%u",
sa->sa.sin6_port);
}
break;
#endif
case O_NETGRAPH:
snprintf(SNPARGS(action2, 0), "Netgraph %d",
cmd->arg1);

View File

@ -152,13 +152,26 @@ ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
switch (ipfw) {
case IP_FW_PASS:
/* next_hop may be set by ipfw_chk */
if (args.next_hop == NULL)
if (args.next_hop == NULL && args.next_hop6 == NULL)
break; /* pass */
#ifndef IPFIREWALL_FORWARD
ret = EACCES;
#else
{
struct m_tag *fwd_tag;
size_t len;
KASSERT(args.next_hop == NULL || args.next_hop6 == NULL,
("%s: both next_hop=%p and next_hop6=%p not NULL", __func__,
args.next_hop, args.next_hop6));
#ifdef INET6
if (args.next_hop6 != NULL)
len = sizeof(struct sockaddr_in6);
#endif
#ifdef INET
if (args.next_hop != NULL)
len = sizeof(struct sockaddr_in);
#endif
/* Incoming packets should not be tagged so we do not
* m_tag_find. Outgoing packets may be tagged, so we
@ -169,18 +182,28 @@ ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
if (fwd_tag != NULL) {
m_tag_unlink(*m0, fwd_tag);
} else {
fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
sizeof(struct sockaddr_in), M_NOWAIT);
fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, len,
M_NOWAIT);
if (fwd_tag == NULL) {
ret = EACCES;
break; /* i.e. drop */
}
}
bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
#ifdef INET6
if (args.next_hop6 != NULL) {
bcopy(args.next_hop6, (fwd_tag+1), len);
if (in6_localip(&args.next_hop6->sin6_addr))
(*m0)->m_flags |= M_FASTFWD_OURS;
}
#endif
#ifdef INET
if (args.next_hop != NULL) {
bcopy(args.next_hop, (fwd_tag+1), len);
if (in_localip(args.next_hop->sin_addr))
(*m0)->m_flags |= M_FASTFWD_OURS;
}
#endif
m_tag_prepend(*m0, fwd_tag);
if (in_localip(args.next_hop->sin_addr))
(*m0)->m_flags |= M_FASTFWD_OURS;
}
#endif
break;

View File

@ -86,6 +86,7 @@ struct ip_fw_args {
struct mbuf *m; /* the mbuf chain */
struct ifnet *oif; /* output interface */
struct sockaddr_in *next_hop; /* forward address */
struct sockaddr_in6 *next_hop6; /* ipv6 forward address */
/*
* On return, it points to the matching rule.

View File

@ -723,6 +723,17 @@ check_ipfw_struct(struct ip_fw *rule, int size)
return EINVAL;
#endif
#ifdef INET6
case O_FORWARD_IP6:
#ifdef IPFIREWALL_FORWARD
if (cmdlen != F_INSN_SIZE(ipfw_insn_sa6))
goto bad_size;
goto check_action;
#else
return (EINVAL);
#endif
#endif /* INET6 */
case O_DIVERT:
case O_TEE:
if (ip_divert_ptr == NULL)

View File

@ -573,11 +573,9 @@ tcp_input(struct mbuf *m, int off0)
uint8_t sig_checked = 0;
#endif
uint8_t iptos = 0;
#ifdef INET
#ifdef IPFIREWALL_FORWARD
struct m_tag *fwd_tag;
#endif
#endif /* INET */
#ifdef INET6
struct ip6_hdr *ip6 = NULL;
int isipv6;
@ -776,14 +774,55 @@ tcp_input(struct mbuf *m, int off0)
}
#endif
#ifdef INET
#ifdef IPFIREWALL_FORWARD
/*
* Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain.
*/
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
#endif /* IPFIREWALL_FORWARD */
if (fwd_tag != NULL && isipv6 == 0) { /* IPv6 support is not yet */
#ifdef INET6
#ifdef IPFIREWALL_FORWARD
if (isipv6 && fwd_tag != NULL) {
struct sockaddr_in6 *next_hop6;
next_hop6 = (struct sockaddr_in6 *)(fwd_tag + 1);
/*
* Transparently forwarded. Pretend to be the destination.
* Already got one like this?
*/
inp = in6_pcblookup_mbuf(&V_tcbinfo,
&ip6->ip6_src, th->th_sport, &ip6->ip6_dst, th->th_dport,
INPLOOKUP_WLOCKPCB, m->m_pkthdr.rcvif, m);
if (!inp) {
/*
* It's new. Try to find the ambushing socket.
* Because we've rewritten the destination address,
* any hardware-generated hash is ignored.
*/
inp = in6_pcblookup(&V_tcbinfo, &ip6->ip6_src,
th->th_sport, &next_hop6->sin6_addr,
next_hop6->sin6_port ? ntohs(next_hop6->sin6_port) :
th->th_dport, INPLOOKUP_WILDCARD |
INPLOOKUP_WLOCKPCB, m->m_pkthdr.rcvif);
}
/* Remove the tag from the packet. We don't need it anymore. */
m_tag_delete(m, fwd_tag);
} else
#endif /* IPFIREWALL_FORWARD */
if (isipv6) {
inp = in6_pcblookup_mbuf(&V_tcbinfo, &ip6->ip6_src,
th->th_sport, &ip6->ip6_dst, th->th_dport,
INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB,
m->m_pkthdr.rcvif, m);
}
#endif /* INET6 */
#if defined(INET6) && defined(INET)
else
#endif
#ifdef INET
#ifdef IPFIREWALL_FORWARD
if (fwd_tag != NULL) {
struct sockaddr_in *next_hop;
next_hop = (struct sockaddr_in *)(fwd_tag+1);
@ -810,25 +849,11 @@ tcp_input(struct mbuf *m, int off0)
m_tag_delete(m, fwd_tag);
} else
#endif /* IPFIREWALL_FORWARD */
inp = in_pcblookup_mbuf(&V_tcbinfo, ip->ip_src,
th->th_sport, ip->ip_dst, th->th_dport,
INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB,
m->m_pkthdr.rcvif, m);
#endif /* INET */
{
#ifdef INET6
if (isipv6)
inp = in6_pcblookup_mbuf(&V_tcbinfo, &ip6->ip6_src,
th->th_sport, &ip6->ip6_dst, th->th_dport,
INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB,
m->m_pkthdr.rcvif, m);
#endif
#if defined(INET) && defined(INET6)
else
#endif
#ifdef INET
inp = in_pcblookup_mbuf(&V_tcbinfo, ip->ip_src,
th->th_sport, ip->ip_dst, th->th_dport,
INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB,
m->m_pkthdr.rcvif, m);
#endif
}
/*
* If the INPCB does not exist then all data in the incoming

View File

@ -441,28 +441,6 @@ udp_input(struct mbuf *m, int off)
} else
UDPSTAT_INC(udps_nosum);
#ifdef IPFIREWALL_FORWARD
/*
* Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain.
*/
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
if (fwd_tag != NULL) {
struct sockaddr_in *next_hop;
/*
* Do the hack.
*/
next_hop = (struct sockaddr_in *)(fwd_tag + 1);
ip->ip_dst = next_hop->sin_addr;
uh->uh_dport = ntohs(next_hop->sin_port);
/*
* Remove the tag from the packet. We don't need it anymore.
*/
m_tag_delete(m, fwd_tag);
}
#endif
if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
in_broadcast(ip->ip_dst, ifp)) {
struct inpcb *last;
@ -568,9 +546,41 @@ udp_input(struct mbuf *m, int off)
/*
* Locate pcb for datagram.
*/
inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport,
ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB,
ifp, m);
#ifdef IPFIREWALL_FORWARD
/*
* Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain.
*/
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
if (fwd_tag != NULL) {
struct sockaddr_in *next_hop;
next_hop = (struct sockaddr_in *)(fwd_tag + 1);
/*
* Transparently forwarded. Pretend to be the destination.
* Already got one like this?
*/
inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport,
ip->ip_dst, uh->uh_dport, INPLOOKUP_RLOCKPCB, ifp, m);
if (!inp) {
/*
* It's new. Try to find the ambushing socket.
* Because we've rewritten the destination address,
* any hardware-generated hash is ignored.
*/
inp = in_pcblookup(&V_udbinfo, ip->ip_src,
uh->uh_sport, next_hop->sin_addr,
next_hop->sin_port ? htons(next_hop->sin_port) :
uh->uh_dport, INPLOOKUP_WILDCARD |
INPLOOKUP_RLOCKPCB, ifp);
}
/* Remove the tag from the packet. We don't need it anymore. */
m_tag_delete(m, fwd_tag);
} else
#endif /* IPFIREWALL_FORWARD */
inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport,
ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD |
INPLOOKUP_RLOCKPCB, ifp, m);
if (inp == NULL) {
if (udp_log_in_vain) {
char buf[4*sizeof "123"];

View File

@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_ipfw.h"
#include "opt_ipsec.h"
#include "opt_ipstealth.h"
@ -50,6 +51,7 @@ __FBSDID("$FreeBSD$");
#include <sys/syslog.h>
#include <net/if.h>
#include <net/netisr.h>
#include <net/route.h>
#include <net/pfil.h>
@ -98,10 +100,16 @@ ip6_forward(struct mbuf *m, int srcrt)
struct mbuf *mcopy = NULL;
struct ifnet *origifp; /* maybe unnecessary */
u_int32_t inzone, outzone;
struct in6_addr src_in6, dst_in6;
struct in6_addr src_in6, dst_in6, odst;
#ifdef IPSEC
struct secpolicy *sp = NULL;
int ipsecrt = 0;
#endif
#ifdef SCTP
int sw_csum;
#endif
#ifdef IPFIREWALL_FORWARD
struct m_tag *fwd_tag;
#endif
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
@ -345,13 +353,15 @@ ip6_forward(struct mbuf *m, int srcrt)
goto skip_routing;
skip_ipsec:
#endif
again:
bzero(&rin6, sizeof(struct route_in6));
dst = (struct sockaddr_in6 *)&rin6.ro_dst;
dst->sin6_len = sizeof(struct sockaddr_in6);
dst->sin6_family = AF_INET6;
dst->sin6_addr = ip6->ip6_dst;
#ifdef IPFIREWALL_FORWARD
again2:
#endif
rin6.ro_rt = rtalloc1((struct sockaddr *)dst, 0, 0);
if (rin6.ro_rt != NULL)
RT_UNLOCK(rin6.ro_rt);
@ -554,6 +564,7 @@ ip6_forward(struct mbuf *m, int srcrt)
if (!PFIL_HOOKED(&V_inet6_pfil_hook))
goto pass;
odst = ip6->ip6_dst;
/* Run through list of hooks for output packets. */
error = pfil_run_hooks(&V_inet6_pfil_hook, &m, rt->rt_ifp, PFIL_OUT, NULL);
if (error != 0)
@ -562,6 +573,59 @@ ip6_forward(struct mbuf *m, int srcrt)
goto freecopy;
ip6 = mtod(m, struct ip6_hdr *);
/* See if destination IP address was changed by packet filter. */
if (!IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst)) {
m->m_flags |= M_SKIP_FIREWALL;
/* If destination is now ourself drop to ip6_input(). */
if (in6_localip(&ip6->ip6_dst)) {
m->m_flags |= M_FASTFWD_OURS;
if (m->m_pkthdr.rcvif == NULL)
m->m_pkthdr.rcvif = V_loif;
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
m->m_pkthdr.csum_flags |=
CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
m->m_pkthdr.csum_data = 0xffff;
}
m->m_pkthdr.csum_flags |=
CSUM_IP_CHECKED | CSUM_IP_VALID;
#ifdef SCTP
if (m->m_pkthdr.csum_flags & CSUM_SCTP)
m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID;
#endif
error = netisr_queue(NETISR_IPV6, m);
goto out;
} else
goto again; /* Redo the routing table lookup. */
}
#ifdef IPFIREWALL_FORWARD
/* See if local, if yes, send it to netisr. */
if (m->m_flags & M_FASTFWD_OURS) {
if (m->m_pkthdr.rcvif == NULL)
m->m_pkthdr.rcvif = V_loif;
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
m->m_pkthdr.csum_flags |=
CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
m->m_pkthdr.csum_data = 0xffff;
}
#ifdef SCTP
if (m->m_pkthdr.csum_flags & CSUM_SCTP)
m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID;
#endif
error = netisr_queue(NETISR_IPV6, m);
goto out;
}
/* Or forward to some other address? */
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
if (fwd_tag) {
dst = (struct sockaddr_in6 *)&rin6.ro_dst;
bcopy((fwd_tag+1), dst, sizeof(struct sockaddr_in6));
m->m_flags |= M_SKIP_FIREWALL;
m_tag_delete(m, fwd_tag);
goto again2;
}
#endif /* IPFIREWALL_FORWARD */
pass:
error = nd6_output(rt->rt_ifp, origifp, m, dst, rt);
if (error) {

View File

@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$");
#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_ipfw.h"
#include "opt_ipsec.h"
#include "opt_route.h"
@ -91,6 +92,7 @@ __FBSDID("$FreeBSD$");
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/in_systm.h>
#include <net/if_llatbl.h>
#ifdef INET
@ -357,6 +359,17 @@ ip6_input(struct mbuf *m)
*/
ip6_delaux(m);
if (m->m_flags & M_FASTFWD_OURS) {
/*
* Firewall changed destination to local.
*/
m->m_flags &= ~M_FASTFWD_OURS;
ours = 1;
deliverifp = m->m_pkthdr.rcvif;
ip6 = mtod(m, struct ip6_hdr *);
goto hbhcheck;
}
/*
* mbuf statistics
*/
@ -533,6 +546,24 @@ ip6_input(struct mbuf *m)
ip6 = mtod(m, struct ip6_hdr *);
srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst);
#ifdef IPFIREWALL_FORWARD
if (m->m_flags & M_FASTFWD_OURS) {
m->m_flags &= ~M_FASTFWD_OURS;
ours = 1;
deliverifp = m->m_pkthdr.rcvif;
goto hbhcheck;
}
if (m_tag_find(m, PACKET_TAG_IPFORWARD, NULL) != NULL) {
/*
* Directly ship the packet on. This allows forwarding
* packets originally destined to us to some other directly
* connected host.
*/
ip6_forward(m, 1);
goto out;
}
#endif /* IPFIREWALL_FORWARD */
passin:
/*
* Disambiguate address scope zones (if there is ambiguity).

View File

@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$");
#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_ipfw.h"
#include "opt_ipsec.h"
#include "opt_sctp.h"
#include "opt_route.h"
@ -90,6 +91,7 @@ __FBSDID("$FreeBSD$");
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
@ -229,6 +231,9 @@ ip6_output(struct mbuf *m0, struct ip6_pktopts *opt,
int segleft_org = 0;
struct secpolicy *sp = NULL;
#endif /* IPSEC */
#ifdef IPFIREWALL_FORWARD
struct m_tag *fwd_tag;
#endif
ip6 = mtod(m, struct ip6_hdr *);
if (ip6 == NULL) {
@ -850,7 +855,8 @@ skip_ipsec2:;
if (!IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst)) {
m->m_flags |= M_SKIP_FIREWALL;
/* If destination is now ourself drop to ip6_input(). */
if (in6_localaddr(&ip6->ip6_dst)) {
if (in6_localip(&ip6->ip6_dst)) {
m->m_flags |= M_FASTFWD_OURS;
if (m->m_pkthdr.rcvif == NULL)
m->m_pkthdr.rcvif = V_loif;
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
@ -870,7 +876,33 @@ skip_ipsec2:;
goto again; /* Redo the routing table lookup. */
}
/* XXX: IPFIREWALL_FORWARD */
#ifdef IPFIREWALL_FORWARD
/* See if local, if yes, send it to netisr. */
if (m->m_flags & M_FASTFWD_OURS) {
if (m->m_pkthdr.rcvif == NULL)
m->m_pkthdr.rcvif = V_loif;
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
m->m_pkthdr.csum_flags |=
CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
m->m_pkthdr.csum_data = 0xffff;
}
#ifdef SCTP
if (m->m_pkthdr.csum_flags & CSUM_SCTP)
m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID;
#endif
error = netisr_queue(NETISR_IPV6, m);
goto done;
}
/* Or forward to some other address? */
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
if (fwd_tag) {
dst = (struct sockaddr_in6 *)&ro->ro_dst;
bcopy((fwd_tag+1), dst, sizeof(struct sockaddr_in6));
m->m_flags |= M_SKIP_FIREWALL;
m_tag_delete(m, fwd_tag);
goto again;
}
#endif /* IPFIREWALL_FORWARD */
passout:
/*

View File

@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_ipfw.h"
#include "opt_ipsec.h"
#include <sys/param.h>
@ -181,6 +182,9 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
int off = *offp;
int plen, ulen;
struct sockaddr_in6 fromsa;
#ifdef IPFIREWALL_FORWARD
struct m_tag *fwd_tag;
#endif
ifp = m->m_pkthdr.rcvif;
ip6 = mtod(m, struct ip6_hdr *);
@ -377,9 +381,43 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
/*
* Locate pcb for datagram.
*/
inp = in6_pcblookup_mbuf(&V_udbinfo, &ip6->ip6_src, uh->uh_sport,
&ip6->ip6_dst, uh->uh_dport, INPLOOKUP_WILDCARD |
INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m);
#ifdef IPFIREWALL_FORWARD
/*
* Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain.
*/
fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL);
if (fwd_tag != NULL) {
struct sockaddr_in6 *next_hop6;
next_hop6 = (struct sockaddr_in6 *)(fwd_tag + 1);
/*
* Transparently forwarded. Pretend to be the destination.
* Already got one like this?
*/
inp = in6_pcblookup_mbuf(&V_udbinfo,
&ip6->ip6_src, uh->uh_sport, &ip6->ip6_dst, uh->uh_dport,
INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m);
if (!inp) {
/*
* It's new. Try to find the ambushing socket.
* Because we've rewritten the destination address,
* any hardware-generated hash is ignored.
*/
inp = in6_pcblookup(&V_udbinfo, &ip6->ip6_src,
uh->uh_sport, &next_hop6->sin6_addr,
next_hop6->sin6_port ? htons(next_hop6->sin6_port) :
uh->uh_dport, INPLOOKUP_WILDCARD |
INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif);
}
/* Remove the tag from the packet. We don't need it anymore. */
m_tag_delete(m, fwd_tag);
} else
#endif /* IPFIREWALL_FORWARD */
inp = in6_pcblookup_mbuf(&V_udbinfo, &ip6->ip6_src,
uh->uh_sport, &ip6->ip6_dst, uh->uh_dport,
INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB,
m->m_pkthdr.rcvif, m);
if (inp == NULL) {
if (udp_log_in_vain) {
char ip6bufs[INET6_ADDRSTRLEN];

View File

@ -0,0 +1,369 @@
#!/bin/sh
#-
# Copyright (c) 2010, "Bjoern A. Zeeb" <bz@FreeBSD.org>
# Copyright (c) 2011, Sandvine Incorporated ULC.
# 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 AUTHOR 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 AUTHOR 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.
#
# $FreeBSD$
#
#
# Test ipfw fwd for IPv4 and IPv6 using VIMAGE, testing that as well.
# For no test the packet header contents must be changed but always
# keeping the original destination.
#
case `id -u` in
0) ;;
*) echo "ERROR: Must be run as superuser." >&2
exit 2
esac
epair_base()
{
local ep
ep=`ifconfig epair create`
expr ${ep} : '\(.*\).'
}
debug_err()
{
local _p
_p="$1"
case "${DEBUG}" in
"") ;;
*)
echo " ~~ start of debug ~~"
echo " ~~ left:"
jexec ${ljid} /sbin/ipfw show
echo " ~~ middle:"
jexec ${mjid} /sbin/ipfw show
echo " ~~ right:"
jexec ${rjid} /sbin/ipfw show
echo " ~~ result file:"
cat ${_p}.1
echo " ~~ log file:"
cat ${_p}
echo " ~~ end of debug ~~"
;;
esac
}
check_cleanup_result_file()
{
local _p
_p="$1"
if test ! -s ${_p}.1; then
echo "FAIL (output file empty)."
debug_err ${_p}
else
read line < ${_p}.1
# Netcat adds 'X's in udp mode.
l="/${line#*/}"
if test "${l}" = "${_p}"; then
echo "PASS."
else
echo "FAIL (expected: '${_p}' got '${l}')."
debug_err ${_p}
fi
fi
rm -f ${_p}.1
rm -f ${_p}
}
# Transparent proxy scenario (local address).
run_test_tp()
{
local _descr
local _sip _dip _fip _fport _dport _p
local _nc_af _nc_p
local _lport
descr="$1"
_sip="$2"
_dip="$3"
_fip="$4"
_fport="$5"
_dport="$6"
_p="$7"
_nc_af="$8"
_lport=${_dport}
case "${_fport}" in
"") _lport="${_dport}" ;;
*) _lport="${_fport#,}" ;;
esac
case "${_p}" in
udp) _nc_p="-u" ;;
esac
OUT=`mktemp -t "ipfwfwd$$-XXXXXX"`
echo -n "${descr} (${OUT}).."
(
jexec ${ljid} /sbin/ipfw -f flush
jexec ${ljid} /sbin/ipfw -f zero
jexec ${mjid} /sbin/ipfw -f flush
jexec ${mjid} /sbin/ipfw -f zero
jexec ${rjid} /sbin/ipfw -f flush
jexec ${rjid} /sbin/ipfw -f zero
jexec ${mjid} /sbin/ipfw add 100 fwd ${_fip}${_fport} ${_p} from ${_sip} to ${_dip}
jexec ${mjid} /bin/sh -c "nc -w 10 ${_nc_af} -n ${_nc_p} -l ${_fip} ${_lport} > ${OUT}.1 &"
jexec ${rjid} /bin/sh -c "echo '${OUT}' | nc -w 1 -v ${_nc_af} -n ${_nc_p} ${_dip} ${_dport}"
) > ${OUT} 2>&1
check_cleanup_result_file "${OUT}"
}
# Transparent redirect scenario (non-local address).
run_test_nh()
{
local _descr
local _sip _dip _fip _fport _dport _p
local _nc_af _nc_p
local _lport
descr="$1"
_sip="$2"
_dip="$3"
_fip="$4"
_fport="$5"
_dport="$6"
_p="$7"
_nc_af="$8"
_lport=${_dport}
case "${_fport}" in
"") _lport="${_dport}" ;;
*) _lport="${_fport#,}" ;;
esac
case "${_p}" in
udp) _nc_p="-u" ;;
esac
OUT=`mktemp -t "ipfwfwd$$-XXXXXX"`
echo -n "${descr} (${OUT}).."
(
jexec ${ljid} /sbin/ipfw -f flush
jexec ${ljid} /sbin/ipfw -f zero
jexec ${mjid} /sbin/ipfw -f flush
jexec ${mjid} /sbin/ipfw -f zero
jexec ${rjid} /sbin/ipfw -f flush
jexec ${rjid} /sbin/ipfw -f zero
jexec ${mjid} /sbin/ipfw add 100 fwd ${_fip} ${_p} from ${_sip} to ${_dip}
jexec ${ljid} /bin/sh -c "nc -w 10 ${_nc_af} -n ${_nc_p} -l ${_dip} ${_lport} > ${OUT}.1 &"
jexec ${rjid} /bin/sh -c "echo '${OUT}' | nc -w 1 -v ${_nc_af} -n ${_nc_p} ${_dip} ${_dport}"
) > ${OUT} 2>&1
check_cleanup_result_file "${OUT}"
}
echo "==> Setting up test network"
kldload -q ipfw > /dev/null 2>&1
# Start left (sender) jail.
ljid=`jail -i -c -n lef$$ host.hostname=left.example.net vnet persist`
# Start middle (ipfw) jail.
mjid=`jail -i -c -n mid$$ host.hostname=center.example.net vnet persist`
# Start right (non-local ip redirects go to here) jail.
rjid=`jail -i -c -n right$$ host.hostname=right.example.net vnet persist`
echo "left ${ljid} middle ${mjid} right ${rjid}"
# Create networking.
#
# jail: left middle right
# ifaces: lmep:a ---- lmep:b mrep:a ---- mrep:b
#
jexec ${mjid} sysctl net.inet.ip.forwarding=1
jexec ${mjid} sysctl net.inet6.ip6.forwarding=1
jexec ${mjid} sysctl net.inet6.ip6.accept_rtadv=0
lmep=$(epair_base)
ifconfig ${lmep}a vnet ${ljid}
ifconfig ${lmep}b vnet ${mjid}
jexec ${ljid} ifconfig lo0 inet 127.0.0.1/8
jexec ${ljid} ifconfig lo0 inet 192.0.2.5/32 alias # Test 9-10
jexec ${ljid} ifconfig lo0 inet6 2001:db8:1::1/128 alias # Test 11-12
jexec ${ljid} ifconfig ${lmep}a inet 192.0.2.1/30 up
jexec ${ljid} ifconfig ${lmep}a inet6 2001:db8::1/64 alias
jexec ${ljid} route add default 192.0.2.2
jexec ${ljid} route add -inet6 default 2001:db8::2
jexec ${mjid} ifconfig lo0 inet 127.0.0.1/8
jexec ${mjid} ifconfig lo0 inet 192.0.2.255/32 alias # Test 1-4
jexec ${mjid} ifconfig lo0 inet6 2001:db8:ffff::1/128 alias # Test 5-8
jexec ${mjid} ifconfig ${lmep}b inet 192.0.2.2/30 up
jexec ${mjid} ifconfig ${lmep}b inet6 2001:db8::2/64 alias
jexec ${mjid} route add default 192.0.2.1
mrep=$(epair_base)
ifconfig ${mrep}a vnet ${mjid}
ifconfig ${mrep}b vnet ${rjid}
jexec ${mjid} ifconfig ${mrep}a inet 192.0.2.5/30 up
jexec ${mjid} ifconfig ${mrep}a inet6 2001:db8:1::1/64 alias
jexec ${rjid} ifconfig lo0 inet 127.0.0.1/8
jexec ${rjid} ifconfig ${mrep}b inet 192.0.2.6/30 up
jexec ${rjid} ifconfig ${mrep}b inet6 2001:db8:1::2/64 alias
jexec ${rjid} route add default 192.0.2.5
jexec ${rjid} route add -inet6 default 2001:db8:1::1
# ------------------------------------------------------------------------------
# Tests
#
# The jails are not chrooted to they all share the same base filesystem.
# This means we can put results into /tmp and just collect them from here.
#
echo "==> Running tests"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=1
run_test_tp "TEST ${i} IPv4 UDP redirect local to other local address, same port" \
192.0.2.6 192.0.2.5 192.0.2.255 "" 12345 udp "-4"
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 UDP redirect local to other local address, different port" \
192.0.2.6 192.0.2.5 192.0.2.255 ",65534" 12345 udp "-4"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 TCP redirect local to other local address, same port" \
192.0.2.6 192.0.2.5 192.0.2.255 "" 12345 tcp "-4"
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 TCP redirect local to other local address, different port" \
192.0.2.6 192.0.2.5 192.0.2.255 ",65534" 12345 tcp "-4"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 UDP redirect foreign to local address, same port" \
192.0.2.6 192.0.2.1 192.0.2.255 "" 12345 udp "-4"
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 UDP redirect foreign to local address, different port" \
192.0.2.6 192.0.2.1 192.0.2.255 ",65534" 12345 udp "-4"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 TCP redirect foreign to local address, same port" \
192.0.2.6 192.0.2.1 192.0.2.255 "" 12345 tcp "-4"
i=$((i + 1))
run_test_tp "TEST ${i} IPv4 TCP redirect foreign to local address, different port" \
192.0.2.6 192.0.2.1 192.0.2.255 ",65534" 12345 tcp "-4"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 UDP redirect local to other local address, same port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 udp "-6"
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 UDP redirect local to other local address, different port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 udp "-6"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 TCP redirect local to other local address, same port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 tcp "-6"
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 TCP redirect local to other local address, different port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 tcp "-6"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 UDP redirect foreign to local address, same port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 udp "-6"
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 UDP redirect foreign to local address, different port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 udp "-6"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 TCP redirect foreign to local address, same port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 tcp "-6"
i=$((i + 1))
run_test_tp "TEST ${i} IPv6 TCP redirect foreign to local address, different port" \
2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 tcp "-6"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_nh "TEST ${i} IPv4 UDP redirect to foreign address" \
192.0.2.6 192.0.2.5 192.0.2.1 "" 12345 udp "-4"
i=$((i + 1))
run_test_nh "TEST ${i} IPv4 TCP redirect to foreign address" \
192.0.2.6 192.0.2.5 192.0.2.1 "" 12345 tcp "-4"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
i=$((i + 1))
run_test_nh "TEST ${i} IPv6 UDP redirect to foreign address" \
2001:db8:1::2 2001:db8:1::1 2001:db8::1 "" 12345 udp "-6"
i=$((i + 1))
run_test_nh "TEST ${i} IPv6 TCP redirect to foreign address" \
2001:db8:1::2 2001:db8:1::1 2001:db8::1 "" 12345 tcp "-6"
################################################################################
#
# Cleanup
#
echo "==> Cleaning up in 3 seconds"
# Let VIMAGE network stacks settle to avoid panics while still "experimental".
sleep 3
jail -r ${rjid}
jail -r ${mjid}
jail -r ${ljid}
for jid in ${rjid} ${mjid} ${ljid}; do
while : ; do
x=`jls -as -j ${jid} jid 2>/dev/null`
case "${x}" in
jid=*) echo "Waiting for jail ${jid} to stop." >&2
sleep 1
continue
;;
esac
break
done
done
ifconfig ${lmep}a destroy
ifconfig ${mrep}a destroy
# end