Byeo

socket system call 1 (syscall_socket ~ __sock_create) 본문

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

socket system call 1 (syscall_socket ~ __sock_create)

BKlee 2024. 3. 31. 16:09
반응형

 int socket(int domain, int type, int protocol);

 

 

1. syscall_64.tbl

 이전 포스트에서 리눅스 시스템 콜을 추가하면 서 확인했던 점은 시스템 콜의 시작 부분이 syscall_64.tbl에 등록되어 있다는 점이었습니다.

 

 마찬가지로 linux 5.15 기준으로 socket 함수를 찾아봅니다.

...
40	common	sendfile		sys_sendfile64
41	common	socket			sys_socket
42	common	connect			sys_connect
...

 

이는 마찬가지로 syscalls.h에 함수 정의가 되어 있습니다.

/* net/socket.c */
asmlinkage long sys_socket(int, int, int);

 

함수 정의는 net/socket.c에 정의가 되어있대요.

 

2.  SYSCALL_DEFINE3 (net/socket:1564)

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
	return __sys_socket(family, type, protocol);
}

 

해당 파일에 가보면, 우리가 기존에 잘 알고 있는 socket(family, type, protocol)의 형태로 SYSCALL_DEFINE3이 되어있는 것을 확인할 수 있습니다. 참고로 SYSCALL_DEFINEx는 syscalls.h에 앞에 sys_를 붙이도록 정의가 되어있습니다. 3은 인자의 개수를 의미하죠.

 

* 참고:

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

// --> SYSCALL_DEFINE3(socket, ...) == SYSCALL_DEFINEx(3, _socket, __VA_ARGS__)

#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

// --> __SYSCALL_DEFINEx(3, _socket, __VA_ARGS__)


#define __SYSCALL_DEFINEx(x, name, ...)					\
	__diag_push();							\
	__diag_ignore(GCC, 8, "-Wattribute-alias",			\
		      "Type aliasing is used to sanitize syscall arguments");\
	asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))	\

// --> asmlinkage long sys_socket(__MAP(3, __SC_DECL, __VA_ARGS__))

 

 

따라서, socket 시스템콜을 호출하면 결국 int __sys_socket(int family, int type, int protocol)을 호출함을 알 수 있네요.

 

 

 

 

 

 

 

3. __sys_socket() (net/socket.c:1537)

int __sys_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;
	int flags;

	/* Check the SOCK_* constants for consistency.  */
	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		return retval;

	return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

 

BUILD_BUG_ON은 그 조건이 참이라면 compile을 중단하는 함수입니다. 따라서 4줄의 BUILD_BUG_ON은 여러 #defined 변수에 이상이 없는지 체크하는 라인이겠군요.

/**
 * BUILD_BUG_ON - break compile if a condition is true.
 * @condition: the condition which the compiler should know is false.
 *
 * If you have some code which relies on certain constants being equal, or
 * some other compile-time-evaluated condition, you should use BUILD_BUG_ON to
 * detect if someone changes it.
 */
#define BUILD_BUG_ON(condition) \
	BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)

 

 

그 이후로는 type에 대해서 SOCK_CLOEXECSOCK_NONBLOCK을 체크합니다. 해당 값은 socket을 생성할 때 userspace에서 bitwise OR을 통해서 전달할 수 있는 것으로 보입니다.

 

단, 이 값은 sock_create에 들어갈 내용은 아니고, sock_map_fd에서 사용됩니다.

 

  • SOCK_NONBLOCK: socket을 nonblocking으로 동작하게 만듭니다. 
  • SOCK_CLOEXEC: userspace application에서 다른 application을 exec할 때 child에게 fd를 물려주지 않습니다. [각주:1]

 

sock_create로 넘어갈 때, type &= SOCK_TYPE_MASK에 의해서 SOCK_CLOEXEC와 SOCK_NONBLOCK은 정보를 잃게 됩니다.

// include/uapi/asm-generic/fcntl.h:39

#define O_NONBLOCK	00004000 
#define O_CLOEXEC	02000000

 

// include/linux/net.h:61
enum sock_type {
	SOCK_STREAM	= 1,
	SOCK_DGRAM	= 2,
	SOCK_RAW	= 3,
	SOCK_RDM	= 4,
	SOCK_SEQPACKET	= 5,
	SOCK_DCCP	= 6,
	SOCK_PACKET	= 10,
};


#define SOCK_TYPE_MASK 0xf

/* Flags for socket, socketpair, accept4 */
#define SOCK_CLOEXEC	O_CLOEXEC
#ifndef SOCK_NONBLOCK
#define SOCK_NONBLOCK	O_NONBLOCK
#endif

 

