ARPerations.c

/*
    File:       ARPerations.c
 
    Contains:   Standard high-level ARP operations.
 
    Written by: Quinn "The Eskimo!" 
 
    Copyright:  Copyright © 1997-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/21/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                                            fixed output problem by changing '\r' to '\n'  
                                            OSStatus ARPGetCacheReport(Handle cacheReport) 
                
 
*/
// Lots of standard OT constructs.
 
#include <OpenTransport.h>
#include <OpenTptClient.h>
 
///////////////////////////////////////////////////////////////////
// Pick up the names of the standard OT modules.
 
#include <modnames.h>
 
///////////////////////////////////////////////////////////////////
// Pick up types and constants for I_STR ioctls.
 
#include <stropts.h>
 
///////////////////////////////////////////////////////////////////
// Pick up handle operations for ARPGetCacheReport.
 
#include <Memory.h>
 
///////////////////////////////////////////////////////////////////
// Standard C string operations.
 
#include <string.h>
 
///////////////////////////////////////////////////////////////////
// Constants and types for ARP module messages.
 
#include "OTARPModule.h"
 
///////////////////////////////////////////////////////////////////
// Our own prototypes.
 
#include "ARPerations.h"
 
///////////////////////////////////////////////////////////////////
 
// The following equates are actually exported by <miioccom.h>, but
//  they are commented out for some reason )-:  So instead of including
//  <miioccom.h> we just define them again here.
 
#define MIOC_ND         'c'     /* ioctl's for Mentat's nd device */
 
// The following equates define the two "Name Dispatch" ioctls
// for setting and getting OT internal parameters.
 
#define ND_GET          MIOC_CMD(MIOC_ND, 0)    /* Get a value */
#define ND_SET          MIOC_CMD(MIOC_ND, 1)    /* Set a value */
 
// The name of the Name Dispatch variable you have to get in
// order to get a dump of the ARP cache.
 
#define ARP_ND_CACHE_REPORT "arp_cache_report"
 
enum {
    kARP_ND_CACHE_REPORT_Length = 17            // strlen(ARP_ND_CACHE_REPORT) + 1
};
 
OSStatus ARPGetCacheReport(Handle cacheReport)
    // See comment in header file.
{
    OSStatus err;
    StreamRef arpStream;
    struct strioctl nddIOCommand;
    SInt32 i;
    char ndCommandBuffer[kARP_ND_CACHE_REPORT_Length];
    UInt32 realDataSize;
    SInt8 cacheReportHandleState;
    
    // First open up a raw STREAMS connection to the ARP module.
    
    arpStream = OTStreamOpen(MI_ARP_NAME, 0, &err);
    
    if (err == noErr) {
 
        // Switch the stream in sync/blocking mode.  To make the
        // code easier, we're going to do this synchronously.
 
        (void) OTStreamSetBlocking(arpStream);
        (void) OTStreamSetSynchronous(arpStream);
        
        // Copy the name of the ND variable we're trying
        // to get into our buffer.
 
        OTStrCopy(ndCommandBuffer, ARP_ND_CACHE_REPORT);
    
        // The ND_GET ioctl returns a value and sets ic_len.  A negative
        //  value is an error and you can give up now (-:  The rule for
        //  positive values is a bit weirder.  ic_len is always set
        //  to the amount of data that is actually returned.  If the
        //  data available exceeds the available buffer space (as
        //  defined by the ic_len on input), the ioctl returns
        //  a positive number that is the amount of buffer space
        //  needed.  So we first call it with a minimal buffer
        //  then give it the buffer space it requires.  Obviously
        //  there's a concurrency race here, which we conveniently
        //  ignore in this sample by return kEAGAINErr.
 
        // First get the size of data buffer we need to allocate by
        // doing the ioctl with a small buffer and examining the result.
        
        nddIOCommand.ic_cmd = ND_GET;
        nddIOCommand.ic_timout = 0;
        nddIOCommand.ic_len = kARP_ND_CACHE_REPORT_Length;  // Length of ND name including...
        nddIOCommand.ic_dp = ndCommandBuffer;               // ...the zero terminator.
        
        err = OTStreamIoctl(arpStream, I_STR, &nddIOCommand);
 
        if (err >= noErr) {
 
            if (err > noErr) {
            
                // The first ioctl returned a positive number telling
                // us how big the data returned was.  We turn around
                // around make the ioctl again, this time passing
                // in an appropriately sized buffer.
 
                realDataSize = err;
                SetHandleSize(cacheReport, realDataSize);
                err = MemError();
                
                if (err == noErr) {
                    cacheReportHandleState = HGetState(cacheReport);
                    HLock(cacheReport);
                    
                    OTStrCopy(*cacheReport, ARP_ND_CACHE_REPORT);
 
                    nddIOCommand.ic_cmd = ND_GET;
                    nddIOCommand.ic_timout = 0;
                    nddIOCommand.ic_len = realDataSize;
                    nddIOCommand.ic_dp = *cacheReport;
 
                    err = OTStreamIoctl(arpStream, I_STR, &nddIOCommand);
                    
                    HSetState(cacheReport, cacheReportHandleState);
                }
            }
            
            if (err == noErr) {
            
                // Everything is cool, we have the ARP report.  Now
                // all we have remaining is a trivial format
                // conversion.  The format returned is straight
                // text with zero characters as the line terminator.
                // We mutate the line terminators into standard EOL '\n'
                
                for (i = 0; i < realDataSize; i++) {
                    if ( (*cacheReport)[i] == '\0' ) {
                        (*cacheReport)[i] = '\n';
                    }
                }
                
            } else {
                // Whoah, ARP table changed size!
                err = kEAGAINErr;
            }
        }
    }
    
    // Clean up.
    
    if (arpStream != nil) {
        (void) OTStreamClose(arpStream);
    }
    return (err);
}
 
