title: P2P UDP connection with STUN
body: <p>Good day Everyone! 
  I've been trying for quite a while to send packets p2p using UDP and GCDAsyncUDPSocket library. So i found an open source library to connect to STUN server and keep external port open. It works quite well and i've already had success with sending data in local network. (After having trouble with own router which refused to connect p2p via udp in local network - still no trouble with Android created network)</p>
  
  <p>The problem occurs  when i try to send data using UDP hole punching.</p>
  
  <p>STUNClient: 
  <a href="https://github.com/soulfly/STUN-iOS" rel="nofollow">https://github.com/soulfly/STUN-iOS</a></p>
  
  <p>UDPClient: <a href="https://github.com/robbiehanson/CocoaAsyncSocket/tree/master/GCD/Xcode/UdpEchoClient/Mobile/UdpEchoClient" rel="nofollow">https://github.com/robbiehanson/CocoaAsyncSocket/tree/master/GCD/Xcode/UdpEchoClient/Mobile/UdpEchoClient</a></p>
  
  <p>UDPServer: <a href="https://github.com/robbiehanson/CocoaAsyncSocket/tree/master/GCD/Xcode/UdpEchoServer/Mobile/UdpEchoServer" rel="nofollow">https://github.com/robbiehanson/CocoaAsyncSocket/tree/master/GCD/Xcode/UdpEchoServer/Mobile/UdpEchoServer</a></p>
  
  <p>My modified code for UDPServer: </p>
  
  <blockquote>
    <p>ViewController.h:</p>
  </blockquote>
  
  <pre><code>#import &lt;UIKit/UIKit.h&gt;
  #import "STUNClient.h"
  
  @interface ViewController : UIViewController &lt;STUNClientDelegate&gt;
  {
      IBOutlet UITextField *portField;
      IBOutlet UIButton *startStopButton;
      IBOutlet UIWebView *webView;
  }
  - (IBAction)startStop:(id)sender;
  - (IBAction)startStun:(id)sender;
  @end
  </code></pre>
  
  <blockquote>
    <p>ViewController.m</p>
  </blockquote>
  
  <pre><code>#import "ViewController.h"
  #import "GCDAsyncUdpSocket.h"
  #import "DDLog.h"
  
  // Log levels: off, error, warn, info, verbose
  static const int ddLogLevel = LOG_LEVEL_VERBOSE;
  
  #define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__]
  
  
  @interface ViewController ()
  {
      BOOL isRunning;
      GCDAsyncUdpSocket *udpSocket;
      NSMutableString *log;
      STUNClient* stunClient;
  }
  
  @end
  
  @implementation ViewController
  
  - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  {
      if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
      {
          log = [[NSMutableString alloc] init];
  
          // Setup our socket.
          // The socket will invoke our delegate methods using the usual delegate paradigm.
          // However, it will invoke the delegate methods on a specified GCD delegate dispatch queue.
          // 
          // Now we can configure the delegate dispatch queues however we want.
          // We could simply use the main dispatch queue, so the delegate methods are invoked on the main thread.
          // Or we could use a dedicated dispatch queue, which could be helpful if we were doing a lot of processing.
          // 
          // The best approach for your application will depend upon convenience, requirements and performance.
          // 
          // For this simple example, we're just going to use the main thread.
          stunClient= [[STUNClient alloc] init];
  
          udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
  
  
      }
      return self;
  }
  
  
  - (void)viewDidLoad
  {
      [super viewDidLoad];
  
      webView.dataDetectorTypes = UIDataDetectorTypeNone;
  
      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(keyboardWillShow:)
                                                   name:UIKeyboardWillShowNotification 
                                                 object:nil];
  
      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(keyboardWillHide:)
                                                   name:UIKeyboardWillHideNotification
                                                 object:nil];
  }
  
  - (void)viewDidUnload
  {
      [super viewDidUnload];
  
      [[NSNotificationCenter defaultCenter] removeObserver:self];
  }
  
  - (void)getKeyboardHeight:(float *)keyboardHeightPtr
          animationDuration:(double *)animationDurationPtr
                       from:(NSNotification *)notification
  {
      float keyboardHeight;
      double animationDuration;
  
      // UIKeyboardCenterBeginUserInfoKey:
      // The key for an NSValue object containing a CGRect
      // that identifies the start frame of the keyboard in screen coordinates.
  
      CGRect beginRect = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
      CGRect endRect   = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  
      if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
      {
          keyboardHeight = ABS(beginRect.origin.x - endRect.origin.x);
      }
      else
      {
          keyboardHeight = ABS(beginRect.origin.y - endRect.origin.y);
      }
  
      // UIKeyboardAnimationDurationUserInfoKey
      // The key for an NSValue object containing a double that identifies the duration of the animation in seconds.
  
      animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  
      if (keyboardHeightPtr) *keyboardHeightPtr = keyboardHeight;
      if (animationDurationPtr) *animationDurationPtr = animationDuration;
  }
  
  - (void)keyboardWillShow:(NSNotification *)notification
  {
      float keyboardHeight = 0.0F;
      double animationDuration = 0.0;
  
      [self getKeyboardHeight:&amp;keyboardHeight animationDuration:&amp;animationDuration from:notification];
  
      CGRect webViewFrame = webView.frame;
      webViewFrame.size.height -= keyboardHeight;
  
      void (^animationBlock)(void) = ^{
  
          webView.frame = webViewFrame;
      };
  
      UIViewAnimationOptions options = 0;
  
      [UIView animateWithDuration:animationDuration
                            delay:0.0
                          options:options
                       animations:animationBlock
                       completion:NULL];
  }
  
  - (void)keyboardWillHide:(NSNotification *)notification
  {
      float keyboardHeight = 0.0F;
      double animationDuration = 0.0;
  
      [self getKeyboardHeight:&amp;keyboardHeight animationDuration:&amp;animationDuration from:notification];
  
      CGRect webViewFrame = webView.frame;
      webViewFrame.size.height += keyboardHeight;
  
      void (^animationBlock)(void) = ^{
  
          webView.frame = webViewFrame;
      };
  
      UIViewAnimationOptions options = 0;
  
      [UIView animateWithDuration:animationDuration
                            delay:0.0
                          options:options
                       animations:animationBlock
                       completion:NULL];
  }
  
  - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
  {
      DDLogError(@"webView:didFailLoadWithError: %@", error);
  }
  
  - (void)webViewDidFinishLoad:(UIWebView *)sender
  {
      NSString *scrollToBottom = @"window.scrollTo(document.body.scrollWidth, document.body.scrollHeight);";
  
      [sender stringByEvaluatingJavaScriptFromString:scrollToBottom];
  }
  
  - (void)logError:(NSString *)msg
  {
      NSString *prefix = @"&lt;font color=\"#B40404\"&gt;";
      NSString *suffix = @"&lt;/font&gt;&lt;br/&gt;";
  
      [log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
  
      NSString *html = [NSString stringWithFormat:@"&lt;html&gt;&lt;body&gt;\n%@\n&lt;/body&gt;&lt;/html&gt;", log];
      [webView loadHTMLString:html baseURL:nil];
  }
  
  - (void)logInfo:(NSString *)msg
  {
      NSString *prefix = @"&lt;font color=\"#6A0888\"&gt;";
      NSString *suffix = @"&lt;/font&gt;&lt;br/&gt;";
  
      [log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
  
      NSString *html = [NSString stringWithFormat:@"&lt;html&gt;&lt;body&gt;\n%@\n&lt;/body&gt;&lt;/html&gt;", log];
      [webView loadHTMLString:html baseURL:nil];
  }
  
  - (void)logMessage:(NSString *)msg
  {
      NSString *prefix = @"&lt;font color=\"#000000\"&gt;";
      NSString *suffix = @"&lt;/font&gt;&lt;br/&gt;";
  
      [log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
  
      NSString *html = [NSString stringWithFormat:@"&lt;html&gt;&lt;body&gt;%@&lt;/body&gt;&lt;/html&gt;", log];
      [webView loadHTMLString:html baseURL:nil];
  }
  
  - (IBAction)startStop:(id)sender
  {
      if (isRunning)
      {
          // STOP udp echo server
  
          [udpSocket close];
  
          [self logInfo:@"Stopped Udp Echo server"];
          isRunning = false;
  
          [portField setEnabled:YES];
          [startStopButton setTitle:@"Start" forState:UIControlStateNormal];
      }
      else
      {
          //*START udp echo server
  
          int port = [portField.text intValue];
          if (port &lt; 0 || port &gt; 65535)
          {
              portField.text = @"";
              port = 0;
          }
  
          NSError *error = nil;
  
          if (![udpSocket bindToPort:port error:&amp;error])
          {
              [self logError:FORMAT(@"Error starting server (bind): %@", error)];
              return;
          }
  
          if (![udpSocket beginReceiving:&amp;error])
          {
              [udpSocket close];
  
              [self logError:FORMAT(@"Error starting server (recv): %@", error)];
              return;
          }
  
          [self logInfo:FORMAT(@"Udp Echo server started on port %hu", [udpSocket localPort])];
          isRunning = YES;
  
          [portField setEnabled:NO];
          [startStopButton setTitle:@"Stop" forState:UIControlStateNormal];
  
      }
  }
  -(void)didReceivePublicIPandPort:(NSDictionary *) data{
      NSLog(@"Public IP=%@, public Port=%@, NAT is Symmetric: %@", [data objectForKey:publicIPKey],
            [data objectForKey:publicPortKey], [data objectForKey:isPortRandomization]);
  
  
      int portT=   [((NSNumber*)[data objectForKey:publicPortKey]) intValue];
      NSString* ipT=[data objectForKey:publicIPKey];
      [self logInfo:FORMAT(@"STUN says external IP: %@ PORT %d", ipT,portT)];
  
      [self logInfo:FORMAT(@"LOCAL IP: %@  LOCAL PORT %d", [udpSocket localHost],[udpSocket localPort])];
      isRunning =YES;
      [stunClient startSendIndicationMessage];
  }
  - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
                                                 fromAddress:(NSData *)address
                                           withFilterContext:(id)filterContext
  {
      if (!isRunning) return;
  
      NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      if (msg)
      {
          /* If you want to get a display friendly version of the IPv4 or IPv6 address, you could do this:
  
          NSString *host = nil;
          uint16_t port = 0;
          [GCDAsyncUdpSocket getHost:&amp;host port:&amp;port fromAddress:address];
  
          */
  
          [self logMessage:msg];
      }
      else
      {
          [self logError:@"Error converting received data into UTF-8 String"];
      }
  
      [udpSocket sendData:data toAddress:address withTimeout:-1 tag:0];
  }
  - (IBAction)startStun:(id)sender
  {
       [stunClient requestPublicIPandPortWithUDPSocket:udpSocket delegate:self];
  }
  @end
  </code></pre>
  
  <blockquote>
    <p>What I do:</p>
  </blockquote>
  
  <ol>
  <li><p>Turn Client and Server app ON. </p></li>
  <li><p>Push STUN Button - which
      launches `- (IBAction)startStun:(id)sender;</p></li>
  <li>Manually input external ip and port from STUN server in ClientApp.</li>
  <li>Send.</li>
  </ol>
  
  <p>Maybe it is router problems? Or i do not completely understand hole punching methodology?</p>