Byeo

socket system call 2 (inet_create) 본문

프로그래밍 (Programming)/네트워크 스택

socket system call 2 (inet_create)

BKlee 2024. 3. 31. 21:20
반응형

이전 포스트: https://byeo.tistory.com/entry/socket-system-call

 

 

socket system call 1

int socket(int domain, int type, int protocol); socket 시스템 콜의 동작을 한 번 들여다 봅니다! https://man7.org/linux/man-pages/man2/socket.2.html linux 5.15 기준입니다. 1. syscall_64.tbl 이전 포스트에서 리눅스 시스템 콜

byeo.tistory.com

 

 

 

5. inet_create (net/ipv4/af_inet.c:248)

사실 address family가 달라지면 호출될 함수가 달라지겠죠. 예를 들어서 AF_INET6을 지정했다면 inet6_create가 불릴거에요 (net/ipv6/af_inet6:111).

 

그렇지만, 여기서는 가장 범용적으로 쓰이는 IPv4를 한 번 들여다보도록 하겠습니다. 

 

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
{
	struct sock *sk;
	struct inet_protosw *answer;
	struct inet_sock *inet;
	struct proto *answer_prot;
	unsigned char answer_flags;
	int try_loading_module = 0;
	int err;

	if (protocol < 0 || protocol >= IPPROTO_MAX)
		return -EINVAL;

	sock->state = SS_UNCONNECTED;

 

inet_create에서 가장 먼저 protocol의 범위를 확인합니다. protocol의 범위는 최대 263의 값을 지니고 있으며, include/uapi/linux/in.h:28에 정의되어있습니다.

 

lookup_protocol:
	err = -ESOCKTNOSUPPORT;
	rcu_read_lock();
	list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

		err = 0;
		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		} else {
			/* Check for the two wild cases. */
			if (IPPROTO_IP == protocol) {
				protocol = answer->protocol;
				break;
			}
			if (IPPROTO_IP == answer->protocol)
				break;
		}
		err = -EPROTONOSUPPORT;
	}

 

다음은 inetsw라는 링크드 리스트를 순회하면서 사용자가 지정한 protocol과 리스트 엔트리의 answer->protocol이 일치하는지 확인합니다. 

 

(1) 이 inetsw라는 링크드 리스트 배열은 어디서 초기화가 되었던 걸까요? 

이는 우리가 위에서 살펴보았던 inet_init 함수의 더 아래에 있었습니다. (net/ipv4/af_inet.c:1987)

	/* Register the socket-side information for inet_create. */
	for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
		INIT_LIST_HEAD(r);

	for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
		inet_register_protosw(q);

 

여기서 윗 for문은 list 초기화로 보이고, 아래 for문이 직접 등록하는 것으로 보입니다.

 

(2) inetsw_array의 배열을 순회하는 것으로 보이는데, 이 inetsw_array는 뭘까요?

이는 커널 코드 내에서 직접 static으로 박혀있는 코드로, inet4가 지원하는 type/protocol pair를 선언한 구조체 배열입니다. (net/ipv4/af_inet.c:1122)

/* Upon startup we insert all the elements in inetsw_array[] into
 * the linked list inetsw.
 */
static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
		.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
	},

	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		.prot =       &udp_prot,
		.ops =        &inet_dgram_ops,
		.flags =      INET_PROTOSW_PERMANENT,
       },

       {
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_ICMP,
		.prot =       &ping_prot,
		.ops =        &inet_sockraw_ops,
		.flags =      INET_PROTOSW_REUSE,
       },

       {
	       .type =       SOCK_RAW,
	       .protocol =   IPPROTO_IP,	/* wild card */
	       .prot =       &raw_prot,
	       .ops =        &inet_sockraw_ops,
	       .flags =      INET_PROTOSW_REUSE,
       }
};

 

여기서 보면 알 수 있듯, inet4가 담당하는 protocol은 TCP, UDP, ICMP, IP로 보이네요. IPPROTO_TCP 이런 값들은 왠지 친숙하죠?

 

(3) 최종적으로 inet_register_protosw 함수가 하는 역할은?

해당 함수는 list를 순회하면서 이미 protocol이 등록되어 있는지 확인하고, protocol이 등록 되어있다면 permenant일 때는 에러, permenant가 아니라면 override를 수행하는 것으로 보입니다.