///////////////////////////////////////////////////////////////////
 
// The following routines are used to copy variable length data structures
// in the ARP command block that we're assembling.
 
static void CopyIntoCommandBuffer(char *buffer, UInt32 *currentOffset, void *data, UInt32 dataLength)
    // Copy dataLength bytes from data into an ARP command buffer.
    // buffer is a pointer to an ARP command block.
    // currentOffset comes in as the number of bytes that are currently being
    // used in the block, and is adjusted to represent the number of bytes we copied in.
    // data is the address of the bytes to copy in.
    // dataLength is the number of bytes to copy in.
{
    OTMemcpy(&buffer[*currentOffset], data, dataLength);
    (*currentOffset) += dataLength;
}
 
static void CopyInterfaceIntoCommandBuffer(char *buffer, UInt32 *currentOffset, char *interfaceName)
    // Copy an interface name into an ARP command buffer.
    // buffer is a pointer to an ARP command block.  This is assumed to point
    // to at least an arc_t, although the other ARP command structures all
    // begin with an arc_t, so it works for the other structures as well.
    // currentOffset comes in as the number of bytes that are currently being
    // used in the block, and is adjusted to represent the number of bytes we copied in.
    // interfaceName is the address of a C string.
{
    arc_t *arpCommandPtr;
    UInt32 interfaceNameLength;
    
    interfaceNameLength = OTStrLength(interfaceName) + 1;   // copy the string and the final null
    arpCommandPtr = (arc_t *) buffer;
    arpCommandPtr->arc_name_offset = *currentOffset;
    arpCommandPtr->arc_name_length = interfaceNameLength;
    CopyIntoCommandBuffer(buffer, currentOffset, interfaceName, interfaceNameLength);
}
 
static void CopyProtoIntoCommandBuffer(char *buffer, UInt32 *currentOffset,
                                        void *protoAddress, UInt32 protoSize)
    // Copy an protocol address into an ARP command buffer.
    // buffer is a pointer to an ARP command block.  This is assumed to point
    // to at least an arc_t, although the other ARP command structures all
    // begin with an arc_t, so it works for the other structures as well.
    // currentOffset comes in as the number of bytes that are currently being
    // used in the block, and is adjusted to represent the number of bytes we copied in.
    // protoAddress is the address of the bytes of the protocol address to copy in.
    // protoSize is the number of bytes to copy in.
{
    arc_t *arpCommandPtr;
    
    arpCommandPtr = (arc_t *) buffer;
    arpCommandPtr->arc_proto_addr_offset = *currentOffset;
    arpCommandPtr->arc_proto_addr_length = protoSize;
    CopyIntoCommandBuffer(buffer, currentOffset, protoAddress, protoSize);
}
 
