IPAddress from NSData using Bonjour NSNetService in MonoTouch? - c#

I'm using Xamarin + MonoTouch on iOS to browse for a web server on the network that I can then download files from.
The NSNetService that is passed into the resolve event handler contains addresses as NSData. I can't find a good way to turn that NSData into an actual IP address that I can then build a URL from, i.e. http:// < IPAddress > /folder/file.htm
This is my NSNetService.AddressResolved event handler:
private void OnServiceResolved(object sender, EventArgs args)
{
NSNetService service = (NSNetService)sender;
// service.Port is valid.
// service.HostName is valid.
// but we want the IP addres, which is in service.Addresses.
// None of the following four methods works quite right.
IPAddress address = (IPAddress)service.Addresses [0]; // Cannot convert type NSData to IPAddress
SocketAddress address2 = (SocketAddress)service.Addresses[0]; // Cannot convert NSData to SocketAddress. A binary copy might work?
IPHostEntry entry = (IPHostEntry)service.Addresses [0]; // Cannot convert NSData to IPHostEntry
IPHostEntry entry2 = Dns.GetHostByName (service.HostName); // This kinda works, but is dumb. Didn't we just resolve?
}
What's the right way to get the IP address of the service from an NSNetService in a resolve event?

The NSNetService.Addresses property gives you NSData instances which must be converted into something that IPAddress (or other .NET types) can digest. E.g.
MemoryStream ms = new MemoryStream ();
(ns.Addresses [0] as NSData).AsStream ().CopyTo (ms);
IPAddress ip = new IPAddress (ms.ToArray ());
Note that this can return you an IPv6 address (or format that IPAddress won't accept). You might want to iterate all the Addresses to find the best one.
I'll look into adding a convenience method into future versions of Xamarin.iOS.
UPDATE
A more complete version, that returns an IPAddress, would look like:
static IPAddress CreateFrom (NSData data)
{
byte[] address = null;
using (MemoryStream ms = new MemoryStream ()) {
data.AsStream ().CopyTo (ms);
address = ms.ToArray ();
}
SocketAddress sa = new SocketAddress (AddressFamily.InterNetwork, address.Length);
// do not overwrite the AddressFamily we provided
for (int i = 2; i < address.Length; i++)
sa [i] = address [i];
IPEndPoint ep = new IPEndPoint (IPAddress.Any, 0);
return (ep.Create (sa) as IPEndPoint).Address;
}

Related

How to use tcplistener and tcpclient on device and computer with different ip in same Network?

I'm trying to communicate with a modbus device in my network at ip 192.168.1.76. My host computer address is 192.168.1.132. I'm not able to connect to or listen to device ip.
basically i'm using NModbus4 library. I've created a ModbusTCPSlave and attached the tcp listener to it. then i assigned ModbusSlaveRequestReceived event to that slave. but it gives nothing in return when i try to change register values directly from Modscan software.
Main()
{
var masterEndpoint = new IPEndPoint(IPAddress.Parse("192.168.1.132"), 502);
var listener = new TcpListener(IPAddress.Any, 502);
listener.Start();
var slave = ModbusTcpSlave.CreateTcp(255, new TcpListener(masterEndpoint), 10);
slave.ModbusSlaveRequestReceived += Modbus_Request_Event;
slave.Listen();
}
private static void Modbus_Request_Event(object sender, Modbus.Device.ModbusSlaveRequestEventArgs e)
{
//disassemble packet from master
byte fc = e.Message.FunctionCode;
byte[] data = e.Message.MessageFrame;
byte[] byteStartAddress = new byte[] { data[3], data[2] };
byte[] byteNum = new byte[] { data[5], data[4] };
Int16 StartAddress = BitConverter.ToInt16(byteStartAddress, 0);
Int16 NumOfPoint = BitConverter.ToInt16(byteNum, 0);
Console.WriteLine(fc.ToString() + "," + StartAddress.ToString() + "," + NumOfPoint.ToString());
}
I expect to get function code, start address and number of points in console application when any register value is changed
I copied your code. Changed the IP address to my "server" and it worked.
So, the issue you are having is either in the setup of your "server" or in the PLC program.
I thought that I had to do some port forwarding on my router. I did not. It did not make a difference.
Server setup:
Your "server"'s IP address needs to be static. Whether your 'server' is your development system or not. And don't forget when you deploy... Server's IP address has to be static as well (not that it wouldn't be...just saying)
Add an inbound Firewall rule to allow connections to the port, in this case 502, otherwise you'll have to allow access every time you launch/start a test.
PLC program
I am using Click PLC's by Koyo. Not sure if this is the rule for all PLC's or not; but, we had to add a line of code to "write" the values we wanted to pick up off the TCP stream. Without the write, the PLC was not sending out a request to join the TcpListener.
Last:
The code to start your listener only needs to be this:
var listener = new TcpListener(IPAddress.Parse("192.168.1.244"), 502);
listener.Start();
var slave = ModbusTcpSlave.CreateTcp(255, listener, 10);
slave.ModbusSlaveRequestReceived += Modbus_Request_Event;
slave.Listen();

