/*
 *            Copyright (c) 1998-2003 by NETsilicon Inc.
 *
 *  This software is copyrighted by and is the sole property of 
 *  NETsilicon.  All rights, title, ownership, or other interests
 *  in the software remain the property of NETsilicon.  This
 *  software may only be used in accordance with the corresponding
 *  license agreement.  Any unauthorized use, duplication, transmission,  
 *  distribution, or disclosure of this software is expressly forbidden.
 *
 *  This Copyright notice may not be removed or modified without prior
 *  written consent of NETsilicon.
 *
 *  NETsilicon, reserves the right to modify this software
 *  without notice.
 *
 *  NETsilicon
 *  411 Waverley Oaks Road                  USA 781.647.1234
 *  Suite 304                               http://www.netsilicon.com
 *  Waltham, MA 02452                       AmericaSales@netsilicon.com
 *
 *************************************************************************
 *
 *  Description
 *  ====================================================================
 *  This file contains the network interface configuration routines.
 * 01/09/04	JZW		In function NAIfconfigSetIpAddress added call to
 *                              ip_router_advertisements after calling
 *                              NAGetAppDisableRouterAds. Add include of appconf_api.h
 *
 */
#include <errno.h>
#include <string.h>
#include "std.h"
#include "netdev.h"
#include "fpublic.h"
#include "naifconfig.h"
#include "appconf_api.h"
#include "bsp.h"
#include "ace.h"
#include "ace_params.h"

extern netdev * ll_dev_exists(const char * devname, int  *ipas_index,  int  *alias );
extern int fnsErr2BSD(int fnsVal);
static int clearIpAddress(const char *ifname);

#define BROADCAST_IP_ADDR   0xffffffff

#define IS_ARP_ANNOUNECEMENT        TRUE
#define IS_ARP_PROBE                FALSE
#define BLOCK_ON_ARP_PROBES         TRUE
#define DONT_BLOCK_ON_ARP_PROBES    FALSE


/**
 * The Interface Configuration API provides functions to:
 *
 * <list type="unordered">
 *  <item> Add a new network device </item>
 *  <item> Determine whether the device is running </item>
 *  <item> Bring the device up </item>
 *  <item> Configure a network interface with IP address </item>
 *  <item> Un-configure the IP address for the network interface </item>
 *  <item> Convert an intheface name to the device name </item>
 * </list>
 *
 *
 * Several logical interfaces might correspond to one device. For example,
 * if both eth0 and eth0:0 are configured with IP addresses on a system with 
 * one Ethernet device, both IP addresses will correspond to the same Ethernet 
 * MAC address.
 *
 *
 * @external
 * @overview Interface Configuration
 * @category Interface_Config
 * @since 6.0
 */

/**
 * Determines whether the device for the network interface (host) you specified is up
 * and can send and receive link-level packets.
 *
 * @external
 * @category Interface_Config  
 * @since 6.0
 *
 * @param ifname    Null-terminated string that identifies the 
 *                  network interface, for example "eth0" or
 *                  "eth0:0".
 *
 * @return TRUE     The device is up.
 * @return FALSE    Either the device is down or it does not exist.
 */
BOOLEAN NAIfconfigIsDeviceUp(const char * ifname)
{
    BOOLEAN online = FALSE;
    int ipas_idx, alias;
    netdev *ndp;
    use_critical;

    NA_ASSERT(ifname != NULL);
    
    critical;
    ndp = ll_dev_exists(ifname, &ipas_idx, &alias);

    if( ndp != NULL) 
    {
        online = ((ndp->nd_flags & (F_N_ONLINE|F_N_LINE_COMING_UP)) != 0);
    }
    normal;

    if (ifname != NULL && ndp == NULL)
    {
        NA_DBPF("NAIfconfig: Device %s does not exist!\n", ifname);
    }
    return online;

} /* NAIfconfigIsDeviceUp */

/**
 * Converts an intheface name to the device name.
 * For example, both eth0 and eth0:0 would be converted to eth0.
 *
 * @external
 * @category Interface_Config  
 * @since 6.0
 *
 * @param devname   Null-terminated string that specifies the device name. 
 * @param ifname    Null-terminated string that specifies the network interface name. 
 *
 */
