Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel copy-Info.plist
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel copy-Info.plist	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel copy-Info.plist	(working copy)
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleDisplayName</key>
+	<string>iOSTunnel</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>XPC!</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(MARKETING_VERSION)</string>
+	<key>CFBundleVersion</key>
+	<string>137</string>
+	<key>NSExtension</key>
+	<dict>
+		<key>NSExtensionPointIdentifier</key>
+		<string>com.apple.networkextension.packet-tunnel</string>
+		<key>NSExtensionPrincipalClass</key>
+		<string>PacketTunnelProvider</string>
+	</dict>
+</dict>
+</plist>

Property changes on: iOSTunnel copy-Info.plist
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/Info.plist
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/Info.plist	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/Info.plist	(working copy)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>NSExtension</key>
+	<dict>
+		<key>NSExtensionPointIdentifier</key>
+		<string>com.apple.networkextension.packet-tunnel</string>
+		<key>NSExtensionPrincipalClass</key>
+		<string>PacketTunnelProvider</string>
+	</dict>
+</dict>
+</plist>
Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.h	(working copy)
@@ -0,0 +1,42 @@
+//
+//  PacketTunnelProvider.m
+//  iOSTunnel
+//
+//  Created by Apple on 2023/10/20.
+//
+
+typedef enum {
+    kPluginStartInvliad = -1,
+    kPluginStartByContainer,
+    kPluginStartOnDemand
+} PluginStartMethod;
+
+typedef enum {
+    kDNSResolveDefault,
+    kDNSResolveSplit            // Internal domains will be only resolved by VPN DNS.
+} DNSResolveType;
+
+typedef enum {
+    kIPCTypeInvalid = -1,
+    kIPCTypeStatisticsRequest,
+    kIPCTypeStatisticsResponse,
+    kIPCTypeLogPathRequest,
+    kIPCTypeLogPathResponse,
+} IPCType;
+
+#define DNS_RESOLVE_METHOD_SPLIT   inet_addr("0.0.0.2")
+
+#define TOTAL_SPECIAL_ROUTES       1
+
+@import NetworkExtension;
+
+@interface PacketTunnelProvider : NEPacketTunnelProvider
+
+@property (nonatomic) BOOL isOnDemand;
+@property (nonatomic) BOOL isFullTunnel;
+@property (nonatomic) BOOL isEnableSplitDnsSearch;
+
+- (void)stopTunnelIfOnDemand;
+- (void)stopTunnelDirectWithError:(NSError *)error;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/PacketTunnelProvider.m	(working copy)
@@ -0,0 +1,1209 @@
+//
+//  PacketTunnelProvider.m
+//  iOSTunnel
+//
+//  Created by Apple on 2023/10/20.
+//
+
+#import <net/if.h>
+#import <ifaddrs.h>
+#import <arpa/inet.h>
+#import <dns.h>
+#import <resolv.h>
+#import <SystemConfiguration/SCNetworkReachability.h>
+
+#import "ANLogger.h"
+#import "arrayapi.h"
+#import "arrayapi_ex.h"
+#import "vpn_error.h"
+#import "vpnplugin.h"
+#import "PacketTunnelProvider.h"
+
+#define ERROR_SUCCESS       0
+
+#define PLUGIN_ARG_GET(dict, output, key, type, logformat, s) do { \
+    s =  (NSString *)[dict objectForKey:(__bridge NSString *)key]; \
+        if (s) {  \
+        NSLog(logformat, s); \
+        output = (type)[s integerValue]; \
+    } \
+} while(0)
+
+
+NSString * const kAppGroupNamePacket = @"group.net.arraynetworks.MotionProPlus";
+
+
+typedef void (^startCompletionHandler)(NSError *error);
+static startCompletionHandler startHandler = nil;
+static PacketTunnelProvider *tunnelProvider = nil;  // Point to this instance
+
+#pragma mark - Networks Monitor
+
+static SCNetworkReachabilityRef networkReachability;
+static SCNetworkConnectionFlags lastNetworkReachabilityFlag;
+
+static void networkReachabilityCallback(SCNetworkReachabilityRef target,
+                                        SCNetworkConnectionFlags flags,
+                                        void *object) {
+    // Observed flags:
+    // - nearly gone: kSCNetworkFlagsReachable alone (ignored)
+    // - gone: kSCNetworkFlagsTransientConnection | kSCNetworkFlagsReachable | kSCNetworkFlagsConnectionRequired
+    // - connected: kSCNetworkFlagsIsDirect | kSCNetworkFlagsReachable
+    
+    if (lastNetworkReachabilityFlag == flags) {
+        return;
+    }
+    
+    if (flags == (lastNetworkReachabilityFlag | kSCNetworkReachabilityFlagsTransientConnection)) {
+        ANInfo(@"VPN notification, ignore");
+        return;
+    }
+    
+    lastNetworkReachabilityFlag = flags;
+    
+    if ((flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired) && !(flags & kSCNetworkFlagsIsDirect)) {
+        ANInfo(@"Network available :0x%x", flags);
+        array_vpn_reconnect();
+    } else {
+        ANInfo(@"Network unavailable :0x%x", flags);
+    }
+}
+
+static int isIPv6Server(void)
+{
+    char buf[256];
+    
+    bzero(buf, sizeof(buf));
+    array_vpn_get_server_ipv6(buf, sizeof(struct sockaddr_in6));
+    
+    ANDebug(@"Virtual site ipv6 address: %s.", buf);
+    
+    if (!strcmp(buf, "::") && strlen(buf) == 2) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static void stopNotifier(void)
+{
+    if (networkReachability) {
+        SCNetworkReachabilityUnscheduleFromRunLoop(networkReachability, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+        
+        CFRelease(networkReachability);
+        networkReachability = NULL;
+    }
+}
+
+static void startNotifier(const char *serverName)
+{
+    if (networkReachability) {
+        stopNotifier();
+    }
+    
+    if (isIPv6Server()) {
+        ANInfo(@"Don't supervise reachability in IPv6 site.");
+        return;
+    }
+    
+    ANInfo(@"Start network notifier: %s", serverName);
+    networkReachability = SCNetworkReachabilityCreateWithName(NULL, serverName);
+    
+    if (networkReachability == NULL)
+        goto fail;
+    
+    lastNetworkReachabilityFlag = -1;
+    
+    // Get current flag
+    if (!SCNetworkReachabilityGetFlags(networkReachability, &lastNetworkReachabilityFlag)) {
+        goto fail;
+    }
+    
+    ANInfo(@"Current network flag: %d", lastNetworkReachabilityFlag);
+    
+    if (!SCNetworkReachabilitySetCallback(networkReachability, networkReachabilityCallback, NULL)) {
+        goto fail;
+    }
+    
+    if (!SCNetworkReachabilityScheduleWithRunLoop(networkReachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes)) {
+        goto fail;
+    }
+    
+    return;
+    
+fail:
+    if (networkReachability != NULL) {
+        CFRelease(networkReachability);
+        networkReachability = NULL;
+    }
+}
+
+/* timeout values format "2 3 5 8 10" */
+static void get_connect_timeout_values(const char *data, uint8_t *values)
+{
+    const char          *p = data;
+    int                  i = 0;
+    int                  val;
+    
+    while (p && (*p) && isspace(*p)) p++;
+    
+    while (p && (*p) && i < VPN_CONN_TIMEOUT_ARRAY_MAX_LEN) {
+        val = (int)strtol(p, NULL, 10);
+        if (val < 1 || val > 255) break;
+        values[i] = (uint8_t)val;
+        
+        while (isdigit(*p)) p++;
+        while ((*p) && isspace(*p)) p++;
+        ++i;
+    }
+}
+
+static NSMutableArray *get_vpn_search_domain(const char *domain_list)
+{
+    NSMutableArray      *domains;
+    NSString            *str;
+    const char          *start;
+    char                *end;
+    char                buf[256];
+    
+    domains = [[NSMutableArray alloc] init];
+    if (!domains) return domains;
+    
+    start = domain_list;
+    end = strchr(start, ',');
+    
+    while (end) {
+        if ((end - start) > sizeof(buf)-1) {
+            start = end + 1;
+            end = strchr(start, ',');
+            continue;
+        }
+        
+        memcpy(buf, start, end - start);
+        buf[end-start] = 0;
+        start = end + 1;
+        end = strchr(start, ',');
+        
+        str = [NSString stringWithUTF8String:buf];
+        if (!str) goto failed;
+        [domains addObject:str];
+    }
+    
+    if (start && !end) {
+        memset(buf, 0, sizeof(buf));
+        strncpy(buf, start, sizeof(buf)-1);
+        
+        str = [NSString stringWithUTF8String:buf];
+        if (!str) goto failed;
+        [domains addObject:str];
+    }
+    
+    return domains;
+    
+failed:
+    return NULL;
+}
+
+static int set_vpn_clientsubnet(NSMutableArray *routes)
+{
+    struct ifaddrs              *ifaddr;
+    struct ifaddrs              *ifa;
+    struct sockaddr_in          *addr;
+    struct sockaddr_in          *mask;
+    
+    if (!array_vpn_is_clientsubnet_enable()) return 0;
+    
+    if (getifaddrs(&ifaddr) == -1) {
+        ANError(@"getifaddrs failed %d\n", errno);
+        return 1;
+    }
+    
+    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+        if (ifa->ifa_addr == NULL)  continue;
+        
+        if (!(ifa->ifa_flags & IFF_UP)) continue;
+        if (ifa->ifa_flags & IFF_LOOPBACK) continue;
+        if (ifa->ifa_flags & IFF_POINTOPOINT) continue;
+        
+        if (ifa->ifa_addr->sa_family != AF_INET) continue;
+        
+        addr = (struct sockaddr_in*)ifa->ifa_addr;
+        mask = (struct sockaddr_in*)ifa->ifa_netmask;
+        if ((!addr) || (!mask)) continue;
+        
+        NSString *netAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(addr->sin_addr.s_addr)];
+        NSString *netMask = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(mask->sin_addr.s_addr)];
+        NEIPv4Route *excludedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:netAddress subnetMask:netMask];
+        [routes addObject:excludedRoute];
+    }
+    
+    freeifaddrs(ifaddr);
+    return 0;
+}
+
+static void add_proxy_ip_into_excluded_routes(NSMutableArray *routes)
+{
+
+}
+
+static NSMutableArray *get_local_dns_servers()
+{
+    NSMutableArray *dnsServers = [[NSMutableArray alloc] init];
+    
+    res_state res = malloc(sizeof(struct __res_state));
+    if (!res) {
+        ANError(@"Alloc memory for res_state failed.");
+        return nil;
+    }
+    
+    int result = res_ninit(res);
+    
+    if (result == 0) {
+        for (int i = 0; i < res->nscount; i++) {
+            NSString *server = [NSString stringWithUTF8String:inet_ntoa(res->nsaddr_list[i].sin_addr)];
+            [dnsServers addObject:server];
+            ANInfo(@"Local dns server found, ip = %@", server);
+        }
+    } else {
+        ANInfo(@"No dns server found in system configuration.");
+        free(res);
+        return nil;
+    }
+    
+    free(res);
+    return dnsServers;
+}
+
+static DNSResolveType get_dns_resolve_type(vpn_hostmap_array_t *hostmap_array)
+{
+    if (!hostmap_array) {
+        ANInfo(@"Hostmap array is not defined.");
+        return kDNSResolveDefault;
+    }
+    
+    if (hostmap_array->num == 0) {  // Hostmap protocol is valid for IPv4 only.
+        ANInfo(@"Hostmap array is empty.");
+        return kDNSResolveDefault;
+    }
+    
+    for (int i = 0; i < hostmap_array->num; i++) {
+        vpn_hostmap_t *hostmap = hostmap_array->data[i];
+        if (hostmap) {
+            if (hostmap->ip == DNS_RESOLVE_METHOD_SPLIT) {
+                return kDNSResolveSplit;
+            }
+        }
+    }
+    
+    return kDNSResolveDefault;
+}
+
+static NSMutableArray *get_internal_domains(vpn_hostmap_t **hostmap, int hostmap_num)
+{
+    if (!hostmap || hostmap_num <= 0) {
+        ANError(@"Hostmap is empty.");
+        return nil;
+    }
+    
+    NSMutableArray *domainArray = [[NSMutableArray alloc] initWithCapacity:hostmap_num];
+    for (int i = 0; i < hostmap_num; i++) {
+        if (hostmap[i]->ip != DNS_RESOLVE_METHOD_SPLIT) {
+            continue;
+        }
+        
+        NSString *hostmapName = [NSString stringWithUTF8String:hostmap[i]->host];
+        
+        /* special configuration */
+        if ([hostmapName isEqualToString:@"*"] || [hostmapName isEqualToString:@"*."] ||
+            [[hostmapName lowercaseString] isEqualToString:@"all"]) {
+            [domainArray addObject:@""];
+            continue;
+        }
+        
+        if ([hostmapName hasPrefix:@"*."]) {
+            hostmapName = [hostmapName substringFromIndex:2];
+        }
+        [domainArray addObject:hostmapName];
+    }
+    
+    return domainArray;
+}
+
+static BOOL is_address_included(vpn_zone_array_t *include_zone, uint32_t address)
+{
+    for (int i = 0; i < include_zone->num; i++) {
+        vpn_zone_t *zone = &include_zone->data[i];
+        if (((zone->net & zone->mask) ^ (address & zone->mask)) == 0) {
+            ANDebug(@"Local DNS server %d.%d.%d.%d is in included zone.", IPSTR(address));
+            return YES;
+        }
+    }
+    ANDebug(@"Local DNS server %d.%d.%d.%d is not in included zone.", IPSTR(address));
+    
+    return NO;
+}
+
+static void local_dns_filter(vpn_zone_array_t *include_zone, NSMutableArray *localDNS)
+{
+    if (!include_zone || include_zone->num == 0) {
+        ANError(@"Included zone is empty.");
+        return;
+    }
+    if (!localDNS || [localDNS count] == 0) {
+        ANError(@"Local DNS server not found.");
+        return;
+    }
+    
+    uint32_t dnsAddress = 0;
+    for (int i = 0; i < [localDNS count]; i++) {
+        NSString *server = [localDNS objectAtIndex:i];
+        const char *serverString = [server UTF8String];
+        if (!serverString) {
+            continue;
+        }
+        dnsAddress = inet_addr(serverString);
+        if (!is_address_included(include_zone, dnsAddress)) {
+            [localDNS removeObjectAtIndex:i];
+            --i;
+        }
+    }
+}
+
+static BOOL add_local_dns_into_excluded_routes(NEPacketTunnelNetworkSettings *settings, NSArray *dnsArray)
+{
+    if (!dnsArray) {
+        ANError(@"No local DNS server inputted.");
+        return NO;
+    }
+    
+    NSMutableArray *excludedRoutes = [[NSMutableArray alloc] initWithArray:settings.IPv4Settings.excludedRoutes];
+    for (NSString *dnsServer in dnsArray) {
+        ANDebug(@"Add local DNS %@ into excluded routes.", dnsServer);
+        NSString *netMask = @"255.255.255.255";
+        NEIPv4Route *excludedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:dnsServer subnetMask:netMask];
+        [excludedRoutes addObject:excludedRoute];
+    }
+    settings.IPv4Settings.excludedRoutes = excludedRoutes;
+    
+    return YES;
+}
+
+static void compose_fulltunnel(NSMutableArray *includedRoutesArray, uint32_t gateway)
+{
+    if (!includedRoutesArray) {
+        ANError(@"Included routes array is nic, can't append more.");
+        return;
+    }
+    
+    for (int i = 1; i < 256; ++i) {
+        uint32_t net = i;
+        NSString *netString = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(net)];
+        NEIPv4Route *includedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:netString subnetMask:@"255.0.0.0"];
+        includedRoute.gatewayAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(gateway)];
+        [includedRoutesArray addObject:includedRoute];
+    }
+}
+
+static void add_special_routes(NSMutableArray *includedRoutes, uint32_t gateway)
+{
+    static char *specialRoutes[TOTAL_SPECIAL_ROUTES] = {"2.255.255.252"};  // For dynamic ACL.
+    
+    if (!includedRoutes) {
+        ANError(@"Included routes is null, don't add special routes.");
+        return;
+    }
+    
+    for (int i = 0; i < TOTAL_SPECIAL_ROUTES; i++) {
+        NSString *address = [NSString stringWithUTF8String:specialRoutes[i]];
+        NEIPv4Route *includedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:address
+                                                                          subnetMask:@"255.255.255.255"];
+        includedRoute.gatewayAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(gateway)];
+        [includedRoutes addObject:includedRoute];
+    }
+}
+
+static int on_vpn_config_request(const void *in, uint32_t ilen, void *out, uint32_t *olen)
+{
+    ANDebug(@"============>%s is called!<===========", __FUNCTION__);
+    l3vpn_config_t             *cfg = (l3vpn_config_t*)in;
+    
+    if (tunnelProvider != nil) {
+        // Clear settings first.
+        ANDebug(@"Try to clear network settings.");
+        [tunnelProvider setTunnelNetworkSettings:nil completionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Clear tunnel setting failed withe error: %@", error.localizedDescription);
+                return;
+            } else {
+                ANInfo(@"Begin to set tunnel network settings.");
+                
+                uint32_t i;
+                uint32_t virtualSiteIP = 0;
+                array_vpn_get_server_ip(&virtualSiteIP);
+                NSString *ipString = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(virtualSiteIP)];
+                /* Set remote IP */
+                NEPacketTunnelNetworkSettings *settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:ipString];
+                
+                // Set MTU as default
+                settings.MTU = [NSNumber numberWithInt:VPN_PLUGIN_DEFAULT_MTU];
+                
+                /* Split DNS check */
+                DNSResolveType dnsType = get_dns_resolve_type(&cfg->hostmap);
+                ANInfo(@"Split DNS enabled has been set to %zi.", dnsType);
+                
+                /* IPv4 settings */
+                NSArray *IPv4AddrArray = @[[NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->ip)]];
+                NSArray *IPv4MaskArray = @[[NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->netmask)]];
+                
+                settings.IPv4Settings = [[NEIPv4Settings alloc] initWithAddresses:IPv4AddrArray
+                                                                      subnetMasks:IPv4MaskArray];
+                
+                NSMutableArray *includedRoutesArray = [[NSMutableArray alloc] init];
+                if (cfg->full_tunnel) {
+                    tunnelProvider.isFullTunnel = YES;
+                    if (dnsType == kDNSResolveSplit) {
+                        compose_fulltunnel(includedRoutesArray, cfg->ip);
+                    } else {
+                        NSString *net = [NSString stringWithFormat:@"0.0.0.0"];
+                        NEIPv4Route *includedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:net subnetMask:@"0.0.0.0"];
+                        includedRoute.gatewayAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->ip)];
+                        [includedRoutesArray addObject:includedRoute];
+                    }
+                } else {
+                    /* included route */
+                    tunnelProvider.isFullTunnel = NO;
+                    for (i=0; i<cfg->zone_include.num; ++i) {
+                        NSString *netAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->zone_include.data[i].net)];
+                        NSString *netMask = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->zone_include.data[i].mask)];
+                        NEIPv4Route *includedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:netAddress subnetMask:netMask];
+                        includedRoute.gatewayAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->ip)];
+                        [includedRoutesArray addObject:includedRoute];
+                    }
+                    
+                    add_special_routes(includedRoutesArray, cfg->ip);
+                }
+                
+                if (!cfg->full_tunnel && cfg->zone_include.num == 0) {
+                    ANError(@"L3VPN resource is not found, the tunnel may work abnormally!");
+                }
+                
+                settings.IPv4Settings.includedRoutes = includedRoutesArray;
+                
+                /* excluded route */
+                NSMutableArray *excludedRouteArray = [[NSMutableArray alloc] init];
+                
+                set_vpn_clientsubnet(excludedRouteArray);
+                
+                add_proxy_ip_into_excluded_routes(excludedRouteArray);
+                
+                for (i=0; i<cfg->zone_exclude.num; ++i) {
+                    NSString *netAddress = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->zone_exclude.data[i].net)];
+                    NSString *netMask = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->zone_exclude.data[i].mask)];
+                    NEIPv4Route *excludedRoute = [[NEIPv4Route alloc] initWithDestinationAddress:netAddress subnetMask:netMask];
+                    [excludedRouteArray addObject:excludedRoute];
+                }
+                settings.IPv4Settings.excludedRoutes = excludedRouteArray;
+                
+                /* dns dictionary */
+                NSMutableArray *dnsArray = [[NSMutableArray alloc] init];
+                for (i=0; i<cfg->dns.num; ++i) {
+                    NSString *dnsServer = [NSString stringWithFormat:@"%d.%d.%d.%d", IPSTR(cfg->dns.data[i])];
+                    [dnsArray addObject:dnsServer];
+                }
+                ANInfo(@"tunnel config dns=%@", dnsArray);
+                NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroupNamePacket];
+                [userDefaults setValue:dnsArray forKey:@"dns_list"];
+                
+                NSMutableArray *matchDomains = nil;
+                NSMutableArray *localDNS = nil;
+                if (dnsType == kDNSResolveSplit) {
+                    matchDomains = get_internal_domains(cfg->hostmap.data, cfg->hostmap.num);
+                }
+                
+                //Add local DNS into excluded routes.
+                localDNS = get_local_dns_servers();  // Append local dns as supplemental dns.
+                if (localDNS) {
+                    [dnsArray addObjectsFromArray:localDNS];
+                }
+                if (!cfg->full_tunnel) {
+                    // Split tunnel should check whether the DNS is in included zone first.
+                    local_dns_filter(&cfg->zone_include, localDNS);
+                }
+                add_local_dns_into_excluded_routes(settings, localDNS);
+                
+                NEDNSSettings *dns = [[NEDNSSettings alloc] initWithServers:dnsArray];
+                dns.matchDomains = matchDomains ? : @[@""];
+                
+                NSMutableArray *domains = nil;
+                if (cfg->search_domain) {
+                    domains = get_vpn_search_domain(cfg->search_domain);
+                    if (domains) {
+                        dns.searchDomains = domains;
+                        if (matchDomains) {  // Search domains should be added to match domains.
+                            [matchDomains addObjectsFromArray:domains];
+                            dns.matchDomains = matchDomains;
+                        } else {
+                            if (tunnelProvider.isEnableSplitDnsSearch) {
+                                ANInfo(@"user config split dns search feature.");
+                                dns.matchDomains = domains;
+                            }
+                        }
+                    }
+                }
+                settings.DNSSettings = dns;
+                
+                if (cfg->inside_proxy.type == 1 && strlen(cfg->inside_proxy.server) > 0) {
+                    ANInfo(@"vpn netpool enable inside proxy scrpit mode, script=%s.", cfg->inside_proxy.server);
+                    NEProxySettings *proxy = [[NEProxySettings alloc] init];
+                    proxy.autoProxyConfigurationEnabled = true;
+                    NSString *urlString = [[NSString alloc] initWithCString:cfg->inside_proxy.server encoding:NSUTF8StringEncoding];
+                    proxy.proxyAutoConfigurationURL = [NSURL URLWithString:urlString];
+                    settings.proxySettings = proxy;
+                }
+                
+                
+                [tunnelProvider setTunnelNetworkSettings:settings
+                                       completionHandler:^(NSError * _Nullable error) {
+                                           if (error) {
+                                               ANError(@"Set tunnel network failed with error: %@",
+                                                     error.localizedDescription);
+                                           }
+                                       }];
+                
+                ANDebug(@"===========>Tunnel settings: \n%@<==========", settings);
+            }
+        }];
+    } else {
+        ANError(@"Global tunnel provider is nil, can't set tunnel network!");
+    }
+    
+    out = NULL;
+    *olen = 0;
+    
+    return ERROR_SUCCESS;
+}
+
+static int vpn_sdk_callback(int cmd, int error, const void *in, uint32_t ilen, void *out, uint32_t *olen)
+{
+    ANInfo(@"Receive SDK callback %d.", cmd);
+    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroupNamePacket];
+    
+    @autoreleasepool {
+        switch (cmd) {
+            case VPN_CB_CONN_FAILED:
+                ANError(@"VPN callback connect failed, error = %d.", error);
+                [userDefaults setInteger:error forKey:@"tunnel_result"];
+                
+                [tunnelProvider stopTunnelDirectWithError:nil];
+                break;
+                
+            case VPN_CB_CONNECTING:
+                break;
+                
+            case VPN_CB_CONNECTED:
+                ANInfo(@"VPN callback connect successed!");
+                
+                tunnelProvider.reasserting = NO;  // VPN logo will show in system!
+                if (startHandler) {
+                    startHandler(nil);
+                    startHandler = nil;
+                }
+                
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    startNotifier("0.0.0.0");
+                });
+                break;
+                
+            case VPN_CB_CONFIG_REQUEST:
+                return on_vpn_config_request(in, ilen, out, olen);
+                
+            case VPN_CB_DISCONNECTING:
+                break;
+                
+            case VPN_CB_DISCONNECTED: {
+
+                if (error == ERR_SESS_INVALID || error == ERR_SESS_TIMEOUT) {
+                    ANWarn(@"VPN session is invalid, VPN will be closed.");
+                    
+                    if (tunnelProvider.isOnDemand) {
+                        // Display a message to user interface.
+                        __weak typeof(tunnelProvider) weakTunnelProvider = tunnelProvider;
+                        NSString *message = NSLocalizedString(@"Session is invalid, please login again!", nil);
+                        [tunnelProvider displayMessage:message
+                                     completionHandler:^(BOOL success) {
+                                         if (success) {
+                                             ANInfo(@"Tunnel displayed message success!");
+                                         } else {
+                                             ANError(@"Tunnel displayed message failed!");
+                                         }
+                                         
+                                         [weakTunnelProvider stopTunnelDirectWithError:nil];
+                                     }];
+                        break;
+                    } else {
+                        NSError *responseError = [NSError errorWithDomain:(__bridge NSString *)kPluginErrorDomain
+                                                                     code:error
+                                                                 userInfo:nil];
+                        
+                        [tunnelProvider stopTunnelDirectWithError:responseError];
+                    }
+                }
+                break;
+            }
+                
+            case VPN_CB_RECONNECTING:
+                if (tunnelProvider) {
+                    tunnelProvider.reasserting = YES;
+                }
+                break;
+                
+            default:
+                ANWarn(@"callback code %d: not processed.", cmd);
+                break;
+        }
+    }
+    
+    return ERROR_SUCCESS;
+}
+
+static int write_vnic_callback(char *buf, int len) {
+    if (!tunnelProvider) {
+        ANError(@"Global tunnel provider is nil, can't write packet to tun device.");
+        return -1;
+    }
+    
+    if (buf == NULL || len == 0) {
+        ANError(@"Nothing will be sent to vitural nic.");
+        return 0;
+    }
+    
+    @autoreleasepool {
+        NSData *data = [NSData dataWithBytes:buf length:len];
+        NSArray *packets = [[NSArray alloc] initWithObjects:data, nil];
+        NSArray *protocols = [[NSArray alloc] initWithObjects:[NSNumber numberWithInteger:AF_INET], nil];
+        
+        BOOL ret = [tunnelProvider.packetFlow writePackets:packets withProtocols:protocols];
+        if (ret) {
+            return len;
+        } else {
+            ANError(@"Write to vnic failed.");
+            return 0;
+        }
+    }
+}
+
+
+
+@interface PacketTunnelProvider()
+
+@property (nonatomic, strong) NSString *certUUID;
+
+@end
+
+@implementation PacketTunnelProvider
+
+- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(NSError *))completionHandler
+{
+    // Add code here to start the process of connecting the tunnel.
+    ANLogger *logger = [ANLogger sharedInstance];
+    [logger setupWithLogFile:kANTunnelFileName];
+    [logger setLogLevel:LOG_INFO];
+    array_vpn_set_log_level(LOG_INFO, 0);
+    
+    if (!options) {
+        ANError(@"No VPN configuration found in packet tunnel provider");
+        completionHandler([NSError errorWithDomain:(__bridge NSString *)kPluginErrorDomain
+                                              code:ERR_FAILURE
+                                          userInfo:@{@"reason": @"no_option"}]);
+        return;
+    }
+    
+    ANDebug(@"VPN configuration of %@ has been received by packet tunnel.", options);
+    
+    NSDictionary *vendorData = nil;
+    if ([self parseOptions:options] == kPluginStartOnDemand) {
+        vendorData = [self setOnDemandVendorDataWithOptions:options];
+    } else {
+        vendorData = [options objectForKey:@"VendorData"];
+    }
+    
+    tunnelProvider = self;
+    startHandler = completionHandler;
+    
+    array_vpn_set_log_callback(clog_callback);
+    array_vpn_set_write_vnic_callback(write_vnic_callback);
+    
+    if (![self VPNTunnelConnectWithVendorData:vendorData]) {
+        ANError(@"VPN tunnel connect failed, can't start vpn.");
+        return;
+    }
+    [self readPacketFromTun];  // Try to start read the tun device.
+}
+
+- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler
+{
+    // Add code here to start the process of stopping the tunnel.
+    stopNotifier();
+    array_vpn_stop();
+    
+    completionHandler();
+    tunnelProvider = nil;
+}
+
+- (void)handleAppMessage:(NSData *)messageData completionHandler:(void (^)(NSData *))completionHandler
+{
+    // Add code here to handle the message.
+    @autoreleasepool {
+        IPCType type = [self parseIPCRequest:messageData];
+        NSData *responseData = nil;
+        
+        ANDebug(@"IPC type of %d received.", type);
+            
+        switch (type) {
+            case kIPCTypeStatisticsRequest:
+                responseData = [self collectStatisticsInfo];
+                break;
+                
+            default:
+                ANError(@"Invalid IPC type of %d, not processed.", type);
+                break;
+        }
+        
+        completionHandler(responseData);
+    }
+}
+
+- (void)sleepWithCompletionHandler:(void (^)(void))completionHandler
+{
+    // Add code here to get ready to sleep.
+    NSString *methodName = [NSString stringWithUTF8String:__FUNCTION__];
+    ANDebug(@"%@ is called!!!", methodName);
+    
+    [self stopTunnelIfOnDemand];
+    
+    array_vpn_enter_background();
+    
+    completionHandler();
+}
+
+- (void)wake
+{
+    // Add code here to wake up.
+    NSString *methodName = [NSString stringWithUTF8String:__FUNCTION__];
+    ANDebug(@"%@ is called!!!", methodName);
+    
+    array_vpn_enter_foreground();
+}
+
+- (BOOL)VPNTunnelConnectWithVendorData:(NSDictionary *)vendorData
+{
+    vpn_parameter_t     p;
+    NSString            *str;
+    int                 ret;
+    
+    if (!vendorData) {
+        ANError(@"Vendor data is not found in options!");
+        return NO;
+    }
+    
+    memset(&p, 0, sizeof(p));
+    p.port = 443;
+    p.server_type = AG_VPN_DEVICE;
+    
+    const char *host = [self getCStringParamWithDict:vendorData key:kPluginArgHost];
+    if (host == NULL || strlen(host) == 0) {
+        ANError(@"Invalid host passed into vpn plugin: %s", host);
+        return NO;
+    }
+    p.host = host;
+    
+    PLUGIN_ARG_GET(vendorData, p.port, kPluginArgPort, int, @"Server Port = %@", str);
+    PLUGIN_ARG_GET(vendorData, p.flags, kPluginArgFlags, uint32_t, @"Flags = %@", str);
+    
+    p.session = [self getCStringParamWithDict:vendorData key:kPluginArgSession];
+    p.devid = [self getCStringParamWithDict:vendorData key:kPluginArgDeviceID];
+    p.clientid = [self getCStringParamWithDict:vendorData key:kPluginArgClientID];
+    
+    NSData *certData = (NSData *)[vendorData objectForKey:(__bridge NSString *)kPluginArgCertificateData];
+    if (certData) {
+        p.cert_data = [certData bytes];
+        p.cert_data_len = (int)[certData length];
+        ANInfo(@"CertificateData length = %d.", p.cert_data_len);
+    }
+    
+    const char *certPassword = [self getCStringParamWithDict:vendorData key:kPluginArgCertificatePassword];
+    if (certPassword != NULL) {
+        p.cert_password = certPassword;
+    }
+    
+    NSString *speedTunnelEnabled = (NSString *)[vendorData objectForKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    if (speedTunnelEnabled && [speedTunnelEnabled isEqualToString:@"1"]) {
+        p.udp_enable = 1;
+        PLUGIN_ARG_GET(vendorData, p.tunnel_dispatch, kPluginArgSpeedTunnelDispatch, uint8_t, @"SpeedTunnelDispatch = %@", str);
+        PLUGIN_ARG_GET(vendorData, p.udp_encrypt, kPluginArgSpeedTunnelEncrypt, uint8_t, @"SpeedTunnelEncrypt = %@", str);
+    }
+    uint8_t iEnableSplitDnsSearch = 0;
+    PLUGIN_ARG_GET(vendorData, iEnableSplitDnsSearch, kPluginArgSplitTunnelEnableDnsSearch, uint8_t, @"SplitTunnelEnableDnsSearch = %@", str);
+    if (iEnableSplitDnsSearch == 1) {
+        tunnelProvider.isEnableSplitDnsSearch = YES;
+    } else {
+        tunnelProvider.isEnableSplitDnsSearch  = NO;
+    }
+    
+    PLUGIN_ARG_GET(vendorData, p.reconn_max_time, kPluginArgReconnectMaxTime, uint32_t, @"ReconnectMaxTime = %@", str);
+    PLUGIN_ARG_GET(vendorData, p.reconn_max_count, kPluginArgReconnectMaxCount, uint32_t, @"ReconnectMaxCount = %@", str);
+    PLUGIN_ARG_GET(vendorData, p.reconn_interval, kPluginArgReconnectInterval, uint32_t, @"ReconnectInterval = %@", str);
+    PLUGIN_ARG_GET(vendorData, p.server_type, kPluginArgServerType, vpn_device_type_t, @"ServerType = %@", str);
+    PLUGIN_ARG_GET(vendorData, p.reconn_cache_ip_flags, kPluginArgReconnectFlags, uint8_t, @"ReconnectFlags = %@", str);
+    
+    NSString *logLevel = [vendorData objectForKey:(__bridge NSString *)kPluginArgLogLevel];
+    if (logLevel) {
+        const char *log_level = [logLevel UTF8String];
+        if (log_level != NULL) {
+            int level = (int)strtol(log_level, NULL, 10);
+            array_vpn_set_log_level(level, 0);
+            [[ANLogger sharedInstance] setLogLevel:level];
+        }
+    }
+    
+    NSString *customDns = [vendorData objectForKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    if ([customDns length] > 0) {
+        customDns = [NSString stringWithFormat:@"CustomDNS=%@", customDns];
+        p.validcode = [customDns UTF8String];
+    }
+    
+    NSString *connectTimeout = [vendorData objectForKey:(__bridge NSString *)kPluginArgConnectTimeout];
+    if (connectTimeout) {
+        const char *timeout = [connectTimeout UTF8String];
+        if (timeout != NULL) {
+            get_connect_timeout_values(timeout, p.conn_timeout);
+        }
+    }
+    
+    p.callback = vpn_sdk_callback;
+    
+    BOOL isOnDemand = [[vendorData objectForKey:(__bridge NSString *)kPluginArgVPNOnDemand] boolValue];
+    if (isOnDemand) {
+        NSString *certPath = [self getCredentialPath];
+        if (!certPath) {
+            ANError(@"Get certificate path failed, can't start vpn.");
+            return NO;
+        }
+        p.cert_path = [certPath UTF8String];
+        
+        p.flags |= VPN_FLAG_LOGOUT_DEV_SESSION|VPN_FLAG_DESKTOP_DIRECT|VPN_FLAG_MOTIONPRO_V2;
+    }
+    
+
+    
+#define MPCLIENT_SKIP_SDK_LICENSE  9988
+    array_vpn_set_value(MPCLIENT_SKIP_SDK_LICENSE, nil); //need add this skip before call array_vpn_start2. Otherwise, an SDK license is required.
+    ret = array_vpn_start2(p);
+    if (ret != ERROR_SUCCESS) {
+        ANError(@"Start array vpn failed with code: %d.", ret);
+        return NO;
+    }
+    
+    return YES;
+}
+
+- (void)readPacketFromTun
+{
+    [self.packetFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> * _Nonnull packets, NSArray<NSNumber *> * _Nonnull protocols) {
+        @autoreleasepool {
+            for (NSData *packet in packets) {
+                unsigned short len = (unsigned short)[packet length];
+                if (len <= 0) {
+                    ANError(@"Read nothing from tunnel.");
+                    continue;
+                }
+                
+                char *data = (char *)malloc(len);
+                if (data == NULL) {
+                    ANError(@"Alloc memory for packet failed.");
+                    break;
+                }
+                
+                memset(data, 0, len);
+                
+                memcpy(data, [packet bytes], len);
+                write_to_vpn((char *)data, len);
+            }
+            [self readPacketFromTun];
+        }
+    }];
+}
+
+- (void)stopTunnelIfOnDemand
+{
+    if (self.isOnDemand) {
+        array_vpn_stop();
+        
+        [self stopTunnelDirectWithError:nil];
+    }
+}
+
+- (void)stopTunnelDirectWithError:(NSError *)error {
+    stopNotifier();
+    
+    [self cancelTunnelWithError:error];
+    tunnelProvider = nil;
+}
+
+#pragma mark - IPC
+- (IPCType)parseIPCRequest:(NSData *)requestData {
+    IPCType ret = kIPCTypeInvalid;
+    
+    if (!requestData) {
+        ANError(@"Nil IPC request, ignore.");
+        return ret;
+    }
+    
+    NSDictionary *requestDict = [NSJSONSerialization JSONObjectWithData:requestData
+                                                                options:NSJSONReadingMutableContainers
+                                                                  error:nil];
+    if (!requestDict) {
+        ANError(@"Invalid IPC request data.");
+        return ret;
+    }
+    
+    if ([[requestDict objectForKey:(__bridge NSString *)kIPCType]
+         isEqualToString:(__bridge NSString *)kIPCStatsRequest]) {
+        ret = kIPCTypeStatisticsRequest;
+    }
+    
+    return ret;
+}
+
+- (NSData *)collectStatisticsInfo {
+    uint32_t client_ip = 0;
+    uint64_t sent_bytes = 0;
+    uint64_t received_bytes = 0;
+    char server_buf[1024];
+    size_t server_buf_len = sizeof(server_buf);
+    BOOL is_full_tunnel = tunnelProvider.isFullTunnel;
+    
+    memset(server_buf, 0, server_buf_len);
+    
+    // Get info from array sdk.
+    array_vpn_get_server_host(server_buf, server_buf_len);
+    array_vpn_get_virtual_ip(&client_ip);
+    array_vpn_get_stats(&sent_bytes, &received_bytes);
+    
+    NSDictionary *statsDict = @{(__bridge NSString *)kIPCStatsClientIP:@(client_ip),
+                                (__bridge NSString *)kIPCStatsServerHost:@(server_buf),
+                                (__bridge NSString *)kIPCStatsIsFullTunnel:@(is_full_tunnel),
+                                (__bridge NSString *)kIPCStatsSentBytes:@(sent_bytes),
+                                (__bridge NSString *)kIPCStatsReceviedBytes:@(received_bytes)};
+    NSDictionary *IPCDict = @{(__bridge NSString *)kIPCType:(__bridge NSString *)kIPCStatsResponse,
+                              (__bridge NSString *)kIPCResponseData:statsDict};
+    
+    // Transfer to NSData
+    NSData *IPCData = [NSJSONSerialization dataWithJSONObject:IPCDict
+                                                      options:NSJSONWritingPrettyPrinted
+                                                        error:nil];
+    
+    return IPCData;
+}
+
+#pragma mark - Tools
+- (PluginStartMethod)parseOptions:(NSDictionary *)options
+{
+    PluginStartMethod ret = kPluginStartInvliad;
+    
+    if (!options) {
+        ANError(@"Empty options is passed!");
+        return ret;
+    }
+    
+    if ([self isOnDemandStart:options]) {
+        ret = kPluginStartOnDemand;
+        ANInfo(@"Plugin is started by on-demand.");
+    } else {
+        ret = kPluginStartByContainer;
+        ANInfo(@"Plugin is started by container app.");
+    }
+    
+    return ret;
+}
+
+- (BOOL)isOnDemandStart:(NSDictionary *)options
+{
+    BOOL isCertAuth = NO;
+    NSString *serverAddress = nil;
+    NSString *serverPort = @"443";  //As default
+    NSString *authMethod = nil;
+    
+    NSString *host = [options objectForKey:@"ServerAddress"];
+    if (!host || [host length] == 0) {
+        ANInfo(@"Plugin (on-demand) received invalid host");
+        return NO;
+    }
+    
+    NSArray *components = [host componentsSeparatedByString:@":"];
+    if ([components count] == 2) {
+        serverAddress = [components objectAtIndex:0];
+        serverPort = [components objectAtIndex:1];
+    } else {
+        serverAddress = host;
+    }
+    ANInfo(@"Server address is set to \"%@\"", serverAddress);
+    ANInfo(@"Server port is set to \"%@\"", serverPort);
+    
+    _isOnDemand = [[options objectForKey:@"is-on-demand"] boolValue];
+    if (!_isOnDemand) {
+        ANInfo(@"Plugin is not start on-demand.");
+        return NO;
+    }
+    
+    // Auth method must be known if multi auth methods exist.
+    authMethod = [(NSDictionary *)[options objectForKey:@"VendorData"] objectForKey:(__bridge NSString *)kPluginArgMethodName];
+    if (!authMethod) {
+        ANInfo(@"Plugin didn't receive auth method name");
+        return NO;
+    }
+    
+    // Only certificate auth is supported by vpn on-demand.
+    isCertAuth = [[options objectForKey:@"AuthMethod"] isEqualToString:@"Certificate"] &&
+    [options objectForKey:@"CertificateRef"];
+    if (!isCertAuth) {
+        ANInfo(@"The site is not certificate authentication, can't start plugin on-demand.");
+        return NO;
+    }
+    
+    _certUUID = [(NSDictionary *)[options objectForKey:@"VendorData"] objectForKey:(__bridge NSString *)kPluginArgCertUUID];
+    if (!_certUUID) {
+        ANInfo(@"The UUID of certificate is not found, can't start plugin on-demand.");
+        return NO;
+    }
+    
+    return YES;
+}
+
+- (NSDictionary *)setOnDemandVendorDataWithOptions:(NSDictionary *)options
+{
+    if (!options) {
+        ANError(@"Set vendor data with empty options.");
+        return nil;
+    }
+    
+    NSDictionary *originalVendorData = [options objectForKey:@"VendorData"];
+    NSMutableDictionary *newVendorData = [NSMutableDictionary dictionaryWithDictionary:originalVendorData];
+    
+    NSString *serverAddress = nil;
+    NSString *serverPort = @"443";
+    NSString *host = [options objectForKey:@"ServerAddress"];
+    NSArray *components = [host componentsSeparatedByString:@":"];
+    if ([components count] == 2) {
+        serverAddress = [components objectAtIndex:0];
+        serverPort = [components objectAtIndex:1];
+    } else {
+        serverAddress = host;
+    }
+    
+    [newVendorData setObject:serverAddress forKey:(__bridge NSString *)kPluginArgHost];
+    [newVendorData setObject:serverPort forKey:(__bridge NSString *)kPluginArgPort];
+    [newVendorData setObject:[@(YES) stringValue] forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [newVendorData setObject:[@(LOG_INFO) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    [newVendorData setObject:[@(60) stringValue] forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    [newVendorData setObject:[@(YES) stringValue] forKey:(__bridge NSString *)kPluginArgVPNOnDemand];
+    
+    return newVendorData;
+}
+
+- (NSString *)getCredentialPath
+{
+    NSString *path      = nil;
+    CFTypeRef certRef   = NULL;
+    CFTypeRef keyRef    = NULL;
+    
+    certRef = [self retrieveDataWithSecType:kSecClassCertificate certLabel:self.certUUID];
+    if (!certRef) {
+        ANError(@"Retrive cert from keychain failed.");
+        goto fail;
+    }
+    
+    keyRef = [self retrieveDataWithSecType:kSecClassKey certLabel:self.certUUID];
+    if (!keyRef) {
+        ANError(@"Retrive key from keychain failed.");
+        goto fail;
+    }
+    
+    path = [self saveCertAndKeyInFile:(__bridge NSData *)certRef key:(__bridge NSData *)keyRef];
+    
+    CFRelease(certRef);
+    CFRelease(keyRef);
+    
+    return path;
+    
+fail:
+    if (certRef) {
+        CFRelease(certRef);
+    }
+    if (keyRef) {
+        CFRelease(keyRef);
+    }
+    
+    return nil;
+}
+
+- (NSString *)saveCertAndKeyInFile:(NSData *)cert key:(NSData *)key
+{
+    static NSString *certFileName = @"cert.der";
+    static NSString *keyFileName = @"key.der";
+    
+    NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *documentDirectory = [directoryPaths objectAtIndex:0];
+    NSString *certFilePath = [documentDirectory stringByAppendingPathComponent:certFileName];
+    NSString *keyFilePath = [documentDirectory stringByAppendingPathComponent:keyFileName];
+    
+    ANDebug(@"Cert file path: %@", certFilePath);
+    ANDebug(@"Key file path: %@", keyFilePath);
+    
+    [cert writeToFile:certFilePath atomically:YES];
+    [key writeToFile:keyFilePath atomically:YES];
+    
+    NSString *path = [NSString stringWithFormat:@"%@|%@", certFilePath, keyFilePath];
+    
+    return path;
+}
+
+- (CFTypeRef)retrieveDataWithSecType:(CFStringRef)type certLabel:(NSString *)label
+{
+    CFTypeRef result = NULL;
+    NSString *fullLabel = [NSString stringWithFormat:@"NE:%@", label];
+    
+    ANDebug(@"Keychain item type: %@, label: %@", type, fullLabel);
+    
+    NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
+                           (__bridge id)type,
+                           kSecClass,
+                           kSecMatchLimitOne,
+                           kSecMatchLimit,
+                           kCFBooleanTrue,
+                           kSecReturnData,
+                           fullLabel,
+                           kSecAttrLabel,
+                           @"com.apple.managed.vpn.shared",
+                           kSecAttrAccessGroup,
+                           nil];
+    
+    OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result);
+    if (s != errSecSuccess) {
+        ANError(@"Load from keychain failed with error code: %d.", (int)s);
+    }
+    
+    return result;
+}
+
+- (const char *)getCStringParamWithDict:(NSDictionary *)dict key:(CFStringRef)key
+{
+    id value = (NSString *)[dict objectForKey:(__bridge NSString *)key];
+    if (value) {
+        NSLog(@"%@ = %@", key, value);
+        return [value UTF8String];
+    }
+    return NULL;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/iOSTunnel.entitlements
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/iOSTunnel.entitlements	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/iOSTunnel.entitlements	(working copy)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.developer.networking.networkextension</key>
+	<array>
+		<string>app-proxy-provider</string>
+		<string>content-filter-provider</string>
+		<string>packet-tunnel-provider</string>
+	</array>
+	<key>com.apple.developer.networking.vpn.api</key>
+	<array>
+		<string>allow-vpn</string>
+	</array>
+	<key>com.apple.security.application-groups</key>
+	<array>
+		<string>group.net.arraynetworks.MotionProPlus</string>
+	</array>
+	<key>keychain-access-groups</key>
+	<array>
+		<string>$(AppIdentifierPrefix)com.apple.managed.vpn.shared</string>
+	</array>
+</dict>
+</plist>
Index: /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/vpnplugin.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/vpnplugin.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/iOSTunnel/vpnplugin.h	(working copy)
@@ -0,0 +1,62 @@
+//
+//  vpnplugin.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/3/16.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#ifndef vpnplugin_h
+#define vpnplugin_h
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#define kPluginErrorDomain                      CFSTR("ArrayVpnPlugin")
+#define kPluginArgFlags                         CFSTR("PluginArgFlags")
+#define kPluginArgHost                          CFSTR("PluginArgHost")
+#define kPluginArgPort                          CFSTR("PluginArgPort")
+#define kPluginArgServerType                    CFSTR("PluginArgServerType")
+#define kPluginArgCertificateData			    CFSTR("PluginArgCertificateData")
+#define kPluginArgCertificatePassword			CFSTR("PluginArgCertificatePassword")
+#define kPluginArgSession                       CFSTR("PluginArgSession")
+#define kPluginArgDeviceID                      CFSTR("PluginArgDeviceID")
+#define kPluginArgClientID                      CFSTR("PluginArgClientID")
+#define kPluginArgSpeedTunnelEnable             CFSTR("PluginArgSpeedTunnelEnable")
+#define kPluginArgSpeedTunnelDispatch           CFSTR("PluginArgSpeedTunnelDispatch")
+#define kPluginArgSpeedTunnelEncrypt            CFSTR("PluginArgSpeedTunnelEncrypt")
+#define kPluginArgSplitTunnelEnableDnsSearch            CFSTR("PluginArgSplitTunnelEnableDnsSearch")
+#define kPluginArgReconnectMaxTime              CFSTR("PluginArgReconnectMaxTime")
+#define kPluginArgReconnectMaxCount             CFSTR("PluginArgReconnectMaxCount")
+#define kPluginArgReconnectInterval             CFSTR("PluginArgReconnectInterval")
+#define kPluginArgLogLevel             			CFSTR("PluginArgLogLevel")
+#define kPluginArgMtu             				CFSTR("PluginArgMtu")
+#define kPluginArgConnectTimeout              	CFSTR("PluginArgConnectTimeout")
+#define kPluginArgReconnectFlags              	CFSTR("PluginArgReconnectFlags")
+#define kPluginArgReconnectCacheIpTimeout      	CFSTR("PluginArgReconnectCacheIpTimeout")
+#define kPluginArgMethodName                    CFSTR("PluginArgMethodName")
+#define kPluginArgVPNOnDemand                   CFSTR("PluginArgVPNOnDemand")
+#define kPluginArgCertUUID                      CFSTR("PluginArgCertUUID")
+#if !TARGET_OS_IPHONE
+#define kPluginArgProxyAddress                  CFSTR("PluginArgProxyAddress")
+#define kPluginArgProxyUsername                 CFSTR("PluginArgProxyUsername")
+#define kPluginArgProxyPassword                 CFSTR("PluginArgProxyPassword")
+#endif
+
+#define kPluginArgCustomDNSSetting              CFSTR("PluginArgCustomDNSSetting")
+
+#define VPN_PLUGIN_DEFAULT_MTU                  1400
+
+// IPC for statistics information
+#define kIPCType                                CFSTR("IPCType")
+#define kIPCResponseData                        CFSTR("IPCData")
+#define kIPCStatsRequest                        CFSTR("StatsRequest")
+#define kIPCStatsResponse                       CFSTR("StatsResponse")
+#define kIPCStatsServerHost                     CFSTR("StatsServerHost")
+#define kIPCStatsIsFullTunnel                   CFSTR("StatsIsFullTunnel")
+#define kIPCStatsClientIP                       CFSTR("StatsClientIP")
+#define kIPCStatsSentBytes                      CFSTR("StatsSentBytes")
+#define kIPCStatsReceviedBytes                  CFSTR("StatsReceivedBytes")
+
+#define IPSTR(ip)   ((unsigned char*)(&(ip)))[0], ((unsigned char*)(&(ip)))[1], ((unsigned char*)(&(ip)))[2], ((unsigned char*)(&(ip)))[3]
+
+#endif /* vpnplugin_h */
