struct sk_buff
: 패킷을 담는 struct. 유저 데이터에 대한 정보 (payload)와 헤더를 담기 위한 용도로 모든 네트워크 레이어에서 사용된다. 특정 레이어에서의 작업을 돕기 위한 부가 정보를 담는 경우도 있음. 어떻게 모든 레이어에서 이 struct를 공유할 수 있도록 되어있는지 밑에서 살펴볼 것임.
struct net_device
: 네트워크 디바이스를 나타내는 struct. 하드웨어와 소프트웨어 설정에 대한 정보를 담고 있다. net_device
가 언제 어떻게 할당되는지 자세히 보려면 Chapter 8 참고.
참고로 이 책에서는 소켓에 대한 정보를 담는 struct sock
는 다루지 않는다고 함.
보낼/받은 패킷의 헤더를 나타내며 정의는 include/linux/skbuff.h에 있고, 여기저기서 다 쓰이기 위한 큰 struct.
struct sk_buff
는 L2(MAC 등), L3(IP), L4(TCP, UDP)에서 쓰이는데, 레이어를 건너 전달될 때마다 필드 값이 적절히 변화하게 된다.
이 구조체가 L4->L3 또는 L3->L2로 전달될 때 알맞게 header를 append하게 된다. 구조체의 앞쪽에 프로토콜 헤더를 담을 공간을 추가하는 복잡한 작업을 하기 위해서 skb_reserve()
함수(나중에 설명)를 사용하게 된다.
반대로 L2->L3 또는 L3->L4로 전달될 때 하위 레이어의 header는 필요없게 된다. 하지만 이전 레이어의 header를 버퍼에서 없애기보다는 payload의 시작을 나타내는 포인터 값만 변경해줌.
아래는 2.6.12.y 버전. 일단 느낌만 보시길
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;
struct sock *sk;
struct timeval stamp;
struct net_device *dev;
struct net_device *input_dev;
struct net_device *real_dev;
union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw;
} h;
union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;
union {
unsigned char *raw;
} mac;
struct dst_entry *dst;
struct sec_path *sp;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[40];
unsigned int len,
data_len,
mac_len,
csum;
unsigned char local_df,
cloned:1,
nohdr:1,
pkt_type,
ip_summed;
__u32 priority;
unsigned short protocol,
security;
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
unsigned long nfmark;
__u32 nfcache;
__u32 nfctinfo;
struct nf_conntrack *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */
#endif
#endif
/* These elements must be at the end, see alloc_skb() for details. */
unsigned int truesize;
atomic_t users;
unsigned char *head,
*data,
*tail,
*end;
};
struct sk_buff
정의의 마지막 쪽을 보면 다음과 같은 부분이 있다
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */
#endif
#endif
#ifdef CONFIG_NET_SCHED
~ #endif
사이에 낀 CONFIG_NET_SCHED
가 컴파일타임에 정의되어 있어야만 추가되는 필드로, 해당 심볼은 QoS and/or fair queueing 커널 옵션을 켜야 정의된다. 잘 보면 CONFIG_NET_CLS_ACT
에 쓰이는 필드가 네스팅 돼있는데, CONFIG_NET_SCHED
와 CONFIG_NET_CLS_ACT
를 둘 다 켜야만 포함된다.
참고: NET_SCHED — QoS and/or fair queueing 옵션에 대한 설명
이런 식으로 옵션을 켰을 때 커널 data structure의 정의가 변하는 경우에는 해당 옵션을 모듈로 컴파일할 수 없다고 보면 된다.
커널 소스 디렉토리들에 있는 kconfig 파일을 보면 옵션들을 찾아볼 수 있음.
struct sk_buff
의 멤버 변수러프하게 4가지 종류로 분류할 수 있다고 한다.
커널은 sk_buff
구조체를 doubly-linked list로 관리한다. 따라서 next element와 prev element에 대한 포인터가 있음.
struct sk_buff *next;
struct sk_buff *prev;
이 리스트에 추가로 요구되는 기능이 있는데, 리스트 내의 어떤 element에서든 이 리스트의 head를 빠르게 찾을 수 있어야 한다는 것임. 따라서 모든 노드가 리스트 헤드를 가리키는 포인터를 들고 있는다.
struct sk_buff_head *list;
여기서 struct sk_buff_head
라는 구조체가 추가로 쓰이게 되는데, 리스트의 맨 앞에 삽입될 dummy element로 쓰이는 구조인 동시에 리스트에 대한 추가적인 정보를 담고 있음. 앞 두 필드가 리스트에서 앞뒤로 왔다갔다 하기 위한 필드로 sk_buff
와 같기 때문에 같은 리스트로 관리할 수 있는 것임.
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen;
spinlock_t lock;
};
리스트 구조는 다음과 같다. (출처: Understanding the Linux Network Internals)
이 버퍼를 소유하고 있는 소켓에 대한 포인터. 데이터가 로컬에서 생성되는 상황이나, 로컬 프로세스가 데이터를 받고 있는 상황에서 필요함. L4레이어와 유저 애플리케이션에서 소켓 정보를 필요로 하기 때문. Source와 destination이 모두 로컬 머신이 아닌 경우 NULL로 세팅.
struct sock *sk;
Main buffer (head
가 가리키는 것)의 크기와 fragment들에 있는 데이터 크기를 합친 것. 이 크기는 레이어를 움직일 때마다 변함.
unsigned int len;
len
과 다르게 fragment들에 있는 테이터 크기만을 나타냄.
unsigned int data_len;
MAC header의 크기를 나타냄.
unsigned int mac_len;
sk_buff
구조체의 크기를 포함한 버퍼의 총 사이즈를 나타냄. sizeof(struct sk_buff) + len
로 초기화됐다가 len
값이 바뀌면 같이 바뀜.
unsigned int truesize;
이 sk_buff
구조체에 대한 reference count. 실제 데이터를 담고 있는 버퍼에 대한 reference count는 별개로 있음. atomic_inc
atomic_dec
함수를 통해 직접 increment/decrement할 수도 있지만 대부분의 경우 skb_get
and kfree_skb
를 통해 조작.
atomic_t users;
각 레이어에서의 작업을 위한 버퍼 할당을 할 때, 앞뒤로 헤더 또는 추가 데이터를 위한 공간을 남겨놓는다. head
와 end
는 할당된 공간 시작과 끝을 가리키고, data
와 tail
은 실제 데이터의 시작과 끝을 가리킴.
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
다음과 같은 그림으로 나타낼 수 있음 (출처: Understanding the Linux Network Internals)
버퍼가 해제될 때 실행할 루틴. 버퍼가 소켓과 연결되지 않은 경우 이 필드는 초기화되지 않는다. 소켓에 연결된 경우, sock_rfree
또는 sock_wfree
로 초기화되는데, 소켓이 얼마만큼의 메모리를 잡아먹고 있는지를 업데이트하는 동작을 함.
void (*destructor)(struct sk_buff *skb);
보통 수신한 패킷에 대해서만 의미가 있는 타임 스탬프. 가끔 보내는 패킷에 대해 언제 송출할 것인지를 나타내기 위해 쓰이기도 함.
struct timeval stamp
struct net_device
는 네트워크 디바이스를 나타내며 책에 나중에 자세히 나옴. dev
필드는 패킷을 받은 상황에선 어떤 네트워크 장치로부터 패킷을 받았는지를 나타내고, 패킷을 보내는 상황에선 패킷을 내보낼 장치를 나타낸다.
여러 하드웨어 장치가 모여서 하나의 가상 네트워크 장치로 사용될 수 있도록 해주는 커널 기능도 있는데, 이런 경우 패킷을 내보낼 때에는 dev
필드는 가상 장치를 가리키고 있고, device driver가 실제 물리 장치를 선택하여 dev
필드를 업데이트한다. 따라서 패킷 처리 과정에서 dev
필드가 바뀔 수 있음.
struct net_device *dev
패킷을 수신한 장치를 나타내며 로컬에서 패킷이 만들어진 경우 NULL
임. Traffic Control을 위해 사용
struct net_device *input_dev
가상 장치인 경우에만 의미가 있으며 network bonding, VLAN 인터페이스 등에서 사용.
struct net_device *real_dev