void NAIfconfigDeviceFromInterface(char * devname, const char * ifname)
{
    int len = NETOS_MAX_IF_NAMELEN;
    char *ptr;

    NA_ASSERT(ifname != NULL);
    NA_ASSERT(devname != NULL);
    NA_ASSERT(strlen(ifname) < NETOS_MAX_IF_NAMELEN);

    if (devname != NULL && ifname != NULL)
    {
        memset(devname, 0, NETOS_MAX_IF_NAMELEN + 1);

        ptr = strchr(ifname, ':');
        if (ptr)
           len = ptr - ifname;

        if (len > NETOS_MAX_IF_NAMELEN)
            len = NETOS_MAX_IF_NAMELEN;

        memcpy(devname, ifname, len);

    }
} /* NAIfconfigDeviceFromInterface */


/**
 * Brings the device for the network interface (host) you specified up.
 * If the device is already up, this function has no effect and returns TRUE.
 *
 * @external
 * @category Interface_Config  
 * @since 6.0
 *
 * @param ifname    Null-terminated string that identifies the 
 *                  network interface, for example "eth0" or
 *                  "eth0:0".
 *
 * @return 0         The device was successfully brought up, or was already up.
 * @return non-zero  An error occured. Error codes are defined in errno.h.   
 */
int NAIfconfigBringDeviceUp(const char * ifname)
{
    int  result = 0;
    char devname[NETOS_MAX_IF_NAMELEN + 1];

    NA_ASSERT(ifname != NULL);

    if (ifname == NULL)
        result = EFAULT;
    else
    if (NAIfconfigIsDeviceUp(ifname) == FALSE)
    {
        NAIfconfigDeviceFromInterface(devname, ifname);
        result = ll_config(devname, 0, 0, 0, 0);

        if (result != 0)
        {
            result = fnsErr2BSD(result);
            NA_DBPF("NAIfconfig: ll_config failed for %s, 0.0.0.0, errno %d!\n", devname, result);
        }
    }
    return result;

} /* NAIfconfigBringDeviceUp */


/**
 * Assigns an IP address for the network interface you specify.
 * If the network interface is secondary (for example eth0:0), 
 * bring up the device before this call by calling NAIfconfigBringDeviceUp.
 * Calls ip_router_advertisements using as input the value of APP_DISABLE_ROUTER_ADS
 * as defined in appconf.h
 *
 * @external
 * @category Interface_Config  
 * @since 6.0
 *
 * @param ifname    Null-terminated string that identifies the 
 *                  network interface, for example eth0 or eth0:0.
 * @param ipaddr    The IP address for this host on network, can be 0.
 * @param netmask   Subnet mask.
 * @param mtu       Maximum transmission unit.
 *
 * @return 0        The IP address was successfully assigned.
 * @return non-zero An error occured. Error codes are defined in errno.h.   
 */
int NAIfconfigSetIpAddress(const char * ifname, WORD32 ipaddr, WORD32 netmask, int mtu)
{
    int result = 0;
    int RouterAdsStatus = 0;

    NA_ASSERT(ifname != NULL);

    if (ifname == NULL)
    {
        result = EFAULT;
        goto NAIfconfigSetIpAddressRet;
    }
    if (ipaddr == 0)
    {
        result = clearIpAddress(ifname);
    }
    else
    {
        result = ll_config(ifname, ipaddr, netmask, mtu, 0);
        if (result != 0)
        {
	    /* failed to set the ip address */
            result = fnsErr2BSD(result);
            NA_DBPF("NAIfconfig: Setting IP address failed for %s, errno %d!\n", ifname, result);
        } /* endif result != 0 */
	else
	{
	    /* successfully received an IP address */
	    /* get the current state of APP_DISABLE_ROUTER_ADS and configure
	     * ICMP router advertisements accordingly
	     * 1 means disable and 0 means enable so need to negate the result of
	     * the call to NAGetAppDisableRouterAds
	     */
	    if (NAGetAppDisableRouterAds())
	    {
		/* a returned 1 means advertisements are disabled.
		 * ip_router_advertisements needs a 0 for this
		 */
		RouterAdsStatus = 0;
	    }
	    else
	    {
		/* a returned 0 means advertisements are enabled.
		 * ip_router_advertisements needs a 1 for this
		 */
		RouterAdsStatus = 1;
	    }
	    ip_router_advertisements((char *)ifname, RouterAdsStatus);

	} /* endif result == 0 */
    }

NAIfconfigSetIpAddressRet:
    return result;

} /* NAIfconfigSetIpAddress */