void inet_register_protosw(struct inet_protosw *p)
{
	struct list_head *lh;
	struct inet_protosw *answer;
	int protocol = p->protocol;
	struct list_head *last_perm;

	spin_lock_bh(&inetsw_lock);

	if (p->type >= SOCK_MAX)
		goto out_illegal;

	/* If we are trying to override a permanent protocol, bail. */
	last_perm = &inetsw[p->type];
	list_for_each(lh, &inetsw[p->type]) {
		answer = list_entry(lh, struct inet_protosw, list);
		/* Check only the non-wild match. */
		if ((INET_PROTOSW_PERMANENT & answer->flags) == 0)
			break;
		if (protocol == answer->protocol)
			goto out_permanent;
		last_perm = lh;
	}

	/* Add the new entry after the last permanent entry if any, so that
	 * the new entry does not override a permanent entry when matched with
	 * a wild-card protocol. But it is allowed to override any existing
	 * non-permanent entry.  This means that when we remove this entry, the
	 * system automatically returns to the old behavior.
	 */
	list_add_rcu(&p->list, last_perm);

 

예를 들어서, .type= SOCK_DGRAM, .protocol = IPPROTO_ICMP를 등록하는 과정이라면 inetsw[2] 리스트를 순회해봅니다. (SOCK_DGRAM = 2)

 

여기서 같은 protocol을 override하여 등록 할 경우가 언제 있을지는 아직 이해하지 못했습니다. 

 

(4) inet_create-2: 다시 inet_create로 돌아와서

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...
    
    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

		err = 0;
		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		} else {
			/* Check for the two wild cases. */
			if (IPPROTO_IP == protocol) {
				protocol = answer->protocol;
				break;
			}
			if (IPPROTO_IP == answer->protocol)
				break;
		}
		err = -EPROTONOSUPPORT;
	}

 

그래서 이 부분은 결국 사용자가 입력한 protocol이 제대로 맞게 입력 되었는지를 검증하는 과정이겠네요.

 

socket (AF_INET, SOCK_STREAM, IPPROTO_UDP) 와 같은 형태로 입력을 했다면, inetsw[1] 에 IPPROTO_UDP가 없을테니 -EPROTONOSUPPORT 에러가 발생하겠군요.

#define	EPROTONOSUPPORT	93	/* Protocol not supported */

 

실제로 해볼까요?

 


93을 잘 뱉는군요!

 

(5) inet_create-3: module load retry와 permission check

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...
    

	if (unlikely(err)) {
		if (try_loading_module < 2) {
			rcu_read_unlock();
			/*
			 * Be more specific, e.g. net-pf-2-proto-132-type-1
			 * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
			 */
			if (++try_loading_module == 1)
				request_module("net-pf-%d-proto-%d-type-%d",
					       PF_INET, protocol, sock->type);
			/*
			 * Fall back to generic, e.g. net-pf-2-proto-132
			 * (net-pf-PF_INET-proto-IPPROTO_SCTP)
			 */
			else
				request_module("net-pf-%d-proto-%d",
					       PF_INET, protocol);
			goto lookup_protocol;
		} else
			goto out_rcu_unlock;
	}

	err = -EPERM;
	if (sock->type == SOCK_RAW && !kern &&
	    !ns_capable(net->user_ns, CAP_NET_RAW))
		goto out_rcu_unlock;

 

이전 과정에서 알 수 없는 protocol을 접했을 때 module load를 재시도 해봅니다. 

그리고 sock->type이 SOCK_RAW인 경우, namespace가 RAW packet type을 지원할 수 있는지 검증하는 코드도 있는 것으로 보이네요.

 

(6) inet_create-4: protocol functions을 sock에 대입

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...
    

	sock->ops = answer->ops;
	answer_prot = answer->prot;
	answer_flags = answer->flags;
	rcu_read_unlock();

 

answer->ops를 sock->ops로 옮겨줍니다. 이 구조체의 타입은 struct proto_ops이며, 우리가 익숙한 많은 함수들의 함수포인터가 이 곳에 담겨있습니다. 

 