how to achieve AddressFamily in winRT

I am searching something replacement of System.Net.Sockets.AddressFamily.InterNetwork. I found HostName but unable to find something that can do like AdressFamily does. Actually, I am converting following code in winRT. I know winRT supports Windows.Networking.Sockets etc. Please describe some solution.
The code is,
if (System.Net.Sockets.AddressFamily.InterNetwork == _address.AddressFamily && _address.AddressFamily == address.AddressFamily)
{
long longLocal = BitConverter.ToInt32(_address.GetAddressBytes(), 0);
long longNetMask = BitConverter.ToInt32(_netmask.GetAddressBytes(), 0);
long longRemote = BitConverter.ToInt32(address.GetAddressBytes(), 0);
}
_address is also IPAdress
Well AddressFamily does not directly supports WinRT/Metro project so what you need to do is check the ipAddress information like this
using Windows.Networking;
using Windows.Networking.Sockets;
HostName serverHost = new HostName("www.contoso.com");
StreamSocket clientSocket = new Windows.Networking.Sockets.StreamSocket();
// Try to connect to the remote host
await clientSocket.ConnectAsync(serverHost, "http");
var ipAddress = clientSocket.Information.RemoteAddress.DisplayName
To check your condition use _address.Type == HostNameType.Ipv4 then do something but make sure that GetAddressBytes do not exist in HostName class so for that you need to write your own function to convert an ipAddress into bytes.

c# TCPclient set IP

I'm trying to send a packet using httpclient
TcpClient tc = new TcpClient(ip, 4500);
string s = "A7007000601D3B00";
byte[] arr = new byte[s.Length/2];
for ( var i = 0 ; i<arr.Length ; i++ ){
arr[i] = (byte)Convert.ToInt32(s.Substring(i*2,2), 16);
}
NetworkStream stream = tc.GetStream();
stream.Write(arr, 0, arr.Length);
tc.Close();
The problem is that it sends from port 47109, however i need to send the packet using port 46324. How do i set this?
There is an overload of the TcpClient constructor that allows you to bind it to a specific local IP address and port. See the documentation on MSDN.
The reason the example at Is there a way to specify the local port to used in tcpClient? is not working is probably because the first address on the list is not actually the local machine ip address. Something like this might fix the issue and pull the proper local IP address:
string remoteIP = "x.x.x.x";
IPAddress ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).First();
IPEndPoint ipLocalEndPoint = new IPEndPoint(ipAddress, 47109);
TcpClient clientSocket = new TcpClient(ipLocalEndPoint);
clientSocket.Connect(remoteIP, 4500);

GetHostEntry() doesn't resolve the address anymore