/**
 * Detects IP address conflict during initial IP address configuration. 
 * This function will send ARP probes to determine wether another network device uses
 * this IP address. The function blocks until either there is an indication of a conflict 
 * or the last ARP probe times out.
 *
 * An ARP probe is an ARP request with source protocol address equal to 0.
 *
 * If IP address conflict detection is enabled for the network interface ACE 
 * will call this function to detect IP address conflict.
 *
 * @external
 * @category Interface_Config  
 * @since 6.2
 *
 * @param ifname        Null-terminated string that identifies the 
 *                      network interface, for example eth0 or eth0:0
 * @param probe_addr    ARP probe IP address
 * @param num_probes    Number of ARP probes
 * @param interval_ms   Interval between ARP probes in milliseconds
 *
 * @return ENODEV           No network device with this name
 * @return EINVAL           Invalid arguments
 * @return ENOMEM           Not enough memory to run this function
 * @return EADDRNOTAVAIL    IP address conflict
 * @return 0                No IP address conflict
 */
int NADetectAddressConflict(const char * ifname, WORD32 probe_addr, unsigned int num_probes, unsigned int interval_ms)
{

#define DEFAULT_PROBE_INTERVAL  200

    int result;

    NA_ASSERT(ifname != NULL);
    NA_ASSERT(probe_addr != 0 && probe_addr != BROADCAST_IP_ADDR);
    
    if (interval_ms < DEFAULT_PROBE_INTERVAL)
        interval_ms = DEFAULT_PROBE_INTERVAL;
    
    result = ll_arp_address(ifname, (u32) probe_addr, num_probes, 
                            interval_ms, IS_ARP_PROBE, BLOCK_ON_ARP_PROBES);  
    
    if (result)
       result = fnsErr2BSD(result);

    return result;

} /* NADetectAddressConflict */


/**
 * Sends IP address announecements, after the IP address has been 
 * configured. This function does not block. 
 *
 * An ARP announcement is an ARP request with both source and destination protocol address 
 * equal the interface IP address.
 *
 * If IP address conflict detection is enabled for the network interface ACE 
 * will call this function to send ARP address announecements. This function can also be
 * called from duplicate IP address callback, registered with NARegisterDuplicateAddrCallback.
 *
 * @external
 * @category Interface_Config  
 * @since 6.2
 *
 * @param ifname                Null-terminated string that identifies the 
 *                              network interface, for example eth0 or eth0:0
 * @param addr                  IP address to be announeced
 * @param num_announcements     Number of ARP address announecements
 * @param interval_ms           Interval between ARP address announecements in milliseconds
 *
 * @return ENODEV           No network device with this name
 * @return EINVAL           Invalid arguments
 * @return ENOMEM           Not enough memory to run this function
 * @return 0                Announecements will be sent
 */
int NASendAddressAnnouncement(const char * ifname,  WORD32 addr, unsigned int num_announcements, unsigned int interval_ms)
{
    netdev  *ndp;
    int     ipas_index, alias, result;
    use_critical;
    
    NA_ASSERT(ifname != NULL);
    NA_ASSERT(addr != 0 && addr != BROADCAST_IP_ADDR);

    if (ifname == NULL || addr == 0 || addr == BROADCAST_IP_ADDR)
    {
        result = EINVAL;
        goto ret;
    }

    result = ENODEV;
    critical;
    ndp = ll_dev_exists(ifname, &ipas_index,  &alias);
               
	FOR_ACTIVE_ADDRESSES(ndp, ipas_index)
        if( ndp->nd_ipas[ipas_index].nd_ipaddr == addr)
        {
            result = 0;
            break;
        }
    END_FOR_ACTIVE_ADDRESSES
    normal;

    if (result == 0)
    {
        result = ll_arp_address(ifname, addr, num_announcements, 
                                interval_ms, IS_ARP_ANNOUNECEMENT, DONT_BLOCK_ON_ARP_PROBES);

        if (result)
            result = fnsErr2BSD(result);
    }
ret:
    return result;

} /* NASendAddressAnnouncement */

