linux-network

View the Project on GitHub super-learners/linux-network

Chapter 2. Critical Data Structures

살펴볼 데이터 스트럭처들

참고로 이 책에서는 소켓에 대한 정보를 담는 struct sock는 다루지 않는다고 함.

struct sk_buff

보낼/받은 패킷의 헤더를 나타내며 정의는 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_SCHEDCONFIG_NET_CLS_ACT를 둘 다 켜야만 포함된다.

참고: NET_SCHED — QoS and/or fair queueing 옵션에 대한 설명

이런 식으로 옵션을 켰을 때 커널 data structure의 정의가 변하는 경우에는 해당 옵션을 모듈로 컴파일할 수 없다고 보면 된다.

커널 소스 디렉토리들에 있는 kconfig 파일을 보면 옵션들을 찾아볼 수 있음.

struct sk_buff의 멤버 변수

러프하게 4가지 종류로 분류할 수 있다고 한다.

Layout 필드

커널은 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)
the organization of the sk_buff doubly-linked list


이 버퍼를 소유하고 있는 소켓에 대한 포인터. 데이터가 로컬에서 생성되는 상황이나, 로컬 프로세스가 데이터를 받고 있는 상황에서 필요함. 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;

각 레이어에서의 작업을 위한 버퍼 할당을 할 때, 앞뒤로 헤더 또는 추가 데이터를 위한 공간을 남겨놓는다. headend는 할당된 공간 시작과 끝을 가리키고, datatail은 실제 데이터의 시작과 끝을 가리킴.

unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;

다음과 같은 그림으로 나타낼 수 있음 (출처: Understanding the Linux Network Internals)

head/end versus data/tail pointers


버퍼가 해제될 때 실행할 루틴. 버퍼가 소켓과 연결되지 않은 경우 이 필드는 초기화되지 않는다. 소켓에 연결된 경우, sock_rfree 또는 sock_wfree로 초기화되는데, 소켓이 얼마만큼의 메모리를 잡아먹고 있는지를 업데이트하는 동작을 함.

void (*destructor)(struct sk_buff *skb);

General 필드

보통 수신한 패킷에 대해서만 의미가 있는 타임 스탬프. 가끔 보내는 패킷에 대해 언제 송출할 것인지를 나타내기 위해 쓰이기도 함.

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