+ return 0;
+}
+
+#define APN_FALLBACK_IP_FILTER(a) \
+ (IN_LINKLOCAL(ntohl((a)->sin_addr.s_addr)) || \
+ IN_LOOPBACK(ntohl((a)->sin_addr.s_addr)) || \
+ IN_ZERONET(ntohl((a)->sin_addr.s_addr)) || \
+ IN_MULTICAST(ntohl((a)->sin_addr.s_addr)) || \
+ IN_PRIVATE(ntohl((a)->sin_addr.s_addr)))
+
+#define APN_FALLBACK_NOTIF_INTERVAL 2 /* Magic Number */
+static uint64_t last_apn_fallback = 0;
+
+static boolean_t
+apn_fallback_required(proc_t proc, struct socket *so, struct sockaddr_in *p_dstv4)
+{
+ uint64_t timenow;
+ struct sockaddr_storage lookup_default_addr;
+ struct rtentry *rt = NULL;
+
+ VERIFY(proc != NULL);
+
+ if (apn_fallbk_enabled == FALSE) {
+ return FALSE;
+ }
+
+ if (proc == kernproc) {
+ return FALSE;
+ }
+
+ if (so && (so->so_options & SO_NOAPNFALLBK)) {
+ return FALSE;
+ }
+
+ timenow = net_uptime();
+ if ((timenow - last_apn_fallback) < APN_FALLBACK_NOTIF_INTERVAL) {
+ apn_fallbk_log((LOG_INFO, "APN fallback notification throttled.\n"));
+ return FALSE;
+ }
+
+ if (p_dstv4 && APN_FALLBACK_IP_FILTER(p_dstv4)) {
+ return FALSE;
+ }
+
+ /* Check if we have unscoped IPv6 default route through cellular */
+ bzero(&lookup_default_addr, sizeof(lookup_default_addr));
+ lookup_default_addr.ss_family = AF_INET6;
+ lookup_default_addr.ss_len = sizeof(struct sockaddr_in6);
+
+ rt = rtalloc1((struct sockaddr *)&lookup_default_addr, 0, 0);
+ if (NULL == rt) {
+ apn_fallbk_log((LOG_INFO, "APN fallback notification could not find "
+ "unscoped default IPv6 route.\n"));
+ return FALSE;
+ }
+
+ if (!IFNET_IS_CELLULAR(rt->rt_ifp)) {
+ rtfree(rt);
+ apn_fallbk_log((LOG_INFO, "APN fallback notification could not find "
+ "unscoped default IPv6 route through cellular interface.\n"));
+ return FALSE;
+ }
+
+ /*
+ * We have a default IPv6 route, ensure that
+ * we do not have IPv4 default route before triggering
+ * the event
+ */
+ rtfree(rt);
+ rt = NULL;
+
+ bzero(&lookup_default_addr, sizeof(lookup_default_addr));
+ lookup_default_addr.ss_family = AF_INET;
+ lookup_default_addr.ss_len = sizeof(struct sockaddr_in);
+
+ rt = rtalloc1((struct sockaddr *)&lookup_default_addr, 0, 0);
+
+ if (rt) {
+ rtfree(rt);
+ rt = NULL;
+ apn_fallbk_log((LOG_INFO, "APN fallback notification found unscoped "
+ "IPv4 default route!\n"));
+ return FALSE;
+ }
+
+ {
+ /*
+ * We disable APN fallback if the binary is not a third-party app.
+ * Note that platform daemons use their process name as a
+ * bundle ID so we filter out bundle IDs without dots.
+ */
+ const char *bundle_id = cs_identity_get(proc);
+ if (bundle_id == NULL ||
+ bundle_id[0] == '\0' ||
+ strchr(bundle_id, '.') == NULL ||
+ strncmp(bundle_id, "com.apple.", sizeof("com.apple.") - 1) == 0) {
+ apn_fallbk_log((LOG_INFO, "Abort: APN fallback notification found first-"
+ "party bundle ID \"%s\"!\n", (bundle_id ? bundle_id : "NULL")));
+ return FALSE;
+ }
+ }
+
+ {
+ /*
+ * The Apple App Store IPv6 requirement started on
+ * June 1st, 2016 at 12:00:00 AM PDT.
+ * We disable APN fallback if the binary is more recent than that.
+ * We check both atime and birthtime since birthtime is not always supported.
+ */
+ static const long ipv6_start_date = 1464764400L;
+ vfs_context_t context;
+ struct stat64 sb;
+ int vn_stat_error;
+
+ bzero(&sb, sizeof(struct stat64));
+ context = vfs_context_create(NULL);
+ vn_stat_error = vn_stat(proc->p_textvp, &sb, NULL, 1, 0, context);
+ (void)vfs_context_rele(context);
+
+ if (vn_stat_error != 0 ||
+ sb.st_atimespec.tv_sec >= ipv6_start_date ||
+ sb.st_birthtimespec.tv_sec >= ipv6_start_date) {
+ apn_fallbk_log((LOG_INFO, "Abort: APN fallback notification found binary "
+ "too recent! (err %d atime %ld mtime %ld ctime %ld birthtime %ld)\n",
+ vn_stat_error, sb.st_atimespec.tv_sec, sb.st_mtimespec.tv_sec,
+ sb.st_ctimespec.tv_sec, sb.st_birthtimespec.tv_sec));
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+apn_fallback_trigger(proc_t proc, struct socket *so)
+{
+ pid_t pid = 0;
+ struct kev_msg ev_msg;
+ struct kev_netevent_apnfallbk_data apnfallbk_data;
+
+ last_apn_fallback = net_uptime();
+ pid = proc_pid(proc);
+ uuid_t application_uuid;
+ uuid_clear(application_uuid);
+ proc_getexecutableuuid(proc, application_uuid,
+ sizeof(application_uuid));
+
+ bzero(&ev_msg, sizeof(struct kev_msg));
+ ev_msg.vendor_code = KEV_VENDOR_APPLE;
+ ev_msg.kev_class = KEV_NETWORK_CLASS;
+ ev_msg.kev_subclass = KEV_NETEVENT_SUBCLASS;
+ ev_msg.event_code = KEV_NETEVENT_APNFALLBACK;
+
+ bzero(&apnfallbk_data, sizeof(apnfallbk_data));
+
+ if (so->so_flags & SOF_DELEGATED) {
+ apnfallbk_data.epid = so->e_pid;
+ uuid_copy(apnfallbk_data.euuid, so->e_uuid);
+ } else {
+ apnfallbk_data.epid = so->last_pid;
+ uuid_copy(apnfallbk_data.euuid, so->last_uuid);
+ }
+
+ ev_msg.dv[0].data_ptr = &apnfallbk_data;
+ ev_msg.dv[0].data_length = sizeof(apnfallbk_data);
+ kev_post_msg(&ev_msg);
+ apn_fallbk_log((LOG_INFO, "APN fallback notification issued.\n"));