- Today
- Total
Byeo
socket system call 2 (inet_create) 본문
이전 포스트: 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은 다음 포스트에서 작성하겠습니다~
'프로그래밍 (Programming) > 네트워크 스택' 카테고리의 다른 글
socket system call 4 (sock_map_fd) (0) | 2024.04.14 |
---|---|
socket system call 3 (tcp_v4_init_sock) (0) | 2024.04.06 |
socket system call 1 (syscall_socket ~ __sock_create) (0) | 2024.03.31 |
추가한 System Call을 함수 명으로 호출하기 (glibc) (0) | 2024.03.22 |
Linux System Call 추가하기 (0) | 2024.03.16 |