1 //------------------------------------------------------------------------------
2 // <copyright file="ServicePoint.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Net {
8     using System.Net.Sockets;
9     using System.Collections;
10     using System.IO;
11     using System.Threading;
12     using System.Security.Authentication.ExtendedProtection;
13     using System.Security.Cryptography.X509Certificates;
14     using System.Net.Security;
15     using System.Globalization;
16     using System.Collections.Generic;
17     using System.Runtime.CompilerServices;
18 
BindIPEndPoint(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)19     public delegate IPEndPoint BindIPEndPoint(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount);
20 
21 
22     // ServicePoints are never created directly but always handed out by the
23     // ServicePointManager. The ServicePointManager and the ServicePoints must be in
24     // the same name space so that the ServicePointManager can call the
25     // internal constructor
26 
27     /// <devdoc>
28     ///    <para>Provides connection management for other classes.</para>
29     /// </devdoc>
30     [FriendAccessAllowed]
31     public class ServicePoint {
32 
33         internal const int LoopbackConnectionLimit = Int32.MaxValue;
34 
35         private int                 m_ConnectionLeaseTimeout;
36         private TimerThread.Queue   m_ConnectionLeaseTimerQueue;
37         private bool                m_ProxyServicePoint;
38         private bool                m_UserChangedLimit;
39         private bool                m_UseNagleAlgorithm;
40         private TriState            m_HostLoopbackGuess;
41         private int                 m_ReceiveBufferSize;
42         private bool                m_Expect100Continue;
43         private bool                m_Understands100Continue;
44         private HttpBehaviour       m_HttpBehaviour;
45         private string              m_LookupString;
46         private int                 m_ConnectionLimit;
47         private Hashtable           m_ConnectionGroupList;
48         private Uri                 m_Address;
49         private string              m_Host;
50         private int                 m_Port;
51         private TimerThread.Queue   m_IdlingQueue;
52         private TimerThread.Timer   m_ExpiringTimer;
53         private DateTime            m_IdleSince;
54         private string              m_ConnectionName;
55         private int                 m_CurrentConnections;
56         private bool                m_HostMode;
57         private BindIPEndPoint      m_BindIPEndPointDelegate = null;
58         private object              m_CachedChannelBinding;
59 
60         private static readonly AsyncCallback m_ConnectCallbackDelegate = new AsyncCallback(ConnectSocketCallback);
61 
62         private readonly TimerThread.Callback m_IdleConnectionGroupTimeoutDelegate;
63 
64 #if !FEATURE_PAL
65         private object m_ServerCertificateOrBytes;
66         private object m_ClientCertificateOrBytes;
67 #endif // !FEATURE_PAL
68 
69         private bool m_UseTcpKeepAlive = false;
70         private int m_TcpKeepAliveTime;
71         private int m_TcpKeepAliveInterval;
72 
73         internal string LookupString {
74             get {
75                 return m_LookupString;
76             }
77         }
78 
79         internal string Hostname {
80             get {
81                 return m_HostName;
82             }
83         }
84 
85         internal bool IsTrustedHost {
86             get {
87                 return m_IsTrustedHost;
88             }
89         }
90 
91         public BindIPEndPoint BindIPEndPointDelegate {
92             get {
93                 return m_BindIPEndPointDelegate;
94             }
95             set {
96 #if MONO_FEATURE_CAS
97                 ExceptionHelper.InfrastructurePermission.Demand();
98 #endif
99                 m_BindIPEndPointDelegate = value;
100             }
101         }
102 
103         //
104         // constructors
105         //
ServicePoint(Uri address, TimerThread.Queue defaultIdlingQueue, int defaultConnectionLimit, string lookupString, bool userChangedLimit, bool proxyServicePoint)106         internal ServicePoint(Uri address, TimerThread.Queue defaultIdlingQueue, int defaultConnectionLimit, string lookupString, bool userChangedLimit, bool proxyServicePoint) {
107             GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::.ctor(" + lookupString+")");
108             if (Logging.On) Logging.Enter(Logging.Web, this, "ServicePoint", address.DnsSafeHost + ":" + address.Port);
109 
110             m_ProxyServicePoint     = proxyServicePoint;
111             m_Address               = address;
112             m_ConnectionName        = address.Scheme;
113             m_Host                  = address.DnsSafeHost;
114             m_Port                  = address.Port;
115             m_IdlingQueue           = defaultIdlingQueue;
116             m_ConnectionLimit       = defaultConnectionLimit;
117             m_HostLoopbackGuess     = TriState.Unspecified;
118             m_LookupString          = lookupString;
119             m_UserChangedLimit      = userChangedLimit;
120             m_UseNagleAlgorithm     = ServicePointManager.UseNagleAlgorithm;
121             m_Expect100Continue     = ServicePointManager.Expect100Continue;
122             m_ConnectionGroupList   = new Hashtable(10);
123             m_ConnectionLeaseTimeout = System.Threading.Timeout.Infinite;
124             m_ReceiveBufferSize     = -1;
125             m_UseTcpKeepAlive       = ServicePointManager.s_UseTcpKeepAlive;
126             m_TcpKeepAliveTime      = ServicePointManager.s_TcpKeepAliveTime;
127             m_TcpKeepAliveInterval  = ServicePointManager.s_TcpKeepAliveInterval;
128 
129             // it would be safer to make sure the server is 1.1
130             // but assume it is at the beginning, and update it later
131             m_Understands100Continue = true;
132             m_HttpBehaviour         = HttpBehaviour.Unknown;
133 
134             // upon creation, the service point should be idle, by default
135             m_IdleSince             = DateTime.Now;
136             m_ExpiringTimer         = m_IdlingQueue.CreateTimer(ServicePointManager.IdleServicePointTimeoutDelegate, this);
137             m_IdleConnectionGroupTimeoutDelegate = new TimerThread.Callback(IdleConnectionGroupTimeoutCallback);
138         }
139 
140 
141 
ServicePoint(string host, int port, TimerThread.Queue defaultIdlingQueue, int defaultConnectionLimit, string lookupString, bool userChangedLimit, bool proxyServicePoint)142         internal ServicePoint(string host, int port, TimerThread.Queue defaultIdlingQueue, int defaultConnectionLimit, string lookupString, bool userChangedLimit, bool proxyServicePoint) {
143             GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::.ctor(" + lookupString+")");
144             if (Logging.On) Logging.Enter(Logging.Web, this, "ServicePoint", host + ":" + port);
145 
146             m_ProxyServicePoint     = proxyServicePoint;
147             m_ConnectionName        = "ByHost:"+host+":"+port.ToString(CultureInfo.InvariantCulture);
148             m_IdlingQueue           = defaultIdlingQueue;
149             m_ConnectionLimit       = defaultConnectionLimit;
150             m_HostLoopbackGuess     = TriState.Unspecified;
151             m_LookupString          = lookupString;
152             m_UserChangedLimit      = userChangedLimit;
153             m_ConnectionGroupList   = new Hashtable(10);
154             m_ConnectionLeaseTimeout = System.Threading.Timeout.Infinite;
155             m_ReceiveBufferSize     = -1;
156             m_Host = host;
157             m_Port = port;
158             m_HostMode = true;
159 
160             // upon creation, the service point should be idle, by default
161             m_IdleSince             = DateTime.Now;
162             m_ExpiringTimer         = m_IdlingQueue.CreateTimer(ServicePointManager.IdleServicePointTimeoutDelegate, this);
163             m_IdleConnectionGroupTimeoutDelegate = new TimerThread.Callback(IdleConnectionGroupTimeoutCallback);
164         }
165 
166 
167 
168         // methods
169 
170         internal object CachedChannelBinding
171         {
172             get { return m_CachedChannelBinding; }
173         }
174 
SetCachedChannelBinding(Uri uri, ChannelBinding binding)175         internal void SetCachedChannelBinding(Uri uri, ChannelBinding binding)
176         {
177             if (uri.Scheme == Uri.UriSchemeHttps)
178             {
179                 m_CachedChannelBinding = (binding != null ? (object)binding : (object)DBNull.Value);
180             }
181         }
182 
183         /*++
184 
185             FindConnectionGroup       -
186 
187             Searches for the a Group object that actually holds the connections
188               that we want to peak at.
189 
190 
191             Input:
192                     request                 - Request that's being submitted.
193                     connName                - Connection Name if needed
194 
195             Returns:
196                     ConnectionGroup
197 
198         --*/
199 
FindConnectionGroup(string connName, bool dontCreate)200         private ConnectionGroup FindConnectionGroup(string connName, bool dontCreate) {
201             string lookupStr = ConnectionGroup.MakeQueryStr(connName);
202 
203             GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::FindConnectionGroup() lookupStr:[" + ValidationHelper.ToString(connName) + "]");
204 
205             ConnectionGroup entry = m_ConnectionGroupList[lookupStr] as ConnectionGroup;
206 
207             if (entry==null && !dontCreate) {
208                 entry = new ConnectionGroup(this, connName);
209                 GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::FindConnectionGroup() adding ConnectionGroup lookupStr:[" + lookupStr + "]");
210 
211                 m_ConnectionGroupList[lookupStr] = entry;
212             }
213             else {
214                 GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::FindConnectionGroup() using existing ConnectionGroup");
215             }
216             GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::FindConnectionGroup() returning ConnectionGroup:" + ValidationHelper.ToString(entry) + (entry!=null ? " ConnLimit:" + entry.ConnectionLimit.ToString() : ""));
217             return entry;
218         }
219 
220 
221         /// <devdoc>
222         ///    <para>
223         ///     Tempory for getting a new Connection for FTP client, for the time being
224         ///    </para>
225         /// </devdoc>
GetConnection(PooledStream PooledStream, object owner, bool async, out IPAddress address, ref Socket abortSocket, ref Socket abortSocket6)226         internal Socket GetConnection(PooledStream PooledStream, object owner, bool async, out IPAddress address, ref Socket abortSocket, ref Socket abortSocket6)
227         {
228             Socket socket = null;
229             Socket socket6 = null;
230             Socket finalSocket = null;
231             Exception innerException = null;
232             WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
233             address = null;
234 
235             //
236             // if we will not create a tunnel through a proxy then create
237             // and connect the socket we will use for the connection
238             //
239 
240             //
241             // IPv6 Support: If IPv6 is enabled, then we create a second socket that ServicePoint
242             //               will use if it wants to connect via IPv6.
243             //
244             if ( Socket.OSSupportsIPv4 ) {
245                 socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
246             }
247 
248             if ( Socket.OSSupportsIPv6 ) {
249                 socket6 = new Socket(AddressFamily.InterNetworkV6,SocketType.Stream,ProtocolType.Tcp);
250             }
251 
252             abortSocket = socket;
253             abortSocket6 = socket6;
254 
255             //
256             // Setup socket timeouts for sync requests
257             //
258             //
259 
260             ConnectSocketState state = null;
261 
262             if (async) {
263                 state = new ConnectSocketState(this, PooledStream, owner, socket, socket6);
264             }
265 
266             ws = ConnectSocket(socket, socket6, ref finalSocket, ref address, state, out innerException);
267 
268             if (ws == WebExceptionStatus.Pending) {
269                 return null;
270             }
271 
272             if (ws != WebExceptionStatus.Success) {
273                 throw new WebException(
274                     NetRes.GetWebStatusString(ws),
275                     ws == WebExceptionStatus.ProxyNameResolutionFailure || ws == WebExceptionStatus.NameResolutionFailure ? Host : null,
276                     innerException,
277                     ws,
278                     null, /* no response */
279                     WebExceptionInternalStatus.ServicePointFatal);
280             }
281 
282             //
283             // There should be no means for socket to be null at this
284             // point, but the damage is greater if we just carry on
285             // without ensuring that it's good.
286             //
287             if ( finalSocket == null ) {
288                 throw new IOException(SR.GetString(SR.net_io_transportfailure));
289             }
290 
291             CompleteGetConnection(socket, socket6, finalSocket, address);
292             return finalSocket;
293         }
294 
295         /// <devdoc>
296         ///    <para>
297         ///     Complete the GetConnection(...) call, the function was divided for async completion
298         ///    </para>
299         /// </devdoc>
CompleteGetConnection(Socket socket, Socket socket6, Socket finalSocket, IPAddress address)300         private void CompleteGetConnection(Socket socket, Socket socket6, Socket finalSocket, IPAddress address) {
301             //
302             // Decide which socket to retain
303             //
304             if ( finalSocket.AddressFamily == AddressFamily.InterNetwork ) {
305                 if ( socket6 != null ) {
306                     socket6.Close();
307                     socket6 = null;
308                 }
309             }
310             else {
311                 if (socket != null) {
312                     socket.Close();
313                     socket = null;
314                 }
315             }
316 
317             // make this configurable from the user:
318             if (!UseNagleAlgorithm) {
319                 finalSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);
320             }
321             if (ReceiveBufferSize != -1) {
322                 finalSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, ReceiveBufferSize);
323             }
324             if (m_UseTcpKeepAlive) {
325 
326                 // Marshal a byte array containing the params for WsaIoctl
327                 // struct tcp_keepalive {
328                 //    u_long  onoff;
329                 //    u_long  keepalivetime;
330                 //    u_long  keepaliveinterval;
331                 // };
332                 byte[] input = new byte[sizeof(int)*3];
333                 input[0]  = 1;
334                 input[4]  = (byte)  (m_TcpKeepAliveTime & 0xff);
335                 input[5]  = (byte) ((m_TcpKeepAliveTime >>  8) & 0xff);
336                 input[6]  = (byte) ((m_TcpKeepAliveTime >> 16) & 0xff);
337                 input[7]  = (byte) ((m_TcpKeepAliveTime >> 24) & 0xff);
338                 input[8]  = (byte)  (m_TcpKeepAliveInterval & 0xff);
339                 input[9]  = (byte) ((m_TcpKeepAliveInterval >>  8) & 0xff);
340                 input[10] = (byte) ((m_TcpKeepAliveInterval >> 16) & 0xff);
341                 input[11] = (byte) ((m_TcpKeepAliveInterval >> 24) & 0xff);
342 
343                 // do WSAIoctl call
344                 finalSocket.IOControl(
345                                 IOControlCode.KeepAliveValues,
346                                 input,
347                                 null);
348             }
349 
350 
351             //return CreateConnection(NetworkStream stream, IPAddress address);
352             //return new NetworkStream(finalSocket, true);
353         }
354 
355 
356         /*++
357 
358             SubmitRequest       - Submit a request for sending.
359 
360             The service point submit handler. This is called when a request needs
361             to be submitted to the network. This routine is asynchronous; the caller
362             passes in an HttpSubmitDelegate that is invoked when the caller
363             can use the underlying network. The delegate is invoked with the
364             stream that it can write to.
365 
366 
367             In this version, we use HttpWebRequest. In the future we use IRequest
368 
369             Input:
370                     Request                 - Request that's being submitted.
371                     SubmitDelegate          - Delegate to be invoked.
372 
373             Returns:
374                     Nothing.
375 
376         --*/
377 
SubmitRequest(HttpWebRequest request)378         internal virtual void SubmitRequest(HttpWebRequest request) {
379             SubmitRequest(request, null);
380         }
381 
382         // userReqeustThread says whether we can post IO from this thread or not.
SubmitRequest(HttpWebRequest request, string connName)383         internal void SubmitRequest(HttpWebRequest request, string connName)
384         {
385             //
386             // We attempt to locate a free connection sitting on our list
387             //  avoiding multiple loops of the same the list.
388             //  We do this, by enumerating the list of the connections,
389             //   looking for Free items, and the least busy item
390             //
391             Connection connToUse;
392             ConnectionGroup connGroup;
393             bool forcedsubmit = false;
394             lock(this) {
395                 GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::SubmitRequest() Finding ConnectionGroup:[" + connName + "]");
396                 connGroup = FindConnectionGroup(connName, false);
397                 GlobalLog.Assert(connGroup != null, "ServicePoint#{0}::SubmitRequest()|connGroup == null", ValidationHelper.HashString(this));
398             }
399 
400             do {
401                 connToUse = connGroup.FindConnection(request, connName, out forcedsubmit);
402                 // The request could be already aborted
403                 if (connToUse == null)
404                     return;
405 
406                 GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::SubmitRequest() Using Connection#" + ValidationHelper.HashString(connToUse));
407                 // finally sumbit delegate
408                 if (connToUse.SubmitRequest(request, forcedsubmit)) {
409                     break;
410                 }
411             } while (true);
412         }
413 
414         // properties
415 
416         /// <devdoc>
417         ///    <para>
418         ///       Gets and sets timeout for when connections should be recycled.
419         ///    </para>
420         /// </devdoc>
421         public int ConnectionLeaseTimeout {
422             get {
423                 return m_ConnectionLeaseTimeout;
424             }
425             set {
426                 if ( !ValidationHelper.ValidateRange(value, Timeout.Infinite, Int32.MaxValue)) {
427                     throw new ArgumentOutOfRangeException("value");
428                 }
429                 if (value != m_ConnectionLeaseTimeout) {
430                     m_ConnectionLeaseTimeout = value;
431                     m_ConnectionLeaseTimerQueue = null;
432                 }
433             }
434         }
435 
436         /// <summary>
437         /// <para>Returns a timer queue that can be used internally to create timers of
438         /// ConnectionLeaseTimeout duration.</para>
439         /// </summary>
440         internal TimerThread.Queue ConnectionLeaseTimerQueue {
441             get {
442                 TimerThread.Queue queue = m_ConnectionLeaseTimerQueue;
443                 if (queue == null) {
444                     queue = TimerThread.GetOrCreateQueue(ConnectionLeaseTimeout);
445                     m_ConnectionLeaseTimerQueue = queue;
446                 }
447                 return m_ConnectionLeaseTimerQueue;
448             }
449         }
450 
451         // Only the scheme and hostport, for example http://www.microsoft.com
452         /// <devdoc>
453         ///    <para>
454         ///       Gets the Uniform Resource Identifier of the <see cref='System.Net.ServicePoint'/>.
455         ///    </para>
456         /// </devdoc>
457         public Uri Address {
458             get {
459                 if(m_HostMode){
460                     throw new NotSupportedException(SR.GetString(SR.net_servicePointAddressNotSupportedInHostMode));
461                 }
462 
463 #if MONO_FEATURE_CAS
464                 // Don't let low-trust apps discover the proxy information.
465                 if (m_ProxyServicePoint)
466                 {
467                     ExceptionHelper.WebPermissionUnrestricted.Demand();
468                 }
469 #endif
470 
471                 return m_Address;
472             }
473         }
474 
475         internal Uri InternalAddress
476         {
477             get
478             {
479                 GlobalLog.Assert(!m_HostMode, "ServicePoint#{0}::InternalAddress|Can't be used in Host Mode.", ValidationHelper.HashString(this));
480                 return m_Address;
481             }
482         }
483 
484         internal string Host {
485             get {
486                 if(m_HostMode){
487                     return m_Host;
488                 }
489                 return m_Address.Host;
490             }
491         }
492 
493         internal int Port {
494             get {
495                 return m_Port;
496             }
497         }
498 
499 
500         //
501         // Gets or sets the maximum idle time allowed for connections of this ServicePoint and then for ServicePoint itself
502         // Default value coming in ctor is ServicePointManager.s_MaxServicePointIdleTime which 100 sec
503         //
504         public int MaxIdleTime {
505             get {
506                 return m_IdlingQueue.Duration;
507             }
508             set {
509                 if ( !ValidationHelper.ValidateRange(value, Timeout.Infinite, Int32.MaxValue)) {
510                     throw new ArgumentOutOfRangeException("value");
511                 }
512 
513                 // Already set?
514                 if (value == m_IdlingQueue.Duration)
515                     return;
516 
517                 lock(this) {
518                     // Make sure we can cancel the existing one.  If not, we already idled out.
519                     if (m_ExpiringTimer == null || m_ExpiringTimer.Cancel())
520                     {
521                         m_IdlingQueue = TimerThread.GetOrCreateQueue(value);
522                         if (m_ExpiringTimer != null)
523                         {
524                             // Need to create a one-off timer for the remaining period.
525                             double elapsedDouble = (DateTime.Now - m_IdleSince).TotalMilliseconds;
526                             int elapsed = elapsedDouble >= (double) Int32.MaxValue ? Int32.MaxValue : (int) elapsedDouble;
527                             int timeLeft = value == Timeout.Infinite ? Timeout.Infinite : elapsed >= value ? 0 : value - elapsed;
528                             m_ExpiringTimer = TimerThread.CreateQueue(timeLeft).CreateTimer(ServicePointManager.IdleServicePointTimeoutDelegate, this);
529                         }
530                     }
531                 }
532             }
533         }
534 
535         /// <devdoc>
536         ///    <para>
537         ///       Gets or sets the Nagling algorithm on the connections that are created to this <see cref='System.Net.ServicePoint'/>.
538         ///       Changing this value does not affect existing connections but only to new ones that are created from that moment on.
539         ///    </para>
540         /// </devdoc>
541         public bool UseNagleAlgorithm {
542             get {
543                 return m_UseNagleAlgorithm;
544             }
545             set {
546                 m_UseNagleAlgorithm = value;
547             }
548         }
549 
550         /// <devdoc>
551         ///    <para>
552         ///       Gets and sets the socket's receive buffer size.
553         ///    </para>
554         /// </devdoc>
555         public int ReceiveBufferSize {
556             get {
557                 return m_ReceiveBufferSize;
558             }
559             set {
560                 if ( !ValidationHelper.ValidateRange(value, -1, Int32.MaxValue)) {
561                     throw new ArgumentOutOfRangeException("value");
562                 }
563                 m_ReceiveBufferSize = value;
564             }
565 
566         }
567 
568 
569         /// <devdoc>
570         ///    <para>
571         ///       Gets or sets indication whether 100-continue behaviour is desired when using this <see cref='System.Net.ServicePoint'/>.
572         ///       Changing this value does not affect existing connections but only to new ones that are created from that moment on.
573         ///    </para>
574         /// </devdoc>
575         public bool Expect100Continue {
576             set {
577                 m_Expect100Continue = value;
578             }
579             get {
580                 return m_Expect100Continue;
581             }
582         }
583 
584         /// <devdoc>
585         ///    <para>
586         ///       Gets the date/time that the <see cref='System.Net.ServicePoint'/> went idle.
587         ///    </para>
588         /// </devdoc>
589         public DateTime IdleSince {
590             get {
591                 return m_IdleSince;
592             }
593         }
594 
595         // HTTP Server Version
596         /// <devdoc>
597         ///    <para>
598         ///       The version of the protocol being used on this <see cref='System.Net.ServicePoint'/>.
599         ///    </para>
600         /// </devdoc>
601         public virtual Version ProtocolVersion {
602             get {
603                 return (m_HttpBehaviour>HttpBehaviour.HTTP10 || m_HttpBehaviour == HttpBehaviour.Unknown) ? HttpVersion.Version11 : HttpVersion.Version10;
604             }
605         }
606 
607         // Contains set accessor for Version property. Version is a read-only
608         // property at the API
609         internal HttpBehaviour HttpBehaviour {
610             get {
611                 return m_HttpBehaviour;
612             }
613             set {
614                 m_HttpBehaviour = value;
615                 //
616                 // if version is greater than HTTP/1.1, and server undesrtood
617                 // 100 Continue so far, keep expecting it.
618                 //
619                 m_Understands100Continue = m_Understands100Continue && (m_HttpBehaviour>HttpBehaviour.HTTP10 || m_HttpBehaviour == HttpBehaviour.Unknown);
620             }
621         }
622 
623         /// <devdoc>
624         ///    <para>
625         ///       Gets the connection name established by the <see cref='System.Net.WebRequest'/> that created the connection.
626         ///    </para>
627         /// </devdoc>
628         public string ConnectionName {
629             get {
630                 return m_ConnectionName;
631             }
632         }
633 
634         /*
635         /// <devdoc>
636         ///    Gets the connection mode in use by the <see cref='System.Net.ServicePoint'/>. One of the <see cref='System.Net.ConnectionModes'/>
637         ///    values.
638         /// </devdoc>
639         internal ConnectionModes ConnectionMode {
640             get {
641                 return m_HttpBehaviour>=HttpBehaviour.HTTP11 ? ConnectionModes.Pipeline : ConnectionModes.Persistent;
642             }
643         }
644         */
645 
646         /// <devdoc>
647         ///     Removes the specified Connection group from the ServicePoint, destroys safe and unsafe groups, but not internal.
648         /// </devdoc>
649 
CloseConnectionGroup(string connectionGroupName)650         public bool CloseConnectionGroup(string connectionGroupName) {
651             GlobalLog.Enter("ServicePoint#" + ValidationHelper.HashString(this) + "::CloseConnectionGroup() lookupStr:[" + connectionGroupName + "]");
652             if ( ReleaseConnectionGroup(HttpWebRequest.GenerateConnectionGroup(connectionGroupName, false, false).ToString())  ||
653                  ReleaseConnectionGroup(HttpWebRequest.GenerateConnectionGroup(connectionGroupName, true, false).ToString())  ||
654                  ConnectionPoolManager.RemoveConnectionPool(this, connectionGroupName)) {
655 
656                 GlobalLog.Leave("ServicePoint#" + ValidationHelper.HashString(this) + "::CloseConnectionGroup()","true");
657                 return true;
658             }
659             GlobalLog.Leave("ServicePoint#" + ValidationHelper.HashString(this) + "::CloseConnectionGroup()","false");
660             return false;
661         }
662 
CloseConnectionGroupInternal(string connectionGroupName)663         internal void CloseConnectionGroupInternal(string connectionGroupName) {
664 
665             // Release all internal connection groups (both 'safe' and 'unsafe') with the given name. We're not
666             // interested in the result value (it's OK if it is 'false', i.e. not found).
667             // We don't need to call ConnectionPoolManager.RemoveConnectionPool() since this method is only used for
668             // HTTP.
669             string connectionGroupPrefixSafe = HttpWebRequest.GenerateConnectionGroup(connectionGroupName, false, true).ToString();
670             string connectionGroupPrefixUnsafe = HttpWebRequest.GenerateConnectionGroup(connectionGroupName, true, true).ToString();
671             List<string> connectionGroupNames = null;
672 
673             lock (this) {
674                 // Find all connecion groups starting with the provided prefix. We just compare prefixes, since connection
675                 // groups may include suffixes for client certificates, SSL over proxy, authentication IDs.
676                 foreach (var item in m_ConnectionGroupList.Keys) {
677                     string current = item as string;
678                     if (current.StartsWith(connectionGroupPrefixSafe, StringComparison.Ordinal) ||
679                         current.StartsWith(connectionGroupPrefixUnsafe, StringComparison.Ordinal)) {
680                         if (connectionGroupNames == null) {
681                             connectionGroupNames = new List<string>();
682                         }
683                         connectionGroupNames.Add(current);
684                     }
685                 }
686             }
687 
688             // If this service point contains any connection groups with the provided prefix, remove them.
689             if (connectionGroupNames != null) {
690                 foreach (string item in connectionGroupNames) {
691                     ReleaseConnectionGroup(item);
692                 }
693             }
694         }
695 
696         /// <devdoc>
697         ///    <para>
698         ///       Gets or sets the maximum number of connections allowed on this <see cref='System.Net.ServicePoint'/>.
699         ///    </para>
700         /// </devdoc>
701         public int ConnectionLimit
702         {
703             get
704             {
705                 // If there hasn't been a DNS resolution yet, make a guess based on the host name.  It might change
706                 // when DNS is finally done, but that's ok.  It can change anyway based on other factors like redirects.
707                 if (!m_UserChangedLimit && m_IPAddressInfoList == null && m_HostLoopbackGuess == TriState.Unspecified)
708                 {
709                     // This can only happen the first time through, and before any ConnectionGroups are made.
710                     lock (this)
711                     {
712                         if (!m_UserChangedLimit && m_IPAddressInfoList == null && m_HostLoopbackGuess == TriState.Unspecified)
713                         {
714                             // First check if it's just an IP address anyway.
715                             IPAddress addr = null;
716                             if (IPAddress.TryParse(m_Host,out addr))
717                             {
718                                 m_HostLoopbackGuess = IsAddressListLoopback(new IPAddress[] { addr }) ? TriState.True : TriState.False;
719                             }
720                             else
721                             {
722                                 m_HostLoopbackGuess = NclUtilities.GuessWhetherHostIsLoopback(m_Host) ? TriState.True : TriState.False;
723                             }
724                         }
725                     }
726                 }
727 
728                 return m_UserChangedLimit || (m_IPAddressInfoList == null ? m_HostLoopbackGuess != TriState.True : !m_IPAddressesAreLoopback) ? m_ConnectionLimit : LoopbackConnectionLimit;
729             }
730 
731             set
732             {
733                 if (value <= 0)
734                 {
735                     throw new ArgumentOutOfRangeException("value");
736                 }
737 
738                 if (!m_UserChangedLimit || m_ConnectionLimit != value)
739                 {
740                     lock (this)
741                     {
742                         if (!m_UserChangedLimit || m_ConnectionLimit != value)
743                         {
744                             m_ConnectionLimit = value;
745                             m_UserChangedLimit = true;
746 
747                             // Don't want to call ResolveConnectionLimit() or ConnectionLimit before setting m_UserChangedLimit
748                             // in order to avoid the 'guess' logic in ConnectionLimit.
749                             ResolveConnectionLimit();
750                         }
751                     }
752                 }
753             }
754         }
755 
756         // Must be called under lock.
ResolveConnectionLimit()757         private void ResolveConnectionLimit()
758         {
759             int limit = ConnectionLimit;
760             foreach (ConnectionGroup cg in m_ConnectionGroupList.Values)
761             {
762                 cg.ConnectionLimit = limit;
763             }
764         }
765 
766         /// <devdoc>
767         ///    <para>
768         ///       Gets the current number of connections associated with this
769         ///    <see cref='System.Net.ServicePoint'/>.
770         ///    </para>
771         /// </devdoc>
772         public int CurrentConnections {
773             get {
774                 int connections = 0;
775                 lock(this)
776                 {
777                     foreach (ConnectionGroup group in m_ConnectionGroupList.Values)
778                     {
779                         connections += group.CurrentConnections;
780                     }
781                 }
782                 return connections;
783             }
784         }
785 
786 #if !FEATURE_PAL
787         /// <devdoc>
788         ///    <para>
789         ///       Gets the certificate received for this <see cref='System.Net.ServicePoint'/>.
790         ///    </para>
791         /// </devdoc>
792         public  X509Certificate Certificate {
793             get {
794                     object chkCert = m_ServerCertificateOrBytes;
795                     if (chkCert != null && chkCert.GetType() == typeof(byte[]))
796                         return (X509Certificate)(m_ServerCertificateOrBytes = new X509Certificate((byte[]) chkCert));
797                     else
798                         return chkCert as X509Certificate;
799                 }
800         }
UpdateServerCertificate(X509Certificate certificate)801         internal void UpdateServerCertificate(X509Certificate certificate)
802         {
803             if (certificate != null)
804                 m_ServerCertificateOrBytes = certificate.GetRawCertData();
805             else
806                 m_ServerCertificateOrBytes = null;
807         }
808 
809         /// <devdoc>
810         /// <para>
811         /// Gets the Client Certificate sent by us to the Server.
812         /// </para>
813         /// </devdoc>
814         public  X509Certificate ClientCertificate {
815             get {
816                 object chkCert = m_ClientCertificateOrBytes;
817                 if (chkCert != null && chkCert.GetType() == typeof(byte[]))
818                     return (X509Certificate)(m_ClientCertificateOrBytes = new X509Certificate((byte[]) chkCert));
819                 else
820                     return chkCert as X509Certificate;
821             }
822         }
UpdateClientCertificate(X509Certificate certificate)823         internal void UpdateClientCertificate(X509Certificate certificate)
824         {
825             if (certificate != null)
826                 m_ClientCertificateOrBytes = certificate.GetRawCertData();
827             else
828                 m_ClientCertificateOrBytes = null;
829         }
830 
831 #endif // !FEATURE_PAL
832 
833 
834         /// <devdoc>
835         ///    <para>
836         ///       Indicates that the <see cref='System.Net.ServicePoint'/> supports pipelined connections.
837         ///    </para>
838         /// </devdoc>
839         public bool SupportsPipelining {
840             get {
841                 return (m_HttpBehaviour>HttpBehaviour.HTTP10 || m_HttpBehaviour==HttpBehaviour.Unknown);
842             }
843         }
844 
845         //
846         // SetTcpKeepAlive
847         //
848         // Enable/Disable the use of TCP keepalive option on ServicePoint
849         // connections. This method does not affect existing ServicePoints.
850         // When a ServicePoint is constructed it will inherit the current
851         // settings.
852         //
853         // Parameters:
854         //
855         // enabled - if true enables the use of the TCP keepalive option
856         // for ServicePoint connections.
857         //
858         // keepAliveTime - specifies the timeout, in milliseconds, with no
859         // activity until the first keep-alive packet is sent. Ignored if
860         // enabled parameter is false.
861         //
862         // keepAliveInterval - specifies the interval, in milliseconds, between
863         // when successive keep-alive packets are sent if no acknowledgement is
864         // received. Ignored if enabled parameter is false.
865         //
SetTcpKeepAlive( bool enabled, int keepAliveTime, int keepAliveInterval)866         public void SetTcpKeepAlive(
867                             bool enabled,
868                             int keepAliveTime,
869                             int keepAliveInterval) {
870 
871             GlobalLog.Enter(
872                 "ServicePoint::SetTcpKeepAlive()" +
873                 " enabled: " + enabled.ToString() +
874                 " keepAliveTime: " + keepAliveTime.ToString() +
875                 " keepAliveInterval: " + keepAliveInterval.ToString()
876             );
877             if (enabled) {
878                 m_UseTcpKeepAlive = true;
879                 if (keepAliveTime <= 0) {
880                     throw new ArgumentOutOfRangeException("keepAliveTime");
881                 }
882                 if (keepAliveInterval <= 0) {
883                     throw new ArgumentOutOfRangeException("keepAliveInterval");
884                 }
885                 m_TcpKeepAliveTime = keepAliveTime;
886                 m_TcpKeepAliveInterval = keepAliveInterval;
887             } else {
888                 m_UseTcpKeepAlive = false;
889                 m_TcpKeepAliveTime = 0;
890                 m_TcpKeepAliveInterval =0;
891             }
892             GlobalLog.Leave("ServicePoint::SetTcpKeepAlive()");
893         }
894 
895         //
896         // Internal Properties
897         //
898 
899         internal bool Understands100Continue {
900             set {
901                 m_Understands100Continue = value;
902             }
903             get {
904                 return m_Understands100Continue;
905             }
906         }
907 
908         //
909         // InternalProxyServicePoint
910         //
911         // Indicates if we are using this service point to represent
912         //  a proxy connection, if so we may have to use special
913         //  semantics when creating connections
914         //
915 
916         internal bool InternalProxyServicePoint {
917             get {
918                 return m_ProxyServicePoint;
919             }
920         }
921 
922         //
923         // IncrementConnection
924         //
925         // call to indicate that we now are starting a new
926         //  connection within this service point
927         //
928 
IncrementConnection()929         internal void IncrementConnection() {
930             GlobalLog.Enter("ServicePoint#" + ValidationHelper.HashString(this) + "::IncrementConnection()", m_CurrentConnections.ToString());
931             // we need these to be atomic operations
932             lock(this) {
933                 m_CurrentConnections++;
934                 if (m_CurrentConnections==1) {
935                     GlobalLog.Assert(m_ExpiringTimer != null, "ServicePoint#{0}::IncrementConnection|First connection active, but ServicePoint wasn't idle.", ValidationHelper.HashString(this));
936 
937                     //
938 
939 
940 
941 
942                     m_ExpiringTimer.Cancel();
943                     m_ExpiringTimer = null;
944                 }
945             }
946             GlobalLog.Leave("ServicePoint#" + ValidationHelper.HashString(this) + "::IncrementConnection()", m_CurrentConnections.ToString());
947         }
948 
949         //
950         // DecrementConnection
951         //
952         // call to indicate that we now are removing
953         //  a connection within this connection group
954         //
955 
DecrementConnection()956         internal void DecrementConnection() {
957             // The timer thread is allowed to call this.  (It doesn't call user code and doesn't block.)
958             GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer, "ServicePoint#" + ValidationHelper.HashString(this) + "::DecrementConnection");
959             GlobalLog.Enter("ServicePoint#" + ValidationHelper.HashString(this) + "::DecrementConnection()", m_CurrentConnections.ToString());
960 
961             // we need these to be atomic operations
962             lock(this) {
963                 m_CurrentConnections--;
964                 if (m_CurrentConnections==0) {
965                     GlobalLog.Assert(m_ExpiringTimer == null, "ServicePoint#{0}::DecrementConnection|Expiring timer set on non-idle ServicePoint.", ValidationHelper.HashString(this));
966                     m_IdleSince = DateTime.Now;
967                     m_ExpiringTimer = m_IdlingQueue.CreateTimer(ServicePointManager.IdleServicePointTimeoutDelegate, this);
968                 }
969                 else if ( m_CurrentConnections < 0 ) {
970                     m_CurrentConnections = 0;
971                     Diagnostics.Debug.Assert(false, "ServicePoint; Too many decrements.");
972                 }
973             }
974             GlobalLog.Leave("ServicePoint#" + ValidationHelper.HashString(this) + "::DecrementConnection()", m_CurrentConnections.ToString());
975         }
976 
977 #if !FEATURE_PAL
SetupHandshakeDoneProcedure(TlsStream secureStream, Object request)978         internal RemoteCertValidationCallback SetupHandshakeDoneProcedure(TlsStream secureStream, Object request) {
979             // Use a private adapter to connect tlsstream and this service point
980             return HandshakeDoneProcedure.CreateAdapter(this, secureStream, request);
981         }
982 
983         // This is an adapter class that ties a servicePoint and a TlsStream on the SSL handshake completion
984         private class HandshakeDoneProcedure {
985             TlsStream    m_SecureStream;
986             Object       m_Request;
987             ServicePoint m_ServicePoint;
988 
CreateAdapter(ServicePoint serviePoint, TlsStream secureStream, Object request)989             internal static RemoteCertValidationCallback CreateAdapter(ServicePoint serviePoint, TlsStream secureStream, Object request)
990             {
991                 HandshakeDoneProcedure adapter = new HandshakeDoneProcedure(serviePoint, secureStream, request);
992                 return new RemoteCertValidationCallback(adapter.CertValidationCallback);
993             }
994 
HandshakeDoneProcedure(ServicePoint serviePoint, TlsStream secureStream, Object request)995             private HandshakeDoneProcedure (ServicePoint serviePoint, TlsStream secureStream, Object request) {
996                 m_ServicePoint  = serviePoint;
997                 m_SecureStream  = secureStream;
998                 m_Request       = request;
999             }
1000 
CertValidationCallback(string hostName, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)1001             private bool CertValidationCallback(string hostName, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
1002                 m_ServicePoint.UpdateServerCertificate(certificate);
1003                 m_ServicePoint.UpdateClientCertificate(m_SecureStream.ClientCertificate);
1004                 bool useDefault = true;
1005 
1006                 // Request specific validator takes priority
1007                 HttpWebRequest httpWebRequest = m_Request as HttpWebRequest;
1008                 if (httpWebRequest != null && httpWebRequest.ServerCertValidationCallback != null)
1009                 {
1010                     return httpWebRequest.ServerCertValidationCallback.
1011                                           Invoke(m_Request,
1012                                                  certificate,
1013                                                  chain,
1014                                                  sslPolicyErrors);
1015                 }
1016 
1017                 // If a policy is set, call the user callback inside the ExecutionContext.
1018                 if (ServicePointManager.GetLegacyCertificatePolicy() != null && (m_Request is WebRequest))
1019                 {
1020                     useDefault = false;
1021 
1022                     bool checkResult = ServicePointManager.CertPolicyValidationCallback.
1023                                                            Invoke(hostName,
1024                                                                   m_ServicePoint,
1025                                                                   certificate,
1026                                                                   (WebRequest) m_Request,
1027                                                                   chain,
1028                                                                   sslPolicyErrors);
1029 
1030                     if (checkResult == false){
1031                         if (!ServicePointManager.CertPolicyValidationCallback.UsesDefault
1032                             || ServicePointManager.ServerCertificateValidationCallback == null)
1033                             return checkResult;
1034                     }
1035                 }
1036 
1037                 if (ServicePointManager.ServerCertificateValidationCallback != null)
1038                 {
1039                     useDefault = false;
1040                     return ServicePointManager.ServerCertValidationCallback.
1041                                                Invoke(m_Request,
1042                                                       certificate,
1043                                                       chain,
1044                                                       sslPolicyErrors);
1045                 }
1046 
1047                 if (useDefault)
1048                     return sslPolicyErrors == SslPolicyErrors.None;
1049 
1050                 return true;
1051             }
1052 
1053 
1054         }
1055 
1056 #endif // !FEATURE_PAL
1057 
IdleConnectionGroupTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)1058         private void IdleConnectionGroupTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context) {
1059             ConnectionGroup connectionGroup = (ConnectionGroup)context;
1060 
1061             if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_closed_idle,
1062                 "ConnectionGroup", connectionGroup.GetHashCode()));
1063 
1064             ReleaseConnectionGroup(connectionGroup.Name);
1065         }
1066 
CreateConnectionGroupTimer(ConnectionGroup connectionGroup)1067         internal TimerThread.Timer CreateConnectionGroupTimer(ConnectionGroup connectionGroup) {
1068             return m_IdlingQueue.CreateTimer(m_IdleConnectionGroupTimeoutDelegate, connectionGroup);
1069         }
1070 
1071         /// <devdoc>
1072         ///    <para>
1073         ///       Sets connections in this group to not be KeepAlive.
1074         ///       This is called to force cleanup of the ConnectionGroup by the
1075         ///       NTLM and Negotiate authentication modules.
1076         ///    </para>
1077         /// </devdoc>
ReleaseConnectionGroup(string connName)1078         internal bool ReleaseConnectionGroup(string connName) {
1079 
1080             ConnectionGroup connectionGroup = null;
1081 
1082             //
1083             // look up the ConnectionGroup based on the name
1084             //
1085             lock(this) {
1086 
1087                 connectionGroup = FindConnectionGroup(connName, true);
1088                 //
1089                 // force all connections on the ConnectionGroup to not be KeepAlive
1090                 //
1091                 if (connectionGroup == null) {
1092                     return false;
1093                 }
1094 
1095                 // Cancel the timer so it doesn't fire later and clean up a different
1096                 // connection group with the same name.
1097                 connectionGroup.CancelIdleTimer();
1098 
1099                 //
1100                 // remove ConnectionGroup from our Hashtable
1101                 //
1102                 m_ConnectionGroupList.Remove(connName);
1103             }
1104 
1105             // Don't call the following under the lock: ConnectionGroup will call into Connection that
1106             // may take a lock on the Connection. ServicePoint should never call members under the lock that
1107             // end up taking a lock on a Connection (risk of deadlock).
1108             connectionGroup.DisableKeepAliveOnConnections();
1109 
1110             return true;
1111         }
1112 
1113         /// <devdoc>
1114         ///    <para>
1115         ///       - Sets all connections in all connections groups to not be KeepAlive.
1116         ///       - Causes all connections to be closed, if they are not active
1117         ///       - Removes all references to all connection groups and their connections
1118         ///       does essentially a "ReleaseConnectionGroup" of each group in this ServicePoint
1119         ///    </para>
1120         /// </devdoc>
ReleaseAllConnectionGroups()1121         internal void ReleaseAllConnectionGroups()
1122         {
1123             // The timer thread is allowed to call this.  (It doesn't call user code and doesn't block.)
1124             GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer, "ServicePoint#" + ValidationHelper.HashString(this) + "::ReleaseAllConnectionGroups");
1125 
1126             // To avoid deadlock (can't lock a ServicePoint followed by a Connection), copy out all the
1127             // connection groups in a lock, then release them all outside of it.
1128             ArrayList cgs = new ArrayList(m_ConnectionGroupList.Count);
1129             lock(this)
1130             {
1131                 foreach (ConnectionGroup cg in m_ConnectionGroupList.Values)
1132                 {
1133                     cgs.Add(cg);
1134                 }
1135                 m_ConnectionGroupList.Clear();
1136             }
1137             foreach (ConnectionGroup cg in cgs)
1138             {
1139                 cg.DisableKeepAliveOnConnections();
1140             }
1141         }
1142 
1143 
1144         /// <summary>
1145         ///    <para>Internal class, used to store state for async Connect</para>
1146         /// </summary>
1147         private class ConnectSocketState {
ConnectSocketState(ServicePoint servicePoint, PooledStream pooledStream, object owner, Socket s4, Socket s6)1148             internal ConnectSocketState(ServicePoint servicePoint, PooledStream pooledStream, object owner, Socket s4, Socket s6)
1149             {
1150                 this.servicePoint = servicePoint;
1151                 this.pooledStream = pooledStream;
1152                 this.owner = owner;
1153                 this.s4 = s4;
1154                 this.s6 = s6;
1155             }
1156             internal ServicePoint servicePoint;
1157             internal Socket s4;
1158             internal Socket s6;
1159             internal object owner;
1160             internal IPAddress[] addresses;
1161             internal int currentIndex;
1162             internal int i;
1163             internal int unsuccessfulAttempts;
1164             internal bool connectFailure;
1165             internal PooledStream pooledStream;
1166         }
1167 
1168 
1169         /// <summary>
1170         ///    <para>Proviates an async callback that is called when Connect completes [part of ConnectSocket(...)]</para>
1171         /// </summary>
ConnectSocketCallback(IAsyncResult asyncResult)1172         private static void ConnectSocketCallback(IAsyncResult asyncResult) {
1173             ConnectSocketState state = (ConnectSocketState)asyncResult.AsyncState;
1174             Socket socket = null;
1175             IPAddress address = null;
1176             Exception innerException = null;
1177             Exception exception = null;
1178             WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
1179 
1180 
1181             try {
1182                 ws = state.servicePoint.ConnectSocketInternal(state.connectFailure, state.s4, state.s6, ref socket, ref address, state, asyncResult, out innerException);
1183             }
1184             catch (SocketException socketException) {
1185                 exception = socketException;
1186             }
1187             catch (ObjectDisposedException socketException) {
1188                 exception = socketException;
1189             }
1190 
1191             if (ws == WebExceptionStatus.Pending) {
1192                 return;
1193             }
1194 
1195             if (ws == WebExceptionStatus.Success) {
1196                 try {
1197                     state.servicePoint.CompleteGetConnection(state.s4, state.s6, socket, address);
1198                 }
1199                 catch (SocketException socketException) {
1200                     exception = socketException;
1201                 }
1202                 catch (ObjectDisposedException socketException) {
1203                     exception = socketException;
1204                 }
1205 
1206             }
1207             else {
1208                 exception = new WebException(
1209                         NetRes.GetWebStatusString(ws),
1210                         ws == WebExceptionStatus.ProxyNameResolutionFailure || ws == WebExceptionStatus.NameResolutionFailure ? state.servicePoint.Host : null,
1211                         innerException,
1212                         ws,
1213                         null, /* no response */
1214                         WebExceptionInternalStatus.ServicePointFatal);
1215             }
1216             try {
1217                 state.pooledStream.ConnectionCallback(state.owner, exception, socket, address);
1218             }
1219             catch
1220             {
1221                 if (socket != null && socket.CleanedUp)
1222                     return;   // The connection was aborted and requests dispatched
1223                 throw;
1224             }
1225 
1226         }
1227 
BindUsingDelegate(Socket socket, IPEndPoint remoteIPEndPoint)1228         private void BindUsingDelegate(Socket socket, IPEndPoint remoteIPEndPoint)
1229         {
1230             IPEndPoint clonedRemoteIPEndPoint = new IPEndPoint(remoteIPEndPoint.Address, remoteIPEndPoint.Port);
1231             int retryCount;
1232 
1233             for (retryCount=0; retryCount<int.MaxValue; retryCount++) {
1234                 IPEndPoint localIPEndPoint = BindIPEndPointDelegate(this, clonedRemoteIPEndPoint, retryCount);
1235                 if (localIPEndPoint == null)
1236                     break;
1237 
1238                 try {
1239                     socket.InternalBind(localIPEndPoint);
1240                 }
1241                 catch {
1242                     continue;
1243                 }
1244                 break;
1245             }
1246             if (retryCount == int.MaxValue)
1247                 throw new OverflowException("Reached maximum number of BindIPEndPointDelegate retries");
1248         }
1249 
1250 
1251         /// <summary>
1252         ///    <para>Set SocketOptionName.ReuseUnicastPort (SO_REUSE_UNICASTPORT) socket option on the outbound connection.</para>
1253         /// </summary>
SetUnicastReusePortForSocket(Socket socket)1254         private void SetUnicastReusePortForSocket(Socket socket)
1255         {
1256             bool reusePort;
1257 
1258             if (ServicePointManager.ReusePortSupported.HasValue && !ServicePointManager.ReusePortSupported.Value) {
1259                 // We tried to set the socket option before and it isn't supported on this system.  So, we'll save some
1260                 // time by not trying again.
1261                 reusePort = false;
1262             }
1263             else {
1264                 reusePort = ServicePointManager.ReusePort;
1265             }
1266 
1267             if (reusePort) {
1268                 // This socket option is defined in Windows 10.0 or later.  It is also
1269                 // available if an LDR servicing patch has been installed on downlevel OS.
1270                 try {
1271                     socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, 0x1);
1272                     if (Logging.On) {
1273                         Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_set_socketoption_reuseport,
1274                             "Socket", socket.GetHashCode()));
1275                     }
1276 
1277                     ServicePointManager.ReusePortSupported = true;
1278                 }
1279                 catch (SocketException) {
1280                     // The socket option is not supported.  We will ignore this error and fail gracefully.
1281                     if (Logging.On) {
1282                         Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_set_socketoption_reuseport_not_supported,
1283                             "Socket", socket.GetHashCode()));
1284                     }
1285                     ServicePointManager.ReusePortSupported = false;
1286                 }
1287                 catch (Exception ex) {
1288                     // We want to preserve app compat and trap any other unusual exceptions.
1289                     if (Logging.On) {
1290                         Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_unexpected_exception, ex.Message));
1291                     }
1292                 }
1293             }
1294         }
1295 
1296         /// <summary>
1297         ///    <para>This is the real logic for doing the Connect with IPv4 and IPv6 addresses, see ConnectSocket for details</para>
1298         /// </summary>
ConnectSocketInternal(bool connectFailure, Socket s4, Socket s6, ref Socket socket, ref IPAddress address, ConnectSocketState state, IAsyncResult asyncResult, out Exception exception)1299         private WebExceptionStatus ConnectSocketInternal(bool connectFailure, Socket s4, Socket s6, ref Socket socket,
1300             ref IPAddress address, ConnectSocketState state, IAsyncResult asyncResult, out Exception exception) {
1301             IPEndPoint remoteIPEndPoint;
1302             exception = null;
1303 
1304             //
1305             // so, here we are: we have the EndPoint we need to connect to, all we
1306             // need to do is call into winsock and try to connect to this HTTP server.
1307             //
1308             // this is how we do it:
1309             // we'll keep trying to Connect() until either:
1310             // (1) Connect() succeeds (on which we increment the number of connections opened) or
1311             // (2) we can't get any new address for this host.
1312             //
1313             // (1) is pretty easy, here's how we do (2):
1314             // If the hostinformation is every marked as failed, we will automatically refresh it
1315             // the next time it is read.
1316             // If we fail the first time using the DNS information and the DNS information is recent,
1317             // which mean's it either hasn't been tried or it failed, we will mark the
1318             // hostinformation as failed, and quit.  Otherwise we'll refresh the DNS information and
1319             // try one more time. If we fail the second time, then we'll mark the DNS information
1320             // as failed and quit.
1321             IPAddress[] addresses = null;
1322             for (int unsuccessfulAttempts = 0; unsuccessfulAttempts < 2; unsuccessfulAttempts++) {
1323 
1324                 int currentIndex;
1325                 int i = 0;
1326 
1327                 // Use asyncResult to make sure it is only called at initiation time.
1328                 if (asyncResult == null)
1329                 {
1330                     // the second time, determine if the list was recent.
1331 
1332                     addresses = GetIPAddressInfoList(out currentIndex, addresses);
1333 
1334                     //the addresses were recent, or we couldn't resolve the addresses.
1335                     if (addresses == null || addresses.Length == 0)
1336                         break;
1337                 }
1338                 else
1339                 {
1340                     GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::ConnectSocketInternal() resuming previous state");
1341 
1342                     addresses = state.addresses;
1343                     currentIndex = state.currentIndex;
1344                     i = state.i;
1345                     unsuccessfulAttempts = state.unsuccessfulAttempts;
1346                 }
1347 
1348                 //otherwise, try all of the addresses in the list.
1349                 for (; i < addresses.Length; i++)
1350                 {
1351                     IPAddress ipAddressInfo = addresses[currentIndex];
1352                     try {
1353                         remoteIPEndPoint = new IPEndPoint(ipAddressInfo, m_Port);
1354                         Socket attemptSocket;
1355                         if ( remoteIPEndPoint.Address.AddressFamily==AddressFamily.InterNetwork ) {
1356                             attemptSocket = s4;
1357                         }
1358                         else {
1359                             attemptSocket = s6;
1360                         }
1361 
1362                         if (state != null)
1363                         {
1364                             if (asyncResult != null)
1365                             {
1366                                 IAsyncResult asyncResultCopy = asyncResult;
1367                                 asyncResult = null;
1368                                 attemptSocket.EndConnect(asyncResultCopy);
1369                             }
1370                             else {
1371                                 GlobalLog.Print("ServicePoint#" + ValidationHelper.HashString(this) + "::ConnectSocketInternal() calling BeginConnect() to:" + remoteIPEndPoint.ToString());
1372 
1373                                 // save off our state and do our async call
1374                                 state.addresses = addresses;
1375                                 state.currentIndex = currentIndex;
1376                                 state.i = i;
1377                                 state.unsuccessfulAttempts = unsuccessfulAttempts;
1378                                 state.connectFailure = connectFailure;
1379 
1380                                 if (!attemptSocket.IsBound) {
1381                                     if (ServicePointManager.ReusePort) {
1382                                         SetUnicastReusePortForSocket(attemptSocket);
1383                                     }
1384 
1385                                     if (BindIPEndPointDelegate != null) {
1386                                         BindUsingDelegate(attemptSocket, remoteIPEndPoint);
1387                                     }
1388                                 }
1389 
1390                                 attemptSocket.UnsafeBeginConnect(remoteIPEndPoint, m_ConnectCallbackDelegate, state);
1391                                 return WebExceptionStatus.Pending;
1392                             }
1393                         }
1394                         else {
1395                             if (!attemptSocket.IsBound) {
1396                                     if (ServicePointManager.ReusePort) {
1397                                         SetUnicastReusePortForSocket(attemptSocket);
1398                                     }
1399 
1400                                 if (BindIPEndPointDelegate != null) {
1401                                     BindUsingDelegate(attemptSocket, remoteIPEndPoint);
1402                                 }
1403                             }
1404 
1405                             attemptSocket.InternalConnect(remoteIPEndPoint);
1406                         }
1407                         socket  = attemptSocket;
1408                         address = ipAddressInfo;
1409                         exception = null;
1410                         UpdateCurrentIndex(addresses, currentIndex);
1411                         return WebExceptionStatus.Success;
1412                     }
1413                     catch (ObjectDisposedException)
1414                     {
1415                         // This can happen if the request has been aborted and the attemptSocket got closed.
1416                         return WebExceptionStatus.RequestCanceled;
1417                     }
1418                     catch (Exception e)
1419                     {
1420                         if (NclUtilities.IsFatal(e)) throw;
1421 
1422                         exception = e;
1423                         connectFailure = true;
1424                     }
1425                     currentIndex++;
1426                     if (currentIndex >= addresses.Length) {
1427                         currentIndex = 0;
1428                     }
1429                 }
1430             }
1431 
1432             Failed(addresses);
1433 
1434             return connectFailure ? WebExceptionStatus.ConnectFailure :
1435                 InternalProxyServicePoint ? WebExceptionStatus.ProxyNameResolutionFailure :
1436                 WebExceptionStatus.NameResolutionFailure;
1437         }
1438 
1439         /// <summary>
1440         ///    <para>private implimentation of ConnectSocket(...)</para>
1441         /// </summary>
ConnectSocket(Socket s4, Socket s6, ref Socket socket, ref IPAddress address, ConnectSocketState state, out Exception exception)1442         private WebExceptionStatus ConnectSocket(Socket s4, Socket s6, ref Socket socket, ref IPAddress address,
1443             ConnectSocketState state, out Exception exception) {
1444             //
1445             // we need this for the call to connect()
1446             //
1447             return ConnectSocketInternal(false, s4, s6, ref socket, ref address, state, null, out exception);
1448         }
1449 
1450         [System.Diagnostics.Conditional("DEBUG")]
DebugMembers(int requestHash)1451         internal void DebugMembers(int requestHash) {
1452             foreach(ConnectionGroup connectGroup in  m_ConnectionGroupList.Values) {
1453                 if (connectGroup!=null) {
1454                     try {
1455                         connectGroup.DebugMembers(requestHash);
1456                     }
1457                     catch {
1458                     }
1459                 }
1460             }
1461         }
1462 
1463 
1464         //
1465         // Previously: class IPHostInformation
1466         //
1467 
1468         private string              m_HostName = String.Empty;
1469         private bool                m_IsTrustedHost = true; // CBT: False if the DNS resolve changed the host
1470         private IPAddress[]         m_IPAddressInfoList;
1471         private int                 m_CurrentAddressInfoIndex;
1472         private bool                m_ConnectedSinceDns = false;
1473         private bool                m_AddressListFailed = false;
1474         private DateTime            m_LastDnsResolve;
1475         private bool                m_IPAddressesAreLoopback;
1476 
Failed(IPAddress[] addresses)1477         private void Failed(IPAddress[] addresses)
1478         {
1479             if (addresses == m_IPAddressInfoList){
1480                lock(this){
1481                    if (addresses == m_IPAddressInfoList){
1482                        m_AddressListFailed = true;
1483                    }
1484                }
1485            }
1486         }
1487 
1488 
1489         //if dns round robin is enabled, we don't want to update the index
1490         //because other connections may have skipped to the next address.
1491         //we need a better mechanism to handle dead connections
UpdateCurrentIndex(IPAddress[] addresses, int currentIndex)1492         private void UpdateCurrentIndex(IPAddress[] addresses, int currentIndex)
1493         {
1494             if (addresses == m_IPAddressInfoList && (m_CurrentAddressInfoIndex != currentIndex || !m_ConnectedSinceDns)){
1495                 lock(this){
1496                     if (addresses == m_IPAddressInfoList){
1497                         if (!ServicePointManager.EnableDnsRoundRobin ) {
1498                             m_CurrentAddressInfoIndex = currentIndex;
1499                         }
1500                         m_ConnectedSinceDns = true;
1501                     }
1502                 }
1503             }
1504         }
1505 
1506 
1507         private bool HasTimedOut {
1508             get {
1509                 int dnsRefreshTimeout = ServicePointManager.DnsRefreshTimeout;
1510                 return dnsRefreshTimeout != Timeout.Infinite &&
1511                     (m_LastDnsResolve + new TimeSpan(0, 0, 0, 0, dnsRefreshTimeout)) < DateTime.UtcNow;
1512             }
1513         }
1514 
1515 
1516         // If addresses is specified, we determine if the addresslist is recent
1517         // If the answer is yes, we return null.  Whether its recent is determined by whether
1518         // or not the current hostinformation has ever been marked as succeeded or failed (meaning
1519         // even tried). If it isn't recent, we'll refresh the addresslist.
1520 
GetIPAddressInfoList(out int currentIndex, IPAddress[] addresses)1521         private IPAddress[] GetIPAddressInfoList(out int currentIndex, IPAddress[] addresses)
1522         {
1523             IPHostEntry ipHostEntry = null;
1524             currentIndex = 0;
1525             bool needDnsResolution = false;
1526             bool dnsResolutionFailed = false;
1527 
1528             // Phase 1: Decide if we need to do a DNS resolution
1529             lock (this) {
1530 
1531                 // return null if the current hostinformation has never been marked as succeeded or failed
1532                 // (the hostinformation hasn't been used) and it hasn't changed.
1533 
1534                 if (addresses != null && !m_ConnectedSinceDns && !m_AddressListFailed && addresses == m_IPAddressInfoList)
1535                     return null;
1536 
1537                 // refresh the list if its already failed, or if the addresslist isn't recent
1538                 if (m_IPAddressInfoList == null || m_AddressListFailed || addresses == m_IPAddressInfoList || HasTimedOut) {
1539                     m_CurrentAddressInfoIndex = 0;
1540                     m_ConnectedSinceDns = false;
1541                     m_AddressListFailed = false;
1542                     m_LastDnsResolve = DateTime.UtcNow;
1543 
1544                     needDnsResolution = true;
1545                 }
1546             }
1547 
1548             // Phase 2: If we have to do a DNS resolution now, then do it now
1549             if (needDnsResolution) {
1550                 try {
1551                     dnsResolutionFailed = !Dns.TryInternalResolve(m_Host, out ipHostEntry);
1552                 }
1553                 catch (Exception exception)
1554                 {
1555                     if (NclUtilities.IsFatal(exception)) throw;
1556                     dnsResolutionFailed = true;
1557                     GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::GetIPAddressInfoList() Dns.InternalResolveFast() failed with exception:\r\n" + exception.ToString());
1558                 }
1559             }
1560 
1561             // Phase 3: If we did a DNS resolution, then deal with the results safely under a lock
1562             lock (this) {
1563                 if (needDnsResolution) {
1564 
1565                     m_IPAddressInfoList = null;
1566 
1567                     if (!dnsResolutionFailed) {
1568                         if (ipHostEntry!=null && ipHostEntry.AddressList!=null && ipHostEntry.AddressList.Length>0) {
1569                             SetAddressList(ipHostEntry);
1570                         }
1571                         else {
1572                             GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::GetIPAddressInfoList() Dns.InternalResolveFast() failed with null");
1573                         }
1574                     } else {
1575                         GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::GetIPAddressInfoList() Dns.InternalResolveFast() had thrown an exception");
1576                     }
1577                 }
1578 
1579                 if (m_IPAddressInfoList!=null && m_IPAddressInfoList.Length > 0) {
1580                     GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::GetIPAddressInfoList() m_IPAddressInfoList = "+m_IPAddressInfoList);
1581                     currentIndex = m_CurrentAddressInfoIndex;
1582 
1583                     //auto increment index for next connect request if round robin is enabled
1584                     if (ServicePointManager.EnableDnsRoundRobin)
1585                     {
1586                         m_CurrentAddressInfoIndex++;
1587                         if (m_CurrentAddressInfoIndex >= m_IPAddressInfoList.Length) {
1588                             m_CurrentAddressInfoIndex = 0;
1589                         }
1590                     }
1591                     return m_IPAddressInfoList;
1592                 }
1593             }
1594             GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::GetIPAddressInfoList() GetIPAddressInfoList returning null");
1595             return null;
1596         }
1597 
1598         //
1599         // Called under lock(this)
1600         //
SetAddressList(IPHostEntry ipHostEntry)1601         private void SetAddressList(IPHostEntry ipHostEntry)
1602         {
1603             GlobalLog.Print("IPHostInformation#" + ValidationHelper.HashString(this) + "::SetAddressList("+ipHostEntry.HostName+")");
1604             //
1605             // Create an array of IPAddress of the appropriate size, then
1606             // get a list of our local addresses. Walk through the input
1607             // address list. Copy each address in the address list into
1608             // our array, and if the address is a loopback address, mark it as
1609             // such.
1610             //
1611             // Only update the member with successfull final result.
1612             // In case of an exception m_IPAddressInfoList will stay as null
1613             //
1614             bool wasLoopback = m_IPAddressesAreLoopback;
1615             bool wasNull = m_IPAddressInfoList == null;
1616 
1617             m_IPAddressesAreLoopback = IsAddressListLoopback(ipHostEntry.AddressList);
1618             m_IPAddressInfoList = ipHostEntry.AddressList;
1619             m_HostName = ipHostEntry.HostName;
1620             m_IsTrustedHost = ipHostEntry.isTrustedHost;
1621 
1622             if (wasNull || wasLoopback != m_IPAddressesAreLoopback)
1623             {
1624                 ResolveConnectionLimit();
1625             }
1626         }
1627 
IsAddressListLoopback(IPAddress[] addressList)1628         private static bool IsAddressListLoopback(IPAddress[] addressList)
1629         {
1630             GlobalLog.Print("IPHostInformation::CheckAddressList(" + addressList.Length + ")");
1631 
1632             //
1633             // Walk through each member of the input list, copying it into our temp array.
1634             //
1635 
1636             int i, k;
1637             IPAddress[] localAddresses = null;
1638             try {
1639                 localAddresses = NclUtilities.LocalAddresses;
1640             }
1641             catch (Exception exception)
1642             {
1643                 if (NclUtilities.IsFatal(exception)) throw;
1644 
1645                 // ATTN: If LocalAddresses has failed terribly we will treat just resolved name as a remote server.
1646                 //
1647 
1648                 if (Logging.On)
1649                 {
1650                     Logging.PrintError(Logging.Web, SR.GetString(SR.net_log_retrieving_localhost_exception, exception));
1651                     Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_resolved_servicepoint_may_not_be_remote_server));
1652                 }
1653             }
1654 
1655             for (i = 0; i < addressList.Length; i++)
1656             {
1657                 // First, check to see if the current address is a loopback address.
1658                 if (IPAddress.IsLoopback(addressList[i]))
1659                 {
1660                     continue;
1661                 }
1662 
1663                 if (localAddresses != null)
1664                 {
1665                     // See if the current IP address is a local address, and if
1666                     // so mark it as such.
1667                     for (k = 0; k < localAddresses.Length; k++)
1668                     {
1669                         //
1670                         // IPv6 Changes: Use .Equals for this check !
1671                         //
1672                         if (addressList[i].Equals(localAddresses[k]))
1673                         {
1674                             break;
1675                         }
1676                     }
1677                     if (k < localAddresses.Length)
1678                     {
1679                         continue;
1680                     }
1681                 }
1682 
1683                 break;
1684             }
1685 
1686             return i == addressList.Length;
1687         }
1688     }
1689 }
1690