그리고 socket_create를 호출할 때, struct socket이라는 인자가 하나 더 늘어나네요. 해당 생김새는 다음과 같습니다.

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @type: socket type (%SOCK_STREAM, etc)
 *  @flags: socket flags (%SOCK_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wq: wait queue for several uses
 */
struct socket {
	socket_state		state;

	short			type;

	unsigned long		flags;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;

	struct socket_wq	wq;
};

 

 

 

 

 

 

 

 

4. socket_create (net/socket.c:1513)

int sock_create(int family, int type, int protocol, struct socket **res)
{
	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

 

이는 바로 __socket_create를 호출합니다. (wrapper function)

 

/**
 *	__sock_create - creates a socket
 *	@net: net namespace
 *	@family: protocol family (AF_INET, ...)
 *	@type: communication type (SOCK_STREAM, ...)
 *	@protocol: protocol (0, ...)
 *	@res: new socket
 *	@kern: boolean for kernel space sockets
 *
 *	Creates a new socket and assigns it to @res, passing through LSM.
 *	Returns 0 or an error. On failure @res is set to %NULL. @kern must
 *	be set to true if the socket resides in kernel space.
 *	This function internally uses GFP_KERNEL.
 */

int __sock_create(struct net *net, int family, int type, int protocol,
			 struct socket **res, int kern)
{
	int err;
	struct socket *sock;
	const struct net_proto_family *pf;

	/*
	 *      Check protocol is in range
	 */
	if (family < 0 || family >= NPROTO)
		return -EAFNOSUPPORT;
	if (type < 0 || type >= SOCK_MAX)
		return -EINVAL;

	/* Compatibility.

	   This uglymoron is moved from INET layer to here to avoid
	   deadlock in module load.
	 */
	if (family == PF_INET && type == SOCK_PACKET) {
		pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
			     current->comm);
		family = PF_PACKET;
	}

	err = security_socket_create(family, type, protocol, kern);
	if (err)
		return err;

	/*
	 *	Allocate the socket and allow the family to set things up. if
	 *	the protocol is 0, the family is instructed to select an appropriate
	 *	default.
	 */
	sock = sock_alloc();
	if (!sock) {
		net_warn_ratelimited("socket: no more sockets\n");
		return -ENFILE;	/* Not exactly a match, but its the
				   closest posix thing */
	}

	sock->type = type;

#ifdef CONFIG_MODULES
	/* Attempt to load a protocol module if the find failed.
	 *
	 * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
	 * requested real, full-featured networking support upon configuration.
	 * Otherwise module support will break!
	 */
	if (rcu_access_pointer(net_families[family]) == NULL)
		request_module("net-pf-%d", family);
#endif

	rcu_read_lock();
	pf = rcu_dereference(net_families[family]);
	err = -EAFNOSUPPORT;
	if (!pf)
		goto out_release;

	/*
	 * We will call the ->create function, that possibly is in a loadable
	 * module, so we have to bump that loadable module refcnt first.
	 */
	if (!try_module_get(pf->owner))
		goto out_release;

	/* Now protected by module ref count */
	rcu_read_unlock();

	err = pf->create(net, sock, protocol, kern);
	if (err < 0)
		goto out_module_put;

	/*
	 * Now to bump the refcnt of the [loadable] module that owns this
	 * socket at sock_release time we decrement its refcnt.
	 */
	if (!try_module_get(sock->ops->owner))
		goto out_module_busy;

	/*
	 * Now that we're done with the ->create function, the [loadable]
	 * module can have its refcnt decremented
	 */
	module_put(pf->owner);
	err = security_socket_post_create(sock, family, type, protocol, kern);
	if (err)
		goto out_sock_release;
	*res = sock;

	return 0;

out_module_busy:
	err = -EAFNOSUPPORT;
out_module_put:
	sock->ops = NULL;
	module_put(pf->owner);
out_sock_release:
	sock_release(sock);
	return err;

out_release:
	rcu_read_unlock();
	goto out_sock_release;
}

 

함수 인자는 주석에 적혀있는 것처럼

  • net은 namespace를,
  • kern은 kernel을 위한 socket인지를 의미합니다. userspace로부터의 흐름을 타고 있기 때문에 0입니다. wrapper function에서 0으로 호출을 하고 있죠 (__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);).

 

(1) family 체크

	if (family < 0 || family >= NPROTO)
		return -EAFNOSUPPORT;

 

family의 값 범위가 유효한지 확인합니다. Linux 5.15 기준 NPROTO ( = AF_MAX)는 46입니다 (include/uapi/linux/net.h:25, include/linux/socket.h:230). Address family가 참 많네요. 참고로 protocol family와 완전 동일합니다. 역사적인 이유로 갈렸다고 하네요.

 즉, PF_INET = AF_INET입니다. 

 

