R&D/OS

[펌] UDP 패킷 (NIC->Kernel)

sunshout 2010. 1. 11. 16:13

UDP Receiving

   

   

1. NIC 네크워크를 통해 자신의 local MAC address 해당하는 이더넷 프레임이나

    Link Layer Broadcast Frame 받게 되면 Interrupt 발생하게 된다.

2. NIC DMA PIO등의 메커니즘을 통해 데이터를 RAM으로 옮겨놓고 sk buffer 할당 받은후

    커널로 처리과정(netif_rx) 넘기게 된다.

3. Kernel 버전 2.4 향상되면서 softirq라는 매카니즘이 추가되면서 network stack 관리를

    Bottom Half에서 softirq 담당하게 되었다.

    Bottom Half 여러 개의 CPU 장착된 시스템이라도 순간에 하나의 CPU에서만 처리될

    있었던 것에 반해 softirq매커니즘은 CPU 하나씩 처리될 있으므로 SMP환경에서 성능을

    극대화시킬 있게 된다.

   

   

[3COM 3c509 ethernet card 경우]

   

1. el3_interrupt(int irq, void *dev_id, struct pt_regs *regs)  [drivers/net/3c509.c]

{

  ...

  el3_rx();

  ...

}

   

2. el3_rx() [drivers/net/3c509.c]

{

   ...

dev_alloc_skb(); [include/linux

{

  /*

  sk_buff 새로 하나 할당 받는다.

  __dev_alloc_skb 함수를 호출하며  GFP_ATOMIC gfp_mask 인자로 넘긴다.

  옵션은 인터럽트에 의해서만 사용되며 또한 인터럽트는 옵션으로만 함수를 호출.

  */

  return __dev_alloc_skb(length, GFP_ATOMIC);

}

...

      insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), (pkt_len + 3) >> 2);

      --> "ipaddr+RX_FIFO" 로부터 "(pkt_len + 3) >> 2" 만큼의 데이터를

            "skb_put(skb,pkt_len)"으로 복사해온다.

...

}

   

3. insl () [include/asm_sh/io.h]

{

  Device로부터 Kernel 영역의 메모리로 주어진 길이 만큼의 데이터를 복사해온다.

}

   

4. net_if_rx()  [net/core/dev.c]

{

    /* device로부터 패킷을 읽어들여 상위 layer에서 처리할 있도록 큐에 저장한다. */

...

this_cpu = smp_processor_id();

   

// softnet_data 구조체는 수신되는 패킷들을 저장해 두는 큐로 사용.

// CPU 하나씩 부착되어 있다.

queue = &softnet_data[this_cpu];

   

netdev_rx_stat[this_cpu].total++;

   

/* 큐의 크기가 backlog한계치를 넘어서지 않았을 경우 */

if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {

if (queue->input_pkt_queue.qlen) { // 패킷이 있을 경우

  if (queue->throttle)                 // queue full 경우 버린다.

    goto drop;

   

enqueue:

  dev_hold(skb->dev);

   

/* 큐의 끝의 패킷을 저장한다. */

  __skb_queue_tail(&queue->input_pkt_queue,skb);

   

  local_irq_restore(flags);

   

#ifndef OFFLINE_SAMPLE

  get_sample_stats(this_cpu);

#endif

  return queue->cng_level;  // congestion level반환

}

   

   

/* 수신된 패킷은 없으나 현재 수신 큐가 full 경우 */

if (queue->throttle) { 

  queue->throttle = 0;  // 꽉차 있지 않다고 설정 변경

   

#ifdef CONFIG_NET_HW_FLOWCONTROL 

               // flow control 활성화 경우 netdev_dropping값을 낮추고

               // net device 활성화시킨다.

  if (atomic_dec_and_test(&netdev_dropping))

    netdev_wakeup();

#endif

}

   

netif_rx_schedule(&queue->blog_dev); 

}

   

/* 수신큐의 길이가 backlog 한계치를 넘어선 경우이다. */

if (queue->throttle == 0) { 

  queue->throttle = 1;    // 수신큐 셋팅

  netdev_rx_stat[this_cpu].throttled++; // queue full 횟수 증가

#ifdef CONFIG_NET_HW_FLOWCONTROL

  atomic_inc(&netdev_dropping);  // netdev_dropping값으로 flow control 수행됨

#endif

}

   

drop:  // 자원 해제

netdev_rx_stat[this_cpu].dropped++;

local_irq_restore(flags);

   

kfree_skb(skb);

return NET_RX_DROP;

}

   

   

5. netif_rx_schedule [include/linux/netdevice.h]   // 커널의 스케줄링 큐에 등록 --> do_softirq() 호출

