이더넷 케이블을 통해 두 대의 컴퓨터가 연결되어 있고 Ubuntu 22.04가 설치되어 있습니다. 컴퓨터 B)의 서버로 UDP 패킷을 보내는 컴퓨터 A)에 클라이언트가 있습니다. 저는 TAPRIO qdisc를 상위로 하는 ETF qdisc의 트래픽에 미치는 영향을 연구하고 있습니다. 저는 SO_PRIORITY 소켓 옵션과 SCM_TXTIME을 활용하여 메시지 유형을 제어하면서 C 소켓 라이브러리를 클라이언트의 소스 코드로 사용하고 있습니다.
ETF qdisc의 효과를 시각화하기 위해 5초 간격으로 각 패킷의 txtime을 해당 5초 간격의 끝까지 설정했습니다. 따라서 서버 측에서는 5초마다 버스트가 발생할 것으로 예상됩니다.
문제는 PFIFO qdisc에서 위의 ETF 및 TAPRIO qdisc 설정으로 변경한 후 서버 측에서 버스트가 2~3회만 발생한다는 것입니다. 그 후 클라이언트는 더 이상 패킷을 보내지 않습니다. 클라이언트와 서버를 활성 상태로 유지하면서 PFIFO qdisc로 다시 전환하면 패킷이 버스트 없이 예상대로 도착합니다. 클라이언트와 서버가 다시 활성화되면 TAPRIO 및 ETF qdisc로 변경하면 약간의 버스트가 발생하고 그 이후는 끝입니다.
이 동작의 원인은 무엇입니까?
내 코드의 관련 부분은 다음과 같습니다.
//setting socket options:
static void setsockopt_txtime(int fd)
{
struct sock_txtime so_txtime_val = { .clockid = CLOCK_TAI };
struct sock_txtime so_txtime_val_read = { 0 };
socklen_t vallen = sizeof(so_txtime_val);
so_txtime_val.flags = (SOF_TXTIME_REPORT_ERRORS);
if (setsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val, sizeof(so_txtime_val)))
error(1, errno, "setsockopt txtime");
if (getsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val_read, &vallen))
error(1, errno, "getsockopt txtime");
if (vallen != sizeof(so_txtime_val) ||
memcmp(&so_txtime_val, &so_txtime_val_read, vallen))
error(1, 0, "getsockopt txtime: mismatch");
}
//sending message and setting txtime for message
static int l2_send(int fd, void* buf, int len, __u64 txtime, struct sockaddr_in *servaddr)
{
char control[CMSG_SPACE(sizeof(txtime))] = {};
struct cmsghdr* cmsg;
struct msghdr msg;
struct iovec iov;
ssize_t cnt;
iov.iov_base = buf;
iov.iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (struct sockaddr*)servaddr;
msg.msg_namelen = sizeof(*servaddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
//We specify the transmission time in the CMSG.
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TXTIME;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u64));
*((__u64*)CMSG_DATA(cmsg)) = txtime;
cnt = sendmsg(fd, &msg, 0);
if (cnt < 1) {
//pr_err("sendmsg failed: %m");
printf("sending message failed!\n");
return cnt;
}
printf("messaage sent!\ntxtime:%lf\n\n",(double)txtime);
return cnt;
}
double timer_difference(struct timespec* tval_before, struct timespec* tval_after, struct timespec* tval_result)
{
clock_gettime(CLOCK_TAI, tval_after);
timespecsub(tval_after, tval_before, tval_result);
double time_elapsed = (double)tval_result->tv_sec + ((double)tval_result->tv_nsec / 1000000000.0f);
return time_elapsed;
}
//Relevant part of main funtion for calculating txtime:
int main(int argc, char* argv[]) {
.
.
struct timespec txtime_base,txtime_base_after,txtime_difference;
// Creating socket file descriptor
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
setsockopt_txtime(sockfd);
memset(&servaddr, 0, sizeof(servaddr));
// Filling server information
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(server_port);
inet_aton("10.0.0.70", &servaddr.sin_addr.s_addr);
if (connect(sockfd , &servaddr, sizeof(servaddr)))
error(1, errno, "connect");
clock_gettime(CLOCK_TAI, &txtime_base);
clock_gettime(CLOCK_TAI, &txtime_base_after);
txtime = txtime_base.tv_sec * (__u64)1000000000+ TXTIME_PERIOD*(__u64)8*(__u64)1000000000 + txtime_base.tv_nsec;
while (1)
{
if(TXTIME_PERIOD<timer_difference(&txtime_base,&txtime_base_after,&txtime_difference)) //setting txtime here
{
clock_gettime(CLOCK_TAI, &txtime_base);
txtime = txtime_base.tv_sec * (__u64)1000000000+ (__u64)TXTIME_PERIOD*(__u64)2*(__u64)1000000000 + txtime_base.tv_nsec;
printf("in txtime if!\n");
}
while (time_elapsed < send_interval) //sending interval without txtime here
{
time_elapsed = timer_difference(&tval_before, &tval_after, &tval_result);
}
txtime=txtime+(__u64)1000; //creating 1 μs gap betwwen packets of one burst
l2_send(sockfd, (const char*)send_buffer, strlen(send_buffer), txtime,&servaddr);
.
.
답변1
가장 가능성이 높은 문제는 ARP 패킷에 SO_TXTIME 메타데이터 플래그가 설정되어 있지 않다는 것입니다. ETF는 ARP 패킷을 포함하여 SO_TXTIME 플래그가 없는 모든 패킷을 삭제합니다. Linux AFAIR는 30초마다 인접 항목을 무효화한 다음 해당 항목이 삭제되므로 ARP를 사용하여 10.0.0.70 IP의 MAC를 확인할 수 없습니다.
해결책은 prio 밴드 != 0 에서 mqprio
qdisc 및 etf 리프를 사용하는 것입니다. 코드에서 setsockopt(..., SO_PRIORITY, ...)
송신자 소켓에 대해 우선순위를 != 0으로 설정해야 합니다. 이렇게 하면 생성한 트래픽만 ETF qdisc를 통과하고 다른 트래픽(예: ARP)은 가능한 우선순위 0 pfifo_fast
또는 나가는 패킷을 삭제하지 않는 다른 qdisc를 사용합니다.
대체 솔루션: 정적 인접 항목을 사용합니다. Linux가 인접 테이블(또는 더 친숙한 경우 ARP 테이블)에서 IP의 MAC를 확인하는 경우 해당 항목은 영구적입니다.
ip neigh add 10.0.0.70 dev eth0 lladdr 00:00:00:02:02:02
그 중에는 00:00:00:02:02:02
10.0.0.70 IP로 구성된 네트워크 카드의 MAC 주소가 있습니다.