///////////////////////////////////////////////////////////////////
 
OSStatus ARPAddEntry(char *interfaceName,
                        UInt32 arpProto,
                        UInt32 flags,
                        void *protoAddress, UInt32 protoSize, void *protoMask,
                        void *hardwareAddress, UInt32 hardwareSize)
    // See comment in header file.
{
    OSStatus err;
    StreamRef arpStream;
    char arpCommandBuffer[256];
    area_t *arpAddCommandPtr;
    UInt32 currentOffset;
    struct strioctl arpIOControl;
 
    // First up, open a simple stream to the ARP as a driver.
 
    arpStream = OTStreamOpen(MI_ARP_NAME, 0, &err);
    
    if (err == noErr) {
        (void) OTStreamSetBlocking(arpStream);
        (void) OTStreamSetSynchronous(arpStream);
 
        // Initialise the command buffer for this command.
 
        arpAddCommandPtr = (area_t *) &arpCommandBuffer[0];
        currentOffset = sizeof(area_t);
 
        arpAddCommandPtr->area_arc.arc_cmd = AR_ENTRY_ADD;
        
        CopyInterfaceIntoCommandBuffer(arpCommandBuffer, &currentOffset, interfaceName);
            
        arpAddCommandPtr->area_arc.arc_proto = arpProto;
        
        CopyProtoIntoCommandBuffer(arpCommandBuffer, &currentOffset, protoAddress, protoSize);
 
        arpAddCommandPtr->area_arc.arc_flags = flags;
        arpAddCommandPtr->area_arc.arc_client_q = nil;  // ignored by ARP for this command
 
        arpAddCommandPtr->area_proto_mask_offset = currentOffset;
        CopyIntoCommandBuffer(arpCommandBuffer, &currentOffset, protoMask, protoSize);
        
        arpAddCommandPtr->area_hw_addr_offset = currentOffset;
        arpAddCommandPtr->area_hw_addr_length = hardwareSize;
        CopyIntoCommandBuffer(arpCommandBuffer, &currentOffset, hardwareAddress, hardwareSize);
 
        // Initialise the I_STR ioctl structure.
 
        arpIOControl.ic_cmd = AR_ENTRY_ADD;
        arpIOControl.ic_timout = 0;
        arpIOControl.ic_len = currentOffset;
        arpIOControl.ic_dp = &arpCommandBuffer[0];
        
        // Send the ioctl.
 
        err = OTStreamIoctl(arpStream, I_STR, &arpIOControl);
    }
 
    // Clean up.
    
    if (arpStream != nil) {
        (void) OTStreamClose(arpStream);
    }
    return (err);
}
 
///////////////////////////////////////////////////////////////////
 
OSStatus ARPDeleteEntry(char *interfaceName,
                        UInt32 arpProto,
                        void *protoAddress, UInt32 protoSize)
    // See comment in header file.
{
    OSStatus err;
    StreamRef arpStream;
    char arpCommandBuffer[256];
    arc_t *arpDeleteCommandPtr;
    UInt32 currentOffset;
    struct strioctl arpIOControl;
 
    // First up, open a simple stream to the ARP as a driver.
 
    arpStream = OTStreamOpen(MI_ARP_NAME, 0, &err);
    
    if (err == noErr) {
        (void) OTStreamSetBlocking(arpStream);
        (void) OTStreamSetSynchronous(arpStream);
 
        // Initialise the command buffer for this command.
 
        arpDeleteCommandPtr = (arc_t *) &arpCommandBuffer[0];
        currentOffset = sizeof(arc_t);
 
        arpDeleteCommandPtr->arc_cmd = AR_ENTRY_DELETE;
        
        CopyInterfaceIntoCommandBuffer(arpCommandBuffer, &currentOffset, interfaceName);
            
        arpDeleteCommandPtr->arc_proto = arpProto;
        
        CopyProtoIntoCommandBuffer(arpCommandBuffer, &currentOffset, protoAddress, protoSize);
 
        arpDeleteCommandPtr->arc_flags = 0;         // ignored by ARP for this command
        arpDeleteCommandPtr->arc_client_q = nil;    // ignored by ARP for this command
 
        // Initialise the I_STR ioctl structure.
 
        arpIOControl.ic_cmd = AR_ENTRY_DELETE;
        arpIOControl.ic_timout = 0;
        arpIOControl.ic_len = currentOffset;
        arpIOControl.ic_dp = &arpCommandBuffer[0];
        
        // Send the ioctl.
 
        err = OTStreamIoctl(arpStream, I_STR, &arpIOControl);
    }
 
    // Clean up.
    
    if (arpStream != nil) {
        (void) OTStreamClose(arpStream);
    }
    return (err);
}
 
