- Today
- Total
Byeo
socket system call 1 (syscall_socket ~ __sock_create) 본문
socket system call 1 (syscall_socket ~ __sock_create)
BKlee 2024. 3. 31. 16:09int 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
이전 포스트에서 리눅스 시스템 콜을 추가하면 서 확인했던 점은 시스템 콜의 시작 부분이 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_CLOEXEC와 SOCK_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
'프로그래밍 (Programming) > 네트워크 스택' 카테고리의 다른 글
socket system call 3 (tcp_v4_init_sock) (0) | 2024.04.06 |
---|---|
socket system call 2 (inet_create) (0) | 2024.03.31 |
추가한 System Call을 함수 명으로 호출하기 (glibc) (0) | 2024.03.22 |
Linux System Call 추가하기 (0) | 2024.03.16 |
VSCode에 Linux Kernel Source Code 환경 구성하기 (0) | 2024.03.12 |