[bcl] Add missing APIs for netstandard parity and expose a few APIs on mobile
[mono.git] / mcs / class / System / System.Net.Sockets / UdpClient.cs
1 //
2 // System.Net.Sockets.UdpClient.cs
3 //
4 // Author:
5 //    Gonzalo Paniagua Javier <gonzalo@ximian.com>
6 //    Sridhar Kulkarni (sridharkulkarni@gmail.com)
7 //    Marek Safar (marek.safar@gmail.com)
8 //
9 // Copyright (C) Ximian, Inc. http://www.ximian.com
10 // Copyright 2011 Xamarin Inc.
11 //
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System;
35 using System.Net;
36 using System.Threading.Tasks;
37
38 namespace System.Net.Sockets
39 {
40         public class UdpClient : IDisposable
41         {
42                 private bool disposed = false;
43                 private bool active = false;
44                 private Socket socket;
45                 private AddressFamily family = AddressFamily.InterNetwork;
46                 private byte[] recvbuffer;
47         
48                 public UdpClient () : this(AddressFamily.InterNetwork)
49                 {
50                 }
51
52                 public UdpClient(AddressFamily family)
53                 {
54                         if(family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6)
55                                 throw new ArgumentException ("Family must be InterNetwork or InterNetworkV6", "family");
56
57                         this.family = family;
58                         InitSocket (null);
59                 }
60
61                 public UdpClient (int port)
62                 {
63                         if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
64                                 throw new ArgumentOutOfRangeException ("port");
65
66                         this.family = AddressFamily.InterNetwork;
67
68                         IPEndPoint localEP = new IPEndPoint (IPAddress.Any, port);
69                         InitSocket (localEP);
70                 }
71
72                 public UdpClient (IPEndPoint localEP)
73                 {
74                         if (localEP == null)
75                                 throw new ArgumentNullException ("localEP");
76
77                         this.family = localEP.AddressFamily;
78
79                         InitSocket (localEP);
80                 }
81
82                 public UdpClient (int port, AddressFamily family)
83                 {
84                         if (family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6)
85                                 throw new ArgumentException ("Family must be InterNetwork or InterNetworkV6", "family");
86
87                         if (port < IPEndPoint.MinPort ||
88                             port > IPEndPoint.MaxPort) {
89                                 throw new ArgumentOutOfRangeException ("port");
90                         }
91
92                         this.family = family;
93
94                         IPEndPoint localEP;
95
96                         if (family == AddressFamily.InterNetwork)
97                                 localEP = new IPEndPoint (IPAddress.Any, port);
98                         else
99                                 localEP = new IPEndPoint (IPAddress.IPv6Any, port);
100                         InitSocket (localEP);
101                 }
102
103                 public UdpClient (string hostname, int port)
104                 {
105                         if (hostname == null)
106                                 throw new ArgumentNullException ("hostname");
107
108                         if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
109                                 throw new ArgumentOutOfRangeException ("port");
110
111                         InitSocket (null);
112                         Connect (hostname, port);
113                 }
114
115                 private void InitSocket (EndPoint localEP)
116                 {
117                         if(socket != null) {
118                                 socket.Close();
119                                 socket = null;
120                         }
121
122                         socket = new Socket (family, SocketType.Dgram, ProtocolType.Udp);
123
124                         if (localEP != null)
125                                 socket.Bind (localEP);
126                 }
127
128                 public void AllowNatTraversal (bool allowed)
129                 {
130                         if (allowed)
131                                 socket.SetIPProtectionLevel (IPProtectionLevel.Unrestricted);
132                         else
133                                 socket.SetIPProtectionLevel (IPProtectionLevel.EdgeRestricted);
134                 }
135
136                 public void Close ()
137                 {
138                         Dispose ();
139                 }
140 #region Connect
141
142                 void DoConnect (IPEndPoint endPoint)
143                 {
144                         /* Catch EACCES and turn on SO_BROADCAST then,
145                          * as UDP sockets don't have it set by default
146                          */
147                         try {
148                                 socket.Connect (endPoint);
149                         } catch (SocketException ex) {
150                                 if (ex.ErrorCode == (int)SocketError.AccessDenied) {
151                                         socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
152                                         
153                                         socket.Connect (endPoint);
154                                 } else {
155                                         throw;
156                                 }
157                         }
158                 }
159                 
160                 public void Connect (IPEndPoint endPoint)
161                 {
162                         CheckDisposed ();
163                         if (endPoint == null)
164                                 throw new ArgumentNullException ("endPoint");
165
166                         DoConnect (endPoint);
167                         active = true;
168                 }
169
170                 public void Connect (IPAddress addr, int port)
171                 {
172                         if (addr == null)
173                                 throw new ArgumentNullException ("addr");
174
175                         if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
176                                 throw new ArgumentOutOfRangeException ("port");
177
178
179                         Connect (new IPEndPoint (addr, port));
180                 }
181
182                 public void Connect (string hostname, int port)
183                 {
184                         if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
185                                 throw new ArgumentOutOfRangeException ("port");
186
187                         IPAddress[] addresses = Dns.GetHostAddresses (hostname);
188                         for(int i=0; i<addresses.Length; i++) {
189                                 try {
190                                         this.family = addresses[i].AddressFamily;
191                                         Connect (new IPEndPoint (addresses[i], port));
192                                         break;
193                                 } catch(Exception e) {
194                                         if(i == addresses.Length - 1){
195                                                 if(socket != null) {
196                                                         socket.Close();
197                                                         socket = null;
198                                                 }
199                                                 /// This is the last entry, re-throw the exception
200                                                 throw e;
201                                         }
202                                 }
203                         }
204                 }
205 #endregion
206                 #region Multicast methods
207                 public void DropMulticastGroup (IPAddress multicastAddr)
208                 {
209                         CheckDisposed ();
210                         if (multicastAddr == null)
211                                 throw new ArgumentNullException ("multicastAddr");
212
213                         if(family == AddressFamily.InterNetwork)
214                                 socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.DropMembership,
215                                         new MulticastOption (multicastAddr));
216                         else
217                                 socket.SetSocketOption (SocketOptionLevel.IPv6, SocketOptionName.DropMembership,
218                                         new IPv6MulticastOption (multicastAddr));
219                 }
220
221                 public void DropMulticastGroup (IPAddress multicastAddr,
222                                                 int ifindex)
223                 {
224                         CheckDisposed ();
225
226                         /* LAMESPEC: exceptions haven't been specified
227                          * for this overload.
228                          */
229                         if (multicastAddr == null) {
230                                 throw new ArgumentNullException ("multicastAddr");
231                         }
232
233                         /* Does this overload only apply to IPv6?
234                          * Only the IPv6MulticastOption has an
235                          * ifindex-using constructor.  The MS docs
236                          * don't say.
237                          */
238                         if (family == AddressFamily.InterNetworkV6) {
239                                 socket.SetSocketOption (SocketOptionLevel.IPv6, SocketOptionName.DropMembership, new IPv6MulticastOption (multicastAddr, ifindex));
240                         }
241                 }
242                 
243                 public void JoinMulticastGroup (IPAddress multicastAddr)
244                 {
245                         CheckDisposed ();
246
247                         if (multicastAddr == null)
248                                 throw new ArgumentNullException ("multicastAddr");
249
250                         if(family == AddressFamily.InterNetwork)
251                                 socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.AddMembership,
252                                         new MulticastOption (multicastAddr));
253                         else
254                                 socket.SetSocketOption (SocketOptionLevel.IPv6, SocketOptionName.AddMembership,
255                                         new IPv6MulticastOption (multicastAddr));
256                 }
257
258                 public void JoinMulticastGroup (int ifindex,
259                                                 IPAddress multicastAddr)
260                 {
261                         CheckDisposed ();
262
263                         if (multicastAddr == null)
264                                 throw new ArgumentNullException ("multicastAddr");
265
266                         if (family == AddressFamily.InterNetworkV6)
267                                 socket.SetSocketOption (SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption (multicastAddr, ifindex));
268                         else
269                                 throw new SocketException ((int) SocketError.OperationNotSupported);
270                 }
271
272                 public void JoinMulticastGroup (IPAddress multicastAddr, int timeToLive)
273                 {
274                         CheckDisposed ();
275                         if (multicastAddr == null)
276                                 throw new ArgumentNullException ("multicastAddr");
277                         if (timeToLive < 0 || timeToLive > 255)
278                                 throw new ArgumentOutOfRangeException ("timeToLive");
279
280                         JoinMulticastGroup (multicastAddr);
281                         if(family == AddressFamily.InterNetwork)
282                                 socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive,
283                                         timeToLive);
284                         else
285                                 socket.SetSocketOption (SocketOptionLevel.IPv6, SocketOptionName.MulticastTimeToLive,
286                                         timeToLive);
287                 }
288
289                 public void JoinMulticastGroup (IPAddress multicastAddr,
290                                                 IPAddress localAddress)
291                 {
292                         CheckDisposed ();
293
294                         if (family == AddressFamily.InterNetwork)
295                                 socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption (multicastAddr, localAddress));
296                         else
297                                 throw new SocketException ((int) SocketError.OperationNotSupported);
298                 }
299
300                 #endregion
301                 #region Data I/O
302                 public byte [] Receive (ref IPEndPoint remoteEP)
303                 {
304                         CheckDisposed ();
305
306                         byte [] recBuffer = new byte [65536]; // Max. size
307                         EndPoint endPoint = (EndPoint) remoteEP;
308                         int dataRead = socket.ReceiveFrom (recBuffer, ref endPoint);
309                         if (dataRead < recBuffer.Length)
310                                 recBuffer = CutArray (recBuffer, dataRead);
311
312                         remoteEP = (IPEndPoint) endPoint;
313                         return recBuffer;
314                 }
315
316                 int DoSend (byte[] dgram, int bytes, IPEndPoint endPoint)
317                 {
318                         /* Catch EACCES and turn on SO_BROADCAST then,
319                          * as UDP sockets don't have it set by default
320                          */
321                         try {
322                                 if (endPoint == null) {
323                                         return(socket.Send (dgram, 0, bytes,
324                                                             SocketFlags.None));
325                                 } else {
326                                         return(socket.SendTo (dgram, 0, bytes,
327                                                               SocketFlags.None,
328                                                               endPoint));
329                                 }
330                         } catch (SocketException ex) {
331                                 if (ex.ErrorCode == (int)SocketError.AccessDenied) {
332                                         socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
333                                         if (endPoint == null) {
334                                                 return(socket.Send (dgram, 0, bytes, SocketFlags.None));
335                                         } else {
336                                                 return(socket.SendTo (dgram, 0, bytes, SocketFlags.None, endPoint));
337                                         }
338                                 } else {
339                                         throw;
340                                 }
341                         }
342                 }
343                 
344                 public int Send (byte [] dgram, int bytes)
345                 {
346                         CheckDisposed ();
347                         if (dgram == null)
348                                 throw new ArgumentNullException ("dgram");
349
350                         if (!active)
351                                 throw new InvalidOperationException ("Operation not allowed on " + 
352                                                                      "non-connected sockets.");
353
354                         return(DoSend (dgram, bytes, null));
355                 }
356
357                 public int Send (byte [] dgram, int bytes, IPEndPoint endPoint)
358                 {
359                         CheckDisposed ();
360                         if (dgram == null)
361                                 throw new ArgumentNullException ("dgram is null");
362                         
363                         if (active) {
364                                 if (endPoint != null)
365                                         throw new InvalidOperationException ("Cannot send packets to an " +
366                                                                              "arbitrary host while connected.");
367
368                                 return(DoSend (dgram, bytes, null));
369                         }
370
371                         return(DoSend (dgram, bytes, endPoint));
372                 }
373
374                 public int Send (byte [] dgram, int bytes, string hostname, int port)
375                 {
376                         return Send (dgram, bytes, 
377                                      new IPEndPoint (Dns.GetHostAddresses (hostname) [0], port));
378                 }
379
380                 private byte [] CutArray (byte [] orig, int length)
381                 {
382                         byte [] newArray = new byte [length];
383                         Buffer.BlockCopy (orig, 0, newArray, 0, length);
384
385                         return newArray;
386                 }
387 #endregion
388
389                 IAsyncResult DoBeginSend (byte[] datagram, int bytes,
390                                           IPEndPoint endPoint,
391                                           AsyncCallback requestCallback,
392                                           object state)
393                 {
394                         /* Catch EACCES and turn on SO_BROADCAST then,
395                          * as UDP sockets don't have it set by default
396                          */
397                         try {
398                                 if (endPoint == null) {
399                                         return(socket.BeginSend (datagram, 0, bytes, SocketFlags.None, requestCallback, state));
400                                 } else {
401                                         return(socket.BeginSendTo (datagram, 0, bytes, SocketFlags.None, endPoint, requestCallback, state));
402                                 }
403                         } catch (SocketException ex) {
404                                 if (ex.ErrorCode == (int)SocketError.AccessDenied) {
405                                         socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
406                                         if (endPoint == null) {
407                                                 return(socket.BeginSend (datagram, 0, bytes, SocketFlags.None, requestCallback, state));
408                                         } else {
409                                                 return(socket.BeginSendTo (datagram, 0, bytes, SocketFlags.None, endPoint, requestCallback, state));
410                                         }
411                                 } else {
412                                         throw;
413                                 }
414                         }
415                 }
416                 
417                 public IAsyncResult BeginSend (byte[] datagram, int bytes,
418                                                AsyncCallback requestCallback,
419                                                object state)
420                 {
421                         return(BeginSend (datagram, bytes, null,
422                                           requestCallback, state));
423                 }
424                 
425                 public IAsyncResult BeginSend (byte[] datagram, int bytes,
426                                                IPEndPoint endPoint,
427                                                AsyncCallback requestCallback,
428                                                object state)
429                 {
430                         CheckDisposed ();
431
432                         if (datagram == null) {
433                                 throw new ArgumentNullException ("datagram");
434                         }
435                         
436                         return(DoBeginSend (datagram, bytes, endPoint,
437                                             requestCallback, state));
438                 }
439                 
440                 public IAsyncResult BeginSend (byte[] datagram, int bytes,
441                                                string hostname, int port,
442                                                AsyncCallback requestCallback,
443                                                object state)
444                 {
445                         return(BeginSend (datagram, bytes, new IPEndPoint (Dns.GetHostAddresses (hostname) [0], port), requestCallback, state));
446                 }
447                 
448                 public int EndSend (IAsyncResult asyncResult)
449                 {
450                         CheckDisposed ();
451                         
452                         if (asyncResult == null) {
453                                 throw new ArgumentNullException ("asyncResult is a null reference");
454                         }
455                         
456                         return(socket.EndSend (asyncResult));
457                 }
458
459                 public IAsyncResult BeginReceive (AsyncCallback requestCallback, object state)
460                 {
461                         CheckDisposed ();
462
463                         recvbuffer = new byte[8192];
464                         
465                         EndPoint ep;
466                         
467                         if (family == AddressFamily.InterNetwork) {
468                                 ep = new IPEndPoint (IPAddress.Any, 0);
469                         } else {
470                                 ep = new IPEndPoint (IPAddress.IPv6Any, 0);
471                         }
472                         
473                         return(socket.BeginReceiveFrom (recvbuffer, 0, 8192,
474                                                         SocketFlags.None,
475                                                         ref ep,
476                                                         requestCallback, state));
477                 }
478                 
479                 public byte[] EndReceive (IAsyncResult asyncResult, ref IPEndPoint remoteEP)
480                 {
481                         CheckDisposed ();
482                         
483                         if (asyncResult == null) {
484                                 throw new ArgumentNullException ("asyncResult is a null reference");
485                         }
486                         
487                         EndPoint ep;
488                         
489                         if (family == AddressFamily.InterNetwork) {
490                                 ep = new IPEndPoint (IPAddress.Any, 0);
491                         } else {
492                                 ep = new IPEndPoint (IPAddress.IPv6Any, 0);
493                         }
494                         
495                         int bytes = socket.EndReceiveFrom (asyncResult,
496                                                            ref ep);
497                         remoteEP = (IPEndPoint)ep;
498
499                         /* Need to copy into a new array here, because
500                          * otherwise the returned array length is not
501                          * 'bytes'
502                          */
503                         byte[] buf = new byte[bytes];
504                         Array.Copy (recvbuffer, buf, bytes);
505                         
506                         return(buf);
507                 }
508                                 
509 #region Properties
510                 protected bool Active {
511                         get { return active; }
512                         set { active = value; }
513                 }
514
515                 public Socket Client {
516                         get { return socket; }
517                         set { socket = value; }
518                 }
519
520                 public int Available
521                 {
522                         get {
523                                 return(socket.Available);
524                         }
525                 }
526                 
527                 public bool DontFragment
528                 {
529                         get {
530                                 return(socket.DontFragment);
531                         }
532                         set {
533                                 socket.DontFragment = value;
534                         }
535                 }
536
537                 public bool EnableBroadcast
538                 {
539                         get {
540                                 return(socket.EnableBroadcast);
541                         }
542                         set {
543                                 socket.EnableBroadcast = value;
544                         }
545                 }
546                 
547                 public bool ExclusiveAddressUse
548                 {
549                         get {
550                                 return(socket.ExclusiveAddressUse);
551                         }
552                         set {
553                                 socket.ExclusiveAddressUse = value;
554                         }
555                 }
556                 
557                 public bool MulticastLoopback
558                 {
559                         get {
560                                 return(socket.MulticastLoopback);
561                         }
562                         set {
563                                 socket.MulticastLoopback = value;
564                         }
565                 }
566                 
567                 public short Ttl
568                 {
569                         get {
570                                 return(socket.Ttl);
571                         }
572                         set {
573                                 socket.Ttl = value;
574                         }
575                 }
576
577 #endregion
578 #region Disposing
579                 public void Dispose ()
580                 {
581                         Dispose (true);
582                         GC.SuppressFinalize (this);
583                 }
584
585                 protected virtual void Dispose (bool disposing)
586                 {
587                         if (disposed)
588                                 return;
589                         disposed = true;
590
591                         if (disposing){
592                                 if (socket != null)
593                                         socket.Close ();
594
595                                 socket = null;
596                         }
597                 }
598                 
599                 ~UdpClient ()
600                 {
601                         Dispose (false);
602                 }
603                 
604                 private void CheckDisposed ()
605                 {
606                         if (disposed)
607                                 throw new ObjectDisposedException (GetType().FullName);
608                 }               
609 #endregion
610
611                 
612                 public Task<UdpReceiveResult> ReceiveAsync ()
613                 {
614                         return Task<UdpReceiveResult>.Factory.FromAsync (BeginReceive, r => {
615                                 IPEndPoint remoteEndPoint = null;
616                                 return new UdpReceiveResult (EndReceive (r, ref remoteEndPoint), remoteEndPoint);
617                         }, null);
618                 }
619
620                 public Task<int> SendAsync (byte[] datagram, int bytes)
621                 {
622                         return Task<int>.Factory.FromAsync (BeginSend, EndSend, datagram, bytes, null);
623                 }
624
625                 public Task<int> SendAsync (byte[] datagram, int bytes, IPEndPoint endPoint)
626                 {
627                         return Task<int>.Factory.FromAsync (BeginSend, EndSend, datagram, bytes, endPoint, null);
628                 }
629
630                 public Task<int> SendAsync (byte[] datagram, int bytes, string hostname, int port)
631                 {
632                         var t = Tuple.Create (datagram, bytes, hostname, port, this);
633
634                         return Task<int>.Factory.FromAsync ((callback, state) => {
635                                 var d = (Tuple<byte[], int, string, int, UdpClient>) state;
636                                 return d.Item5.BeginSend (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
637                         }, EndSend, t);
638  
639                 }
640         }
641 }
642