Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 173 additions & 2 deletions perf.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@

#include <assert.h>
#include <arpa/inet.h>
#include <errno.h>
#include <float.h>
#include <ifaddrs.h>
#include <math.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 ||
Expand All @@ -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");
Expand Down