OTTraceRouteSample.c

/*
    File:               OTTraceRouteSample.c
 
    Contains:       A trivial traceroute implementation.
 
    Written by: Quinn "The Eskimo!" 
 
    Copyright:  Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
 
                            You may incorporate this Apple sample source code into your program(s) without
                            restriction. This Apple sample source code has been provided "AS IS" and the
                            responsibility for its operation is yours. You are not permitted to redistribute
                            this Apple sample source code as "Apple sample source code" after having made
                            changes. If you're going to re-distribute the source, we require that you make
                            it clear in the source that the code was descended from Apple sample source
                            code, but that you've made changes.
 
    Change History (most recent first):
                            7/23/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
#include <stdio.h>
#include <OpenTransport.h>
#include <OpenTptInternet.h>
#include <Events.h>
/////////////////////////////////////////////////////////////////////
 
static OSStatus CreateAndConfigUDP(EndpointRef *ep)
{
    OSStatus err;
    
    *ep = OTOpenEndpoint(OTCreateConfiguration(kUDPName), 0, nil, &err);
    
    if (err == noErr) {
    err = OTBind(*ep, nil, nil);
    
        // no others options to negotiate at this stage
    }
    
    return (err);
}
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus CreateAndConfigICMP(EndpointRef *ep)
{
    OSStatus err;
    
    *ep = OTOpenEndpoint(OTCreateConfiguration(kRawIPName), 0, nil, &err);
    
    if (err == noErr) {
    err = OTBind(*ep, nil, nil);
    
        // no others options to negotiate at this stage
        
        // You might think we need to negotiate the XTI_GENERIC/XTI_PROTOTYPE
        // option to request ICMP packets (ie protocol 2).  This is not
        //  necessary because rawip endpoints default to that protocol.
    }
    
    return (err);
}
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus DoNegotiateIP_TTLOption(EndpointRef ep, long ttl)
{
    // According to the XTI spec, IP_TTL is an INET_IP level option that
    //  determines the TTL of an IP packet.  The value of this option is
    //  a UInt8.  This routine simply negotiates that option on the ep
    //  endpoint.
    OSStatus    err;
    TOption*    opt;                                                // points to buf, makes it easier to access
    TOptMgmt    req;
    TOptMgmt    ret;
    UInt8           buf[kOTFourByteOptionSize]; // define buffer for options, although we only
                                                                                // use a "1 byte option", we define a "4 byte option"
                                                                                // buffer to hold the returning options
    
    // Point opt to the start of buf.  This allows us to set the items in buf easily.
    opt = (TOption*)buf;
 
    // Setup the fields of the options buffer...
    
    opt->level  = INET_IP;
    opt->name   = IP_TTL;
    opt->len    = kOTOneByteOptionSize;         // Note that kOTOneByteOptionSize != 1, it also
    opt->status = 0;                                            //  includes the size of the option header.
    *(UInt8*)opt->value = ttl;
 
    // Set up the req structure to denote the options we're requesting...
    
    req.opt.buf = buf;
    req.opt.len = kOTOneByteOptionSize;
    req.flags   = T_NEGOTIATE;
 
    // Set up the ret structure to hold the options we got...
    
    ret.opt.buf = buf;
    ret.opt.maxlen = kOTFourByteOptionSize;
 
    err = OTOptionManagement(ep, &req, &ret);
    
    // If no error then return the option status value...
    
    if (err == kOTNoError) {
        if (opt->status != T_SUCCESS)
            err = opt->status;
        else
            err = kOTNoError;
    }
                
    return (err);
}
 
/////////////////////////////////////////////////////////////////////
 
// this is the data we send in our UDP packets...
 
static unsigned char udp_data[8] = {0, 1, 2, 3, 4, 5, 6, 7};
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus SendUDPWithTTL(EndpointRef ep, InetHost dest, long ttl)
{
    OSStatus err;
    InetAddress dest_addr;
    TUnitData udata;
    OTResult look;
    
    err = DoNegotiateIP_TTLOption(ep, ttl);
 
    if (err == noErr) {
 
        OTInitInetAddress(&dest_addr, 33434, dest);     
 
        // 33434 is the default port for unix traceroute.
        //  It was chosen because it's unlikely that anyone will be listening on this
        //  port.  Hence any packets that make it through will generate an ICMP
        //  port unreachable error.
 
        udata.addr.len = sizeof(dest_addr);
        udata.addr.buf = (unsigned char *) &dest_addr;
        
        udata.opt.len = 0;
        udata.opt.buf = nil;
        
        udata.udata.len = sizeof(udp_data);
        udata.udata.buf = &udp_data[0];
 
        // The act of sending is a little more complicated than it should be.
        //  Basically the ICMP errors that come back from all these bogus (short TTL)
        //  packets that I send, end up as datagram errors on the sending endpoint.
        //  If you attempt to send with a T_UDERR sitting on the endpoint, you get
        //  a kOTLookErr.
        //
        // I addresses this by junking the error and looping when I get a T_UDERR.
        
        do {
            err = OTSndUData(ep, &udata);
            if (err == kOTLookErr) {
                look = OTLook(ep);
                if (look == T_UDERR) {
                    printf("¥Junking T_UDERR.\n");
                    fflush(stdout);
                    (void) OTRcvUDErr(ep, nil);     // clear the error condition without receiving the error info
                    err = 666;                                      // and attempt to send again
                    // Yeah, yeah, I know that using error codes to control program flow is bad
                    //  style.  Hey, I was in hurry!        
                }
            }
        } while (err == 666);
    }
 
    return (err);
}
 
/////////////////////////////////////////////////////////////////////
 
// we use this buffer to hold incoming ICMP packets
 
static UInt8 icmp_data[5000];
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus WaitAndPrintICMPs(EndpointRef ep, Boolean *done)
{
    TUnitData udata;
    long start_time;
    OSStatus err;
    InetAddress src_addr;
    
    start_time = TickCount();
    
    // Wait for 3 seconds and print out any ICMP packets we get back.
    
    do {
 
        // Set up the received...
        udata.addr.buf = (UInt8*) &src_addr;
        udata.addr.maxlen = sizeof(struct InetAddress);
        udata.opt.buf = nil;
        udata.opt.maxlen = 0;
        udata.udata.buf = icmp_data;
        udata.udata.maxlen = sizeof(icmp_data);
        
        // Look for a packet...
        
        err = OTRcvUData(ep, &udata, nil);
        if (err == noErr) {
            // Print out salient information from the packet...
            printf("¥¥¥Got packet!¥¥¥\n");
            
            printf("ICMP from = %d.%d.%d.%d\n", icmp_data[12], icmp_data[13], icmp_data[14], icmp_data[15]);
            printf("ICMP type = %d\n", icmp_data[20]);
            printf("ICMP code = %d\n", icmp_data[21]);
            
            // Stop if the traceroute is at an end.  Note that this code assumes that
            //  the ICMP header will start 20 bytes into the packet.  This is correct
            //  for 99% of IP packets, but not correct in general.  If the IP packet
            //  has IP level options, they will be inserted between the 20 byte IP
            //  header and the payload, thereby stuffing up this calculation.  I was
            //  slack and ignored this issue.  You should not!
            
            if (icmp_data[20] == 3 && icmp_data[21] == 3) {
                // type 3 = destination unreachable
                // code 3 = port unreachable
                // These two imply that we're trying to deliver the packet on the destination
                //  host, and it couldn't be delivered because the port is wrong.  The fact
                //  that we're hitting the destination host means we can stop the trace.
                *done = true;
            }
            
            fflush(stdout);
        } else if (err == kOTNoDataErr) {
            err = noErr;
        }
    } while (err == noErr && TickCount() < start_time + 3 * 60 && !*done);
 
    return (err);
}
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus DoTraceRoute(InetHost dest)
{
    OSStatus err;
    EndpointRef udp_ep = nil;
    EndpointRef icmp_ep = nil;
    long ttl;
    Boolean done;
    
    // Create the endpoints and negotiate the options...
    err = CreateAndConfigUDP(&udp_ep);
    if (err == noErr) {
        err = CreateAndConfigICMP(&icmp_ep);
    }
    
    // Do the main traceroute loop...
    
    ttl = 1;
    done = false;
    do {
        printf("\nSending with TTL = %d.\n", ttl);
        err = SendUDPWithTTL(udp_ep, dest, ttl);
        if (err == noErr) {
            err = WaitAndPrintICMPs(icmp_ep, &done);
        }
        if (err == noErr) {
            ttl += 1;
        }
    } while (err == noErr && ttl < 30 && !done);
 
    if (done) {
        printf("Traceroute completed successfully!\n");
    }
 
    // clean up
    if (udp_ep != nil) {
        (void) OTCloseProvider(udp_ep);
    }
    if (icmp_ep != nil) {
        (void) OTCloseProvider(icmp_ep);
    }
    return err;
}
 
/////////////////////////////////////////////////////////////////////
 
void main(void) {
    OSStatus err;
    
    printf("Hello Cruel World!\n");
    
    err = InitOpenTransport();
    if (err == noErr) {
        
        err = DoTraceRoute(0x822B0202);         // apple.com
    
        CloseOpenTransport();
    }
    
    if (err == noErr) {
        printf("Success!\n");
    } else {
        printf("Failure!  Error = %d.\n", err);
    }
    printf("Done.  Press command-Q to Quit.\n");
}