Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Namjae Jeon | 2530 | 99.41% | 10 | 62.50% |
Tobias Klauser | 6 | 0.24% | 1 | 6.25% |
Dan Carpenter | 3 | 0.12% | 1 | 6.25% |
Hyeoncheol Lee | 2 | 0.08% | 1 | 6.25% |
Li Yang | 2 | 0.08% | 1 | 6.25% |
Enzo Matsumiya | 1 | 0.04% | 1 | 6.25% |
Muhammad Usama Anjum | 1 | 0.04% | 1 | 6.25% |
Total | 2545 | 16 |
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org> * Copyright (C) 2018 Samsung Electronics Co., Ltd. */ #include <linux/freezer.h> #include "smb_common.h" #include "server.h" #include "auth.h" #include "connection.h" #include "transport_tcp.h" #define IFACE_STATE_DOWN BIT(0) #define IFACE_STATE_CONFIGURED BIT(1) static atomic_t active_num_conn; struct interface { struct task_struct *ksmbd_kthread; struct socket *ksmbd_socket; struct list_head entry; char *name; struct mutex sock_release_lock; int state; }; static LIST_HEAD(iface_list); static int bind_additional_ifaces; struct tcp_transport { struct ksmbd_transport transport; struct socket *sock; struct kvec *iov; unsigned int nr_iov; }; static struct ksmbd_transport_ops ksmbd_tcp_transport_ops; static void tcp_stop_kthread(struct task_struct *kthread); static struct interface *alloc_iface(char *ifname); #define KSMBD_TRANS(t) (&(t)->transport) #define TCP_TRANS(t) ((struct tcp_transport *)container_of(t, \ struct tcp_transport, transport)) static inline void ksmbd_tcp_nodelay(struct socket *sock) { tcp_sock_set_nodelay(sock->sk); } static inline void ksmbd_tcp_reuseaddr(struct socket *sock) { sock_set_reuseaddr(sock->sk); } static inline void ksmbd_tcp_rcv_timeout(struct socket *sock, s64 secs) { lock_sock(sock->sk); if (secs && secs < MAX_SCHEDULE_TIMEOUT / HZ - 1) sock->sk->sk_rcvtimeo = secs * HZ; else sock->sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; release_sock(sock->sk); } static inline void ksmbd_tcp_snd_timeout(struct socket *sock, s64 secs) { sock_set_sndtimeo(sock->sk, secs); } static struct tcp_transport *alloc_transport(struct socket *client_sk) { struct tcp_transport *t; struct ksmbd_conn *conn; t = kzalloc(sizeof(*t), GFP_KERNEL); if (!t) return NULL; t->sock = client_sk; conn = ksmbd_conn_alloc(); if (!conn) { kfree(t); return NULL; } conn->transport = KSMBD_TRANS(t); KSMBD_TRANS(t)->conn = conn; KSMBD_TRANS(t)->ops = &ksmbd_tcp_transport_ops; return t; } static void free_transport(struct tcp_transport *t) { kernel_sock_shutdown(t->sock, SHUT_RDWR); sock_release(t->sock); t->sock = NULL; ksmbd_conn_free(KSMBD_TRANS(t)->conn); kfree(t->iov); kfree(t); } /** * kvec_array_init() - initialize a IO vector segment * @new: IO vector to be initialized * @iov: base IO vector * @nr_segs: number of segments in base iov * @bytes: total iovec length so far for read * * Return: Number of IO segments */ static unsigned int kvec_array_init(struct kvec *new, struct kvec *iov, unsigned int nr_segs, size_t bytes) { size_t base = 0; while (bytes || !iov->iov_len) { int copy = min(bytes, iov->iov_len); bytes -= copy; base += copy; if (iov->iov_len == base) { iov++; nr_segs--; base = 0; } } memcpy(new, iov, sizeof(*iov) * nr_segs); new->iov_base += base; new->iov_len -= base; return nr_segs; } /** * get_conn_iovec() - get connection iovec for reading from socket * @t: TCP transport instance * @nr_segs: number of segments in iov * * Return: return existing or newly allocate iovec */ static struct kvec *get_conn_iovec(struct tcp_transport *t, unsigned int nr_segs) { struct kvec *new_iov; if (t->iov && nr_segs <= t->nr_iov) return t->iov; /* not big enough -- allocate a new one and release the old */ new_iov = kmalloc_array(nr_segs, sizeof(*new_iov), GFP_KERNEL); if (new_iov) { kfree(t->iov); t->iov = new_iov; t->nr_iov = nr_segs; } return new_iov; } static unsigned short ksmbd_tcp_get_port(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: return ntohs(((struct sockaddr_in *)sa)->sin_port); case AF_INET6: return ntohs(((struct sockaddr_in6 *)sa)->sin6_port); } return 0; } /** * ksmbd_tcp_new_connection() - create a new tcp session on mount * @client_sk: socket associated with new connection * * whenever a new connection is requested, create a conn thread * (session thread) to handle new incoming smb requests from the connection * * Return: 0 on success, otherwise error */ static int ksmbd_tcp_new_connection(struct socket *client_sk) { struct sockaddr *csin; int rc = 0; struct tcp_transport *t; struct task_struct *handler; t = alloc_transport(client_sk); if (!t) { sock_release(client_sk); return -ENOMEM; } csin = KSMBD_TCP_PEER_SOCKADDR(KSMBD_TRANS(t)->conn); if (kernel_getpeername(client_sk, csin) < 0) { pr_err("client ip resolution failed\n"); rc = -EINVAL; goto out_error; } handler = kthread_run(ksmbd_conn_handler_loop, KSMBD_TRANS(t)->conn, "ksmbd:%u", ksmbd_tcp_get_port(csin)); if (IS_ERR(handler)) { pr_err("cannot start conn thread\n"); rc = PTR_ERR(handler); free_transport(t); } return rc; out_error: free_transport(t); return rc; } /** * ksmbd_kthread_fn() - listen to new SMB connections and callback server * @p: arguments to forker thread * * Return: 0 on success, error number otherwise */ static int ksmbd_kthread_fn(void *p) { struct socket *client_sk = NULL; struct interface *iface = (struct interface *)p; int ret; while (!kthread_should_stop()) { mutex_lock(&iface->sock_release_lock); if (!iface->ksmbd_socket) { mutex_unlock(&iface->sock_release_lock); break; } ret = kernel_accept(iface->ksmbd_socket, &client_sk, SOCK_NONBLOCK); mutex_unlock(&iface->sock_release_lock); if (ret) { if (ret == -EAGAIN) /* check for new connections every 100 msecs */ schedule_timeout_interruptible(HZ / 10); continue; } if (server_conf.max_connections && atomic_inc_return(&active_num_conn) >= server_conf.max_connections) { pr_info_ratelimited("Limit the maximum number of connections(%u)\n", atomic_read(&active_num_conn)); atomic_dec(&active_num_conn); sock_release(client_sk); continue; } ksmbd_debug(CONN, "connect success: accepted new connection\n"); client_sk->sk->sk_rcvtimeo = KSMBD_TCP_RECV_TIMEOUT; client_sk->sk->sk_sndtimeo = KSMBD_TCP_SEND_TIMEOUT; ksmbd_tcp_new_connection(client_sk); } ksmbd_debug(CONN, "releasing socket\n"); return 0; } /** * ksmbd_tcp_run_kthread() - start forker thread * @iface: pointer to struct interface * * start forker thread(ksmbd/0) at module init time to listen * on port 445 for new SMB connection requests. It creates per connection * server threads(ksmbd/x) * * Return: 0 on success or error number */ static int ksmbd_tcp_run_kthread(struct interface *iface) { int rc; struct task_struct *kthread; kthread = kthread_run(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s", iface->name); if (IS_ERR(kthread)) { rc = PTR_ERR(kthread); return rc; } iface->ksmbd_kthread = kthread; return 0; } /** * ksmbd_tcp_readv() - read data from socket in given iovec * @t: TCP transport instance * @iov_orig: base IO vector * @nr_segs: number of segments in base iov * @to_read: number of bytes to read from socket * @max_retries: maximum retry count * * Return: on success return number of bytes read from socket, * otherwise return error number */ static int ksmbd_tcp_readv(struct tcp_transport *t, struct kvec *iov_orig, unsigned int nr_segs, unsigned int to_read, int max_retries) { int length = 0; int total_read; unsigned int segs; struct msghdr ksmbd_msg; struct kvec *iov; struct ksmbd_conn *conn = KSMBD_TRANS(t)->conn; iov = get_conn_iovec(t, nr_segs); if (!iov) return -ENOMEM; ksmbd_msg.msg_control = NULL; ksmbd_msg.msg_controllen = 0; for (total_read = 0; to_read; total_read += length, to_read -= length) { try_to_freeze(); if (!ksmbd_conn_alive(conn)) { total_read = -ESHUTDOWN; break; } segs = kvec_array_init(iov, iov_orig, nr_segs, total_read); length = kernel_recvmsg(t->sock, &ksmbd_msg, iov, segs, to_read, 0); if (length == -EINTR) { total_read = -ESHUTDOWN; break; } else if (ksmbd_conn_need_reconnect(conn)) { total_read = -EAGAIN; break; } else if (length == -ERESTARTSYS || length == -EAGAIN) { /* * If max_retries is negative, Allow unlimited * retries to keep connection with inactive sessions. */ if (max_retries == 0) { total_read = length; break; } else if (max_retries > 0) { max_retries--; } usleep_range(1000, 2000); length = 0; continue; } else if (length <= 0) { total_read = length; break; } } return total_read; } /** * ksmbd_tcp_read() - read data from socket in given buffer * @t: TCP transport instance * @buf: buffer to store read data from socket * @to_read: number of bytes to read from socket * @max_retries: number of retries if reading from socket fails * * Return: on success return number of bytes read from socket, * otherwise return error number */ static int ksmbd_tcp_read(struct ksmbd_transport *t, char *buf, unsigned int to_read, int max_retries) { struct kvec iov; iov.iov_base = buf; iov.iov_len = to_read; return ksmbd_tcp_readv(TCP_TRANS(t), &iov, 1, to_read, max_retries); } static int ksmbd_tcp_writev(struct ksmbd_transport *t, struct kvec *iov, int nvecs, int size, bool need_invalidate, unsigned int remote_key) { struct msghdr smb_msg = {.msg_flags = MSG_NOSIGNAL}; return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, iov, nvecs, size); } static void ksmbd_tcp_disconnect(struct ksmbd_transport *t) { free_transport(TCP_TRANS(t)); if (server_conf.max_connections) atomic_dec(&active_num_conn); } static void tcp_destroy_socket(struct socket *ksmbd_socket) { int ret; if (!ksmbd_socket) return; /* set zero to timeout */ ksmbd_tcp_rcv_timeout(ksmbd_socket, 0); ksmbd_tcp_snd_timeout(ksmbd_socket, 0); ret = kernel_sock_shutdown(ksmbd_socket, SHUT_RDWR); if (ret) pr_err("Failed to shutdown socket: %d\n", ret); sock_release(ksmbd_socket); } /** * create_socket - create socket for ksmbd/0 * @iface: interface to bind the created socket to * * Return: 0 on success, error number otherwise */ static int create_socket(struct interface *iface) { int ret; struct sockaddr_in6 sin6; struct sockaddr_in sin; struct socket *ksmbd_socket; bool ipv4 = false; ret = sock_create(PF_INET6, SOCK_STREAM, IPPROTO_TCP, &ksmbd_socket); if (ret) { if (ret != -EAFNOSUPPORT) pr_err("Can't create socket for ipv6, fallback to ipv4: %d\n", ret); ret = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &ksmbd_socket); if (ret) { pr_err("Can't create socket for ipv4: %d\n", ret); goto out_clear; } sin.sin_family = PF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(server_conf.tcp_port); ipv4 = true; } else { sin6.sin6_family = PF_INET6; sin6.sin6_addr = in6addr_any; sin6.sin6_port = htons(server_conf.tcp_port); lock_sock(ksmbd_socket->sk); ksmbd_socket->sk->sk_ipv6only = false; release_sock(ksmbd_socket->sk); } ksmbd_tcp_nodelay(ksmbd_socket); ksmbd_tcp_reuseaddr(ksmbd_socket); ret = sock_setsockopt(ksmbd_socket, SOL_SOCKET, SO_BINDTODEVICE, KERNEL_SOCKPTR(iface->name), strlen(iface->name)); if (ret != -ENODEV && ret < 0) { pr_err("Failed to set SO_BINDTODEVICE: %d\n", ret); goto out_error; } if (ipv4) ret = kernel_bind(ksmbd_socket, (struct sockaddr *)&sin, sizeof(sin)); else ret = kernel_bind(ksmbd_socket, (struct sockaddr *)&sin6, sizeof(sin6)); if (ret) { pr_err("Failed to bind socket: %d\n", ret); goto out_error; } ksmbd_socket->sk->sk_rcvtimeo = KSMBD_TCP_RECV_TIMEOUT; ksmbd_socket->sk->sk_sndtimeo = KSMBD_TCP_SEND_TIMEOUT; ret = kernel_listen(ksmbd_socket, KSMBD_SOCKET_BACKLOG); if (ret) { pr_err("Port listen() error: %d\n", ret); goto out_error; } iface->ksmbd_socket = ksmbd_socket; ret = ksmbd_tcp_run_kthread(iface); if (ret) { pr_err("Can't start ksmbd main kthread: %d\n", ret); goto out_error; } iface->state = IFACE_STATE_CONFIGURED; return 0; out_error: tcp_destroy_socket(ksmbd_socket); out_clear: iface->ksmbd_socket = NULL; return ret; } static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); struct interface *iface; int ret, found = 0; switch (event) { case NETDEV_UP: if (netif_is_bridge_port(netdev)) return NOTIFY_OK; list_for_each_entry(iface, &iface_list, entry) { if (!strcmp(iface->name, netdev->name)) { found = 1; if (iface->state != IFACE_STATE_DOWN) break; ret = create_socket(iface); if (ret) return NOTIFY_OK; break; } } if (!found && bind_additional_ifaces) { iface = alloc_iface(kstrdup(netdev->name, GFP_KERNEL)); if (!iface) return NOTIFY_OK; ret = create_socket(iface); if (ret) break; } break; case NETDEV_DOWN: list_for_each_entry(iface, &iface_list, entry) { if (!strcmp(iface->name, netdev->name) && iface->state == IFACE_STATE_CONFIGURED) { tcp_stop_kthread(iface->ksmbd_kthread); iface->ksmbd_kthread = NULL; mutex_lock(&iface->sock_release_lock); tcp_destroy_socket(iface->ksmbd_socket); iface->ksmbd_socket = NULL; mutex_unlock(&iface->sock_release_lock); iface->state = IFACE_STATE_DOWN; break; } } break; } return NOTIFY_DONE; } static struct notifier_block ksmbd_netdev_notifier = { .notifier_call = ksmbd_netdev_event, }; int ksmbd_tcp_init(void) { register_netdevice_notifier(&ksmbd_netdev_notifier); return 0; } static void tcp_stop_kthread(struct task_struct *kthread) { int ret; if (!kthread) return; ret = kthread_stop(kthread); if (ret) pr_err("failed to stop forker thread\n"); } void ksmbd_tcp_destroy(void) { struct interface *iface, *tmp; unregister_netdevice_notifier(&ksmbd_netdev_notifier); list_for_each_entry_safe(iface, tmp, &iface_list, entry) { list_del(&iface->entry); kfree(iface->name); kfree(iface); } } static struct interface *alloc_iface(char *ifname) { struct interface *iface; if (!ifname) return NULL; iface = kzalloc(sizeof(struct interface), GFP_KERNEL); if (!iface) { kfree(ifname); return NULL; } iface->name = ifname; iface->state = IFACE_STATE_DOWN; list_add(&iface->entry, &iface_list); mutex_init(&iface->sock_release_lock); return iface; } int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz) { int sz = 0; if (!ifc_list_sz) { struct net_device *netdev; rtnl_lock(); for_each_netdev(&init_net, netdev) { if (netif_is_bridge_port(netdev)) continue; if (!alloc_iface(kstrdup(netdev->name, GFP_KERNEL))) return -ENOMEM; } rtnl_unlock(); bind_additional_ifaces = 1; return 0; } while (ifc_list_sz > 0) { if (!alloc_iface(kstrdup(ifc_list, GFP_KERNEL))) return -ENOMEM; sz = strlen(ifc_list); if (!sz) break; ifc_list += sz + 1; ifc_list_sz -= (sz + 1); } bind_additional_ifaces = 0; return 0; } static struct ksmbd_transport_ops ksmbd_tcp_transport_ops = { .read = ksmbd_tcp_read, .writev = ksmbd_tcp_writev, .disconnect = ksmbd_tcp_disconnect, };
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1