{

   if (netif_rx_schedule_prep(dev))

      __netif_rx_schedule();

     {

        ...

          __cpu_raise_softirq(cpu, NET_RX_SOFTIRQ);

/*

cpu softirq NET_RX_SOFTIRQ만큼 raise(pending)시킨후

cpu local irq local bottom half 카운트가 모두 0일경우

softirq daemon 활성화 시킨다.

여기서는 NET_RX_SOFTIRQ값을 설정해서 net_rx_action() [net/core/dev.c]

실행된다.

*/

     }

}

   

   

6. net_rx_action [ net/core/dev.c ]

{

  CPU Queue 부터 패킷을 꺼내어 패킷 타입(struct packet_type) 살펴

  처리함수 (packet_type.func) 패킷과 함게 처리를 넘긴다.

  UDP Packet 경우 IP Packet 처리하는 함수 ip_rcv 호출된다.

}

   

   

7. ip_rcv [ net/ipv4/ip_input.c ]

{

  /* IP Packet 처리가 시작되는 뿌리이다. */

   

  ...

  // skb pull(한쪽방향으로 데이타를 밀어 넣음) 시킬 있는가를 확인

  if (!pskb_may_pull(skb, sizeof(struct iphdr)))

    goto inhdr_error;

   

   

  ...

   

  // validation 처리

   

  ...

   

  /* 버퍼의 크기가 패킷 길이 보다 클경우 자원낭비방지를 위해

      버퍼를 잘라낸다. */

  if (skb->len > len) {

    __pskb_trim(skb, len);

    if (skb->ip_summed == CHECKSUM_HW)

      skb->ip_summed = CHECKSUM_NONE;

  }

   

 // network filter 후킹한다.

return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);

}

   

8. NF_HOOK () [ include/linux/netfilter.h ]

{

  넘겨진 ip_rcv_finish함수를 skb 인자로 넘겨 호출한다.

  나머지 인자들은 debug시에 쓰이든데 debug모드에서는 NF_HOOK

  nf_hook_slow() [ net/core/netfilter.c ] 선언된다.

}

   

9. ip_rcv_finish() [net/ipv4/ip_input.c]

{

  ...

  if (iph->ihl > 5) {

    ...

   

    /* skb 헤더공간이 부족할 경우 pskb_expand_head() 통해

        충분한 공간을 원자적으로 할당 받아 헤더를 복사한다. */

    if (skb_cow(skb, skb_headroom(skb))

      goto drop;

     

    /* 옵션의 유효성 검사를 수행하고 skb 옵션 필드들에 값을 채운다.

        opt여기선 NULL이므로 skb->data IP 헤더를 가리켜야한다.

        skb 값이 유효하나 처리 에러가 발생했을 경우 패킷 송신자에게

        parameter error 존재함을 알리는 ICMP 패킷을 전송한다.

      */

    if (ip_options_compile(NULL, skb))

      goto inhdr_error;

  }

   

  ...

   

  return skb->dst->input(skb);  // udp packet 경우 udp_rcv 가리킨다.

}

   

10. udp_rcv() [ net/ipv4/udp.c ]

{

  ...

   

   /* multicast/broadcast 패킷일 경우 multicast처리용 함수로 작업 이전 */

   if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))

     return udp_v4_mcast_deliver(skb, uh, saddr, daddr);

{

  // 패킷의 목적지 주소에 해당하는 수신큐의 리스트를 hast테이블에서 얻는다.

  sk = udp_hash[ntohs(uh->dest) & (UDP_HTABLE_SIZE - 1)];

   

  ...

   

  do {  // 모든 수신큐에 대해

struct sk_buff *skb1 = skb;

   

sknext = udp_v4_mcast_next(sk->next, uh->dest, daddr,

                                           uh->source, saddr, dif);

if(sknext)

              skb1 = skb_clone(skb, GFP_ATOMIC); // 패킷 복사해서

   

if(skb1)

              udp_queue_rcv_skb(sk, skb1); // 수신큐의 끝에 집어 넣는다.

sk = sknext;

  } while(sknext);

  ...

}

   

   

   //  udp hash table에서 패킷에 해당하는 sock 찾아낸다.

   sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);

   

   // 찾았을 경우 sk 수신 마지막에 skb 추가한다.

   if (sk != NULL) {

      udp_queue_rcv_skb(sk, skb);

      sock_put(sk);

      return 0;

   }

  ...

   

  // 이곳까지 넘어왔다는 것은 원치 않는 패킷을 받은 경우이다.

  // 송신자에게 Destination Unreachable/Port Unreachable ICMP  패킷을 전송

  icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);

   

}

   

