Traceroute problems with Sockets

T

Tim Gallivan

Hi gurus!

I have included some code for performing a c# trace route ... this is rather
difficult to explain. The code works fine in a single use scenario, you can
trace route to anywhere you like. You can even make a few changes and turn
it into a viable ping routine.

The problem occurs when you run the code multiple times (for example, you
may want to run a "heartbeat" check of 5-10 traces to known hosts in
internet-land. When you do this, the sockets can pick up (s.ReceiveFrom)
traffic from the other instances (I've even tried changing the port for each
socket, since by definition a socket is a port and ip address - to no
avail). I can get this to happen by running 2 instances of the console
program simultaneously, one tracing to a good address, the other to a bad
one.

You'll see in the commented code below that when you get an echo reply, you
also get the id(entification) back, so you can match that up. But the
timeexceeded packets don't have the id(entification), so you can't match
them up and that's (IMHO) the root of the problem.

Am I missing something here?

Thanks in advance for your suggestions,
Tim Gallivan


//ICMP constants
struct ICMPConstants
{
public const int ICMP_ECHOREPLY= 0; // Echo reply query
public const int ICMP_TIMEEXCEEDED= 11; // TTL exceeded error
public const int ICMP_ECHOREQ= 8; // Echo request query
public const int MAX_TTL= 256; // Max TTL
}

//ICMP header, size is 8 bytes
struct ICMP
{
public byte type; // Type
public byte code; // Code
public ushort checksum; // Checksum
public ushort id; // Identification
public ushort seq; // Sequence
}

// ICMP Echo Request, size is 12+ 32 (PACKET_SIZE as
// defined in class Trace)= 44 bytes
struct REQUEST
{
public ICMP m_icmp;
public byte []m_data;
}

class Trace
{
const int PACKET_SIZE= 32;

[STAThread]
static void Main()
{
try
{
// //verify command line
// if(args.Length== 0)
// {
// Console.WriteLine("usage: trace <hostname>");
// return;
// }

//Create Raw ICMP Socket
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Raw,
ProtocolType.Icmp);
//destination
IPEndPoint ipdest = new
IPEndPoint(Dns.Resolve("10.248.245.24").AddressList[0],80);
// IPEndPoint ipdest = new
IPEndPoint(Dns.Resolve(args[0]).AddressList[0],80);
//Source
IPEndPoint ipsrc = new
IPEndPoint(Dns.GetHostByName(Dns.GetHostName()).AddressList[0],80);
EndPoint epsrc = (EndPoint)ipsrc;

ICMP ip = new ICMP();
ip.type = ICMPConstants.ICMP_ECHOREQ;
ip.code = 0;
ip.checksum = 0;

//any number you feel is kinda unique :)
ip.id = (ushort)DateTime.Now.Millisecond;
Console.WriteLine("ID "+ip.id.ToString());
ip.seq = 0;

REQUEST req = new REQUEST();
req.m_icmp = ip;
req.m_data = new Byte[PACKET_SIZE];

//Initialize data
for (int i = 0; i < req.m_data.Length; i++)
{
req.m_data = (byte)'S';
}

//this function gets byte array from the REQUEST structure
Byte[] ByteSend = CreatePacket(req);

//send requests with increasing number of TTL
for(int ittl=1; ittl<= ICMPConstants.MAX_TTL; ittl++)
//for(int ittl=1; ittl<=4; ittl++)
{
Byte[] ByteRecv = new Byte[256];
//Socket options to set TTL and Timeouts

s.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.IpTimeToLive,ittl);

//s.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.IpTimeToLive,255);

s.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.SendTimeout,1000
0);

s.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReceiveTimeout,1
0000);

//Get current time
DateTime dt= DateTime.Now;
//Send Request
int iRet= s.SendTo(ByteSend, ByteSend.Length, SocketFlags.None,
ipdest);
//check for Win32 SOCKET_ERROR
if(iRet== -1)
Console.WriteLine("error sending data");

//Receive
iRet= s.ReceiveFrom(ByteRecv, ByteRecv.Length, SocketFlags.None, ref
epsrc);
Console.WriteLine("Rcd from: "+epsrc.Serialize());

//Calculate time required
TimeSpan ts= DateTime.Now- dt;;

//check if response is OK
if(iRet== -1)
{
Console.WriteLine("error getting data");
}


// 24 and 24 are the id(entifier) and sequence #
for(int iCt = 24;iCt<=25;iCt++)
{
Console.WriteLine(iCt.ToString()+"
"+BitConverter.ToInt16(ByteSend,iCt)+"
"+BitConverter.ToInt16(ByteRecv,iCt));
}

//56 byte packets (will be time exceeded) but how do we know which
instance they came from?
if((iRet == PACKET_SIZE+4+20)&&(BitConverter.ToInt16(ByteRecv,24) ==
BitConverter.ToInt16(ByteSend,4)))
{
Console.WriteLine("Rcd from: "+epsrc.Serialize());
}

//a 60 byte packet and the id that we sent matches the id we received
.... this should be the echo reply
if((iRet == PACKET_SIZE+8+20)&&(BitConverter.ToInt16(ByteRecv,24) ==
BitConverter.ToInt16(ByteSend,4)))
{
Console.WriteLine("Rcd from: "+epsrc.Serialize());

//if we get an echo reply, we've reached the target, get out
if(ByteRecv[20] == ICMPConstants.ICMP_ECHOREPLY)
{
Console.WriteLine("24 "+BitConverter.ToInt16(ByteRecv,24));
Console.WriteLine("25 "+BitConverter.ToInt16(ByteRecv,25));
Console.WriteLine("26 "+BitConverter.ToInt16(ByteRecv,26));
Console.WriteLine("27 "+BitConverter.ToInt16(ByteRecv,27));
Console.WriteLine("TTL= {0,-5} IP= {1,-20} Time= {2,3}ms", ittl,
((IPEndPoint)epsrc).Address, ts.Milliseconds);
break;
}

//time exceeded means we're not there yet
else if(ByteRecv[20] != ICMPConstants.ICMP_TIMEEXCEEDED)
{
Console.WriteLine("unexpected reply, quitting...");
break;
}
}
ip.seq++;
} //loop
s.Close();
}
catch(SocketException e)
{
Console.WriteLine(e.Message);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}

