/*
- * Copyright (c) 2000-2013 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2014 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <net/ntstat.h>
#include <net/net_osdep.h>
#include <net/dlil.h>
+#include <net/net_perf.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#endif
#endif /* IPSEC */
+#if NECP
+#include <net/necp.h>
+#endif /* NECP */
+
#if IPFIREWALL
#include <netinet/ip_fw.h>
#if IPDIVERT
u_short ip_id;
+static int sysctl_reset_ip_output_stats SYSCTL_HANDLER_ARGS;
+static int sysctl_ip_output_measure_bins SYSCTL_HANDLER_ARGS;
+static int sysctl_ip_output_getperf SYSCTL_HANDLER_ARGS;
static void ip_out_cksum_stats(int, u_int32_t);
static struct mbuf *ip_insertoptions(struct mbuf *, struct mbuf *, int *);
static int ip_optcopy(struct ip *, struct ip *);
CTLFLAG_RW | CTLFLAG_LOCKED, &ip_select_srcif_debug, 0,
"log source interface selection debug info");
+static int ip_output_measure = 0;
+SYSCTL_PROC(_net_inet_ip, OID_AUTO, output_perf,
+ CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
+ &ip_output_measure, 0, sysctl_reset_ip_output_stats, "I",
+ "Do time measurement");
+
+static uint64_t ip_output_measure_bins = 0;
+SYSCTL_PROC(_net_inet_ip, OID_AUTO, output_perf_bins,
+ CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED, &ip_output_measure_bins, 0,
+ sysctl_ip_output_measure_bins, "I",
+ "bins for chaining performance data histogram");
+
+static net_perf_t net_perf;
+SYSCTL_PROC(_net_inet_ip, OID_AUTO, output_perf_data,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED,
+ 0, 0, sysctl_ip_output_getperf, "S,net_perf",
+ "IP output performance data (struct net_perf, net/net_perf.h)");
+
#define IMO_TRACE_HIST_SIZE 32 /* size of trace history */
/* For gdb */
ipfilter_t inject_filter_ref = NULL;
struct mbuf *packetlist;
uint32_t sw_csum, pktcnt = 0, scnt = 0, bytecnt = 0;
+ uint32_t packets_processed = 0;
unsigned int ifscope = IFSCOPE_NONE;
struct flowadv *adv = NULL;
+ struct timeval start_tv;
#if IPSEC
struct socket *so = NULL;
struct secpolicy *sp = NULL;
#endif /* IPSEC */
+#if NECP
+ necp_kernel_policy_result necp_result = 0;
+ necp_kernel_policy_result_parameter necp_result_parameter;
+ necp_kernel_policy_id necp_matched_policy_id = 0;
+#endif /* NECP */
#if IPFIREWALL
int ipfwoff;
struct sockaddr_in *next_hop_from_ipfwd_tag = NULL;
#if IPSEC
struct ipsec_output_state ipsec_state;
#endif /* IPSEC */
+#if NECP
+ struct route necp_route;
+#endif /* NECP */
#if IPFIREWALL || DUMMYNET
struct ip_fw_args args;
#endif /* IPFIREWALL || DUMMYNET */
struct ipf_pktopts ipf_pktopts;
} ipobz;
#define ipsec_state ipobz.ipsec_state
+#define necp_route ipobz.necp_route
#define args ipobz.args
#define sro_fwd ipobz.sro_fwd
#define saved_route ipobz.saved_route
boolean_t nocell : 1; /* set once */
boolean_t isbroadcast : 1;
boolean_t didfilter : 1;
+ boolean_t noexpensive : 1; /* set once */
+ boolean_t awdl_unrestricted : 1; /* set once */
#if IPFIREWALL_FORWARD
boolean_t fwd_rewrite_src : 1;
#endif /* IPFIREWALL_FORWARD */
uint32_t raw;
} ipobf = { .raw = 0 };
+#define IP_CHECK_RESTRICTIONS(_ifp, _ipobf) \
+ (((_ipobf).nocell && IFNET_IS_CELLULAR(_ifp)) || \
+ ((_ipobf).noexpensive && IFNET_IS_EXPENSIVE(_ifp)) || \
+ (!(_ipobf).awdl_unrestricted && IFNET_IS_AWDL_RESTRICTED(_ifp)))
+
+ if (ip_output_measure)
+ net_perf_start_time(&net_perf, &start_tv);
KERNEL_DEBUG(DBG_FNC_IP_OUTPUT | DBG_FUNC_START, 0, 0, 0, 0, 0);
VERIFY(m0->m_flags & M_PKTHDR);
if (ipsec_bypass == 0 && !(flags & IP_NOIPSEC)) {
/* If packet is bound to an interface, check bound policies */
if ((flags & IP_OUTARGS) && (ipoa != NULL) &&
- (ipoa->ipoa_flags & IPOAF_BOUND_IF) &&
- ipoa->ipoa_boundif != IFSCOPE_NONE) {
+ (ipoa->ipoa_flags & IPOAF_BOUND_IF) &&
+ ipoa->ipoa_boundif != IFSCOPE_NONE) {
if (ipsec4_getpolicybyinterface(m, IPSEC_DIR_OUTBOUND,
- &flags, ipoa, &sp) != 0)
+ &flags, ipoa, &sp) != 0)
goto bad;
}
}
#endif /* IPSEC */
-
+
VERIFY(ro != NULL);
if (ip_doscopedroute && (flags & IP_OUTARGS)) {
}
}
- if ((flags & IP_OUTARGS) && (ipoa->ipoa_flags & IPOAF_NO_CELLULAR)) {
- ipobf.nocell = TRUE;
- ipf_pktopts.ippo_flags |= IPPOF_NO_IFT_CELLULAR;
- }
-
if (flags & IP_OUTARGS) {
+ if (ipoa->ipoa_flags & IPOAF_NO_CELLULAR) {
+ ipobf.nocell = TRUE;
+ ipf_pktopts.ippo_flags |= IPPOF_NO_IFT_CELLULAR;
+ }
+ if (ipoa->ipoa_flags & IPOAF_NO_EXPENSIVE) {
+ ipobf.noexpensive = TRUE;
+ ipf_pktopts.ippo_flags |= IPPOF_NO_IFF_EXPENSIVE;
+ }
+ if (ipoa->ipoa_flags & IPOAF_AWDL_UNRESTRICTED)
+ ipobf.awdl_unrestricted = TRUE;
adv = &ipoa->ipoa_flowadv;
adv->code = FADV_SUCCESS;
ipoa->ipoa_retflags = 0;
}
+
+#if IPSEC
+ if (ipsec_bypass == 0 && !(flags & IP_NOIPSEC)) {
+ so = ipsec_getsocket(m);
+ if (so != NULL) {
+ (void) ipsec_setsocket(m, NULL);
+ }
+ }
+#endif /* IPSEC */
#if DUMMYNET
if (args.fwa_ipfw_rule != NULL || args.fwa_pf_rule != NULL) {
}
RT_UNLOCK(ro->ro_rt);
}
-#if IPSEC
- if (ipsec_bypass == 0 && !(flags & IP_NOIPSEC)) {
- so = ipsec_getsocket(m);
- (void) ipsec_setsocket(m, NULL);
- }
-#endif /* IPSEC */
+
#if IPFIREWALL
if (args.fwa_ipfw_rule != NULL)
goto skip_ipsec;
}
#endif /* DUMMYNET */
-#if IPSEC
- if (ipsec_bypass == 0 && !(flags & IP_NOIPSEC)) {
- so = ipsec_getsocket(m);
- (void) ipsec_setsocket(m, NULL);
- }
-#endif /* IPSEC */
-
loopit:
+ packets_processed++;
ipobf.isbroadcast = FALSE;
ipobf.didfilter = FALSE;
#if IPFIREWALL_FORWARD
ia0 = in_selectsrcif(ip, ro, ifscope);
/*
- * If the source address belongs to a cellular interface
- * and the caller forbids our using interfaces of such
- * type, pretend that there is no route.
+ * If the source address belongs to a restricted
+ * interface and the caller forbids our using
+ * interfaces of such type, pretend that there is no
+ * route.
*/
- if (ipobf.nocell && ia0 != NULL &&
- IFNET_IS_CELLULAR(ia0->ifa_ifp)) {
+ if (ia0 != NULL &&
+ IP_CHECK_RESTRICTIONS(ia0->ifa_ifp, ipobf)) {
IFA_REMREF(ia0);
ia0 = NULL;
error = EHOSTUNREACH;
rtalloc_scoped_ign(ro, ign, ifscope);
/*
- * If the route points to a cellular interface and the
- * caller forbids our using interfaces of such type,
+ * If the route points to a cellular/expensive interface
+ * and the caller forbids our using interfaces of such type,
* pretend that there is no route.
*/
- if (ipobf.nocell && ro->ro_rt != NULL) {
+ if (ro->ro_rt != NULL) {
RT_LOCK_SPIN(ro->ro_rt);
- if (IFNET_IS_CELLULAR(ro->ro_rt->rt_ifp)) {
+ if (IP_CHECK_RESTRICTIONS(ro->ro_rt->rt_ifp,
+ ipobf)) {
RT_UNLOCK(ro->ro_rt);
ROUTE_RELEASE(ro);
if (flags & IP_OUTARGS) {
if (imo->imo_multicast_ifp != NULL)
ifp = imo->imo_multicast_ifp;
IMO_UNLOCK(imo);
-#if MROUTING
- if (vif != -1 && (!(flags & IP_RAWOUTPUT) ||
- ip->ip_src.s_addr == INADDR_ANY))
- ip->ip_src.s_addr = ip_mcast_src(vif);
-#endif /* MROUTING */
} else if (!(flags & IP_RAWOUTPUT)) {
vif = -1;
ip->ip_ttl = ttl;
}
ip_mloopback(srcifp, ifp, m, dst, hlen);
}
-#if MROUTING
- else {
- /*
- * If we are acting as a multicast router, perform
- * multicast forwarding as if the packet had just
- * arrived on the interface to which we are about
- * to send. The multicast forwarding function
- * recursively calls this function, using the
- * IP_FORWARDING flag to prevent infinite recursion.
- *
- * Multicasts that are looped back by ip_mloopback(),
- * above, will be forwarded by the ip_input() routine,
- * if necessary.
- */
- if (ip_mrouter && !(flags & IP_FORWARDING)) {
- /*
- * Check if rsvp daemon is running. If not,
- * don't set ip_moptions. This ensures that
- * the packet is multicast and not just sent
- * down one link as prescribed by rsvpd.
- */
- if (!rsvp_on)
- imo = NULL;
- if (ip_mforward(ip, ifp, m, imo) != 0) {
- m_freem(m);
- if (inm != NULL)
- INM_REMREF(inm);
- OSAddAtomic(1, &ipstat.ips_cantforward);
- goto done;
- }
- }
- }
-#endif /* MROUTING */
if (inm != NULL)
INM_REMREF(inm);
/*
ipf_unref();
}
-#if IPSEC
- /* temporary for testing only: bypass ipsec alltogether */
+#if NECP
+ /* Process Network Extension Policy. Will Pass, Drop, or Rebind packet. */
+ necp_matched_policy_id = necp_ip_output_find_policy_match (m,
+ flags, (flags & IP_OUTARGS) ? ipoa : NULL, &necp_result, &necp_result_parameter);
+ if (necp_matched_policy_id) {
+ necp_mark_packet_from_ip(m, necp_matched_policy_id);
+ switch (necp_result) {
+ case NECP_KERNEL_POLICY_RESULT_PASS:
+ /* Check if the interface is allowed */
+ if (!necp_packet_is_allowed_over_interface(m, ifp)) {
+ error = EHOSTUNREACH;
+ goto bad;
+ }
+ goto skip_ipsec;
+ case NECP_KERNEL_POLICY_RESULT_DROP:
+ case NECP_KERNEL_POLICY_RESULT_SOCKET_DIVERT:
+ /* Flow divert packets should be blocked at the IP layer */
+ error = EHOSTUNREACH;
+ goto bad;
+ case NECP_KERNEL_POLICY_RESULT_IP_TUNNEL: {
+ /* Verify that the packet is being routed to the tunnel */
+ struct ifnet *policy_ifp = necp_get_ifnet_from_result_parameter(&necp_result_parameter);
+ if (policy_ifp == ifp) {
+ /* Check if the interface is allowed */
+ if (!necp_packet_is_allowed_over_interface(m, ifp)) {
+ error = EHOSTUNREACH;
+ goto bad;
+ }
+ goto skip_ipsec;
+ } else {
+ if (necp_packet_can_rebind_to_ifnet(m, policy_ifp, &necp_route, AF_INET)) {
+ /* Check if the interface is allowed */
+ if (!necp_packet_is_allowed_over_interface(m, policy_ifp)) {
+ error = EHOSTUNREACH;
+ goto bad;
+ }
+
+ /* Set ifp to the tunnel interface, since it is compatible with the packet */
+ ifp = policy_ifp;
+ ro = &necp_route;
+ goto skip_ipsec;
+ } else {
+ error = ENETUNREACH;
+ goto bad;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ /* Catch-all to check if the interface is allowed */
+ if (!necp_packet_is_allowed_over_interface(m, ifp)) {
+ error = EHOSTUNREACH;
+ goto bad;
+ }
+#endif /* NECP */
+#if IPSEC
if (ipsec_bypass != 0 || (flags & IP_NOIPSEC))
goto skip_ipsec;
KERNEL_DEBUG(DBG_FNC_IPSEC4_OUTPUT | DBG_FUNC_START, 0, 0, 0, 0, 0);
- /* May have been set above if packet was bound */
if (sp == NULL) {
/* get SP for this packet */
- if (so == NULL)
- sp = ipsec4_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND,
- flags, &error);
- else
+ if (so != NULL) {
sp = ipsec4_getpolicybysock(m, IPSEC_DIR_OUTBOUND,
- so, &error);
-
+ so, &error);
+ } else {
+ sp = ipsec4_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND,
+ flags, &error);
+ }
if (sp == NULL) {
IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
KERNEL_DEBUG(DBG_FNC_IPSEC4_OUTPUT | DBG_FUNC_END,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0);
goto bad;
}
}
if (sp->ipsec_if) {
/* Verify the redirect to ipsec interface */
if (sp->ipsec_if == ifp) {
- /* Set policy for mbuf */
- m->m_pkthdr.ipsec_policy = sp->id;
goto skip_ipsec;
}
goto bad;
struct ip *, ip, struct ip6_hdr *, NULL);
error = ipsec4_output(&ipsec_state, sp, flags);
+ if (ipsec_state.tunneled == 6) {
+ m0 = m = NULL;
+ error = 0;
+ goto bad;
+ }
m0 = m = ipsec_state.m;
((ntohl(ip->ip_src.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET ||
(ntohl(ip->ip_dst.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) {
OSAddAtomic(1, &ipstat.ips_badaddr);
- m_freem(m);
error = EADDRNOTAVAIL;
- goto done;
+ goto bad;
}
ip_output_checksum(ifp, m, (IP_VHL_HL(ip->ip_vhl) << 2),
error = dlil_output(ifp, PF_INET, m, ro->ro_rt,
SA(dst), 0, adv);
+ if (dlil_verbose && error) {
+ printf("dlil_output error on interface %s: %d\n",
+ ifp->if_xname, error);
+ }
scnt = 0;
goto done;
} else {
error = dlil_output(ifp, PF_INET, packetlist,
ro->ro_rt, SA(dst), 0, adv);
+ if (dlil_verbose && error) {
+ printf("dlil_output error on interface %s: %d\n",
+ ifp->if_xname, error);
+ }
pktcnt = 0;
scnt = 0;
bytecnt = 0;
}
error = dlil_output(ifp, PF_INET, m, ro->ro_rt,
SA(dst), 0, adv);
+ if (dlil_verbose && error) {
+ printf("dlil_output error on interface %s: %d\n",
+ ifp->if_xname, error);
+ }
} else {
m_freem(m);
}
key_freesp(sp, KEY_SADB_UNLOCKED);
}
#endif /* IPSEC */
+#if NECP
+ ROUTE_RELEASE(&necp_route);
+#endif /* NECP */
#if DUMMYNET
ROUTE_RELEASE(&saved_route);
#endif /* DUMMYNET */
#endif /* IPFIREWALL_FORWARD */
KERNEL_DEBUG(DBG_FNC_IP_OUTPUT | DBG_FUNC_END, error, 0, 0, 0, 0);
+ if (ip_output_measure) {
+ net_perf_measure_time(&net_perf, &start_tv, packets_processed);
+ net_perf_histogram(&net_perf, packets_processed);
+ }
return (error);
bad:
- m_freem(m0);
+ if (pktcnt > 0)
+ m0 = packetlist;
+ m_freem_list(m0);
goto done;
#undef ipsec_state
#undef sro_fwd
#undef saved_route
#undef ipf_pktopts
+#undef IP_CHECK_RESTRICTIONS
}
int
int priv;
struct mbuf *m;
int optname;
-
+
if ((error = soopt_getm(sopt, &m)) != 0) /* XXX */
break;
if ((error = soopt_mcopyin(sopt, m)) != 0) /* XXX */
break;
/* once set, it cannot be unset */
- if (!optval && (inp->inp_flags & INP_NO_IFT_CELLULAR)) {
+ if (!optval && INP_NO_CELLULAR(inp)) {
error = EINVAL;
break;
}
#if IPSEC
case IP_IPSEC_POLICY: {
- struct mbuf *m = NULL;
- caddr_t req = NULL;
- size_t len = 0;
-
- if (m != NULL) {
- req = mtod(m, caddr_t);
- len = m->m_len;
- }
- error = ipsec4_get_policy(sotoinpcb(so), req, len, &m);
- if (error == 0)
- error = soopt_mcopyout(sopt, m); /* XXX */
- if (error == 0)
- m_freem(m);
+ error = 0; /* This option is no longer supported */
break;
}
#endif /* IPSEC */
break;
case IP_NO_IFT_CELLULAR:
- optval = (inp->inp_flags & INP_NO_IFT_CELLULAR) ? 1 : 0;
+ optval = INP_NO_CELLULAR(inp) ? 1 : 0;
error = sooptcopyout(sopt, &optval, sizeof (optval));
break;
return (error);
}
+
+static int
+sysctl_reset_ip_output_stats SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+ int error, i;
+
+ i = ip_output_measure;
+ error = sysctl_handle_int(oidp, &i, 0, req);
+ if (error || req->newptr == USER_ADDR_NULL)
+ goto done;
+ /* impose bounds */
+ if (i < 0 || i > 1) {
+ error = EINVAL;
+ goto done;
+ }
+ if (ip_output_measure != i && i == 1) {
+ net_perf_initialize(&net_perf, ip_output_measure_bins);
+ }
+ ip_output_measure = i;
+done:
+ return (error);
+}
+
+static int
+sysctl_ip_output_measure_bins SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+ int error;
+ uint64_t i;
+
+ i = ip_output_measure_bins;
+ error = sysctl_handle_quad(oidp, &i, 0, req);
+ if (error || req->newptr == USER_ADDR_NULL)
+ goto done;
+ /* validate data */
+ if (!net_perf_validate_bins(i)) {
+ error = EINVAL;
+ goto done;
+ }
+ ip_output_measure_bins = i;
+done:
+ return (error);
+}
+
+static int
+sysctl_ip_output_getperf SYSCTL_HANDLER_ARGS
+{
+#pragma unused(oidp, arg1, arg2)
+ if (req->oldptr == USER_ADDR_NULL)
+ req->oldlen = (size_t)sizeof (struct ipstat);
+
+ return (SYSCTL_OUT(req, &net_perf, MIN(sizeof (net_perf), req->oldlen)));
+}
+