11. udp_queue_rcv_skb [ net/ipv4/udp.c ]

{

  // 주어진 소켓의 수신큐에 skb 추가[sock_queue_rcv_skb(sk, skb) 호출]한다.

  //  만약 큐가 있다면 패킷(skb) 버린다.

   ...

if (sock_queue_rcv_skb(sk,skb)<0) {

       UDP_INC_STATS_BH(UdpInErrors);

       IP_INC_STATS_BH(IpInDiscards);

       ip_statistics[smp_processor_id()*2].IpInDelivers--;

       kfree_skb(skb);

       return -1;

}

UDP_INC_STATS_BH(UdpInDatagrams);

return 0;

}

   

12. sock_queue_rcv_skb  [ include/net/sock.h ]

{

  ...

  // skb 소유자가 sk임을 명시, skb 파괴자 명시

  // 파괴자는 소켓버퍼가 이상 필요가 없어지게 되면 커널에 의해 호출돼 자원회수

  skb_set_owner_r(skb, sk);

  {

     skb->sk = sk;

     skb->destructor = sock_rfree;

     atomic_add(skb->truesize, &sk->rmem_alloc);

  }

   

  skb_queue_tail(&sk->receive_queue, skb); // sk 수신큐에 넣는다.

  ...

   

}

   

------------------------------------- 

   

사용자(어플리케이션) 데이타 수신을 위해 recvfrom 함수를 호출하면 system call 발생

(interrupt vector에서 index 0x80 해당하게 된다.)

 "#define SYSCALL_VECTOR 0x80" [ include/asm-i386/hw_irq.h]

   

system call interrupt handler 의해 sys_socketcall함수가 호출되며 경우 UDP 데이터 수신

과정이므로 call인자로 SYS_RECVFROM 넘겨지므로 sys_recvfrom 함수가 호출된다.

   

   

1. asmlinkage long sys_recvfrom [ net/socket.c ]

{

  ...

   

  sock = sockfd_lookup(fd, &err);  // fd 해당하는 소켓을 구한다.

   

  ...

   err=sock_recvmsg(sock, &msg, size, flags);  // 패킷을 읽어들인다.

   

  ...

   

  if(err >= 0 && addr != NULL && msg.msg_namelen)

  {

    // User 영역으로 address 넘긴다.

    err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);

    if(err2<0)

      err=err2;

  }

}

   

2. sock_recvmsg() [ net/socket.c ]

{

struct scm_cookie scm; // socket control message cookie

...

   

// sock operation 함수 포인터에서 데이터 수신용 함수 호출

// 여기선 inet 패킷이기 때문에 inet_recvmsg() 함수 호출

size = sock->ops->recvmsg(sock, msg, size, flags, &scm); --> inet_recvmsg()

   

if (size >= 0)

       scm_recv(sock, msg, &scm, flags); // msg  control message 읽어 들임.

   

return size; // 읽어들인 데이타의 길이를 반환

}

   

3. inet_recvmsg () [ net/ipv4/af_inet.c ]

{

  sk->prot->recvmsg(수신된 패킷의 상위 layer 메시지 수신 처리 함수) 호출하는데

  설정상 UDP패킷이기때문에 udp_recvmsg()호출

}

   

4. udp_recvmsg() [ net/ipv4/udp.c ]

{

...

// sk 수신큐로부터 패킷을 하나 읽어온다.

skb = skb_recv_datagram(sk, flags, noblock, &err);

...

copied = skb->len - sizeof(struct udphdr); // 헤더 길이 제외한 복사할 데이터의 길이

...

// option 따라

if (skb->ip_summed==CHECKSUM_UNNECESSARY) {

    // checksum불필요 --> User IO Vector 버퍼를 복사

    err = skb_copy_datagram_iovec(...);

} else if (msg->msg_flags&MSG_TRUNC) {

    // MSG_TRUNC flag 설정 : checksum 확인후 복사

    err = skb_copy_datagram_iovec(...);

} else {

    err = skb_copy_and_csum_datagram_iovec(...);

{

  주어진 skb checksum 수행하고 User iovec skb 복사

}

}

// 선택 실행

   

...

sock_recv_timestamp(msg, sk, skb); // 수신된 시간 기록

...

if (sin)  // address 복사

{

       sin->sin_family = AF_INET;

       sin->sin_port = skb->h.uh->source;

       sin->sin_addr.s_addr = skb->nh.iph->saddr;

       memset(sin->sin_zero, 0, sizeof(sin->sin_zero));

}

if (sk->protinfo.af_inet.cmsg_flags) // control message flag 설정되어 있다면

       ip_cmsg_recv(msg, skb);       // 메시지 수신

   

}

   

   

[EOF]