Console.ReadLine();
}

public static byte[] CreatePacket( REQUEST req )
{
Byte[] ByteSend= new Byte[PACKET_SIZE+ 8];
//Create Byte array from REQUEST structure
ByteSend[0]= req.m_icmp.type;
ByteSend[1]= req.m_icmp.code;
Array.Copy(BitConverter.GetBytes(req.m_icmp.checksum), 0, ByteSend, 2,
2);
Array.Copy(BitConverter.GetBytes(req.m_icmp.id), 0, ByteSend, 4, 2);
Array.Copy(BitConverter.GetBytes(req.m_icmp.seq), 0, ByteSend, 6, 2);
for(int i=0; i< req.m_data.Length; i++)
{
ByteSend[i+8]= req.m_data;
}

//calculate checksum
int iCheckSum = 0;
for (int i= 0; i < ByteSend.Length; i+= 2)
{
iCheckSum += Convert.ToInt32(BitConverter.ToUInt16(ByteSend,i));
}

iCheckSum = (iCheckSum >> 16) + (iCheckSum & 0xffff);
iCheckSum += (iCheckSum >> 16);

//update byte array to reflect checksum
Array.Copy(BitConverter.GetBytes((ushort)~iCheckSum), 0, ByteSend, 2, 2);
return ByteSend;
}

}
 
J

Joris Dobbelsteen

Tim Gallivan said:
The problem occurs when you run the code multiple times (for example, you
may want to run a "heartbeat" check of 5-10 traces to known hosts in
internet-land. When you do this, the sockets can pick up (s.ReceiveFrom)
traffic from the other instances
(I've even tried changing the port for each
socket, since by definition a socket is a port and ip address - to no
avail).

ICMP does not use ports numbers, so this is good (well it isn't, but it
is)...
I can get this to happen by running 2 instances of the console
program simultaneously, one tracing to a good address, the other to a bad
one.

You'll see in the commented code below that when you get an echo reply, you
also get the id(entification) back, so you can match that up. But the
timeexceeded packets don't have the id(entification), so you can't match
them up and that's (IMHO) the root of the problem.

I think I'm recalling that the ICMP packets returning error contain the
origional packet send (so far it fits in the packet)?


- Joris

<snip>
 
J

Joris Dobbelsteen

RFC792 (Internet Message Control Protocol)
found at http://www.ietf.org/rfc/rfc792.txt

states:

Time Exceeded Message

IP Header
ICMP Header consisting of
Type (8-bit) Code (8-bit) Checksum (16-bit)
32-bit unused
**Internet Header + 64 bits or origional Data Datagram**

Checking out RFC1393 (Traceroute Using an IP Option)
found at http://www.ietf.org/rfc/rfc1393.txt

States:
IP Header
Options
OptionNr (8-bit) Length (8-bit) ID Number (16-bit)
Outbound Hop Count (16-bit) Return Hop Count (16-bit)

So if you ensure your Traceroute packet has the IP Options first with the
Traceroute option you should be able to filter out the correct packet when
returning, because the ID number is in the "Time Exceeded" Message as part
of the start of the original message.

But you need them to set up IPEndpoints ...

I suppose this belongs to:

(Please place the comment inline (makes it easier for me to read) and snip
any stuff you don't need.)

I know that ICMP does not have port numbers, but the sockets are primary
designed to do TCP and UDP which do have port numbers. So probably they are
meaningless in this case, but still part of the IPEndPoint object.

Perhaps you can still get the TraceRoute function by importing it from the
WinSock DLL? This will work for unpriviledged users and is guarenteed to
work.

- Joris

<snip>
 
T

Tim Gallivan

Thanks Joris,

This is probably exactly what I need, but I'm having trouble decoding the
Time Exceeded packet to get my hands on the id number. If anyone is good at
this, I'd appreciate and hand with it.

Thanks,
Tim Gallivan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top