I recently switched from .NET v3.5 to v4.0 Client Profile and at first run GetHostEntry() got problem.
tcpClient.SocketInfo.SourceName = remoteMatcher.Host; // "88.255.126.48"
tcpClient.SocketInfo.SourcePort = remoteMatcher.Port; // 999
IPHostEntry ipEntry = Dns.GetHostEntry(tcpClient.SocketInfo.SourceName);
GetHostEntry() causes an exception:
WSANO_DATA
11004
Valid name, no data record of requested type.
The requested name is valid and was found in the database, but it does not have the correct associated data being resolved for. The usual example for this is a host name-to-address translation attempt (using gethostbyname or WSAAsyncGetHostByName) which uses the DNS (Domain Name Server). An MX record is returned but no A record—indicating the host itself exists, but is not directly reachable.
I'm gonna reboot the machine and wanted to ask this question before all things lost on my mind.
UPDATE:
My workaround:
// .NET Framework v4.0 bug??
IPAddress ip;
if (IPAddress.TryParse(tcpClient.SocketInfo.SourceName, out ip))
tcpClient.SocketInfo.SourceIP = tcpClient.SocketInfo.SourceName;
else
{
IPHostEntry ipEntry = Dns.GetHostEntry(tcpClient.SocketInfo.SourceName);
IPAddress[] addr = ipEntry.AddressList;
tcpClient.SocketInfo.SourceIP = addr[addr.Length - 1].ToString();
}
here is something that I have tried, I recall running into the same problem
feel free to use my example to test your stuff
** I used IPHostEntry instead **
string[] host = (address.Split('#'));
string hostname = host[1];
IPHostEntry IPhst = Dns.Resolve(hostname);
IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], 25);
Socket s= new Socket(endPt.AddressFamily,
SocketType.Stream,ProtocolType.Tcp);
s.Connect(endPt);
or
when I used it to get hostname of email address
try
{
Response.Write("One");
string[] host = (txtEmailAddress.Text.Split('#'));
string hostname = host[1];
Response.Write(host);
IPHostEntry IPhst = Dns.Resolve(hostname);
IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], 25);
Socket s = new Socket(endPt.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
Response.Write(endPt);
s.Connect(endPt);
}
catch (SocketException se)
{
lblErrMsg.Text = se.Message.ToString();
PublicUtils.AddError("Error: " + se.Message + txtEmailAddress.Text);
txtEmailAddress.Focus();
return;
}
Encountered the same problem recently, GetHostEntry does a reverse lookup on host name when given an IP Address, in my particular scenario, NetBIOS on the target machine was turned off, that's why hostname resolution was failing, and GetHostEntry was throwing the above exception mentioned.
GetHostAddresses was more suitable for my needs, it does not do a reverse lookup when given an IP address, it just parses it and returns IPAddress itself.
From MSDN: http://msdn.microsoft.com/en-us/library/system.net.dns.gethostaddresses.aspx
GetHostEntry method exhibits the following behavior when an IP string literal is passed:
The method tries to parse the address. If the hostNameOrAddress
contains a legal IP string literal, then the first phase succeeds.
A reverse lookup using the IP address of the IP string literal is
attempted to obtain the host name. This result is set as the
HostName property.
The host name from this reverse lookup is used
again to obtain all the possible IP addresses associated with the
name and set as the AddressList property.

IPAddress.Parse() using port on IPv4

