Fixed DevStudio 2003 build with memory check code.
[pwlib.git] / src / ptclib / pstun.cxx
blob00b5d22bd6439d1313d37c6425c68ab7767a20bc
1 /*
2 * pstun.cxx
4 * STUN Client
6 * Portable Windows Library
8 * Copyright (c) 2003 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
26 * $Log$
27 * Revision 1.26 2007/09/08 11:34:28 rjongbloed
28 * Improved memory checking (leaks etc), especially when using MSVC debug library.
30 * Revision 1.25 2007/08/22 05:04:39 rjongbloed
31 * Added ability to set a specific local port for STUN created sockets.
33 * Revision 1.24 2007/07/22 03:07:31 rjongbloed
34 * Added parameter so can bind STUN socket to specific interface.
36 * Revision 1.23 2007/02/11 13:13:07 shorne
37 * Added message when stun server cannot be reached
39 * Revision 1.22 2006/12/23 15:08:11 shorne
40 * Now Factory loaded for ease of addition of new NAT Methods
42 * Revision 1.21 2006/08/29 18:41:20 dsandras
43 * Check validity of STUN messages.
45 * Revision 1.20 2005/12/05 21:58:36 dsandras
46 * Fixed bug when looking if the cache is still valid.
48 * Revision 1.19 2005/11/30 12:47:41 csoutheren
49 * Removed tabs, reformatted some code, and changed tags for Doxygen
51 * Revision 1.18 2005/07/13 11:15:26 csoutheren
52 * Backported NAT abstraction files from isvo branch
54 * Revision 1.17 2005/06/20 10:55:17 rjongbloed
55 * Changed the timeout and retries so if there is a blocking firewall it does not take 15 seconds to find out!
56 * Added access functions so timeout and retries are application configurable.
57 * Added function (and << operator) to get NAT type enum as string.
59 * Revision 1.16.4.1 2005/04/25 13:19:27 shorne
60 * Add Support for other NAT methods
62 * Revision 1.16 2004/11/25 07:23:46 csoutheren
63 * Added IsSupportingRTP function to simplify detecting when STUN supports RTP
65 * Revision 1.15 2004/10/26 05:58:23 csoutheren
66 * Increased timeout on STUN responses to avoid spurious STUN failures due
67 * to network trsffic/congestion etc
69 * Revision 1.14 2004/08/18 13:16:07 rjongbloed
70 * Fixed STUN CreateSocketPair so first socket is always even.
72 * Revision 1.13 2004/03/14 05:47:52 rjongbloed
73 * Fixed incorrect detection of symmetric NAT (eg Linux masquerading) and also
74 * some NAT systems which are partially blocked due to firewall rules.
76 * Revision 1.12 2004/02/24 11:15:48 rjongbloed
77 * Added function to get external router address, also did a bunch of documentation.
79 * Revision 1.11 2004/02/17 11:11:05 rjongbloed
80 * Added missing #pragma pack() to turn off byte alignment for the last class, thanks Ted Szoczei
82 * Revision 1.10 2004/01/17 17:54:02 rjongbloed
83 * Added function to get server name from STUN client.
85 * Revision 1.9 2003/10/08 22:00:18 dereksmithies
86 * Fix unsigned/signed warning message. Thanks to Craig Southeren.
88 * Revision 1.8 2003/10/05 00:56:25 rjongbloed
89 * Rewrite of STUN to not to use imported code with undesirable license.
91 * Revision 1.5 2003/02/05 06:26:49 robertj
92 * More work in making the STUN usable for Symmetric NAT systems.
94 * Revision 1.4 2003/02/04 07:02:17 robertj
95 * Added ip/port version of constructor.
96 * Removed creating sockets for Open type.
98 * Revision 1.3 2003/02/04 05:55:04 craigs
99 * Added socket pair function
101 * Revision 1.2 2003/02/04 05:06:24 craigs
102 * Added new functions
104 * Revision 1.1 2003/02/04 03:31:04 robertj
105 * Added STUN
109 #ifdef __GNUC__
110 #pragma implementation "pstun.h"
111 #endif
113 #define P_FORCE_STATIC_PLUGIN
115 #include <ptlib.h>
116 #include <ptclib/pstun.h>
117 #include <ptclib/random.h>
119 #define new PNEW
122 #if defined(_WIN32) && !defined(P_FORCE_STATIC_PLUGIN)
123 #error "pstun.cxx must be compiled without precompiled headers"
124 #endif
126 PCREATE_NAT_PLUGIN(STUN);
128 // Sample server is at larry.gloo.net
130 #define DEFAULT_REPLY_TIMEOUT 1000
131 #define DEFAULT_POLL_RETRIES 5
132 #define DEFAULT_NUM_SOCKETS_FOR_PAIRING 4
135 ///////////////////////////////////////////////////////////////////////
137 PSTUNClient::PSTUNClient()
138 : serverAddress(0),
139 serverPort(DefaultPort),
140 replyTimeout(DEFAULT_REPLY_TIMEOUT),
141 pollRetries(DEFAULT_POLL_RETRIES),
142 numSocketsForPairing(DEFAULT_NUM_SOCKETS_FOR_PAIRING),
143 natType(UnknownNat),
144 cachedExternalAddress(0),
145 timeAddressObtained(0)
149 PSTUNClient::PSTUNClient(const PString & server,
150 WORD portBase, WORD portMax,
151 WORD portPairBase, WORD portPairMax)
152 : serverAddress(0),
153 serverPort(DefaultPort),
154 replyTimeout(DEFAULT_REPLY_TIMEOUT),
155 pollRetries(DEFAULT_POLL_RETRIES),
156 numSocketsForPairing(DEFAULT_NUM_SOCKETS_FOR_PAIRING),
157 natType(UnknownNat),
158 cachedExternalAddress(0),
159 timeAddressObtained(0)
161 SetServer(server);
162 SetPortRanges(portBase, portMax, portPairBase, portPairMax);
166 PSTUNClient::PSTUNClient(const PIPSocket::Address & address, WORD port,
167 WORD portBase, WORD portMax,
168 WORD portPairBase, WORD portPairMax)
169 : serverAddress(address),
170 serverPort(port),
171 replyTimeout(DEFAULT_REPLY_TIMEOUT),
172 pollRetries(DEFAULT_POLL_RETRIES),
173 numSocketsForPairing(DEFAULT_NUM_SOCKETS_FOR_PAIRING),
174 natType(UnknownNat),
175 cachedExternalAddress(0),
176 timeAddressObtained(0)
178 SetPortRanges(portBase, portMax, portPairBase, portPairMax);
181 void PSTUNClient::Initialise(const PString & server,
182 WORD portBase, WORD portMax,
183 WORD portPairBase, WORD portPairMax)
185 SetServer(server);
186 SetPortRanges(portBase, portMax, portPairBase, portPairMax);
190 PString PSTUNClient::GetServer() const
192 PStringStream str;
193 str << serverAddress << ':' << serverPort;
194 return str;
198 BOOL PSTUNClient::SetServer(const PString & server)
200 PINDEX colon = server.Find(':');
201 if (colon == P_MAX_INDEX) {
202 if (!PIPSocket::GetHostAddress(server, serverAddress))
203 return FALSE;
205 else {
206 if (!PIPSocket::GetHostAddress(server.Left(colon), serverAddress))
207 return FALSE;
208 serverPort = PIPSocket::GetPortByService("udp", server.Mid(colon+1));
211 return serverAddress.IsValid() && serverPort != 0;
215 BOOL PSTUNClient::SetServer(const PIPSocket::Address & address, WORD port)
217 serverAddress = address;
218 serverPort = port;
219 return serverAddress.IsValid() && serverPort != 0;
222 #pragma pack(1)
224 struct PSTUNAttribute
226 enum Types {
227 MAPPED_ADDRESS = 0x0001,
228 RESPONSE_ADDRESS = 0x0002,
229 CHANGE_REQUEST = 0x0003,
230 SOURCE_ADDRESS = 0x0004,
231 CHANGED_ADDRESS = 0x0005,
232 USERNAME = 0x0006,
233 PASSWORD = 0x0007,
234 MESSAGE_INTEGRITY = 0x0008,
235 ERROR_CODE = 0x0009,
236 UNKNOWN_ATTRIBUTES = 0x000a,
237 REFLECTED_FROM = 0x000b,
238 MaxValidCode
241 PUInt16b type;
242 PUInt16b length;
244 PSTUNAttribute * GetNext() const { return (PSTUNAttribute *)(((const BYTE *)this)+length+4); }
247 class PSTUNAddressAttribute : public PSTUNAttribute
249 public:
250 BYTE pad;
251 BYTE family;
252 PUInt16b port;
253 BYTE ip[4];
255 PIPSocket::Address GetIP() const { return PIPSocket::Address(4, ip); }
257 protected:
258 enum { SizeofAddressAttribute = sizeof(BYTE)+sizeof(BYTE)+sizeof(WORD)+sizeof(PIPSocket::Address) };
259 void InitAddrAttr(Types newType)
261 type = (WORD)newType;
262 length = SizeofAddressAttribute;
263 pad = 0;
264 family = 1;
266 bool IsValidAddrAttr(Types checkType) const
268 return type == checkType && length == SizeofAddressAttribute;
272 class PSTUNMappedAddress : public PSTUNAddressAttribute
274 public:
275 void Initialise() { InitAddrAttr(MAPPED_ADDRESS); }
276 bool IsValid() const { return IsValidAddrAttr(MAPPED_ADDRESS); }
279 class PSTUNChangedAddress : public PSTUNAddressAttribute
281 public:
282 void Initialise() { InitAddrAttr(CHANGED_ADDRESS); }
283 bool IsValid() const { return IsValidAddrAttr(CHANGED_ADDRESS); }
286 class PSTUNChangeRequest : public PSTUNAttribute
288 public:
289 BYTE flags[4];
291 PSTUNChangeRequest() { }
293 PSTUNChangeRequest(bool changeIP, bool changePort)
295 Initialise();
296 SetChangeIP(changeIP);
297 SetChangePort(changePort);
300 void Initialise()
302 type = CHANGE_REQUEST;
303 length = sizeof(flags);
304 memset(flags, 0, sizeof(flags));
306 bool IsValid() const { return type == CHANGE_REQUEST && length == sizeof(flags); }
308 bool GetChangeIP() const { return (flags[3]&4) != 0; }
309 void SetChangeIP(bool on) { if (on) flags[3] |= 4; else flags[3] &= ~4; }
311 bool GetChangePort() const { return (flags[3]&2) != 0; }
312 void SetChangePort(bool on) { if (on) flags[3] |= 2; else flags[3] &= ~2; }
315 class PSTUNMessageIntegrity : public PSTUNAttribute
317 public:
318 BYTE hmac[20];
320 void Initialise()
322 type = MESSAGE_INTEGRITY;
323 length = sizeof(hmac);
324 memset(hmac, 0, sizeof(hmac));
326 bool IsValid() const { return type == MESSAGE_INTEGRITY && length == sizeof(hmac); }
329 struct PSTUNMessageHeader
331 PUInt16b msgType;
332 PUInt16b msgLength;
333 BYTE transactionId[16];
337 #pragma pack()
340 class PSTUNMessage : public PBYTEArray
342 public:
343 enum MsgType {
344 BindingRequest = 0x0001,
345 BindingResponse = 0x0101,
346 BindingError = 0x0111,
348 SharedSecretRequest = 0x0002,
349 SharedSecretResponse = 0x0102,
350 SharedSecretError = 0x0112,
353 PSTUNMessage()
356 PSTUNMessage(MsgType newType, const BYTE * id = NULL)
357 : PBYTEArray(sizeof(PSTUNMessageHeader))
359 SetType(newType, id);
362 void SetType(MsgType newType, const BYTE * id = NULL)
364 SetMinSize(sizeof(PSTUNMessageHeader));
365 PSTUNMessageHeader * hdr = (PSTUNMessageHeader *)theArray;
366 hdr->msgType = (WORD)newType;
367 for (PINDEX i = 0; i < ((PINDEX)sizeof(hdr->transactionId)); i++)
368 hdr->transactionId[i] = id != NULL ? id[i] : (BYTE)PRandom::Number();
371 const PSTUNMessageHeader * operator->() const { return (PSTUNMessageHeader *)theArray; }
373 PSTUNAttribute * GetFirstAttribute() {
375 int length = ((PSTUNMessageHeader *)theArray)->msgLength;
376 if (theArray == NULL || length < (int) sizeof(PSTUNMessageHeader))
377 return NULL;
379 PSTUNAttribute * attr = (PSTUNAttribute *)(theArray+sizeof(PSTUNMessageHeader));
380 PSTUNAttribute * ptr = attr;
382 if (attr->length > GetSize() || attr->type >= PSTUNAttribute::MaxValidCode)
383 return NULL;
385 while (ptr && (BYTE*) ptr < (BYTE*)(theArray+GetSize()) && length >= (int) ptr->length+4) {
387 length -= ptr->length + 4;
388 ptr = ptr->GetNext();
391 if (length != 0)
392 return NULL;
394 return attr;
397 bool Validate()
399 int length = ((PSTUNMessageHeader *)theArray)->msgLength;
400 PSTUNAttribute * attrib = GetFirstAttribute();
401 while (attrib && length > 0) {
402 length -= attrib->length + 4;
403 attrib = attrib->GetNext();
406 return length == 0; // Exactly correct length
409 void AddAttribute(const PSTUNAttribute & attribute)
411 PSTUNMessageHeader * hdr = (PSTUNMessageHeader *)theArray;
412 int oldLength = hdr->msgLength;
413 int attrSize = attribute.length + 4;
414 int newLength = oldLength + attrSize;
415 hdr->msgLength = (WORD)newLength;
416 // hdr pointer may be invalidated by next statement
417 SetMinSize(newLength+sizeof(PSTUNMessageHeader));
418 memcpy(theArray+sizeof(PSTUNMessageHeader)+oldLength, &attribute, attrSize);
421 void SetAttribute(const PSTUNAttribute & attribute)
423 int length = ((PSTUNMessageHeader *)theArray)->msgLength;
424 PSTUNAttribute * attrib = GetFirstAttribute();
425 while (length > 0) {
426 if (attrib->type == attribute.type) {
427 if (attrib->length == attribute.length)
428 *attrib = attribute;
429 else {
430 // More here
432 return;
435 length -= attrib->length + 4;
436 attrib = attrib->GetNext();
439 AddAttribute(attribute);
442 PSTUNAttribute * FindAttribute(PSTUNAttribute::Types type)
444 int length = ((PSTUNMessageHeader *)theArray)->msgLength;
445 PSTUNAttribute * attrib = GetFirstAttribute();
446 while (length > 0) {
447 if (attrib->type == type)
448 return attrib;
450 length -= attrib->length + 4;
451 attrib = attrib->GetNext();
453 return NULL;
457 bool Read(PUDPSocket & socket)
459 if (!socket.Read(GetPointer(1000), 1000))
460 return false;
461 SetSize(socket.GetLastReadCount());
462 return true;
465 bool Write(PUDPSocket & socket) const
467 return socket.Write(theArray, ((PSTUNMessageHeader *)theArray)->msgLength+sizeof(PSTUNMessageHeader)) != FALSE;
470 bool Poll(PUDPSocket & socket, const PSTUNMessage & request, PINDEX pollRetries)
472 for (PINDEX retry = 0; retry < pollRetries; retry++) {
473 if (!request.Write(socket))
474 break;
476 if (Read(socket) && Validate() &&
477 memcmp(request->transactionId, (*this)->transactionId, sizeof(request->transactionId)) == 0)
478 return true;
481 return false;
486 bool PSTUNClient::OpenSocket(PUDPSocket & socket, PortInfo & portInfo, const PIPSocket::Address & binding) const
488 PWaitAndSignal mutex(portInfo.mutex);
490 WORD startPort = portInfo.currentPort;
492 do {
493 portInfo.currentPort++;
494 if (portInfo.currentPort > portInfo.maxPort)
495 portInfo.currentPort = portInfo.basePort;
497 if (socket.Listen(binding, 1, portInfo.currentPort)) {
498 socket.SetSendAddress(serverAddress, serverPort);
499 socket.SetReadTimeout(replyTimeout);
500 return true;
503 } while (portInfo.currentPort != startPort);
505 PTRACE(1, "STUN\tFailed to bind to local UDP port in range "
506 << portInfo.currentPort << '-' << portInfo.maxPort);
507 return false;
511 PSTUNClient::NatTypes PSTUNClient::GetNatType(BOOL force)
513 if (!force && natType != UnknownNat)
514 return natType;
516 PUDPSocket socket;
517 if (!OpenSocket(socket, singlePortInfo, PIPSocket::GetDefaultIpAny()))
518 return natType = UnknownNat;
520 // RFC3489 discovery
522 /* test I - the client sends a STUN Binding Request to a server, without
523 any flags set in the CHANGE-REQUEST attribute, and without the
524 RESPONSE-ADDRESS attribute. This causes the server to send the response
525 back to the address and port that the request came from. */
526 PSTUNMessage requestI(PSTUNMessage::BindingRequest);
527 requestI.AddAttribute(PSTUNChangeRequest(false, false));
528 PSTUNMessage responseI;
529 if (!responseI.Poll(socket, requestI, pollRetries)) {
530 if (socket.GetErrorCode(PChannel::LastWriteError) != PChannel::NoError) {
531 PTRACE(1, "STUN\tError writing to server " << serverAddress << ':' << serverPort << " - " << socket.GetErrorText(PChannel::LastWriteError));
532 return natType = UnknownNat; // No response usually means blocked
535 PTRACE(3, "STUN\tNo response to server " << serverAddress << ':' << serverPort << " - " << socket.GetErrorText(PChannel::LastReadError));
536 return natType = BlockedNat; // No response usually means blocked
539 PSTUNMappedAddress * mappedAddress = (PSTUNMappedAddress *)responseI.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS);
540 if (mappedAddress == NULL) {
541 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress << ':' << serverPort);
542 return natType = UnknownNat; // Protocol error
545 PIPSocket::Address mappedAddressI = mappedAddress->GetIP();
546 WORD mappedPortI = mappedAddress->port;
547 bool notNAT = socket.GetPort() == mappedPortI && PIPSocket::IsLocalHost(mappedAddressI);
549 /* Test II - the client sends a Binding Request with both the "change IP"
550 and "change port" flags from the CHANGE-REQUEST attribute set. */
551 PSTUNMessage requestII(PSTUNMessage::BindingRequest);
552 requestII.AddAttribute(PSTUNChangeRequest(true, true));
553 PSTUNMessage responseII;
554 bool testII = responseII.Poll(socket, requestII, pollRetries);
556 if (notNAT) {
557 // Is not NAT or symmetric firewall
558 return natType = (testII ? OpenNat : SymmetricFirewall);
561 if (testII)
562 return natType = ConeNat;
564 PSTUNChangedAddress * changedAddress = (PSTUNChangedAddress *)responseI.FindAttribute(PSTUNAttribute::CHANGED_ADDRESS);
565 if (changedAddress == NULL)
566 return natType = UnknownNat; // Protocol error
568 // Send test I to another server, to see if restricted or symmetric
569 PIPSocket::Address secondaryServer = changedAddress->GetIP();
570 WORD secondaryPort = changedAddress->port;
571 socket.SetSendAddress(secondaryServer, secondaryPort);
572 PSTUNMessage requestI2(PSTUNMessage::BindingRequest);
573 requestI2.AddAttribute(PSTUNChangeRequest(false, false));
574 PSTUNMessage responseI2;
575 if (!responseI2.Poll(socket, requestI2, pollRetries)) {
576 PTRACE(2, "STUN\tPoll of secondary server " << secondaryServer << ':' << secondaryPort
577 << " failed, NAT partially blocked by firwall rules.");
578 return natType = PartialBlockedNat;
581 mappedAddress = (PSTUNMappedAddress *)responseI2.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS);
582 if (mappedAddress == NULL) {
583 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress << ':' << serverPort);
584 return UnknownNat; // Protocol error
587 if (mappedAddress->port != mappedPortI || mappedAddress->GetIP() != mappedAddressI)
588 return natType = SymmetricNat;
590 socket.SetSendAddress(serverAddress, serverPort);
591 PSTUNMessage requestIII(PSTUNMessage::BindingRequest);
592 requestIII.SetAttribute(PSTUNChangeRequest(false, true));
593 PSTUNMessage responseIII;
594 return natType = (responseIII.Poll(socket, requestIII, pollRetries) ? RestrictedNat : PortRestrictedNat);
598 PString PSTUNClient::GetNatTypeString(NatTypes type)
600 static const char * const Names[NumNatTypes] = {
601 "Unknown NAT",
602 "Open NAT",
603 "Cone NAT",
604 "Restricted NAT",
605 "Port Restricted NAT",
606 "Symmetric NAT",
607 "Symmetric Firewall",
608 "Blocked",
609 "Partially Blocked"
612 if (type < NumNatTypes)
613 return Names[type];
615 return psprintf("<NATType %u>", type);
619 PSTUNClient::RTPSupportTypes PSTUNClient::IsSupportingRTP(BOOL force)
621 switch (GetNatType(force)) {
623 // types that do support RTP
624 case OpenNat:
625 case ConeNat:
626 return RTPOK;
628 // types that support RTP if media sent first
629 case SymmetricFirewall:
630 case RestrictedNat:
631 case PortRestrictedNat:
632 return RTPIfSendMedia;
634 // types that do not support RTP
635 case BlockedNat:
636 case SymmetricNat:
637 return RTPUnsupported;
639 // types that have unknown RTP support
640 case UnknownNat:
641 case PartialBlockedNat:
642 default:
643 break;
646 return RTPUnknown;
649 BOOL PSTUNClient::GetExternalAddress(PIPSocket::Address & externalAddress,
650 const PTimeInterval & maxAge)
652 if (cachedExternalAddress.IsValid() && (PTime() - timeAddressObtained < maxAge)) {
653 externalAddress = cachedExternalAddress;
654 return TRUE;
657 externalAddress = 0; // Set to invalid address
659 PUDPSocket socket;
660 if (!OpenSocket(socket, singlePortInfo, PIPSocket::GetDefaultIpAny()))
661 return false;
663 PSTUNMessage request(PSTUNMessage::BindingRequest);
664 request.AddAttribute(PSTUNChangeRequest(false, false));
665 PSTUNMessage response;
666 if (!response.Poll(socket, request, pollRetries))
668 PTRACE(1, "STUN\tServer " << serverAddress << ':' << serverPort << " unexpectedly went offline.");
669 return false;
672 PSTUNMappedAddress * mappedAddress = (PSTUNMappedAddress *)response.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS);
673 if (mappedAddress == NULL)
675 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress << ':' << serverPort);
676 return false;
680 externalAddress = cachedExternalAddress = mappedAddress->GetIP();
681 timeAddressObtained = PTime();
682 return true;
686 BOOL PSTUNClient::CreateSocket(PUDPSocket * & socket, const PIPSocket::Address & binding, WORD localPort)
688 socket = NULL;
690 switch (GetNatType(FALSE)) {
691 case ConeNat :
692 case RestrictedNat :
693 case PortRestrictedNat :
694 break;
696 case SymmetricNat :
697 if (singlePortInfo.basePort == 0 || singlePortInfo.basePort > singlePortInfo.maxPort)
699 PTRACE(1, "STUN\tInvalid local UDP port range "
700 << singlePortInfo.currentPort << '-' << singlePortInfo.maxPort);
701 return FALSE;
703 break;
705 default : // UnknownNet, SymmetricFirewall, BlockedNat
706 PTRACE(1, "STUN\tCannot create socket using NAT type " << GetNatTypeName());
707 return FALSE;
710 PSTUNUDPSocket * stunSocket = new PSTUNUDPSocket;
712 BOOL opened;
713 if (localPort == 0)
714 opened = OpenSocket(*stunSocket, singlePortInfo, binding);
715 else {
716 PortInfo portInfo = localPort;
717 opened = OpenSocket(*stunSocket, portInfo, binding);
720 if (opened)
722 PSTUNMessage request(PSTUNMessage::BindingRequest);
723 request.AddAttribute(PSTUNChangeRequest(false, false));
724 PSTUNMessage response;
726 if (response.Poll(*stunSocket, request, pollRetries))
728 PSTUNMappedAddress * mappedAddress = (PSTUNMappedAddress *)response.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS);
729 if (mappedAddress != NULL)
731 stunSocket->externalIP = mappedAddress->GetIP();
732 if (GetNatType(FALSE) != SymmetricNat)
733 stunSocket->port = mappedAddress->port;
734 stunSocket->SetSendAddress(0, 0);
735 stunSocket->SetReadTimeout(PMaxTimeInterval);
736 socket = stunSocket;
737 return true;
740 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress << ':' << serverPort);
742 else
743 PTRACE(1, "STUN\tServer " << serverAddress << ':' << serverPort << " unexpectedly went offline.");
746 delete stunSocket;
747 return false;
751 BOOL PSTUNClient::CreateSocketPair(PUDPSocket * & socket1,
752 PUDPSocket * & socket2,
753 const PIPSocket::Address & binding)
755 socket1 = NULL;
756 socket2 = NULL;
758 switch (GetNatType(FALSE)) {
759 case ConeNat :
760 case RestrictedNat :
761 case PortRestrictedNat :
762 break;
764 case SymmetricNat :
765 if (pairedPortInfo.basePort == 0 || pairedPortInfo.basePort > pairedPortInfo.maxPort)
767 PTRACE(1, "STUN\tInvalid local UDP port range "
768 << pairedPortInfo.currentPort << '-' << pairedPortInfo.maxPort);
769 return FALSE;
771 break;
773 default : // UnknownNet, SymmetricFirewall, BlockedNat
774 PTRACE(1, "STUN\tCannot create socket pair using NAT type " << GetNatTypeName());
775 return FALSE;
778 PINDEX i;
780 PList<PSTUNUDPSocket> stunSocket;
781 PList<PSTUNMessage> request;
782 PList<PSTUNMessage> response;
784 for (i = 0; i < numSocketsForPairing; i++)
786 PINDEX idx = stunSocket.Append(new PSTUNUDPSocket);
787 if (!OpenSocket(stunSocket[idx], pairedPortInfo, binding)) {
788 PTRACE(1, "STUN\tUnable to open socket to server " << serverAddress);
789 return false;
792 idx = request.Append(new PSTUNMessage(PSTUNMessage::BindingRequest));
793 request[idx].AddAttribute(PSTUNChangeRequest(false, false));
795 response.Append(new PSTUNMessage);
798 for (i = 0; i < numSocketsForPairing; i++)
800 if (!response[i].Poll(stunSocket[i], request[i], pollRetries))
802 PTRACE(1, "STUN\tServer " << serverAddress << ':' << serverPort << " unexpectedly went offline.");
803 return false;
807 for (i = 0; i < numSocketsForPairing; i++)
809 PSTUNMappedAddress * mappedAddress = (PSTUNMappedAddress *)response[i].FindAttribute(PSTUNAttribute::MAPPED_ADDRESS);
810 if (mappedAddress == NULL)
812 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress << ':' << serverPort);
813 return false;
815 if (GetNatType(FALSE) != SymmetricNat)
816 stunSocket[i].port = mappedAddress->port;
817 stunSocket[i].externalIP = mappedAddress->GetIP();
820 for (i = 0; i < numSocketsForPairing; i++)
822 for (PINDEX j = 0; j < numSocketsForPairing; j++)
824 if ((stunSocket[i].port&1) == 0 && (stunSocket[i].port+1) == stunSocket[j].port) {
825 stunSocket[i].SetSendAddress(0, 0);
826 stunSocket[i].SetReadTimeout(PMaxTimeInterval);
827 stunSocket[j].SetSendAddress(0, 0);
828 stunSocket[j].SetReadTimeout(PMaxTimeInterval);
829 socket1 = &stunSocket[i];
830 socket2 = &stunSocket[j];
831 stunSocket.DisallowDeleteObjects();
832 stunSocket.Remove(socket1);
833 stunSocket.Remove(socket2);
834 stunSocket.AllowDeleteObjects();
835 return true;
840 PTRACE(2, "STUN\tCould not get a pair of adjacent port numbers from NAT");
841 return false;
844 BOOL PSTUNClient::IsAvailable()
847 switch (GetNatType(FALSE)) {
848 case ConeNat :
849 case RestrictedNat :
850 case PortRestrictedNat :
851 break;
853 case SymmetricNat :
854 if (pairedPortInfo.basePort == 0 || pairedPortInfo.basePort > pairedPortInfo.maxPort)
855 return FALSE;
857 break;
859 default : // UnknownNet, SymmetricFirewall, BlockedNat
860 return FALSE;
863 return TRUE;
866 ////////////////////////////////////////////////////////////////
868 PSTUNUDPSocket::PSTUNUDPSocket()
869 : externalIP(0)
874 BOOL PSTUNUDPSocket::GetLocalAddress(Address & addr)
876 if (!externalIP.IsValid())
877 return PUDPSocket::GetLocalAddress(addr);
879 addr = externalIP;
880 return true;
884 BOOL PSTUNUDPSocket::GetLocalAddress(Address & addr, WORD & port)
886 if (!externalIP.IsValid())
887 return PUDPSocket::GetLocalAddress(addr, port);
889 addr = externalIP;
890 port = GetPort();
891 return true;
895 // End of File ////////////////////////////////////////////////////////////////