///////////////////////////////////////////////////////////////////
 
OSStatus ARPCacheQuery(char *interfaceName,
                        UInt32 arpProto,
                        void *protoAddress, UInt32 protoSize,
                        void *hardwareAddress, UInt32 hardwareSize, UInt32 *flags)
    // See comment in header file.
{
    OSStatus err;
    StreamRef arpStream;
    char arpCommandBuffer[256];
    area_t *arpQueryCommandPtr;
    UInt32 currentOffset;
    struct strioctl arpIOControl;
    
    // First up, open a simple stream to the ARP as a driver.
    
    arpStream = OTStreamOpen(MI_ARP_NAME, 0, &err);
    
    if (err == noErr) {
        (void) OTStreamSetBlocking(arpStream);
        (void) OTStreamSetSynchronous(arpStream);
 
        // Initialise the command buffer for this command.
 
        arpQueryCommandPtr = (area_t *) &arpCommandBuffer[0];
        currentOffset = sizeof(area_t);
 
        arpQueryCommandPtr->area_arc.arc_cmd = AR_ENTRY_SQUERY;
        
        CopyInterfaceIntoCommandBuffer(arpCommandBuffer, &currentOffset, interfaceName);
            
        arpQueryCommandPtr->area_arc.arc_proto = arpProto;
        
        CopyProtoIntoCommandBuffer(arpCommandBuffer, &currentOffset, protoAddress, protoSize);
 
        arpQueryCommandPtr->area_arc.arc_flags = 0;         // return value
        arpQueryCommandPtr->area_arc.arc_client_q = nil;    // ignored by ARP for this command
 
        arpQueryCommandPtr->area_proto_mask_offset = 0;     // ignored by ARP for this command
        
        arpQueryCommandPtr->area_hw_addr_offset = currentOffset;
        arpQueryCommandPtr->area_hw_addr_length = hardwareSize;
        currentOffset += hardwareSize;                      // space for return value
 
        // Initialise the I_STR ioctl structure.
 
        arpIOControl.ic_cmd = AR_ENTRY_SQUERY;
        arpIOControl.ic_timout = 0;
        arpIOControl.ic_len = currentOffset;
        arpIOControl.ic_dp = &arpCommandBuffer[0];
        
        // Send the ioctl.
        
        err = OTStreamIoctl(arpStream, I_STR, &arpIOControl);
    }
    if (err == noErr) {
 
        // Copy results out to client buffer.
        
        OTMemcpy( hardwareAddress,                                                  // dest
                    &arpCommandBuffer[arpQueryCommandPtr->area_hw_addr_offset],     // source
                    hardwareSize);                                                  // length
        *flags = arpQueryCommandPtr->area_arc.arc_flags;
    }
    
    // Clean up.
    
    if (arpStream != nil) {
        (void) OTStreamClose(arpStream);
    }
    return (err);
}
 
///////////////////////////////////////////////////////////////////
 
// The ARPInterfaceInfo structure is used to record all the information this
// module stores about an interface that it brings up.  When
// you call ARPInterfaceUp, the interfaceCookie result is really
// a pointer to one of these structures.
 