I'm trying to parse a string containing an IP address and a port using IPAddress.Parse. This works well with IPv6 addresses but not with IPv4 addresses. Can somone explain why this happens?
The code I'm using is:
IPAddress.Parse("[::1]:5"); //Valid
IPAddress.Parse("127.0.0.1:5"); //null
Uri url;
IPAddress ip;
if (Uri.TryCreate(String.Format("http://{0}", "127.0.0.1:5"), UriKind.Absolute, out url) &&
IPAddress.TryParse(url.Host, out ip))
{
IPEndPoint endPoint = new IPEndPoint(ip, url.Port);
}
This happens because the port is not part of the IP address. It belongs to TCP/UDP, and you'll have to strip it out first. The Uri class might be helpful for this.
IPAddress is not IP+Port. You want IPEndPoint.
Example from http://www.java2s.com/Code/CSharp/Network/ParseHostString.htm
public static void ParseHostString(string hostString, ref string hostName, ref int port)
{
hostName = hostString;
if (hostString.Contains(":"))
{
string[] hostParts = hostString.Split(':');
if (hostParts.Length == 2)
{
hostName = hostParts[0];
int.TryParse(hostParts[1], out port);
}
}
}
Edit: Ok, I'll admit that wasn't the most elegant solution. Try this one I wrote (just for you) instead:
// You need to include some usings:
using System.Text.RegularExpressions;
using System.Net;
// Then this code (static is not required):
private static Regex hostPortMatch = new Regex(#"^(?<ip>(?:\[[\da-fA-F:]+\])|(?:\d{1,3}\.){3}\d{1,3})(?::(?<port>\d+))?$", System.Text.RegularExpressions.RegexOptions.Compiled);
public static IPEndPoint ParseHostPort(string hostPort)
{
Match match = hostPortMatch.Match(hostPort);
if (!match.Success)
return null;
return new IPEndPoint(IPAddress.Parse(match.Groups["ip"].Value), int.Parse(match.Groups["port"].Value));
}
Note that this one ONLY accepts IP address, not hostname. If you want to support hostname you'll either have to resolve it to IP or not use IPAddress/IPEndPoint.
IPAddress.Parse is meant to take A string that contains an IP address in dotted-quad notation for IPv4 and in colon-hexadecimal notation for IPv6. So your first example works for IPv6 and your second example fails because it doesnt support a port for IPv4. Link http://msdn.microsoft.com/en-us/library/system.net.ipaddress.parse.aspx
As Tedd Hansen pointed out, what you are trying to parse is not an IP address but an IP endpoint (IP address + port). And since .NET Core 3.0, you can use IPEndPoint.TryParse to parse a string as an IPEndPoint:
if (IPEndPoint.TryParse("127.0.0.1:5", out IPEndPoint endpoint))
{
// parsed successfully, you can use the "endpoint" variable
Console.WriteLine(endpoint.Address.ToString()); // writes "127.0.0.1"
Console.WriteLine(endpoint.Port.ToString()); // writes "5"
}
else
{
// failed to parse
}
If you work on older versions of .net you can take IPEndPoint.Parse implementation from open source: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Primitives/src/System/Net/IPEndPoint.cs
To add my two cents... Since Microsoft itself implemented TryParse in NET Core 3.0 I've opted to stop using my custom IP+Port parser and kindly borrowed their code with some adaptations:
public static class IPEndPointParserExtension
{
public static bool TryParseAsIPEndPoint(this string s, out IPEndPoint result) {
#if NETCOREAPP3_0_OR_GREATER
return IPEndPoint.TryParse(s, out result);
#else
int addressLength = s.Length; // If there's no port then send the entire string to the address parser
int lastColonPos = s.LastIndexOf(':');
// Look to see if this is an IPv6 address with a port.
if (lastColonPos > 0) {
if (s[lastColonPos - 1] == ']')
addressLength = lastColonPos;
// Look to see if this is IPv4 with a port (IPv6 will have another colon)
else if (s.Substring(0, lastColonPos).LastIndexOf(':') == -1)
addressLength = lastColonPos;
}
if (IPAddress.TryParse(s.Substring(0, addressLength), out IPAddress address)) {
long port = 0;
if (addressLength == s.Length ||
(long.TryParse(s.Substring(addressLength + 1), out port)
&& port <= IPEndPoint.MaxPort)) {
result = new IPEndPoint(address, (int)port);
return true;
}
}
result = null;
return false;
#endif
}
public static IPEndPoint AsIPEndPoint(this string s) =>
s.TryParseAsIPEndPoint(out var endpoint)
? endpoint
: throw new FormatException($"'{s}' is not a valid IP Endpoint");
}
My changes were to basically exchange Span<char> for string and make it an extension method of the class String itself. I've also conditionally compile to use Microsoft's implementation if it is available (NET Core 3.0 or greater).
The following nUnit tests show how to use the code:
[Test]
public void CanParseIpv4WithPort() {
var sIp = "192.168.0.233:8080";
if (sIp.TryParseAsIPEndPoint(out var endpoint)) {
var expected = new IPEndPoint(new IPAddress(new byte[] { 192, 168, 0, 233 }), 8080);
Assert.AreEqual(expected, endpoint);
} else
Assert.Fail($"Failed to parse {sIp}");
}
[Test]
public void CanParseIpv6WithPort() {
var sIp = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443";
if (sIp.TryParseAsIPEndPoint(out var endpoint)) {
var expected = new IPEndPoint(IPAddress.Parse("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 443);
Assert.AreEqual(expected, endpoint);
} else
Assert.Fail($"Failed to parse {sIp}");
}
You can also use AsIpEndPoint which will throw an exception if it fails to parse the IP address and port (port is optional):
var ep = "127.0.0.1:9000".AsIPEndPoint();

Categories

Resources