/*
 *  ip_utils.c
 *  ip2xxd
 *
 *  Created by Yves Arrouye on 1/31/12.
 *  Copyright (c) 2012 Yves Arrouye. All rights reserved.
 *
 */

#include <stdio.h>
#include <string.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/ioctl.h>

#include "ip_utils.h"

extern int get_first_if_up_with_mac_addr(sa_family_t sa_family, char *name, size_t *name_len) {
    size_t max_name_len = *name_len;
    
    struct ifaddrs *ifaddrs = NULL, *ifa;
    
    if (getifaddrs(&ifaddrs) == -1) {
        return -1;
    }
    
    for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == sa_family) {
            const char *ifa_name = ifa->ifa_name;
            
            struct ifaddrs *ifa2 = 0;
            for (ifa2 = ifaddrs; ifa2; ifa2 = ifa2->ifa_next) {
                if (ifa2->ifa_addr->sa_family == AF_LINK && !strcmp(ifa_name, ifa2->ifa_name)) {
                    struct sockaddr_dl *address_dl = (struct sockaddr_dl *) ifa->ifa_addr;

                    if (address_dl->sdl_alen) {
                        if (max_name_len < strlen(ifa2->ifa_name)) {   // We do not have enough room to copy the MAC address
                            return 1;
                        }
                        
                        strcpy(name, ifa2->ifa_name); 
                        *name_len = strlen(name);
                    
                        break;
                    }
                }
            }
            
            if (ifa2) {
                break;
            }
        }
    }
    
    freeifaddrs(ifaddrs);
    
    return 0;
}

extern int get_if_info(const char *if_name, sa_family_t sa_family, struct sockaddr *addr, struct sockaddr *netmask, struct sockaddr *dstaddr) {

    struct ifaddrs *ifaddrs = NULL, *ifa;
    
    if (getifaddrs(&ifaddrs) == -1) {
        return -1;
    }
    
    for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == sa_family && !strcmp(if_name, ifa->ifa_name)) {
            memcpy(addr, ifa->ifa_addr, sizeof(*addr));
            memcpy(netmask, ifa->ifa_netmask, sizeof(*netmask));
            memcpy(dstaddr, ifa->ifa_dstaddr, sizeof(*dstaddr));
            break;
        }
    }

    freeifaddrs(ifaddrs);

    return ifa ? 0 : -1;
}

int get_if_mac_address(const char *name, unsigned char *mac, size_t *mac_len) {
    size_t max_mac_len = *mac_len;

    struct ifaddrs *ifaddrs = NULL, *ifa;

    *mac = 0;
    *mac_len = 0;

    if (getifaddrs(&ifaddrs) == -1) {
        return -1;
    }

    for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr->sa_family == AF_LINK && !strcmp(name, ifa->ifa_name)) {
            struct sockaddr_dl *address_dl = (struct sockaddr_dl *) ifa->ifa_addr;
            
            if (max_mac_len < address_dl->sdl_alen) {   // We do not have enough room to copy the MAC address
                return 1;
            }
            
            memcpy(mac, LLADDR(address_dl), address_dl->sdl_alen);
            *mac_len = address_dl->sdl_alen;
            
            break;
        }
    }
    
    freeifaddrs(ifaddrs);
    
    return 0;
}

int get_addr_mac_address(struct in_addr addr, unsigned char *mac, size_t *mac_len) {        
    const char *ifa_name = NULL;
    struct ifaddrs *ifaddrs = NULL, *ifa;
    
    if (getifaddrs(&ifaddrs) == -1) {
        return -1;
    }
    
    for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *address = (struct sockaddr_in *) ifa->ifa_addr;
            if (address->sin_addr.s_addr == addr.s_addr) {
                ifa_name = ifa->ifa_name;
                break;
            }
        }
    }
    
    if (ifa_name != NULL) {
        get_if_mac_address(ifa_name, mac, mac_len);
    }
    
    freeifaddrs(ifaddrs);
    
    return 0;
}

int get_socket_mac_address(int socket, unsigned char *mac, size_t *mac_len) {
    struct ifreq if_request;
    ioctl(socket, SIOCGIFADDR, &if_request);
    struct in_addr addr = ((struct sockaddr_in *) &if_request.ifr_addr)->sin_addr;
    
#ifdef SIOCGIFHWADDR
    ioctl(socket, SIOCGIFHWADDR, &if_request);
    return -1;
#else
    return get_addr_mac_address(addr, mac, mac_len);
#endif
}