/**
 * Sends ARP address brobes. Intended to be used for ongoing IP address conflict detection.
 * This function does not block. 
 *
 * An ARP probe is an ARP request with source protocol address equal to 0.
 *
 * @external
 * @category Interface_Config  
 * @since 6.2
 *
 * @param ifname            Null-terminated string that identifies the 
 *                          network interface, for example eth0.
 * @param addr              IP address to be announeced
 * @param num_probes        Number of ARP address announecements
 * @param interval_ms       Interval between ARP address announecements in milliseconds
 *
 * @return ENODEV           No network device with this name
 * @return EINVAL           Invalid arguments
 * @return ENOMEM           Not enough memory to run this function
 * @return 0                Announecements will be sent
 */
int NASendAddressProbe(char * ifname,  WORD32 addr, unsigned int num_probes, unsigned int interval_ms)
{
    int     result;

    NA_ASSERT(ifname != NULL);
    NA_ASSERT(addr != 0 && addr != BROADCAST_IP_ADDR);

    result = ll_arp_address(ifname, (u32) addr, num_probes, interval_ms, 
                            IS_ARP_PROBE, DONT_BLOCK_ON_ARP_PROBES);

    if (result)
        result = fnsErr2BSD(result);

    return result;

} /* NASendAddressProbes */

/**
 * Used by ACE to get configuration data for IP address conflict 
 * detection for the network interface
 *
 * @external
 * @category Interface_Config  
 * @since 6.2
 *
 * @param ifname    Null-terminated string that identifies the 
 *                  network interface, for example eth0 or eth0:0
 * @param data      Pointer to a configuration structure filled in by 
 *                  this function
 *
 * @return 0        Always
 */
int NAGetAddrConflictData(char const * ifname, NAAddrConflictConfigType * data)
{
    char devname[NETOS_MAX_IF_NAMELEN + 1];

    NA_ASSERT(data != NULL);
    NA_ASSERT(ifname != NULL);

    if (data && ifname)
    {
        memset(data, 0, sizeof *data);
        NAIfconfigDeviceFromInterface(devname, ifname);

#ifdef BSP_ENABLE_ADDR_CONFLICT_DETECTION
#if (BSP_ENABLE_ADDR_CONFLICT_DETECTION == TRUE)

        customizeAceGetAddrConflictData(devname, data);
#endif
#endif
    }
    return 0;
}


/**
 * Registers a duplicate IP address callback, that is called when ARP protocol detects that 
 * another network device uses our, already configured IP address
 *
 * @external
 * @category Interface_Config  
 * @since 6.2
 *
 * @param ifname        Null-terminated string that identifies the 
 *                      network interface, for example eth0 or eth0:0
 * @param dup_addr_fn   Pointer to a callback function. Can be NULL.
 *
 * @return ENODEV       No network device with this name
 * @return 0            The callback was sussessfully installed
 */
int NARegisterDuplicateAddrCallback(const char * ifname, NADuplicateAddrCallbackType dup_addr_fn)
{
    int result;

    NA_ASSERT(ifname != NULL); 

    result = ll_register_dup_addr_callback(ifname, (addr_conflict_cb) dup_addr_fn);
    if (result)
       result = fnsErr2BSD(result);
    
    return result;
}

/******************************************************/

static int clearIpAddress(const char * ifname)
{
    int ipas_idx, alias, result = 0;
    netdev *ndp;
    use_critical;

    if (ifname)
    {
        critical;
        ndp = ll_dev_exists(ifname, &ipas_idx, &alias);
        normal;

        if (ndp == NULL)
            result = ENODEV;
        else
        {
            if( alias >= 0 )
                result = ll_unconfig(ifname);
            else
                result = ll_config(ifname, 0, 0, 0, 0);

            if (result)
                result = fnsErr2BSD(result);
        }
    }
    return result;

} /* clearIpAddress */