struct ARPInterfaceInfo {
    StreamRef interfaceStream;      // The ARP stream for the interface.
    char interfaceName[256];        // The interface name, as calculated by GetARPInterfaceName.
};
typedef struct ARPInterfaceInfo ARPInterfaceInfo, *ARPInterfaceInfoPtr;
 
static OSStatus GetARPInterfaceName(ARPInterfaceInfoPtr interfaceInfo)
    // Get the name for an interface on which we're about to bring up
    // ARP.  ARP forms the interface name by taking the name of the module
    // and appending a number, starting at 0, to make it unique.  Unfortunately
    // we have no way of determining which interfaces are already in use
    // by ARP, so we can't tell what number it will assign.  Therefore
    // this sample just takes a guess, assuming 0.
    // The routine gets the name of the module using the I_LIST ioctl,
    // which returns a list of the module names on a stream, and looking
    // for the last module name in the stream, ie the driver.  There
    // really shouldn't be more than one module in the stream, so the
    // array of 10 str_mlist we allocate for the return result should be
    // more than enough.
{
    OSStatus err;
    struct str_list streamList;
    struct str_mlist streamListNames[10];
    
    streamList.sl_nmods = 10;
    streamList.sl_modlist = streamListNames;
    err = OTStreamIoctl(interfaceInfo->interfaceStream, I_LIST, &streamList);
    if (err >= noErr) {
        if ( streamList.sl_nmods == 0 ) {
            err = -5;
        } else {
            OTStrCopy(interfaceInfo->interfaceName, streamListNames[ streamList.sl_nmods-1 ].l_name );
            OTStrCat(interfaceInfo->interfaceName, "0");
        }
    }
    
    return (err);
}
 
static OSStatus SendARPInterfaceUpDown(ARPInterfaceInfoPtr interfaceInfo, UInt32 arpCommand)
    // Send an AR_INTERFACE_UP or AR_INTERFACE_DOWN command to the
    // ARP interface we just created.  The name of the interface
    // and the controlling stream for the interface are contained
    // in the interfaceInfo pointer.  arpCommand is the actual
    // command to send, ie either AR_INTERFACE_UP or AR_INTERFACE_DOWN.
{
    OSStatus err;
    char arpCommandBuffer[256];
    arc_t *arpCommandPtr;
    UInt32 currentOffset;
    struct strioctl arpIOControl;
    
    // Initialise the command buffer for this command.
    
    arpCommandPtr = (arc_t *) &arpCommandBuffer[0];
    currentOffset = sizeof(arc_t);
 
    arpCommandPtr->arc_cmd = arpCommand;
    
    CopyInterfaceIntoCommandBuffer(arpCommandBuffer, &currentOffset, interfaceInfo->interfaceName);
        
    arpCommandPtr->arc_proto = 0;               // ignored by ARP for this command
    
    arpCommandPtr->arc_proto_addr_offset = 0;   // ignored by ARP for this command
    arpCommandPtr->arc_proto_addr_length = 0;   // ignored by ARP for this command
 
    arpCommandPtr->arc_flags = 0;               // ignored by ARP for this command
    arpCommandPtr->arc_client_q = nil;          // ignored by ARP for this command
 
    // Initialise the I_STR ioctl structure.
 
    arpIOControl.ic_cmd = arpCommand;
    arpIOControl.ic_timout = 0;
    arpIOControl.ic_len = currentOffset;
    arpIOControl.ic_dp = &arpCommandBuffer[0];
    
    // Send the ioctl.
    
    err = OTStreamIoctl(interfaceInfo->interfaceStream, I_STR, &arpIOControl);
    
    return (err);
}
 