(2) sock 체크

	if (type < 0 || type >= SOCK_MAX)
		return -EINVAL;

 

SOCK_MAX는 11입니다.

// include/linux/net.h:61
enum sock_type {
	SOCK_STREAM	= 1,
	SOCK_DGRAM	= 2,
	SOCK_RAW	= 3,
	SOCK_RDM	= 4,
	SOCK_SEQPACKET	= 5,
	SOCK_DCCP	= 6,
	SOCK_PACKET	= 10,
};

#define SOCK_MAX (SOCK_PACKET + 1)

 

* security_socket_create는 security_hook으로 보입니다. 자세한 내용은 https://docs.kernel.org/security/lsm.html를 참고해 주세요.

 

(3) sock_alloc (net/socket.c:621)

/**
 *	sock_alloc - allocate a socket
 *
 *	Allocate a new inode and socket object. The two are bound together
 *	and initialised. The socket is then returned. If we are out of inodes
 *	NULL is returned. This functions uses GFP_KERNEL internally.
 */

struct socket *sock_alloc(void)
{
	struct inode *inode;
	struct socket *sock;

	inode = new_inode_pseudo(sock_mnt->mnt_sb);
	if (!inode)
		return NULL;

	sock = SOCKET_I(inode);

	inode->i_ino = get_next_ino();
	inode->i_mode = S_IFSOCK | S_IRWXUGO;
	inode->i_uid = current_fsuid();
	inode->i_gid = current_fsgid();
	inode->i_op = &sockfs_inode_ops;

	return sock;
}

 

socket을 할당합니다. 사실, 내부적으로 inode를 할당받는 모습이 보입니다.

 

(4) pf (protocol family) 얻기

커널이 실행될 때, 어디선가 sock_register 함수를 통해서 protocol family들에 대한 각 함수를 'net_families' 배열에 저장합니다.

// net/socket.c:223
static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

// net/socket.c:3030
int sock_register(const struct net_proto_family *ops)

 

우리에게 친숙한 TCP, UDP는 inet_init에서 등록되는 것을 볼 수 있었습니다.

// net/ipv4/af_inet.c:1937
static int __init inet_init(void)
{
...
	(void)sock_register(&inet_family_ops);
    
   	if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
		pr_crit("%s: Cannot add ICMP protocol\n", __func__);
	if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
		pr_crit("%s: Cannot add UDP protocol\n", __func__);
	if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
		pr_crit("%s: Cannot add TCP protocol\n", __func__);
        
    ...
}

 

그리고 inet_family_ops 생김새는 다음과 같이 생겼습니다.

// net/ipv4/af_inet.c:1113
static const struct net_proto_family inet_family_ops = {
	.family = PF_INET,
	.create = inet_create,
	.owner	= THIS_MODULE,
};

 

 

(5) pf->create

따라서 'pf = rcu_dereference(net_families[family]);'  함수는 위 구조체 (inet_family_ops)를 가져온 것이라고 볼 수 있겠네요. 

 

그러면 다시, __sock_create 함수에서 다음코드의 pf->create는 inet_create라는 함수겠군요. 

	err = pf->create(net, sock, protocol, kern);
	if (err < 0)
		goto out_module_put;

 

그 밑에 나머지 라인은 마찬가지로 LSM, module 관련 내용이니 제외하고, *res = sock; 을 넣어서 caller에게 다시 sock을 넘겨주는 것을 확인할 수 있었습니다.

 

이제 inet_create를 봐야 할 차례네요.

 

 

(6) struct socket* sock 내부 값 확인

마지막으로 pf->create 들어가기 전에, 할당되었던 socket sock의 값들을 확인해봅시다. 소스코드의 line number를 유지하기 위해서 별도의 함수로 빼냈습니다. 

 

다른 곳에서 socket create를 많이하므로, 우리가 만들어 볼 TCP만 sock 구조체를 출력하도록 조건을 걸었습니다.

// net/socket.c:1464

	if(family == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) {byeo_printk_struct_sock(sock);}
	err = pf->create(net, sock, protocol, kern);



/* BKLEE private function*/
void byeo_printk_struct_sock(struct socket* sock) {
	printk("byeo, struct socket"
		"socket %p, "
		"state %d, "
		"type %d, "
		"flags %ld, "
		"file %p, "
		"sk %p, "
		"proto_ops %p, "
		"wq %p", 
		sock, sock->state, sock->type, sock->flags, sock->file, sock->sk, \
		sock->ops, &sock->wq);
}

 

 

 

(7) 현재까지 구조

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

포스트가 너무 길어져서 다음 글에 이어서 작성하겠습니다~

 

https://byeo.tistory.com/entry/socket-system-call-2

 

반응형
Comments