// include/linux/net.h:137
struct proto_ops {
	int		family;
	struct module	*owner;
	int		(*release)   (struct socket *sock);
	int		(*bind)	     (struct socket *sock,
				      struct sockaddr *myaddr,
				      int sockaddr_len);
	int		(*connect)   (struct socket *sock,
				      struct sockaddr *vaddr,
				      int sockaddr_len, int flags);
	int		(*socketpair)(struct socket *sock1,
				      struct socket *sock2);
	int		(*accept)    (struct socket *sock,
				      struct socket *newsock, int flags, bool kern);
  ...
  ...
  }

 

그리고 answer->ops는 아까 봤던 static struct inet_protosw inetsw_array[] 에서 확인할 수 있죠.

// net/ipv4/af_inet.c:1122
		...
        
        .protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
        
        ...

 

그리고 tcp_proto 사례에는 tcp_ipv4.c에서 찾을 수 있었습니다. (마찬가지로 ipv6를 사용하는 tcp의 op들도 net/ipv6/tcp_ipv6.c:2158에 존재)

// net/ipv4/tcp_ipv4.c:3051

struct proto tcp_prot = {
	.name			= "TCP",
	.owner			= THIS_MODULE,
	.close			= tcp_close,
	.pre_connect		= tcp_v4_pre_connect,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	.ioctl			= tcp_ioctl,
	.init			= tcp_v4_init_sock,
    
    ...
    	.obj_size		= sizeof(struct tcp_sock),
    ...
   }

 

나중에 tcp stack을 팔 때 오게 될 곳일 것 같네요.

 

여기서 .obj_size는 바로 다음 단계인 'struct sock *sk' 할당에서 사용됩니다.

 

 

(7) inet_create-5: struct sock *sk 할당

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...
    

	err = -ENOMEM;
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
	if (!sk)
		goto out;

 

갑자기 의문이 들기 시작합니다. 

 

struct socket과 struct sock은 무슨차이지? 주로 struct socket은 BSD socket (system call)을 위한 구조체로서 사용되고, struct sock은 커널 내에서 socket 관련 자료구조를 관리할 때 사용한다고 합니다. 이 둘은 서로의 포인터를 갖고 있습니다.

 

/**
 *	sk_alloc - All socket objects are allocated here
 *	@net: the applicable net namespace
 *	@family: protocol family
 *	@priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc)
 *	@prot: struct proto associated with this new sock instance
 *	@kern: is this to be a kernel socket?
 */
struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
		      struct proto *prot, int kern)
{
	struct sock *sk;

	sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
	if (sk) {
		sk->sk_family = family;
		/*
		 * See comment in struct sock definition to understand
		 * why we need sk_prot_creator -acme
		 */
		sk->sk_prot = sk->sk_prot_creator = prot;
		sk->sk_kern_sock = kern;
		sock_lock_init(sk);
		sk->sk_net_refcnt = kern ? 0 : 1;
		if (likely(sk->sk_net_refcnt)) {
			get_net(net);
			sock_inuse_add(net, 1);
		}

		sock_net_set(sk, net);
		refcount_set(&sk->sk_wmem_alloc, 1);

		mem_cgroup_sk_alloc(sk);
		cgroup_sk_alloc(&sk->sk_cgrp_data);
		sock_update_classid(&sk->sk_cgrp_data);
		sock_update_netprioidx(&sk->sk_cgrp_data);
		sk_tx_queue_clear(sk);
	}

	return sk;
}

 

여기서 sk_proto_alloc은 다음 함수를 통해서 prot->obj_size 만큼 할당받습니다.

// net/core/sock.c/sk_proto_alloc():1839
        sk = kmalloc(prot->obj_size, priority);

 

proto->obj_size는 'struct tcp_sock' 구조체의 크기입니다. 그리고 내부에 'struct inet_connection_sock'을 포함하고 있으며, 이는 다시 'struct inet_sock'을, 그리고 마지막으로 'struct sock'을 차례로 포함하고 있습니다.

  • struct tcp_sock
  • struct inet_connection_sock: INET connection-oriented sock
  • struct inet_sock: representation of INET sockets
  • struct sock: network layer representation of sockets

그 밑의 mem_cgroup_sk_alloc ~ sock_update_netprioidx는 cgroup과 관련된 처리를 해주는 것으로 보입니다. 아직 관심사는 아니여서 자세히 들여다보지는 않았습니다.

 

sk_tx_queue_clear는 sk->sk_tx_queue_mapping을 초기화합니다.

 

 

 

(8) inet_create-6: 값 초기화

