diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 6d25174b8a81..63f04cf96904 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,7 +1,7 @@ .\" .\" $FreeBSD$ .\" -.Dd Aug 13, 2014 +.Dd March 13, 2015 .Dt IPFW 8 .Os .Sh NAME @@ -2078,6 +2078,8 @@ hook number to move packet to. maximum number of connections. .It Cm ipv4 IPv4 nexthop to fwd packets to. +.It Cm ipv6 +IPv6 nexthop to fwd packets to. .El .Pp The diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c index 4829fecdc417..4ea5b7b84fdb 100644 --- a/sbin/ipfw/tables.c +++ b/sbin/ipfw/tables.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "ipfw2.h" @@ -1384,6 +1385,7 @@ static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg, uint8_t type, uint32_t vmask) { + struct addrinfo hints, *res; uint32_t a4, flag, val, vm; ipfw_table_value *v; uint32_t i; @@ -1494,9 +1496,19 @@ tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg, } break; case IPFW_VTYPE_NH6: - if (strchr(n, ':') != NULL && - inet_pton(AF_INET6, n, &v->nh6) == 1) - break; + if (strchr(n, ':') != NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(n, NULL, &hints, &res) == 0) { + v->nh6 = ((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr; + v->zoneid = ((struct sockaddr_in6 *) + res->ai_addr)->sin6_scope_id; + freeaddrinfo(res); + break; + } + } etype = "ipv6"; break; } @@ -1643,10 +1655,11 @@ static void table_show_value(char *buf, size_t bufsize, ipfw_table_value *v, uint32_t vmask, int print_ip) { + char abuf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2]; + struct sockaddr_in6 sa6; uint32_t flag, i, l; size_t sz; struct in_addr a4; - char abuf[INET6_ADDRSTRLEN]; sz = bufsize; @@ -1702,8 +1715,15 @@ table_show_value(char *buf, size_t bufsize, ipfw_table_value *v, l = snprintf(buf, sz, "%d,", v->dscp); break; case IPFW_VTYPE_NH6: - inet_ntop(AF_INET6, &v->nh6, abuf, sizeof(abuf)); - l = snprintf(buf, sz, "%s,", abuf); + sa6.sin6_family = AF_INET6; + sa6.sin6_len = sizeof(sa6); + sa6.sin6_addr = v->nh6; + sa6.sin6_port = 0; + sa6.sin6_scope_id = v->zoneid; + if (getnameinfo((const struct sockaddr *)&sa6, + sa6.sin6_len, abuf, sizeof(abuf), NULL, 0, + NI_NUMERICHOST) == 0) + l = snprintf(buf, sz, "%s,", abuf); break; } @@ -1862,11 +1882,12 @@ struct _table_value { uint32_t nat; /* O_NAT */ uint32_t nh4; uint8_t dscp; - uint8_t spare0[3]; + uint8_t spare0; + uint16_t spare1; /* -- 32 bytes -- */ struct in6_addr nh6; uint32_t limit; /* O_LIMIT */ - uint32_t spare1; + uint32_t zoneid; uint64_t refcnt; /* Number of references */ }; diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index ab9ec63e626a..9c53793126f3 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -721,7 +721,7 @@ struct _ipfw_dyn_rule { #define IPFW_VTYPE_TAG 0x00000020 /* tag/untag */ #define IPFW_VTYPE_DIVERT 0x00000040 /* divert/tee */ #define IPFW_VTYPE_NETGRAPH 0x00000080 /* netgraph/ngtee */ -#define IPFW_VTYPE_LIMIT 0x00000100 /* IPv6 nexthop */ +#define IPFW_VTYPE_LIMIT 0x00000100 /* limit */ #define IPFW_VTYPE_NH4 0x00000200 /* IPv4 nexthop */ #define IPFW_VTYPE_NH6 0x00000400 /* IPv6 nexthop */ @@ -817,10 +817,11 @@ typedef struct _ipfw_table_value { uint32_t nat; /* O_NAT */ uint32_t nh4; uint8_t dscp; - uint8_t spare0[3]; + uint8_t spare0; + uint16_t spare1; struct in6_addr nh6; uint32_t limit; /* O_LIMIT */ - uint32_t spare1; + uint32_t zoneid; /* scope zone id for nh6 */ uint64_t reserved; } ipfw_table_value; diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index 9c7a3bd8520f..a30f6bf9fef0 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -2387,13 +2387,48 @@ do { \ if (q == NULL || q->rule != f || dyn_dir == MATCH_FORWARD) { struct sockaddr_in *sa; + sa = &(((ipfw_insn_sa *)cmd)->sa); if (sa->sin_addr.s_addr == INADDR_ANY) { - bcopy(sa, &args->hopstore, - sizeof(*sa)); - args->hopstore.sin_addr.s_addr = - htonl(tablearg); - args->next_hop = &args->hopstore; +#ifdef INET6 + /* + * We use O_FORWARD_IP opcode for + * fwd rule with tablearg, but tables + * now support IPv6 addresses. And + * when we are inspecting IPv6 packet, + * we can use nh6 field from + * table_value as next_hop6 address. + */ + if (is_ipv6) { + struct sockaddr_in6 *sa6; + + sa6 = args->next_hop6 = + &args->hopstore6; + sa6->sin6_family = AF_INET6; + sa6->sin6_len = sizeof(*sa6); + sa6->sin6_addr = TARG_VAL( + chain, tablearg, nh6); + /* + * Set sin6_scope_id only for + * link-local unicast addresses. + */ + if (IN6_IS_ADDR_LINKLOCAL( + &sa6->sin6_addr)) + sa6->sin6_scope_id = + TARG_VAL(chain, + tablearg, + zoneid); + } else +#endif + { + sa = args->next_hop = + &args->hopstore; + sa->sin_family = AF_INET; + sa->sin_len = sizeof(*sa); + sa->sin_addr.s_addr = htonl( + TARG_VAL(chain, tablearg, + nh4)); + } } else { args->next_hop = sa; } diff --git a/sys/netpfil/ipfw/ip_fw_pfil.c b/sys/netpfil/ipfw/ip_fw_pfil.c index b53a8cec7d34..61f182d11be6 100644 --- a/sys/netpfil/ipfw/ip_fw_pfil.c +++ b/sys/netpfil/ipfw/ip_fw_pfil.c @@ -59,6 +59,7 @@ __FBSDID("$FreeBSD$"); #ifdef INET6 #include #include +#include #endif #include @@ -197,8 +198,20 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, } #ifdef INET6 if (args.next_hop6 != NULL) { - bcopy(args.next_hop6, (fwd_tag+1), len); - if (in6_localip(&args.next_hop6->sin6_addr)) + struct sockaddr_in6 *sa6; + + sa6 = (struct sockaddr_in6 *)(fwd_tag + 1); + bcopy(args.next_hop6, sa6, len); + /* + * If nh6 address is link-local we should convert + * it to kernel internal form before doing any + * comparisons. + */ + if (sa6_embedscope(sa6, V_ip6_use_defzone) != 0) { + ret = EACCES; + break; + } + if (in6_localip(&sa6->sin6_addr)) (*m0)->m_flags |= M_FASTFWD_OURS; (*m0)->m_flags |= M_IP6_NEXTHOP; } diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index 3f46ddd48f6c..bb5b3bab5ce3 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -102,7 +102,10 @@ struct ip_fw_args { struct inpcb *inp; struct _ip6dn_args dummypar; /* dummynet->ip6_output */ - struct sockaddr_in hopstore; /* store here if cannot use a pointer */ + union { /* store here if cannot use a pointer */ + struct sockaddr_in hopstore; + struct sockaddr_in6 hopstore6; + }; }; MALLOC_DECLARE(M_IPFW); @@ -294,11 +297,12 @@ struct table_value { uint32_t nat; /* O_NAT */ uint32_t nh4; uint8_t dscp; - uint8_t spare0[3]; + uint8_t spare0; + uint16_t spare1; /* -- 32 bytes -- */ struct in6_addr nh6; uint32_t limit; /* O_LIMIT */ - uint32_t spare1; + uint32_t zoneid; /* scope zone id for nh6 */ uint64_t refcnt; /* Number of references */ }; diff --git a/sys/netpfil/ipfw/ip_fw_table_value.c b/sys/netpfil/ipfw/ip_fw_table_value.c index 9d68901b3e45..c798ac1e43d0 100644 --- a/sys/netpfil/ipfw/ip_fw_table_value.c +++ b/sys/netpfil/ipfw/ip_fw_table_value.c @@ -117,6 +117,7 @@ mask_table_value(struct table_value *src, struct table_value *dst, _MCPY(dscp, IPFW_VTYPE_DSCP); _MCPY(nh4, IPFW_VTYPE_NH4); _MCPY(nh6, IPFW_VTYPE_NH6); + _MCPY(zoneid, IPFW_VTYPE_NH6); #undef _MCPY } @@ -666,6 +667,7 @@ ipfw_import_table_value_v1(ipfw_table_value *iv) v.nh4 = iv->nh4; v.nh6 = iv->nh6; v.limit = iv->limit; + v.zoneid = iv->zoneid; memcpy(iv, &v, sizeof(ipfw_table_value)); } @@ -691,6 +693,7 @@ ipfw_export_table_value_v1(struct table_value *v, ipfw_table_value *piv) iv.limit = v->limit; iv.nh4 = v->nh4; iv.nh6 = v->nh6; + iv.zoneid = v->zoneid; memcpy(piv, &iv, sizeof(iv)); }