저는 낮은 대기 시간과 가변 대역폭 링크를 제공하는 터널링 애플리케이션을 개발 중입니다. 이는 트래픽 우선순위 지정이 필요한 시스템에서 작동합니다. 그러나 tun 장치에 대한 트래픽은 분명히 커널에 의해 대기열에 있지만 장치에 어떤 qdisc를 적용하든 기본 pfifo_fast를 포함하여 추가 효과가 없습니다. 즉, 높은 우선 순위로 간주되는 트래픽은 tun 장치와 별도로 처리되지 않습니다. 정상적인 교통.
문제를 보여주기 위해 작은 테스트 애플리케이션을 만들었습니다. 이는 두 개의 tun 장치를 생성하고 두 개의 스레드를 가지며, 각 스레드에는 한 인터페이스에서 다른 인터페이스로 패킷을 전달하는 루프가 있습니다. 수신과 전송 사이에서 각 바이트는 1us씩 지연되어 대략 8Mbps 양방향 링크를 시뮬레이션합니다.
void forward_traffic(int src_fd, int dest_fd) {
char buf[BUFSIZE];
ssize_t nbytes = 0;
while (nbytes >= 0) {
nbytes = read(src_fd, buf, sizeof(buf));
if (nbytes >= 0) {
usleep(nbytes);
nbytes = write(dest_fd, buf, nbytes);
}
}
perror("Read/write TUN device");
exit(EXIT_FAILURE);
}
각 tun 인터페이스를 자체 네임스페이스에 배치함으로써 iperf3을 실행하고 약 8Mbps의 처리량을 얻을 수 있습니다. ip link에 의해 보고되는 기본 txqlen은 500패킷이며 iperf3(-P 20)과 ping을 모두 실행하면 약 670-770ms의 RTT가 표시됩니다. 이는 대략 500 x 1500바이트 대기열과 동일합니다. 실제로 txqlen을 변경하면 지연 시간도 이에 비례하여 변경됩니다. 여태까지는 그런대로 잘됐다.
기본 pfifo_fast qdisc를 사용하여 올바른 ToS 태그가 있는 ping이 일반 대기열을 건너뛰고 ping -Q 0x10과 같이 낮은 RTT를 가져야 한다고 생각했지만 그렇지 않습니다. 다른 ToS/DSCP 값도 동일합니다. 모두 약 700ms의 동일한 RTT를 갖습니다. 또한 동일한 결과로 다양한 다른 qdisc를 시도했습니다. 예를 들어 fq_codel은 대기 시간에 큰 영향을 미치지 않으며 tc -s qdisc는 qdisc가 무엇이든 항상 표시합니다. 백로그는 링크 정체 여부에 관계없이 0입니다(그러나 정체 시 패킷 손실을 표시하는 ip -s 링크가 표시됩니다).
제가 여기서 뭔가 근본적으로 오해하고 있는 걸까요, 아니면 qdisc를 작동시키려면 해야 할 일이 있는 걸까요?
답변1
따라서 커널 소스 코드를 읽고 파헤쳐 본 후에는 tun 드라이버가 네트워크 스택에 사용 중임을 알리지 않기 때문에 qdisc가 작동하지 않는 것 같습니다. 단지 자체 로컬 큐(txqlen에 의해 크기가 설정됨)에 패킷을 저장하고, 큐가 가득 차면 초과 패킷을 삭제합니다.
다음은 스택이 패킷을 보내려고 할 때 호출되는 drivers/net/tun.c에 있는 전송 함수의 관련 비트입니다.
/* Net device start xmit */
static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct tun_struct *tun = netdev_priv(dev);
int txq = skb->queue_mapping;
struct tun_file *tfile;
int len = skb->len;
rcu_read_lock();
tfile = rcu_dereference(tun->tfiles[txq]);
....... Various unrelated things omitted .......
if (ptr_ring_produce(&tfile->tx_ring, skb))
goto drop;
/* Notify and wake up reader process */
if (tfile->flags & TUN_FASYNC)
kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
tfile->socket.sk->sk_data_ready(tfile->socket.sk);
rcu_read_unlock();
return NETDEV_TX_OK;
drop:
this_cpu_inc(tun->pcpu_stats->tx_dropped);
skb_tx_error(skb);
kfree_skb(skb);
rcu_read_unlock();
return NET_XMIT_DROP;
}
}
일반적인 네트워크 인터페이스 드라이버는 netif_stop_queue() 및 netif_wake_queue() 함수를 호출하여 네트워크 스택에서 패킷 흐름을 중지하고 시작해야 합니다. 트래픽이 중지되면 추가 대기열 규칙에 따라 패킷이 대기열에 추가되므로 사용자는 트래픽을 보다 유연하게 관리하고 우선 순위를 지정할 수 있습니다.
어떤 이유로든 탭/튜닝 드라이버는 이를 수행하지 않습니다. 아마도 대부분의 터널이 추가 흐름 제어 없이 단순히 패킷을 캡슐화하여 실제 네트워크 인터페이스로 보내기 때문일 것입니다.
내 결과를 확인하기 위해 위 함수에서 흐름 제어를 중지하여 간단한 테스트를 시도했습니다.
if (ptr_ring_produce(&tfile->tx_ring, skb)) {
netif_stop_queue(dev);
goto drop;
} else if (ptr_ring_full(&tfile->tx_ring)) {
netif_stop_queue(dev);
tun_debug(KERN_NOTICE, tun, "tun_net_xmit stop %lx\n", (size_t)skb);
}
그리고 패킷이 대기열에서 제거된 후 대기열이 비어 있는지 여부에 따라 대기열을 중지/깨우기 위해 tun_ring_recv에 유사한 추가를 수행했습니다.
empty = __ptr_ring_empty(&tfile->tx_ring);
if (empty)
netif_wake_queue(tun->dev);
else
netif_stop_queue(tun->dev);
훌륭한 시스템은 아니며 다중 대기열 터널에서는 작동하지 않지만 qdisc 보고 백로그를 볼 수 있을 만큼 충분히 잘 작동하고 다양한 ToS 수준에서 pfifo_fast를 사용하여 연결할 때 핑 시간과 손실률에 눈에 띄는 차이가 있습니다. 차이가 완전히 로드되었습니다.