static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...
    

	err = 0;
	if (INET_PROTOSW_REUSE & answer_flags)
		sk->sk_reuse = SK_CAN_REUSE;

	inet = inet_sk(sk);
	inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

	inet->nodefrag = 0;

	if (SOCK_RAW == sock->type) {
		inet->inet_num = protocol;
		if (IPPROTO_RAW == protocol)
			inet->hdrincl = 1;
	}

	if (net->ipv4.sysctl_ip_no_pmtu_disc)
		inet->pmtudisc = IP_PMTUDISC_DONT;
	else
		inet->pmtudisc = IP_PMTUDISC_WANT;

	inet->inet_id = 0;

	sock_init_data(sock, sk);

	sk->sk_destruct	   = inet_sock_destruct;
	sk->sk_protocol	   = protocol;
	sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

	inet->uc_ttl	= -1;
	inet->mc_loop	= 1;
	inet->mc_ttl	= 1;
	inet->mc_all	= 1;
	inet->mc_index	= 0;
	inet->mc_list	= NULL;
	inet->rcv_tos	= 0;

 

  •  할당받은 구조체 sk에 몇 가지 값을 집어넣습니다. inet_sk는 sk를 struct inet으로 캐스팅 한 것으로, 같은 구조체인 sk에 struct inet_sk로서 접근할 수 있게 해줍니다.
  • icsk는 inet connection socket으로, inet4에서는 TCP만 해당합니다. (inetsw_array 참고)

(9) sock_init_data

// net/core/sock.c/sock_init_data:3117
void sock_init_data(struct socket *sock, struct sock *sk)
{
	sk_init_common(sk);
	sk->sk_send_head	=	NULL;

	timer_setup(&sk->sk_timer, NULL, 0);

	sk->sk_allocation	=	GFP_KERNEL;
	sk->sk_rcvbuf		=	sysctl_rmem_default;
	sk->sk_sndbuf		=	sysctl_wmem_default;
	sk->sk_state		=	TCP_CLOSE;
	sk_set_socket(sk, sock);

	sock_set_flag(sk, SOCK_ZAPPED);

	if (sock) {
		sk->sk_type	=	sock->type;
		RCU_INIT_POINTER(sk->sk_wq, &sock->wq);
		sock->sk	=	sk;
		sk->sk_uid	=	SOCK_INODE(sock)->i_uid;
	} else {
		RCU_INIT_POINTER(sk->sk_wq, NULL);
		sk->sk_uid	=	make_kuid(sock_net(sk)->user_ns, 0);
	}

	rwlock_init(&sk->sk_callback_lock);
	if (sk->sk_kern_sock)
		lockdep_set_class_and_name(
			&sk->sk_callback_lock,
			af_kern_callback_keys + sk->sk_family,
			af_family_kern_clock_key_strings[sk->sk_family]);
	else
		lockdep_set_class_and_name(
			&sk->sk_callback_lock,
			af_callback_keys + sk->sk_family,
			af_family_clock_key_strings[sk->sk_family]);

	sk->sk_state_change	=	sock_def_wakeup;
	sk->sk_data_ready	=	sock_def_readable;
	sk->sk_write_space	=	sock_def_write_space;
	sk->sk_error_report	=	sock_def_error_report;
	sk->sk_destruct		=	sock_def_destruct;

	sk->sk_frag.page	=	NULL;
	sk->sk_frag.offset	=	0;
	sk->sk_peek_off		=	-1;

	sk->sk_peer_pid 	=	NULL;
	sk->sk_peer_cred	=	NULL;
	spin_lock_init(&sk->sk_peer_lock);

	sk->sk_write_pending	=	0;
	sk->sk_rcvlowat		=	1;
	sk->sk_rcvtimeo		=	MAX_SCHEDULE_TIMEOUT;
	sk->sk_sndtimeo		=	MAX_SCHEDULE_TIMEOUT;

	sk->sk_stamp = SK_DEFAULT_STAMP;
#if BITS_PER_LONG==32
	seqlock_init(&sk->sk_stamp_seq);
#endif
	atomic_set(&sk->sk_zckey, 0);

#ifdef CONFIG_NET_RX_BUSY_POLL
	sk->sk_napi_id		=	0;
	sk->sk_ll_usec		=	sysctl_net_busy_read;
#endif

	sk->sk_max_pacing_rate = ~0UL;
	sk->sk_pacing_rate = ~0UL;
	WRITE_ONCE(sk->sk_pacing_shift, 10);
	sk->sk_incoming_cpu = -1;

	sk_rx_queue_clear(sk);
	/*
	 * Before updating sk_refcnt, we must commit prior changes to memory
	 * (Documentation/RCU/rculist_nulls.rst for details)
	 */
	smp_wmb();
	refcount_set(&sk->sk_refcnt, 1);
	atomic_set(&sk->sk_drops, 0);
}
EXPORT_SYMBOL(sock_init_data);

 

