blob: f59fab5307c468b63b1a0143af8a0a27c7562379 [file] [log] [blame]
/*
* (C) Copyright 2015 Google, Inc.
* All rights reserved.
*
*/
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <linux/types.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
#include "../common/util.h"
#include "../common/io.h"
#include "common.h"
#define WAN_PORT_NAME "eth1_1"
#define ETH_TEST_LINE_MAX 4096
#define TX_BYTES_PATH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/tx_bytes"
#define RX_BYTES_APTH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/rx_bytes"
#define ETH_TRAFFIC_TEST_PERIOD_SYMBOL "-p"
#define ETH_TRAFFIC_MAX_REPORT_PERIOD 50
#define ETH_TRAFFIC_MAX_GE_REPORT_PERIOD 15
#define ETH_TRAFFIC_REPORT_PERIOD 50
#define ETH_PKTS_LEN_DEFAULT 32
#define ETH_STAT_WAIT_PERIOD 1 // sec
#define BUF_SIZ 1536
#define ETH_PKTS_SENT_BEFORE_WAIT 0xFF
#define SCAN_CMD_FORMAT "%256s"
// 1G
#define ETH_TRAFFIC_PER_PERIOD_MAX \
(((unsigned int)ETH_TRAFFIC_MAX_REPORT_PERIOD) * ((unsigned int)131072000))
#define ONE_MEG (1024 * 1024)
#define ETH_STAT_PERCENT_MARGIN 95
void send_mac_pkt(char *if_name, char *out_name, unsigned int xfer_len,
unsigned int xfer_wait, int n,
const unsigned char *dst_mac1) {
int sockfd, i;
struct ifreq if_idx;
struct ifreq if_mac, out_mac;
int tx_len = 0;
char sendbuf[BUF_SIZ];
struct ether_header *eh = (struct ether_header *)sendbuf;
struct sockaddr_ll socket_address;
unsigned char dst_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/* Open RAW socket to send on */
if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) {
perror("socket");
}
/* Get the index of the interface to send on */
memset(&if_idx, 0, sizeof(if_idx));
safe_strncpy(if_idx.ifr_name, if_name, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
}
/* Get the MAC address of the interface to send on */
memset(&out_mac, 0, sizeof(out_mac));
if (out_name != NULL) {
safe_strncpy(out_mac.ifr_name, out_name, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFHWADDR, &out_mac) < 0) {
perror("out SIOCGIFHWADDR");
}
}
memset(&if_mac, 0, sizeof(if_mac));
safe_strncpy(if_mac.ifr_name, if_name, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0) {
perror("SIOCGIFHWADDR");
}
if (out_name != NULL) {
dst_mac[0] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[0];
dst_mac[1] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[1];
dst_mac[2] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[2];
dst_mac[3] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[3];
dst_mac[4] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[4];
dst_mac[5] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[5];
} else if (dst_mac1 != NULL) {
dst_mac[0] = dst_mac1[0];
dst_mac[1] = dst_mac1[1];
dst_mac[2] = dst_mac1[2];
dst_mac[3] = dst_mac1[3];
dst_mac[4] = dst_mac1[4];
dst_mac[5] = dst_mac1[5];
} else {
printf("Invalid out_name and dst_mac.\n");
return;
}
/* Construct the Ethernet header */
// memset(sendbuf, 0, BUF_SIZ);
for (i = 0; i < BUF_SIZ; ++i) {
sendbuf[i] = 0xA5;
}
/* Ethernet header */
eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0];
eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1];
eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2];
eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3];
eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4];
eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5];
eh->ether_dhost[0] = dst_mac[0];
eh->ether_dhost[1] = dst_mac[1];
eh->ether_dhost[2] = dst_mac[2];
eh->ether_dhost[3] = dst_mac[3];
eh->ether_dhost[4] = dst_mac[4];
eh->ether_dhost[5] = dst_mac[5];
/* Ethertype field */
eh->ether_type = htons(ETH_P_IP);
tx_len += sizeof(struct ether_header);
// printf("TX MAC %02x:%02x:%02x:%02x:%02x:%02x RX MAC %02x:%02x:%02x:%02x:%02x:%02x\n", eh->ether_shost[0],eh->ether_shost[1],eh->ether_shost[2],eh->ether_shost[3],eh->ether_shost[4],eh->ether_shost[5],eh->ether_dhost[0],eh->ether_dhost[1],eh->ether_dhost[2],eh->ether_dhost[3],eh->ether_dhost[4],eh->ether_dhost[5]);
/* Packet data */
sendbuf[tx_len++] = 0xde;
sendbuf[tx_len++] = 0xad;
sendbuf[tx_len++] = 0xbe;
sendbuf[tx_len++] = 0xef;
/* Index of the network device */
socket_address.sll_ifindex = if_idx.ifr_ifindex;
/* Address length*/
socket_address.sll_halen = ETH_ALEN;
/* Destination MAC */
socket_address.sll_addr[0] = dst_mac[0];
socket_address.sll_addr[1] = dst_mac[1];
socket_address.sll_addr[2] = dst_mac[2];
socket_address.sll_addr[3] = dst_mac[3];
socket_address.sll_addr[4] = dst_mac[4];
socket_address.sll_addr[5] = dst_mac[5];
/* Send packet */
if (n < 0) {
while (1) {
if (sendto(sockfd, sendbuf, xfer_len, 0,
(struct sockaddr *)&socket_address,
sizeof(struct sockaddr_ll)) < 0) {
printf("Send failed at msg %d\n", i);
break;
}
if (xfer_wait > 0) {
if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
usleep(xfer_wait);
}
}
}
} else {
for (i = 0; i < n; ++i) {
if (sendto(sockfd, sendbuf, xfer_len, 0,
(struct sockaddr *)&socket_address,
sizeof(struct sockaddr_ll)) < 0) {
printf("Send failed at msg %d\n", i);
break;
}
if (xfer_wait > 0) {
if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
usleep(xfer_wait);
}
}
}
}
close(sockfd);
}
static void phy_read_usage(void) {
printf("phy_read <ifname> <reg>\n");
printf("Example:\n");
printf("phy_read lan0 2\n");
}
int phy_read(int argc, char *argv[]) {
int reg, val;
if (argc != 3) {
phy_read_usage();
return -1;
}
reg = strtol(argv[2], NULL, 0);
mdio_init();
mdio_set_interface(argv[1]);
val = mdio_read(reg);
mdio_done();
if (val < 0) {
printf("Read PHY %s reg %d failed\n", argv[1], reg);
return -1;
}
printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
return 0;
}
static void phy_write_usage(void) {
printf("phy_write <ifname> <reg> <val>\n");
printf("Example:\n");
printf("phy_write lan0 22 0x6\n");
}
int phy_write(int argc, char *argv[]) {
int reg, val, rc;
if (argc != 4) {
phy_write_usage();
return -1;
}
reg = strtol(argv[2], NULL, 0);
val = strtol(argv[3], NULL, 16);
mdio_init();
mdio_set_interface(argv[1]);
rc = mdio_write(reg, val);
mdio_done();
if (rc < 0) {
printf("Write PHY %s reg %d val 0x%x failed\n", argv[1], reg, val);
return -1;
}
printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
return 0;
}
/* If extra is not NULL, rsp is returned as the string followed extra */
int scan_command(char *command, char *rsp, char *extra) {
FILE *fp;
fp = popen(command, "r");
if (fp != NULL) {
if (extra != NULL) {
while (fscanf(fp, "%s", rsp) != EOF) {
if (!strcmp(rsp, extra)) {
if (fscanf(fp, "%s", rsp) <= 0)
return -1;
else
return 0;
}
}
} else {
fscanf(fp, SCAN_CMD_FORMAT, rsp);
}
pclose(fp);
} else {
return -1;
}
return 0;
}
int net_stat(unsigned int *rx_bytes, unsigned int *tx_bytes) {
static unsigned int tx_stat = 0;
static unsigned int rx_stat = 0;
char rsp[ETH_TEST_LINE_MAX];
unsigned int tmp;
for (tmp = 0; tmp < 2; ++tmp) {
// system_cmd(RX_BYTES_APTH);
// system_cmd(TX_BYTES_PATH);
scan_command(RX_BYTES_APTH, rsp, NULL);
scan_command(TX_BYTES_PATH, rsp, NULL);
sleep(1);
}
if (scan_command(RX_BYTES_APTH, rsp, NULL) == 0) *rx_bytes = strtoul(rsp, NULL, 10);
if (scan_command(TX_BYTES_PATH, rsp, NULL) == 0) *tx_bytes = strtoul(rsp, NULL, 10);
// printf("STAT: TX %d RX %d\n", *tx_bytes, *rx_bytes);
if (*tx_bytes >= tx_stat) {
*tx_bytes -= tx_stat;
tx_stat += *tx_bytes;
} else {
tmp = *tx_bytes;
// tx_bytes is uint. It will continue to increment till wrap around
// When it wraps around, the current value will be less than the
// previous one. That is why this logic kicked in.
*tx_bytes += (0xffffffff - tx_stat);
tx_stat = tmp;
}
if (*rx_bytes >= rx_stat) {
*rx_bytes -= rx_stat;
rx_stat += *rx_bytes;
} else {
tmp = *rx_bytes;
// rx_bytes is uint. It will continue to increment till wrap around
// When it wraps around, the current value will be less than the
// previous one. That is why this logic kicked in.
*rx_bytes += (0xffffffff - rx_stat);
rx_stat = tmp;
}
return 0;
}
// Return 0 if lost carrier. Otherwise, 1
int get_carrier_state(char *name) {
char command[ETH_TEST_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
snprintf(command, sizeof(command), "cat /sys/class/net/%s/carrier", name);
if (scan_command(command, rsp, NULL) == 0) {
if (strcmp(rsp, "0") != 0) return 1;
}
return 0;
}
// This is the same as sleep but monitor the link carrier every second
// Return true if the carrier is good every second. Otherwise false
bool sleep_and_check_carrier(int duration, char *if_name) {
bool good_carrier = true;
int i;
for (i = 0; i < duration; ++i) {
if (get_carrier_state(if_name) == 0) good_carrier = false;
sleep(1);
}
return good_carrier;
}
int get_if_ip(char *name, unsigned int *ip) {
char command[ETH_TEST_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
bool found = false;
snprintf(command, sizeof(command), "ip addr show %s", name);
if (scan_command(command, rsp, "inet") == 0) {
if (sscanf(rsp, "%u.%u.%u.%u", ip, (ip + 1), (ip + 2), (ip + 3)) <= 0) {
return -1;
}
found = true;
}
if (!found) {
return -1;
}
return 0;
}
static void loopback_test_usage(void) {
printf(
"loopback_test <duration in secs> [<%s print-period in secs>]\n",
ETH_TRAFFIC_TEST_PERIOD_SYMBOL);
printf("- duration >=1 or -1 (forever)\n");
printf("- print-period >= 0 and <= %d\n", ETH_TRAFFIC_MAX_REPORT_PERIOD);
printf("- print-period > 0 if duration > 0\n");
printf("- print-period = 0 prints only the summary\n");
}
int loopback_test(int argc, char *argv[]) {
int duration, num = -1, collected_count = 0;
int pid, pid1, print_period = ETH_TRAFFIC_REPORT_PERIOD;
unsigned int pkt_len = ETH_PKTS_LEN_DEFAULT, rx_bytes, tx_bytes;
bool print_every_period = true, traffic_problem = false, problem = false;
float average_throughput = 0.0, throughput;
unsigned char dst_mac[6] = {0, 0, 0, 0, 0, 0};
bool gig_traffic = false;
if ((argc < 2) || (argc > 5)) {
printf("Invalid number of parameters: %d\n", argc);
loopback_test_usage();
return -1;
}
duration = strtol(argv[1], NULL, 0);
if ((duration < -1) || (duration == 0)) {
printf("Invalid duration %d:%s\n", duration, argv[1]);
loopback_test_usage();
return -1;
}
if (argc == 3) {
if (strcmp(argv[2], "-g") != 0) {
printf("Invalid option %s\n", argv[4]);
loopback_test_usage();
return -1;
} else {
gig_traffic = true;
}
}
if (argc >= 4) {
if (strcmp(argv[2], ETH_TRAFFIC_TEST_PERIOD_SYMBOL) != 0) {
printf("Invalid option %s\n", argv[2]);
loopback_test_usage();
return -1;
}
print_period = strtoul(argv[3], NULL, 0);
if (((print_period == 0) && (duration < 0)) || (print_period < 0) ||
(print_period > ETH_TRAFFIC_MAX_REPORT_PERIOD)) {
printf("Invalid print period: %d:%s\n", print_period, argv[3]);
loopback_test_usage();
return -1;
}
if (print_period == 0) {
print_every_period = false;
print_period = ETH_TRAFFIC_REPORT_PERIOD;
}
}
if (argc == 5) {
if (strcmp(argv[4], "-g") != 0) {
printf("Invalid option %s\n", argv[4]);
loopback_test_usage();
return -1;
} else {
gig_traffic = true;
}
}
net_stat(&rx_bytes, &tx_bytes);
if (gig_traffic) {
/*
system_cmd("ethtool -s eth1_0 autoneg off");
sleep(1);
system_cmd("ethtool -s eth1_0 autoneg on");
sleep(1);
system_cmd("ethtool -s eth1_0 autoneg off");
sleep(1);
system_cmd("ethtool -s eth1_0 autoneg on");
sleep(1);
system_cmd("ethtool -s eth1_0 autoneg off");
sleep(1);
*/
if (print_period > ETH_TRAFFIC_MAX_GE_REPORT_PERIOD)
print_period = ETH_TRAFFIC_MAX_GE_REPORT_PERIOD;
system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 1000");
// printf("1G loopback\n");
// Need to set crossover
} else {
system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 10");
// printf("10M loopback\n");
}
// system_cmd("brctl delif br0 eth1_0");
system_cmd("brctl delif br0 eth1_1");
// system_cmd("brctl delif br0 wifi0");
// system_cmd("ethtool -s " WAN_PORT_NAME " autoneg off duplex full speed 10");
// printf("ethtool -s eth1_0 autoneg off\n");
sleep(9);
pid = fork();
if (pid < 0) {
printf("Server fork error %d, errno %d\n", pid, errno);
return -1;
}
if (pid == 0) {
// Child process
send_mac_pkt(WAN_PORT_NAME, NULL, pkt_len, 1000, num, dst_mac);
// send_mac_pkt(WAN_PORT_NAME, "eth1_0", pkt_len, 0, num, dst_mac);
exit(0);
}
// Parent process
pid1 = pid;
while (duration != 0) {
if (duration >= 0) {
if (duration <= print_period) {
problem = !sleep_and_check_carrier(duration, WAN_PORT_NAME);
print_period = duration;
duration = 0;
kill(pid1, SIGKILL);
// printf("Killed processes %d and %d\n", pid1, pid2);
} else {
duration -= print_period;
problem = !sleep_and_check_carrier(print_period, WAN_PORT_NAME);
}
} else {
problem = !sleep_and_check_carrier(print_period, WAN_PORT_NAME);
}
if (duration > 0) kill(pid1, SIGSTOP);
sleep(ETH_STAT_WAIT_PERIOD);
net_stat(&rx_bytes, &tx_bytes);
printf("carrier %d: TX %d RX %d\n", !(problem), tx_bytes, rx_bytes);
if (duration > 0) kill(pid1, SIGCONT);
++collected_count;
// Give 1% margin
if ((rx_bytes == 0) ||
(((tx_bytes / 100) * ETH_STAT_PERCENT_MARGIN) > rx_bytes)) {
problem = true;
}
if ((rx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX) ||
(tx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX)) {
problem = true;
}
traffic_problem |= problem;
if (!problem) {
throughput = (((float)rx_bytes) * 8) / (float)(print_period * ONE_MEG);
average_throughput += throughput;
} else {
throughput = 0.0;
}
if (print_every_period) {
printf("%s %s: %3.3f Mb/s (%d:%d)\n", (problem) ? FAIL_TEXT : PASS_TEXT,
WAN_PORT_NAME, throughput, tx_bytes, rx_bytes);
}
problem = false;
}
if (gig_traffic) {
// Need to enable crossover
}
average_throughput /= ((float)collected_count);
printf("%s overall %s: %3.3f Mb/s\n",
(traffic_problem) ? FAIL_TEXT : PASS_TEXT, argv[1],
average_throughput);
system_cmd("ethtool -s " WAN_PORT_NAME " autoneg on");
// system_cmd("brctl addif br0 " WAN_PORT_NAME);
// printf("ethtool -s eth1_0 autoneg on\n");
return 0;
}