OSStatus ARPInterfaceUp(char *configString, UInt32 *interfaceCookie)
    // See comment in header file.
{
    OSStatus err;
    ARPInterfaceInfoPtr interfaceInfo;
    OTConfiguration *arpConfig;
    
    // Prepare for failure.
    
    arpConfig = nil;
    
    // Create a record which we use to hold all the information
    // about this interface.  The address of this record is passed
    // back to the client as the interfaceCookie.
    
    err = noErr;
    interfaceInfo = (ARPInterfaceInfoPtr) OTAllocMem(sizeof(ARPInterfaceInfo));
    if ( interfaceInfo == nil) {
        err = kENOMEMErr;
    } else {
        OTMemzero(interfaceInfo, sizeof(ARPInterfaceInfo));
    }
    
    // Now create a configuration from the supplied configString...
    
    if (err == noErr) {
        arpConfig = OTCreateConfiguration(configString);
        if (arpConfig == kOTNoMemoryConfigurationPtr) {
            err = kENOMEMErr;
        } else if (arpConfig == kOTInvalidConfigurationPtr) {
            err = kENXIOErr;
        }
    }
 
    // ... and push the ARP module on top of this configuration.
    // Don't ask me why you have to do this or I'll start to
    // whimper.
    
    if (err == noErr) {
        (void) OTCfigPushParent(arpConfig, MI_ARPM_NAME, &err);
    }
    
    // Now create a stream to device driver over which we'll
    // be running ARP.  Note the use OTCreateStream rather than
    // OTStreamOpen.  This is because the device driver might
    // have extra plumbing underneath it (eg an ATM LANE emulation
    // driver running over an ATM hardware driver), and just
    // opening the driver would not give the driver's configurator
    // chance to set up this plumbing.
    
    if (err == noErr) {
        interfaceInfo->interfaceStream = OTCreateStream(arpConfig, 0, &err);
        if (err != noErr) {
            // Most OT create/open routines seem to return nil if they fail
            // with an error, however it seems that OTCreateSteam is not
            // as friendly.  So if we get an error we nil out our record
            // of the stream to avoid trying to OTStreamClose a bogus stream
            // as we clean up.
            interfaceInfo->interfaceStream = nil;
        }
        arpConfig = nil;
    }
    
    // Get the name of the interface that ARP will create when
    // we start running it over our newly created stream.
 
    if (err == noErr) {
        (void) OTStreamSetBlocking(interfaceInfo->interfaceStream);
        (void) OTStreamSetSynchronous(interfaceInfo->interfaceStream);
        err = GetARPInterfaceName(interfaceInfo);
    }
    
    // Push ARP on top of the driver stream and then tell it that
    // the corresponding interface is up.  ARP will then consider
    // the interface to be active.  It will snarf ARP responses
    // as they go across the network, and you can use the
    // cache query, add and remove commands on that interface.
    
    if (err == noErr) {
        err = OTStreamIoctl(interfaceInfo->interfaceStream, I_PUSH, MI_ARPM_NAME);
    }
    if (err == noErr) {
        err = SendARPInterfaceUpDown(interfaceInfo, AR_INTERFACE_UP);
    }
    
    // Clean up after failure.
    
    if (err != noErr) {
        if (interfaceInfo != nil) {
            if ( interfaceInfo->interfaceStream != nil ) {
                OTStreamClose( interfaceInfo->interfaceStream );
            }
            OTFreeMem(interfaceInfo);
            interfaceInfo = nil;
        }
    }
    
    // General clean up.
    
    if (arpConfig != nil) {
        OTDestroyConfiguration(arpConfig);
    }
    
    // Regardless of error result, copy interfaceInfo out to client buffer.
    // This ensures that the client gets a nil cookie when we get an error.
    
    *interfaceCookie = (UInt32) interfaceInfo;
 
    return (err);
}
 
///////////////////////////////////////////////////////////////////
 
OSStatus ARPInterfaceDown(UInt32 interfaceCookie)
    // See comment in header file.
{
    OSStatus err;
    ARPInterfaceInfoPtr interfaceInfo;
    
    interfaceInfo = (ARPInterfaceInfoPtr) interfaceCookie;
    
    // First tell ARP we're about to bring the interface down.
    
    err = SendARPInterfaceUpDown(interfaceInfo, AR_INTERFACE_DOWN);
    
    // Now close the stream that we opened to run the interface
    // over.
    if (err == noErr) {
        (void) OTStreamClose( interfaceInfo->interfaceStream );
        OTFreeMem(interfaceInfo);
    }
    
    return (err);
}