1/*
2 * Copyright (c) 2013 Apple Inc. All rights reserved.
3 */
4#include <arpa/inet.h>
5#include <SystemConfiguration/SCPrivate.h>
6
7#include "reachability.h"
8#include "scnc_main.h"
9
10static ReachabilityChangedBlock	g_cb_block			= NULL;
11static CFRunLoopRef				g_cb_runloop		= NULL;
12static CFTypeRef				g_cb_runloop_mode	= NULL;
13static dispatch_queue_t			g_reach_queue		= NULL;
14
15static void
16reachability_target_dispose(SCNetworkReachabilityRef target)
17{
18	dispatch_async(g_reach_queue, ^{
19		SCNetworkReachabilitySetCallback(target, NULL, NULL);
20		SCNetworkReachabilitySetDispatchQueue(target, NULL);
21		CFRelease(target);
22	});
23}
24
25static void
26remote_address_reachability_callback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
27{
28	CFStringRef service_id = (CFStringRef)info;
29	int reach_if_index;
30
31	CFRetain(target);
32	CFRetain(service_id);
33
34	SCNetworkReachabilityGetFlags(target, &flags);
35	reach_if_index = SCNetworkReachabilityGetInterfaceIndex(target);
36
37	CFRunLoopPerformBlock(g_cb_runloop, g_cb_runloop_mode, ^{
38		struct service *serv = findbyserviceID(service_id);
39		if (serv != NULL && serv->remote_address_reachability == target) {
40			serv->remote_address_reach_flags = flags;
41			serv->remote_address_reach_ifindex = reach_if_index;
42			g_cb_block(serv);
43		}
44		CFRelease(target);
45		CFRelease(service_id);
46	});
47	CFRunLoopWakeUp(g_cb_runloop);
48}
49
50void
51reachability_init(CFRunLoopRef cb_runloop, CFTypeRef cb_runloop_mode, ReachabilityChangedBlock cb_block)
52{
53	static dispatch_once_t init_reachability = 0;
54
55	dispatch_once(&init_reachability, ^{
56		g_reach_queue = dispatch_queue_create("PPPController reachability dispatch queue", NULL);
57		g_cb_runloop = cb_runloop;
58		CFRetain(g_cb_runloop);
59		g_cb_runloop_mode = cb_runloop_mode;
60		CFRetain(g_cb_runloop_mode);
61		g_cb_block = Block_copy(cb_block);
62	});
63}
64
65void
66reachability_clear(struct service *serv)
67{
68	if (serv->remote_address_reachability != NULL) {
69		reachability_target_dispose(serv->remote_address_reachability);
70	}
71	serv->remote_address_reachability = NULL;
72	serv->remote_address_reach_flags = 0;
73	serv->remote_address_reach_ifindex = -1;
74}
75
76void
77reachability_reset(struct service *serv)
78{
79	CFStringRef remote_address_key = NULL;
80
81	reachability_clear(serv);
82
83	switch (serv->type) {
84		case TYPE_PPP:
85			remote_address_key = kSCPropNetPPPCommRemoteAddress;
86			break;
87		case TYPE_IPSEC:
88			remote_address_key = kSCPropNetIPSecRemoteAddress;
89			break;
90		case TYPE_VPN:
91			remote_address_key = kSCPropNetVPNRemoteAddress;
92			break;
93	}
94
95	if (remote_address_key == NULL) {
96		return;
97	}
98
99	CFStringRef remote_address = CFDictionaryGetValue(serv->systemprefs, remote_address_key);
100	if (isA_CFString(remote_address) && CFStringGetLength(remote_address) > 0) {
101		CFCharacterSetRef slash_set = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, CFSTR("/"));
102		CFCharacterSetRef colon_set = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, CFSTR(":"));
103		CFRange slash_range;
104		CFRange colon_range;
105		char *addr_cstr;
106		CFIndex addr_cstr_len;
107		struct sockaddr_storage sa_storage;
108		CFMutableDictionaryRef reach_options;
109		CFStringRef service_id;
110
111		CFRetain(remote_address);
112
113		memset(&sa_storage, 0, sizeof(sa_storage));
114
115		if (CFStringFindCharacterFromSet(remote_address,
116		                                 slash_set,
117		                                 CFRangeMake(0, CFStringGetLength(remote_address)),
118		                                 0,
119		                                 &slash_range))
120		{
121			CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, remote_address, NULL);
122			if (url != NULL) {
123				/* Check to see if there is a scheme on the URL. If not, add one. The hostname will not parse without a scheme */
124				CFRange schemeRange = CFURLGetByteRangeForComponent(url, kCFURLComponentScheme, NULL);
125				if (schemeRange.location == kCFNotFound) {
126					CFRelease(url);
127					CFStringRef address = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("https://%@"), remote_address);
128					url = CFURLCreateWithString(kCFAllocatorDefault, address, NULL);
129					CFRelease(address);
130				}
131				if (url != NULL) {
132					CFRelease(remote_address);
133					remote_address = CFURLCopyHostName(url);
134					CFRelease(url);
135				}
136			}
137		} else if (CFStringFindCharacterFromSet(remote_address,
138		                                        colon_set,
139		                                        CFRangeMake(0, CFStringGetLength(remote_address)),
140		                                        0,
141		                                        &colon_range))
142		{
143			CFStringRef address = CFStringCreateWithSubstring(kCFAllocatorDefault,
144					remote_address,
145					CFRangeMake(0, colon_range.location));
146			CFRelease(remote_address);
147			remote_address = address;
148		}
149
150		CFRelease(slash_set);
151		CFRelease(colon_set);
152
153		if (remote_address == NULL) {
154			return;
155		}
156
157		addr_cstr_len = CFStringGetLength(remote_address); /* Assume that the address is ASCII */
158		addr_cstr = CFAllocatorAllocate(kCFAllocatorDefault, addr_cstr_len + 1, 0);
159
160		CFStringGetCString(remote_address, addr_cstr, addr_cstr_len, kCFStringEncodingASCII);
161
162		reach_options = CFDictionaryCreateMutable(kCFAllocatorDefault,
163		                                          0,
164		                                          &kCFTypeDictionaryKeyCallBacks,
165		                                          &kCFTypeDictionaryValueCallBacks);
166
167		if (inet_pton(AF_INET, addr_cstr, &((struct sockaddr_in *)&sa_storage)->sin_addr) == 1) {
168			CFDataRef addr_data;
169			struct sockaddr_in *sa_in = (struct sockaddr_in *)&sa_storage;
170			sa_in->sin_family = AF_INET;
171			sa_in->sin_len = sizeof(struct sockaddr_in);
172			addr_data = CFDataCreate(kCFAllocatorDefault, (const uint8_t *)sa_in, sa_in->sin_len);
173			CFDictionarySetValue(reach_options, kSCNetworkReachabilityOptionRemoteAddress, addr_data);
174			CFRelease(addr_data);
175		} else if (inet_pton(AF_INET6, addr_cstr, &((struct sockaddr_in6 *)&sa_storage)->sin6_addr) == 1) {
176			CFDataRef addr_data;
177			struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)&sa_storage;
178			sa_in6->sin6_family = AF_INET6;
179			sa_in6->sin6_len = sizeof(struct sockaddr_in6);
180			addr_data = CFDataCreate(kCFAllocatorDefault, (const uint8_t *)sa_in6, sa_in6->sin6_len);
181			CFDictionarySetValue(reach_options, kSCNetworkReachabilityOptionRemoteAddress, addr_data);
182			CFRelease(addr_data);
183		} else {
184			CFDictionarySetValue(reach_options, kSCNetworkReachabilityOptionNodeName, remote_address);
185		}
186
187		CFRelease(remote_address);
188		CFAllocatorDeallocate(kCFAllocatorDefault, addr_cstr);
189
190		CFDictionarySetValue(reach_options, kSCNetworkReachabilityOptionConnectionOnDemandBypass, kCFBooleanTrue);
191
192		service_id = serv->serviceID;
193		CFRetain(service_id);
194
195		dispatch_async(g_reach_queue, ^{
196			SCNetworkReachabilityRef reach_ref = SCNetworkReachabilityCreateWithOptions(kCFAllocatorDefault, reach_options);
197			CFRelease(reach_options);
198
199			if (reach_ref != NULL) {
200				SCNetworkReachabilityContext reach_ctx = { 0, (void *)service_id, CFRetain, CFRelease, CFCopyDescription };
201				SCNetworkReachabilitySetCallback(reach_ref, remote_address_reachability_callback, &reach_ctx);
202				SCNetworkReachabilitySetDispatchQueue(reach_ref, g_reach_queue);
203
204				CFRunLoopPerformBlock(g_cb_runloop, g_cb_runloop_mode, ^{
205					struct service *serv = findbyserviceID(service_id);
206					Boolean retained = FALSE;
207					if (serv != NULL) {
208						if (serv->remote_address_reachability != NULL) {
209							reachability_target_dispose(serv->remote_address_reachability);
210							serv->remote_address_reachability = NULL;
211						}
212						serv->remote_address_reachability = reach_ref;
213						retained = TRUE;
214						dispatch_async(g_reach_queue, ^{
215							remote_address_reachability_callback(reach_ref, 0, (void *)service_id);
216						});
217					}
218
219					if (!retained) {
220						reachability_target_dispose(reach_ref);
221					}
222				});
223				CFRunLoopWakeUp(g_cb_runloop);
224			} else {
225				SCLog(TRUE, LOG_ERR, CFSTR("reset_reachability: failed to create a reachability target for %@"), remote_address);
226			}
227			CFRelease(service_id);
228		});
229	}
230}
231
232