12 Replies
      Latest reply: Nov 30, 2016 7:01 AM by JMPATLANTA RSS
      SpareChange Level 1 Level 1 (0 points)

        Is there any way (private method or otherwise) to acccess the global proxy settings on an iOS device?

         

        We are experiencing issues with the global proxy, set through an MDM profile, not being respected by iOS. This seems to have started with a late version of iOS 9 and is continuing with iOS 10. I would like to write an app to test for the presences of a proxy PAC file and then test a list of URLs to see how they are processed by the PAC file. I know using NSURL and the high-level APIs are supposed to automatically use the global proxy, but I am trying to write this app for testing purposes.

        I have written an app using CFNetworkCopySystemProxySettings to retrieve the proxy settings, but it returns an empty dictionary on my test iOS device, even though the global proxy is set. Any help would be greatly appreciated.

         

        Sincerely,
        Doug Penny

        • Re: Accessing global proxy settings on iOS
          SpareChange Level 1 Level 1 (0 points)

          Here is a bit more information after doing some additional testing. I'm doing a very basic function call to get the proxy settings on an iOS device:

          let proxies = CFNetworkCopySystemProxySettings()
          print("CFNetwork Proxies: \(proxies)")
          

           

          If I enter the automatic proxy settings under WiFi in Settings.app these are the results:

          CFNetwork Proxies: Optional(Swift.Unmanaged<__ObjC.CFDictionary>(_value: {
              ExceptionsList =     (
                  "*.local",
                  "169.254/16"
              );
              FTPPassive = 1;
              ProxyAutoConfigEnable = 1;
              ProxyAutoConfigURLString = "https://<proxy address removed>/proxy.pac";
              "__SCOPED__" =     {
                  en0 =         {
                      ExceptionsList =             (
                          "*.local",
                          "169.254/16"
                      );
                      FTPPassive = 1;
                      ProxyAutoConfigEnable = 1;
                      ProxyAutoConfigURLString = "https://<proxy address removed>/proxy.pac";
                  };
              };
          }))
          

           

          If I push a global proxy configuration profile to the device via our MDM thse are the results:

          CFNetwork Proxies: Optional(Swift.Unmanaged<__ObjC.CFDictionary>(_value: {
              ExceptionsList =     (
                  "*.local",
                  "169.254/16"
              );
              FTPPassive = 1;
              "__SCOPED__" =     {
                  en0 =         {
                      ExceptionsList =             (
                          "*.local",
                          "169.254/16"
                      );
                      FTPPassive = 1;
                  };
              };
          }))
          

           

          Notice that the ProxyAutoConfig entries are missing. I can verify that the configuration profile is present on the device and can see the proxy settings in Settings.app under General > Device Management > Mobile Device Management.

           

          So I guess the question is should CFNetworkCopySystemProxySettings return proxy settings applied through an MDM or only proxy settings entered for a specfic WiFi network?

            • Re: Accessing global proxy settings on iOS
              eskimo Apple Staff Apple Staff (6,250 points)

              Ah, proxies!, the gift that keeps on giving (-:

              This seems to have started with a late version of iOS 9 and is continuing with iOS 10.

              If you can confirm that there was a specific regression, you should definitely file a bug about that.

              I would like to write an app to test for the presences of a proxy PAC file and then test a list of URLs to see how they are processed by the PAC file.

              Have you tried calling CFNetworkCopyProxiesForURL?  The proxy configuration can change by URL and, as such, CFNetworkCopySystemProxySettings is rarely useful.  However, given your overall goạl it seems that CFNetworkCopyProxiesForURL is the right thing to call anyway.  And hopefully it’ll work (-:

              Share and Enjoy

              Quinn “The Eskimo!”
              Apple Developer Relations, Developer Technical Support, Core OS/Hardware
              let myEmail = "eskimo" + "1" + "@apple.com"

                • Re: Accessing global proxy settings on iOS
                  SpareChange Level 1 Level 1 (0 points)

                  I'll give CFNetworkCopyProxiesForURL a try and see what it is returning. Do I need to retreive the current proxy settings to pass to the function though? If so, wouldn't I just be using CFNetworkCopySystemProxySettings anyway?

                    • Re: Accessing global proxy settings on iOS
                      eskimo Apple Staff Apple Staff (6,250 points)

                      Do I need to retreive the current proxy settings to pass to the function though? If so, wouldn't I just be using CFNetworkCopySystemProxySettings anyway?

                      Right.  The point of this test is twofold:

                      • It’s possible that CFNetworkCopyProxiesForURL will grab the global HTTP proxy via other means.

                      • CFNetworkCopyProxiesForURL is the right way to test whether a particular URL will go through a proxy in general.

                      Share and Enjoy

                      Quinn “The Eskimo!”
                      Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                      let myEmail = "eskimo" + "1" + "@apple.com"

                        • Re: Accessing global proxy settings on iOS
                          SpareChange Level 1 Level 1 (0 points)

                          First off, I admit that I am fairly new to Swift. I'm having trouble getting this to work as coded below:

                          if let myUrl = URL(string: "http://www.apple.com") {
                            if let proxySettings = CFNetworkCopySystemProxySettings() {
                                 let proxies = CFNetworkCopyProxiesForURL(myUrl as CFURL, proxySettings as! CFDictionary)
                                 dump(proxies)
                            }
                          }
                          

                           

                          I'm getting a EXC_BAD_INSTRUCTION error on line 3 when running in the simulator under iOS 10. This seems like fairly straight foroward code that should just work. I'm sure I'm missing something simple, but any help would be greatly appreciatated.

                           

                          Thanks,

                          Doug

                            • Re: Accessing global proxy settings on iOS
                              eskimo Apple Staff Apple Staff (6,250 points)

                              The problem here is that CFNetworkCopySystemProxySettings hasn’t been audited for Swift happiness, and so it returns an Unmanaged<CFDictionary> (you can tell this by option clicking on the proxySettings identifier).  You’ll need to convert the unmanaged reference to a managed reference like so:

                              import Foundation
                              
                              if let myUrl = URL(string: "http://www.apple.com") { 
                                  if let proxySettingsUnmanaged = CFNetworkCopySystemProxySettings() {
                                      let proxySettings = proxySettingsUnmanaged.takeRetainedValue()
                                      let proxiesUnmanaged = CFNetworkCopyProxiesForURL(myUrl as CFURL, proxySettings)
                                      let proxies = proxiesUnmanaged.takeRetainedValue()
                                      print(proxies)
                                  } 
                              }
                              

                              In this case I’m using takeRetainedValue() because both CFNetworkCopySystemProxySettings and CFNetworkCopyProxiesForURL return a +1 reference count per the CF retain/release rules (they have Copy or Create in the name).

                              In real code it’s better to just wrap routines like this with a function that follows Swift conventions.  For example:

                              import Foundation
                              
                              func QCFNetworkCopySystemProxySettings() -> CFDictionary? {
                                  guard let proxiesSettingsUnmanaged = CFNetworkCopySystemProxySettings() else {
                                      return nil
                                  }
                                  return proxiesSettingsUnmanaged.takeRetainedValue()
                              }
                              
                              func QCFNetworkCopyProxiesForURL(_ url: URL, _ proxiesSettings: CFDictionary) -> [[String:AnyObject]] {
                                  let proxiesUnmanaged = CFNetworkCopyProxiesForURL(url as CFURL, proxiesSettings)
                                  let proxies = proxiesUnmanaged.takeRetainedValue()
                                  return proxies as! [[String:AnyObject]]
                              }
                              
                              if let myUrl = URL(string: "http://www.apple.com") { 
                                  if let proxySettings = QCFNetworkCopySystemProxySettings() {
                                      let proxies = QCFNetworkCopyProxiesForURL(myUrl, proxySettings)
                                      print(proxies)
                                  } 
                              }
                              

                              Share and Enjoy

                              Quinn “The Eskimo!”
                              Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                              let myEmail = "eskimo" + "1" + "@apple.com"

                                • Re: Accessing global proxy settings on iOS
                                  SpareChange Level 1 Level 1 (0 points)

                                  Thank you so much for patiently explaining this topic. I am able to now retrieve the proxy settings when they are set directly in Settings.app, but only get kCFProxyTypeNone when a global proxy is set via an MDM.

                                   

                                  When providing a URL for a PAC file in the HTTP Proxy settings for a WiFi network, is it possible to get the returned value after the PAC file is parsed? Currently, CFNetworkCopyProxiesForURL only returns the URL for the PAC file, not the results of parsing the PAC file.

                                   

                                  Again, thank you very much for your patience and assistance.

                                   

                                  Sincerely,

                                  Doug

                                    • Re: Accessing global proxy settings on iOS
                                      eskimo Apple Staff Apple Staff (6,250 points)

                                      When providing a URL for a PAC file in the HTTP Proxy settings for a WiFi network, is it possible to get the returned value after the PAC file is parsed?

                                      I think you mean executed, not parsed, right?  If so, you should take a look at CFNetworkExecuteProxyAutoConfigurationScript and CFNetworkExecuteProxyAutoConfigurationURL, which can run PAC scripts with a target URL and return you the result.

                                      Calling those from Swift is ‘fun’.  There’s a bunch of complexities here, including:

                                      • It’s async, so you have to deal with mapping a Swift object to a CF info pointer and back.

                                      • It’s run loop based, so you have target a specific thread (in my code I always target the main thread).

                                      • The API itself requires that you serialise requests.

                                      The code pasted in below should get you started.

                                      Share and Enjoy

                                      Quinn “The Eskimo!”
                                      Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                                      let myEmail = "eskimo" + "1" + "@apple.com"

                                      import Foundation
                                      
                                      class PACResolver {
                                      
                                          init(script: String) {
                                              self.script = script
                                          }
                                      
                                          let script: String
                                      
                                          enum Result {
                                              case error(error: CFError)
                                              case proxies(proxies: CFArray)
                                          }
                                          typealias Callback = (_ result: Result) -> Void
                                      
                                          private struct Request {
                                              let targetURL: URL
                                              let callback: Callback
                                          }
                                          private var requests: [Request] = []
                                          private var runLoopSource: CFRunLoopSource?
                                      
                                          func resolve(targetURL: URL, callback: @escaping Callback) {
                                              DispatchQueue.main.async {
                                                  let wasEmpty = self.requests.isEmpty
                                                  self.requests.append(Request(targetURL: targetURL, callback: callback))
                                                  if wasEmpty {
                                                      self.startNextRequest()
                                                  }
                                              }
                                          }
                                      
                                          private func startNextRequest() {
                                              guard let request = self.requests.first else {
                                                  return
                                              }
                                      
                                              var context = CFStreamClientContext()
                                              context.info = Unmanaged.passRetained(self).toOpaque()
                                              let rls = CFNetworkExecuteProxyAutoConfigurationScript(
                                                  self.script as CFString,
                                                  request.targetURL as CFURL,
                                                  { (info, proxies, error) in
                                                      let obj = Unmanaged<PACResolver>.fromOpaque(info).takeRetainedValue()
                                                      if let error = error {
                                                          obj.resolveDidFinish(result: .error(error: error))
                                                      } else {
                                                          obj.resolveDidFinish(result: .proxies(proxies: proxies))
                                                      }
                                                  },
                                                  &context
                                              ).takeUnretainedValue()
                                              assert(self.runLoopSource == nil)
                                              self.runLoopSource = rls
                                              CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, .defaultMode)
                                          }
                                      
                                          private func resolveDidFinish(result: Result) {
                                              CFRunLoopSourceInvalidate(self.runLoopSource!)
                                              self.runLoopSource = nil
                                              let request = self.requests.removeFirst()
                                      
                                              request.callback(result)
                                      
                                              self.startNextRequest()
                                          }
                                      }
                                      
                                        • Re: Accessing global proxy settings on iOS
                                          SpareChange Level 1 Level 1 (0 points)

                                          This is a great help... this is really what I was trying to do from the beginning. However, I have been able to confirm what I think is a fairly serious regression in the global HTTP proxy settings. I'll open a bug report on this, but thought I would share here as well. Please let me know if you need the bug report ID#.

                                           

                                          Scenario:

                                          An iPad Air running iOS 9.3.4 and an iPad Air running iOS 10.1; both enrolled in JAMF Pro MDM with the same global HTTP proxy configuration profile installed

                                           

                                          9.3.4 iPad returns:

                                          kCFProxyTypeKey - kCFProxyTypeAutoConfigurationURL

                                          kCFProxyAutoConfigurationURLKey - https://<proxy-pac-url>/proxy.pac

                                           

                                          10.1 iPad returns:

                                          kCFProxyTypeKey - kCFProxyTypeNone

                                           

                                          I have had an opportunity to implement the PAC file execution yet, but am working on that now. I just got these results from CFNetworkCopyProxiesForURL

                                            • Re: Accessing global proxy settings on iOS
                                              eskimo Apple Staff Apple Staff (6,250 points)

                                              Sorry to hear that this is an real regression.

                                              Please let me know if you need the bug report ID#.

                                              Please do post the bug number; Future Quinn™ will appreciate that breadcrumb to follow.

                                              Share and Enjoy

                                              Quinn “The Eskimo!”
                                              Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                                              let myEmail = "eskimo" + "1" + "@apple.com"

                                                • Re: Accessing global proxy settings on iOS
                                                  SpareChange Level 1 Level 1 (0 points)

                                                  The bug number is 28986780.

                                                   

                                                  I am really confused though. Even though the iPad running iOS 10.1 reports None for the proxy type, it is still pulling our proxy server IP address from somewhere. I have reset the network settings, but when off-campus, the iPad is being filtered through our proxy server. However, every request is being filtered through the proxy, not just the ones that don't have a match in the PAC file. Very strange.

                                                   

                                                  Thanks for all of your help with the Quinn.

                                                   

                                                  Sincerely,

                                                  Doug Penny

                                                    • Re: Accessing global proxy settings on iOS
                                                      JMPATLANTA Level 1 Level 1 (10 points)

                                                      Where did you end up on this?  We are having similar problems on our devices that have upgraded to iOS 10+.  The GlobalProxy set from our MDM (MobileIron) policy on our supervised pads worked fine on everything up through 9.3.5 and then went poof when we jumped to iOS 10.x.  We have an open case open but are still in the discovery phase.