diff --git a/perf.c b/perf.c index 9b15019..604f7a5 100644 --- a/perf.c +++ b/perf.c @@ -17,9 +17,12 @@ #include #include +#include #include +#include #include #include +#include #include #include #include @@ -35,6 +38,7 @@ #define MAX_CLIENTS 16384 #define PTP_MCAST_ADDR 0xe0000181 /* 224.0.1.129 */ +#define PTP_MCAST_MAC "\x01\x00\x5e\x00\x01\x81" struct config { enum request_mode mode; @@ -159,6 +163,120 @@ static bool get_iface_mac(struct config *config, char mac[6]) { return true; } +/** + * @brief Detects the network interface based on the destination address. + * + * This function iterates through the system's network interfaces to find one + * that is on the same subnet as the provided destination address. + * + * @param dst_address The destination IPv4 address in host byte order. + * @return A dynamically allocated string containing the interface name if found, + * otherwise NULL. The caller is responsible for freeing the returned string. + */ +static char* detect_interface(uint32_t dst_address) { + struct ifaddrs *ifaddr, *ifa; + char *found_iface = NULL; + uint32_t dst_addr_net = htonl(dst_address); + + if (getifaddrs(&ifaddr) == -1) { + perror("getifaddrs"); + return NULL; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + // Skip interfaces that are not up, are loopback, or not IPv4 + if (ifa->ifa_addr == NULL || + !(ifa->ifa_flags & IFF_UP) || + (ifa->ifa_flags & IFF_LOOPBACK) || + ifa->ifa_addr->sa_family != AF_INET) { + continue; + } + + struct sockaddr_in *sa_addr = (struct sockaddr_in *)ifa->ifa_addr; + struct sockaddr_in *sa_mask = (struct sockaddr_in *)ifa->ifa_netmask; + + // Check if the interface's subnet matches the destination's subnet + if ((sa_addr->sin_addr.s_addr & sa_mask->sin_addr.s_addr) == + (dst_addr_net & sa_mask->sin_addr.s_addr)) { + found_iface = strdup(ifa->ifa_name); + break; // Found a suitable interface + } + } + + freeifaddrs(ifaddr); + return found_iface; +} + +/** + * @brief Detects the destination MAC address using an ARP request. + * + * This function attempts to resolve the MAC address for a given IPv4 address + * on a specific interface. It first checks the system's ARP cache. If the + * entry is not found, it sends a dummy UDP packet to trigger an ARP request + * and then checks the cache again. + * + * @param iface The name of the network interface to use. + * @param dst_ip_host The destination IPv4 address in host byte order. + * @param out_mac A buffer of 6 bytes to store the resulting MAC address. + * @return True if the MAC address was successfully resolved, otherwise false. + */ +static bool detect_mac_address(const char *iface, uint32_t dst_ip_host, char *out_mac) { + int sock; + struct arpreq arp_req; + struct sockaddr_in *sin_addr; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + return false; + } + + memset(&arp_req, 0, sizeof(arp_req)); + + sin_addr = (struct sockaddr_in *)&arp_req.arp_pa; + sin_addr->sin_family = AF_INET; + sin_addr->sin_addr.s_addr = htonl(dst_ip_host); + + strncpy(arp_req.arp_dev, iface, sizeof(arp_req.arp_dev) - 1); + + if (ioctl(sock, SIOCGARP, &arp_req) < 0) { + if (errno == ENXIO) { + // Entry not in cache, trigger ARP request by sending a dummy packet + struct sockaddr_in dst_sock_addr; + memset(&dst_sock_addr, 0, sizeof(dst_sock_addr)); + dst_sock_addr.sin_family = AF_INET; + dst_sock_addr.sin_addr.s_addr = htonl(dst_ip_host); + dst_sock_addr.sin_port = htons(31337); // Port doesn't matter + + sendto(sock, "arp", 3, 0, (struct sockaddr *)&dst_sock_addr, sizeof(dst_sock_addr)); + + usleep(100000); // Wait 100ms for ARP reply + + // Try again + if (ioctl(sock, SIOCGARP, &arp_req) < 0) { + perror("ioctl(SIOCGARP) after trigger"); + close(sock); + return false; + } + } else { + perror("ioctl(SIOCGARP)"); + close(sock); + return false; + } + } + + close(sock); + + // Check if the retrieved entry is complete + if (!(arp_req.arp_flags & ATF_COM)) { + fprintf(stderr, "ERROR: ARP entry for the destination IP is incomplete.\n"); + return false; + } + + memcpy(out_mac, arp_req.arp_ha.sa_data, 6); + return true; +} + static void add_nsec_to_ts(struct timespec *ts, uint64_t nsec) { ts->tv_sec += nsec / 1000000000U; ts->tv_nsec += nsec % 1000000000U; @@ -704,6 +822,59 @@ int main(int argc, char **argv) { } } + if (!config.interface) { + if (!config.dst_address) { + fprintf(stderr, "ERROR: No interface specified with -i. Please provide one.\n"); + goto err; + } + + config.interface = detect_interface(config.dst_address); + + if (config.interface) { + struct in_addr in; + in.s_addr = htonl(config.dst_address); + fprintf(stderr, "INFO: Detected interface '%s' for destination %s\n", + config.interface, inet_ntoa(in)); + } else { + struct in_addr in; + in.s_addr = htonl(config.dst_address); + fprintf(stderr, "ERROR: Could not automatically detect an interface for destination %s.\n", + inet_ntoa(in)); + fprintf(stderr, "Please specify one with -i.\n"); + goto err; + } + } + + if (!dst_mac_set) { + if (config.ptp_mcast) { + memcpy(config.dst_mac, PTP_MCAST_MAC, 6); + dst_mac_set = 1; + fprintf(stderr, "INFO: Using PTP multicast MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + (unsigned char)config.dst_mac[0], (unsigned char)config.dst_mac[1], + (unsigned char)config.dst_mac[2], (unsigned char)config.dst_mac[3], + (unsigned char)config.dst_mac[4], (unsigned char)config.dst_mac[5]); + } else { + if (!config.dst_address) { + fprintf(stderr, "ERROR: Cannot detect MAC without destination IP (-d).\n"); + goto err; + } + if (detect_mac_address(config.interface, config.dst_address, config.dst_mac)) { + dst_mac_set = 1; + fprintf(stderr, "INFO: Detected destination MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + (unsigned char)config.dst_mac[0], (unsigned char)config.dst_mac[1], + (unsigned char)config.dst_mac[2], (unsigned char)config.dst_mac[3], + (unsigned char)config.dst_mac[4], (unsigned char)config.dst_mac[5]); + } else { + struct in_addr in; + in.s_addr = htonl(config.dst_address); + fprintf(stderr, "ERROR: Could not automatically detect MAC for destination %s.\n", + inet_ntoa(in)); + fprintf(stderr, "Please specify one with -m.\n"); + goto err; + } + } + } + if (config.mode == INVALID_MODE || !config.interface || !dst_mac_set || (config.ptp_mcast && config.mode != PTP_DELAY) || !config.dst_address || config.src_bits < 8 || config.src_bits > 32 || @@ -726,10 +897,10 @@ int main(int argc, char **argv) { fprintf(stderr, "\t-D DOMAIN send PTP delay requests\n"); fprintf(stderr, "\t-N DOMAIN send PTP NetSync Monitor (NSM) requests\n"); fprintf(stderr, "\nNetwork options:\n"); - fprintf(stderr, "\t-i INTERFACE specify network interface\n"); + fprintf(stderr, "\t-i INTERFACE specify network interface (can be auto-detected from -d)\n"); fprintf(stderr, "\t-s NETWORK/BITS specify source IPv4 network\n"); fprintf(stderr, "\t-d IP-ADDRESS specify destination IPv4 address\n"); - fprintf(stderr, "\t-m MAC specify destination MAC address\n"); + fprintf(stderr, "\t-m MAC specify destination MAC address (can be auto-detected from -d)\n"); fprintf(stderr, "\nOther options:\n"); fprintf(stderr, "\t-M send multicast PTP delay requests\n"); fprintf(stderr, "\t-r RATE[-RATE] specify minimum and maximum rate (1000-1000000)\n");