sock_init_data는 struct socket *sock과 struct sock *sk를 받아서 서로에게 필요한 값을 넣어줍니다.

 

sk_init_common 함수는 여러 sk_queue들을 초기화합니다.

// net/core/sock.c/sk_init_common:2002

static void sk_init_common(struct sock *sk)
{
	skb_queue_head_init(&sk->sk_receive_queue);
	skb_queue_head_init(&sk->sk_write_queue);
	skb_queue_head_init(&sk->sk_error_queue);
    
    ...
    
}

 

다음 코드를 보면 sock->sk에 sk를 넣어주는 것을 확인할 수 있습니다.

// net/core/sock.c/sock_init_data:3128
    sk_set_socket(sk, sock);

// net/core/sock.c/sock_init_data:3135
sock->sk	=	sk;

 

다른 field들은 아마 나중에 계속 파다보면 언젠가 접근하게 되리라 예상하면서 스킵합니다.

 

(10) struct sock* sk 출력해보기

struct sock* sk는 구조체가 상당히 방대합니다. 그래서 이 중에서 몇 가지만 뽑아보았습니다.

void byeo_printk_struct_sk(struct sock* sk) 
{
	struct inet_sock *inet;
	struct tcp_sock* tcp;

	printk("byeo struct sk (%p) print", sk);
	printk("[skc] skc_dport %d, skc_num %d", sk->sk_dport, sk->sk_num);

	printk("[sk] sk_type %d, sk_protocol %d, sk_gso_type %d, sk_gso_max_size %d\n", sk->sk_type, sk->sk_protocol, sk->sk_gso_type, sk->sk_gso_max_size);
	printk("[sk] rx_buff %p, tx_buff %p, sk_socket %p\n", &sk->sk_receive_queue, &sk->sk_write_queue, sk->sk_socket);
	
	inet = inet_sk(sk);
	printk("[inet] tos %d, is_icsk %d\n", inet->tos, inet->is_icsk);

	tcp = tcp_sk(sk);
	printk("[tcp] tcp_header_len %d, gso_segs %d, bytes_received %lld, segs_in %d", tcp->tcp_header_len, tcp->gso_segs, tcp->bytes_received, tcp->segs_in);
}

 

 

6. tcp_v4_init_sock

// net/ipv4/af_inet.c/inet_create:380
static int inet_create(struct net *net, struct socket *sock, int protocol,
		       int kern)
               
   ...

	if (sk->sk_prot->init) {
		err = sk->sk_prot->init(sk);
		if (err) {
			sk_common_release(sk);
			goto out;
		}
	}

 

inet_create은 이제 마지막으로 sk->sk_prot->init(sk)를 호출하고 return합니다. sk_prot->init은 tcp_prot->init과 같습니다.

 

// net/ipv4/tcp_ipv4.c/tcp_prot:3051

struct proto tcp_prot = {
	.name			= "TCP",
	.owner			= THIS_MODULE,
	.close			= tcp_close,
	.pre_connect		= tcp_v4_pre_connect,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	.ioctl			= tcp_ioctl,
	.init			= tcp_v4_init_sock,
	.destroy		= tcp_v4_destroy_sock,
	.shutdown		= tcp_shutdown,
	.setsockopt		= tcp_setsockopt,
	.getsockopt		= tcp_getsockopt,
	.bpf_bypass_getsockopt	= tcp_bpf_bypass_getsockopt,
	.keepalive		= tcp_set_keepalive,
	.recvmsg		= tcp_recvmsg,
	.sendmsg		= tcp_sendmsg,
	.sendpage		= tcp_sendpage,
	.backlog_rcv		= tcp_v4_do_rcv,
    
    ...
    
}

 

.init = tcp_v4_init_sock

 

tcp_v4_init_sock은 다음 포스트에서 작성하겠습니다~

반응형
Comments