/// <summary> /// Sends a Netbios Name Service request to a unicast address and returns the respective response /// </summary> /// <param name="packet">Netbios Name Service packet to send</param> /// <param name="address">Target address to send the packet to</param> /// <param name="timeOutMilliseconds"> /// If after this timeout following sending the request packet no response has been received, the request is sent again. /// This is tried <see cref="numRetries"/> times. We intentionally use the "wrong" timeout value here. For unicast /// requests RFC 1002 defines a UCAST_REQ_RETRY_TIMEOUT of 5 seconds. However, with the default retry count of 3, this /// results in a maximum time of 15 seconds until we can be sure that no response was sent. This is extremely long and /// tests have shown that even in a WLan, computers respond to requests withing less than 100 ms. We therefore by default /// use the BCAST_REQ_RETRY_TIMEOUT of 250ms, which is meant for broadcast requests, also for unicast requests. /// </param> /// <param name="numRetries">Number of retries. Default is 3.</param> /// <returns> /// A <see cref="Task"/> that completes when either (a) the respective response was received (in which case the result value /// of the Task contains the response packet) or (b) when the send operation was not successful or (c) when after <see cref="numRetries"/> /// retries no response was received (in the latter two cases the result value of the Task is <c>null</c>). /// </returns> public async Task <NbNsPacketBase> SendUnicastRequestAsync(NbNsPacketBase packet, IPAddress address, int timeOutMilliseconds = BCAST_REQ_RETRY_TIMEOUT, int numRetries = BCAST_REQ_RETRY_COUNT) { if (packet == null) { throw new ArgumentNullException("packet"); } if (address == null) { throw new ArgumentNullException("address"); } var identifier = new PacketIdentifier(packet.Header.NameTrnId, address); // If there is a request pending with the same transaction Id and the same target IP address, we do not // send the request again, but simply return the Task representing the already pending request. var newTcs = new TaskCompletionSource <NbNsPacketBase>(); var tcs = _pendingUnicastRequests.GetOrAdd(identifier, newTcs); if (tcs != newTcs) { return(await tcs.Task); } for (var retryCount = 1; retryCount <= numRetries; retryCount++) { // If the send operation was not successful (most likely the given IP address does not exist in the local subnet) // we immediately return null. if (!await SendPacketAsync(packet, address)) { _pendingUnicastRequests.TryRemove(identifier, out newTcs); return(null); } // If we received a response within the given timeout, we return the response; if not, we resend the request if (await Task.WhenAny(tcs.Task, Task.Delay(timeOutMilliseconds)) == tcs.Task) { _pendingUnicastRequests.TryRemove(identifier, out newTcs); return(await tcs.Task); } } _pendingUnicastRequests.TryRemove(identifier, out newTcs); return(null); }
/// <summary> /// Sends a Netbios Name Service packet asynchronously /// </summary> /// <param name="packet">Netbios Name Service packet to send</param> /// <param name="address">Target address to send the packet to</param> /// <param name="timeOutMilliseconds"> /// Maximum time in milliseconds to wait for the send operation to finish. If it takes longer, the target IP address /// most likely does not exist in a local subnet (although the address is part of a local subnet) so that the ARP /// protocol is not able to find the MAC address for the given IP address. /// </param> /// <returns> /// A <see cref="Task"/> that completes with result = <c>true</c> when the packet was successfully sent or /// with result = <c>false</c> if the send operation was not successful witin the given timeout. /// </returns> public async Task <bool> SendPacketAsync(NbNsPacketBase packet, IPAddress address, int timeOutMilliseconds = SEND_TIMEOUT) { if (packet == null) { throw new ArgumentNullException("packet"); } if (address == null) { throw new ArgumentNullException("address"); } var endPoint = new IPEndPoint(address, TARGET_PORT); var tcs = new TaskCompletionSource <int>(); try { _socket.BeginSendTo(packet, 0, packet.Length, SocketFlags.None, endPoint, asynchronousResult => { var t = (TaskCompletionSource <int>)asynchronousResult.AsyncState; try { t.TrySetResult(_socket.EndSendTo(asynchronousResult)); } catch (Exception) { t.TrySetResult(0); } }, tcs); } catch (Exception) { return(false); } if (await Task.WhenAny(tcs.Task, Task.Delay(timeOutMilliseconds)) != tcs.Task) { return(false); } // The send operation was successful if as many bytes as are contained in the packet were actually sent. return(packet.Length == await tcs.Task); }
/// <summary> /// Tries to parse a Netbios Name Service packet from a buffer of bytes /// </summary> /// <param name="buffer">Byte array containing the NbNs packet</param> /// <param name="packet">Parsed NbNs packet if successful, else null</param> /// <returns><c>true</c> if parsing was successful, else <c>false</c></returns> /// <remarks> /// This method is the entry point for parsing Netbios Name Service packets. It returns an object of a class /// derived from <see cref="NbNsPacketBase"/>, which can then be casted based on <see cref="PacketType"/> to /// the respective derived class. /// </remarks> public static bool TryParse(byte[] buffer, out NbNsPacketBase packet) { packet = null; if (buffer == null || buffer.Length < NbNsHeader.NETBIOS_HEADER_LENGTH) { return(false); } NbNsHeader header; if (!NbNsHeader.TryParse(buffer, out header)) { return(false); } if (header.Opcode == NbNsHeader.OpcodeSpecifier.Query && header.IsResponse == false && header.IsRecursionDesired == false) { // Must be a Netbios Node Status Request NbNsNodeStatusRequest result; if (!NbNsNodeStatusRequest.TryParse(header, buffer, out result)) { return(false); } packet = result; } else if (header.Opcode == NbNsHeader.OpcodeSpecifier.Query && header.IsRecursionDesired == false) { // Must be a Netbios Node Status Response NbNsNodeStatusResponse result; if (!NbNsNodeStatusResponse.TryParse(header, buffer, out result)) { return(false); } packet = result; } // ToDo: Parse Further Netbios Name Service packets return(true); }
/// <summary> /// Tries to parse a Netbios Name Service packet from a buffer of bytes /// </summary> /// <param name="buffer">Byte array containing the NbNs packet</param> /// <param name="packet">Parsed NbNs packet if successful, else null</param> /// <returns><c>true</c> if parsing was successful, else <c>false</c></returns> /// <remarks> /// This method is the entry point for parsing Netbios Name Service packets. It returns an object of a class /// derived from <see cref="NbNsPacketBase"/>, which can then be casted based on <see cref="PacketType"/> to /// the respective derived class. /// </remarks> public static bool TryParse(byte[] buffer, out NbNsPacketBase packet) { packet = null; if (buffer == null || buffer.Length < NbNsHeader.NETBIOS_HEADER_LENGTH) return false; NbNsHeader header; if (!NbNsHeader.TryParse(buffer, out header)) return false; if (header.Opcode == NbNsHeader.OpcodeSpecifier.Query && header.IsResponse == false && header.IsRecursionDesired == false) { // Must be a Netbios Node Status Request NbNsNodeStatusRequest result; if (!NbNsNodeStatusRequest.TryParse(header, buffer, out result)) return false; packet = result; } else if (header.Opcode == NbNsHeader.OpcodeSpecifier.Query && header.IsRecursionDesired == false) { // Must be a Netbios Node Status Response NbNsNodeStatusResponse result; if (!NbNsNodeStatusResponse.TryParse(header, buffer, out result)) return false; packet = result; } // ToDo: Parse Further Netbios Name Service packets return true; }
/// <summary> /// Method used in <see cref="_receiverTask"/> to receive Netbios Name Service packets /// </summary> /// <returns>Task that completes when the <see cref="_receiverTask"/> stops receiving packets</returns> private async Task Receive() { var buffer = new byte[RECEIVE_BUFFER_SIZE]; EndPoint localEndPoint = new IPEndPoint(0, 0); while (true) { var tcs = new TaskCompletionSource <EndPoint>(); try { _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref localEndPoint, asynchronousResult => { var t = (TaskCompletionSource <EndPoint>)asynchronousResult.AsyncState; EndPoint ep = new IPEndPoint(0, 0); try { _socket.EndReceiveFrom(asynchronousResult, ref ep); t.TrySetResult(ep); } catch (Exception) { t.TrySetResult(null); } }, tcs); } catch (Exception) { if (_endReceive.Task.IsCompleted) { return; } continue; } // Wait until a new packet has been received or NbNsClient is disposed if (await Task.WhenAny(tcs.Task, _endReceive.Task) == _endReceive.Task) { return; } // RemoteEndPoint is null when there was an exception in EndReceive. In that case, discard the packet. var remoteEndPoint = (await tcs.Task) as IPEndPoint; if (remoteEndPoint == null) { continue; } // If we cannot parse the received packet, discard it. NbNsPacketBase packet; if (!NbNsPacketBase.TryParse(buffer, out packet)) { continue; } // If the received packet is the response to a known request, set the request task result to the received packet. var identifier = new PacketIdentifier(packet.Header.NameTrnId, remoteEndPoint.Address); TaskCompletionSource <NbNsPacketBase> result; if (_pendingUnicastRequests.TryGetValue(identifier, out result)) { result.TrySetResult(packet); } } }