Explorar el Código

Added support for NAT port mapping

Pierre-Olivier Latour hace 10 años
padre
commit
e70a3338a5
Se han modificado 3 ficheros con 134 adiciones y 3 borrados
  1. 33 2
      GCDWebServer/Core/GCDWebServer.h
  2. 93 1
      GCDWebServer/Core/GCDWebServer.m
  3. 8 0
      Mac/main.m

+ 33 - 2
GCDWebServer/Core/GCDWebServer.h

@@ -90,14 +90,26 @@ extern NSString* const GCDWebServerOption_BonjourName;
  */
 extern NSString* const GCDWebServerOption_BonjourType;
 
+/**
+ *  Request a port mapping in the NAT gateway (NSNumber / BOOL).
+ *
+ *  This uses the DNSService API under the hood which supports IPv4 mappings only.
+ *
+ *  The default value is NO.
+ *
+ *  @warning The external port set up by the NAT gateway may be different than
+ *  the one used by the GCDWebServer.
+ */
+extern NSString* const GCDWebServerOption_RequestNATPortMapping;
+
 /**
  *  Only accept HTTP requests coming from localhost i.e. not from the outside
  *  network (NSNumber / BOOL).
  *
  *  The default value is NO.
  *
- *  @warning Bonjour should be disabled if using this option since the server
- *  will not be reachable from the outside network anyway.
+ *  @warning Bonjour and NAT port mapping should be disabled if using this option
+ *  since the server will not be reachable from the outside network anyway.
  */
 extern NSString* const GCDWebServerOption_BindToLocalhost;
 
