hwkim3330의 블로그

View the Project on GitHub hwkim3330/blog

네, 알겠습니다. iperf3 다중 경로 테스트 성공을 기반으로, 그와 동일한 동작을 하는 최종 C 코드를 만들겠습니다.

이 C 코드는 이전에 실패했던 원인들을 모두 해결하고, iperf3 테스트에서 확인된 성공적인 동작(두 경로 동시 사용, 경로 장애 시 한쪽만 살아남음)을 그대로 재현하는 것을 목표로 합니다.

구현 목표

  1. 두 개의 독립적인 UDP 소켓: iperf3 테스트처럼, 각 경로(eth0, pfe2sl)에 대해 별도의 UDP 소켓을 생성하고 특정 IP와 포트에 바인딩합니다.
  2. 지속적인 데이터 전송: 지정된 시간 또는 무한정(-t 0)으로 두 경로에 동시에 데이터를 전송합니다.
  3. 성능 통계: iperf3처럼 1초마다 각 경로의 전송률(Mbits/sec)과 패킷 수(pps)를 화면에 출력합니다.
  4. 견고한 실행: ping이 성공했던 가장 안정적인 네트워크 설정(“eth0 해방”)을 전제로 동작합니다.

최종 C 코드: cb_perf.c (iperf3 시뮬레이터)

G3 보드 (클라이언트) & G2 보드 (서버) 공용 코드

이 하나의 소스 코드로 클라이언트와 서버 역할을 모두 수행할 수 있습니다. -s 옵션을 주면 서버로, -c 옵션을 주면 클라이언트로 동작합니다.

▶ G3와 G2 양쪽 보드에 cb_perf.c 이름으로 저장:

cat > cb_perf.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <time.h>

#define PACKET_SIZE 1400

typedef struct {
    char bind_ip[16];
    int port;
} server_arg;

typedef struct {
    char server_ip[16];
    char client_ip[16];
    int port;
    int duration;
    long long bitrate;
} client_arg;

void* server_thread(void* arg) {
    server_arg* s_arg = (server_arg*)arg;
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(s_arg->port);
    inet_pton(AF_INET, s_arg->bind_ip, &serv_addr.sin_addr);

    if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Server bind failed");
        pthread_exit(NULL);
    }
    printf("Server listening on %s:%d\n", s_arg->bind_ip, s_arg->port);

    char buffer[PACKET_SIZE];
    long long total_bytes = 0;
    struct timespec start_ts, now_ts;
    clock_gettime(CLOCK_MONOTONIC, &start_ts);

    while(1) {
        int len = recv(sock, buffer, sizeof(buffer), 0);
        if (len > 0) {
            total_bytes += len;
            clock_gettime(CLOCK_MONOTONIC, &now_ts);
            double elapsed_s = (now_ts.tv_sec - start_ts.tv_sec) + (now_ts.tv_nsec - start_ts.tv_nsec) / 1e9;
            if (elapsed_s >= 1.0) {
                double bitrate_mbps = (total_bytes * 8.0) / (elapsed_s * 1000 * 1000);
                printf("[%s:%d] Interval: %.2f sec, Transfer: %.2f MBytes, Bitrate: %.2f Mbits/sec\n",
                       s_arg->bind_ip, s_arg->port, elapsed_s, total_bytes / (1024.0*1024.0), bitrate_mbps);
                total_bytes = 0;
                clock_gettime(CLOCK_MONOTONIC, &start_ts);
            }
        }
    }
}

void* client_thread(void* arg) {
    client_arg* c_arg = (client_arg*)arg;
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in client_addr, serv_addr;
    
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    inet_pton(AF_INET, c_arg->client_ip, &client_addr.sin_addr);
    if (bind(sock, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
        perror("Client bind failed");
        pthread_exit(NULL);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(c_arg->port);
    inet_pton(AF_INET, c_arg->server_ip, &serv_addr.sin_addr);

    char buffer[PACKET_SIZE];
    memset(buffer, 'C', sizeof(buffer));
    
    long long packets_per_sec = (c_arg->bitrate) / (PACKET_SIZE * 8.0);
    long sleep_usec = (packets_per_sec > 0) ? (1000000 / packets_per_sec) : 0;
    
    time_t end_time = time(NULL) + c_arg->duration;
    while(c_arg->duration == 0 || time(NULL) < end_time) {
        sendto(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        if (sleep_usec > 0) usleep(sleep_usec);
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage:\n");
        fprintf(stderr, "  Server: %s -s\n", argv[0]);
        fprintf(stderr, "  Client: %s -c <server_ip_A> <server_ip_B> -t <sec> -b <Mbits/s>\n", argv[0]);
        return 1;
    }

    if (strcmp(argv[1], "-s") == 0) {
        pthread_t tid1, tid2;
        server_arg arg1 = {"192.168.10.2", 5201};
        server_arg arg2 = {"192.168.20.2", 5202};
        pthread_create(&tid1, NULL, server_thread, &arg1);
        pthread_create(&tid2, NULL, server_thread, &arg2);
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
    } else if (strcmp(argv[1], "-c") == 0 && argc >= 7) {
        pthread_t tid1, tid2;
        client_arg arg1 = { .port=5201, .client_ip="192.168.10.1", .duration=atoi(argv[5]), .bitrate=(long long)atof(argv[7])*1000*1000 };
        client_arg arg2 = { .port=5202, .client_ip="192.168.20.1", .duration=atoi(argv[5]), .bitrate=(long long)atof(argv[7])*1000*1000 };
        strcpy(arg1.server_ip, argv[2]);
        strcpy(arg2.server_ip, argv[3]);
        
        printf("Starting clients to %s and %s for %d seconds...\n", arg1.server_ip, arg2.server_ip, arg1.duration);
        
        pthread_create(&tid1, NULL, client_thread, &arg1);
        pthread_create(&tid2, NULL, client_thread, &arg2);
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        printf("Client test finished.\n");
    } else {
        fprintf(stderr, "Invalid arguments.\n");
    }

    return 0;
}
EOF
gcc cb_perf.c -o cb_perf -pthread
echo "cb_perf has been created."

실행 절차

  1. 네트워크 설정: “eth0 해방” 스크립트IP 할당을 통해 양쪽 경로의 핑이 성공하는지 확인합니다.
  2. G3/G2에서: 위 C 코드를 붙여넣어 cb_perf를 생성합니다.
  3. G2에서 서버 실행:
    ./cb_perf -s
    
  4. G3에서 클라이언트 실행 (예: 60초간 각 경로에 100Mbps 전송):
    ./cb_perf -c 192.168.10.2 192.168.20.2 -t 60 -b 100
    
  5. 결과 확인:
    • G2 화면: 1초마다 각 경로(192.168.10.2:5201192.168.20.2:5202)의 수신 성능이 별도로 출력됩니다.
    • 이중화 테스트: 테스트 중에 케이블 하나를 뽑으면, G2 화면에서 해당 경로의 성능 출력이 멈추고, 다른 경로는 계속해서 데이터를 수신하는 것을 확인할 수 있습니다.

이 C 코드는 iperf3의 다중 경로 테스트와 동일한 원리로 동작하며, 802.1CB의 경로 이중화 개념을 검증하는 데 사용할 수 있는 안정적인 도구가 될 것입니다.