@@ -213,9 +225,20 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
 /**
  *  This method is called after the Bonjour registration for the server has
  *  successfully completed.
+ *
+ *  Use the "bonjourServerURL" property to retrieve the Bonjour address of the
+ *  server.
  */
 - (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
 
+/**
+ *  This method is called after the NAT port mapping has been updated.
+ *
+ *  Use the "publicServerURL" property to retrieve the public address of the
+ *  server.
+ */
+- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server;
+
 /**
  *  This method is called when the first GCDWebServerConnection is opened by the
  *  server to serve a series of HTTP requests.
@@ -362,6 +385,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
  */
 @property(nonatomic, readonly) NSURL* bonjourServerURL;
 
+/**
+ *  Returns the server's public URL.
+ *
+ *  @warning This property is only valid if the server is running and NAT port
+ *  mapping is active.
+ */
+@property(nonatomic, readonly) NSURL* publicServerURL;
+
 /**
  *  Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
  *  using the default Bonjour name.

+ 93 - 1
GCDWebServer/Core/GCDWebServer.m

@@ -38,6 +38,7 @@
 #endif
 #endif
 #import <netinet/in.h>
+#import <dns_sd.h>
 
 #import "GCDWebServerPrivate.h"
 
@@ -50,6 +51,7 @@
 NSString* const GCDWebServerOption_Port = @"Port";
 NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
 NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
+NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping";
 NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
 NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
 NSString* const GCDWebServerOption_ServerName = @"ServerName";
@@ -171,6 +173,11 @@ static void _ExecuteMainThreadRunLoopSources() {
   dispatch_source_t _source6;
   CFNetServiceRef _registrationService;
   CFNetServiceRef _resolutionService;
+  DNSServiceRef _dnsService;
+  CFSocketRef _dnsSocket;
+  CFRunLoopSourceRef _dnsSource;
+  NSString* _dnsAddress;
+  NSUInteger _dnsPort;
   BOOL _bindToLocalhost;
 #if TARGET_OS_IPHONE
   BOOL _suspendInBackground;
@@ -383,7 +390,7 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
       }
     } else {
       GCDWebServer* server = (__bridge GCDWebServer*)info;
-      GWS_LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
+      GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL);
       if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
         [server.delegate webServerDidCompleteBonjourRegistration:server];
       }
@@ -391,6 +398,41 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
   }
 }
 
+static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) {
+  GWS_DCHECK([NSThread isMainThread]);
+  @autoreleasepool {
+    GCDWebServer* server = (__bridge GCDWebServer*)context;
+    if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) {
+      struct sockaddr_in addr4;
+      bzero(&addr4, sizeof(addr4));
+      addr4.sin_len = sizeof(addr4);
+      addr4.sin_family = AF_INET;
+      addr4.sin_addr.s_addr = externalAddress;  // Already in network byte order
+      server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO);
+      server->_dnsPort = ntohs(externalPort);
+      GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL);
+    } else {
+      GWS_LOG_ERROR(@"DNS service error %i", errorCode);
+      server->_dnsAddress = nil;
+      server->_dnsPort = 0;
+    }
+    if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) {
+      [server.delegate webServerDidUpdateNATPortMapping:server];
+    }
+  }
+}
+
+static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
+  GWS_DCHECK([NSThread isMainThread]);
+  @autoreleasepool {
+    GCDWebServer* server = (__bridge GCDWebServer*)info;
+    DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService);
+    if (status != kDNSServiceErr_NoError) {
+      GWS_LOG_ERROR(@"DNS service error %i", status);
+    }
+  }
+}
+
 static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
   id value = [options objectForKey:key];
   return value ? value : defaultValue;
@@ -580,6 +622,29 @@ static inline NSString* _EncodeBase64(NSString* string) {
     }
   }
   
+  if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
+    DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
+    if (status == kDNSServiceErr_NoError) {
+      CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
+      _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
+      if (_dnsSocket) {
+        CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
+        _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
+        if (_dnsSource) {
+          CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
+        } else {
+          GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
+          GWS_DNOT_REACHED();
+        }
+      } else {
+        GWS_LOG_ERROR(@"Failed creating CFSocket");
+        GWS_DNOT_REACHED();
+      }
+    } else {
+      GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
+    }
+  }
+  
   dispatch_resume(_source4);
   dispatch_resume(_source6);
   GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
@@ -595,6 +660,22 @@ static inline NSString* _EncodeBase64(NSString* string) {
 - (void)_stop {
   GWS_DCHECK(_source4 != NULL);
   
+  if (_dnsService) {
+    _dnsAddress = nil;
+    _dnsPort = 0;
+    if (_dnsSource) {
+      CFRunLoopSourceInvalidate(_dnsSource);
+      CFRelease(_dnsSource);
+      _dnsSource = NULL;
+    }
+    if (_dnsSocket) {
+      CFRelease(_dnsSocket);
+      _dnsSocket = NULL;
+    }
+    DNSServiceRefDeallocate(_dnsService);
+    _dnsService = NULL;
+  }
+  
   if (_registrationService) {
     if (_resolutionService) {
       CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
@@ -746,6 +827,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
   return nil;
 }
 
+- (NSURL*)publicServerURL {
+  if (_source4 && _dnsService && _dnsAddress && _dnsPort) {
+    if (_dnsPort != 80) {
+      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]];
+    } else {
+      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]];
+    }
+  }
+  return nil;
+}
+
 - (BOOL)start {
   return [self startWithPort:kDefaultPort bonjourName:@""];
 }

+ 8 - 0
Mac/main.m

@@ -72,6 +72,10 @@ typedef enum {
   [self _logDelegateCall:_cmd];
 }
 
+- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server {
+  [self _logDelegateCall:_cmd];
+}
+
 - (void)webServerDidConnect:(GCDWebServer*)server {
   [self _logDelegateCall:_cmd];
 }
@@ -142,6 +146,7 @@ int main(int argc, const char* argv[]) {
     NSString* authenticationUser = nil;
     NSString* authenticationPassword = nil;
     BOOL bindToLocalhost = NO;
+    BOOL requestNATPortMapping = NO;
     
     if (argc == 1) {
       fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\n\n", basename((char*)argv[0]));
@@ -191,6 +196,8 @@ int main(int argc, const char* argv[]) {
           authenticationPassword = [NSString stringWithUTF8String:argv[i]];
         } else if (!strcmp(argv[i], "--localhost")) {
           bindToLocalhost = YES;
+        } else if (!strcmp(argv[i], "--nat")) {
+          requestNATPortMapping = YES;
         }
       }
     }
@@ -412,6 +419,7 @@ int main(int argc, const char* argv[]) {
         fprintf(stdout, "\n");
         NSMutableDictionary* options = [NSMutableDictionary dictionary];
         [options setObject:@8080 forKey:GCDWebServerOption_Port];
+        [options setObject:@(requestNATPortMapping) forKey:GCDWebServerOption_RequestNATPortMapping];
         [options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
         [options setObject:@"" forKey:GCDWebServerOption_BonjourName];
         if (authenticationUser && authenticationPassword) {