diff options
Diffstat (limited to 'fs/smb/server/transport_tcp.c')
| -rw-r--r-- | fs/smb/server/transport_tcp.c | 262 |
1 files changed, 147 insertions, 115 deletions
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c index eff7a1d793f0..4bb07937d7ef 100644 --- a/fs/smb/server/transport_tcp.c +++ b/fs/smb/server/transport_tcp.c @@ -22,7 +22,6 @@ struct interface { struct socket *ksmbd_socket; struct list_head entry; char *name; - struct mutex sock_release_lock; int state; }; @@ -37,7 +36,7 @@ struct tcp_transport { unsigned int nr_iov; }; -static struct ksmbd_transport_ops ksmbd_tcp_transport_ops; +static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops; static void tcp_stop_kthread(struct task_struct *kthread); static struct interface *alloc_iface(char *ifname); @@ -56,27 +55,12 @@ 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); + t = kzalloc(sizeof(*t), KSMBD_DEFAULT_GFP); if (!t) return NULL; t->sock = client_sk; @@ -87,23 +71,43 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk) return NULL; } +#if IS_ENABLED(CONFIG_IPV6) + if (client_sk->sk->sk_family == AF_INET6) { + memcpy(&conn->inet6_addr, &client_sk->sk->sk_v6_daddr, 16); + conn->inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr); + } else { + conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr; + conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr); + } +#else + conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr; + conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr); +#endif + down_write(&conn_list_lock); + hash_add(conn_list, &conn->hlist, conn->inet_hash); + up_write(&conn_list_lock); + 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) +static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt) { - kernel_sock_shutdown(t->sock, SHUT_RDWR); - sock_release(t->sock); - t->sock = NULL; + struct tcp_transport *t = TCP_TRANS(kt); - ksmbd_conn_free(KSMBD_TRANS(t)->conn); + sock_release(t->sock); kfree(t->iov); kfree(t); } +static void free_transport(struct tcp_transport *t) +{ + kernel_sock_shutdown(t->sock, SHUT_RDWR); + ksmbd_conn_free(KSMBD_TRANS(t)->conn); +} + /** * kvec_array_init() - initialize a IO vector segment * @new: IO vector to be initialized @@ -151,7 +155,7 @@ static struct kvec *get_conn_iovec(struct tcp_transport *t, unsigned int nr_segs 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); + new_iov = kmalloc_array(nr_segs, sizeof(*new_iov), KSMBD_DEFAULT_GFP); if (new_iov) { kfree(t->iov); t->iov = new_iov; @@ -160,17 +164,6 @@ static struct kvec *get_conn_iovec(struct tcp_transport *t, unsigned int 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 @@ -182,9 +175,9 @@ static unsigned short ksmbd_tcp_get_port(const struct sockaddr *sa) */ 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) { @@ -192,27 +185,26 @@ static int ksmbd_tcp_new_connection(struct socket *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; - } - - KSMBD_TRANS(t)->handler = kthread_run(ksmbd_conn_handler_loop, - KSMBD_TRANS(t)->conn, - "ksmbd:%u", - ksmbd_tcp_get_port(csin)); - if (IS_ERR(KSMBD_TRANS(t)->handler)) { +#if IS_ENABLED(CONFIG_IPV6) + if (client_sk->sk->sk_family == AF_INET6) + handler = kthread_run(ksmbd_conn_handler_loop, + KSMBD_TRANS(t)->conn, "ksmbd:%pI6c", + &KSMBD_TRANS(t)->conn->inet6_addr); + else + handler = kthread_run(ksmbd_conn_handler_loop, + KSMBD_TRANS(t)->conn, "ksmbd:%pI4", + &KSMBD_TRANS(t)->conn->inet_addr); +#else + handler = kthread_run(ksmbd_conn_handler_loop, + KSMBD_TRANS(t)->conn, "ksmbd:%pI4", + &KSMBD_TRANS(t)->conn->inet_addr); +#endif + if (IS_ERR(handler)) { pr_err("cannot start conn thread\n"); - rc = PTR_ERR(KSMBD_TRANS(t)->handler); + rc = PTR_ERR(handler); free_transport(t); } return rc; - -out_error: - free_transport(t); - return rc; } /** @@ -225,24 +217,66 @@ static int ksmbd_kthread_fn(void *p) { struct socket *client_sk = NULL; struct interface *iface = (struct interface *)p; - int ret; + struct ksmbd_conn *conn; + int ret, inet_hash; + unsigned int max_ip_conns; 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); + ret = kernel_accept(iface->ksmbd_socket, &client_sk, 0); + if (ret == -EINVAL) + break; + if (ret) + continue; + + if (!server_conf.max_ip_connections) + goto skip_max_ip_conns_limit; + + /* + * Limits repeated connections from clients with the same IP. + */ +#if IS_ENABLED(CONFIG_IPV6) + if (client_sk->sk->sk_family == AF_INET6) + inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr); + else + inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr); +#else + inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr); +#endif + + max_ip_conns = 0; + down_read(&conn_list_lock); + hash_for_each_possible(conn_list, conn, hlist, inet_hash) { +#if IS_ENABLED(CONFIG_IPV6) + if (client_sk->sk->sk_family == AF_INET6) { + if (memcmp(&client_sk->sk->sk_v6_daddr, + &conn->inet6_addr, 16) == 0) + max_ip_conns++; + } else if (inet_sk(client_sk->sk)->inet_daddr == + conn->inet_addr) + max_ip_conns++; +#else + if (inet_sk(client_sk->sk)->inet_daddr == + conn->inet_addr) + max_ip_conns++; +#endif + if (server_conf.max_ip_connections <= max_ip_conns) { + pr_info_ratelimited("Maximum IP connections exceeded (%u/%u)\n", + max_ip_conns, server_conf.max_ip_connections); + ret = -EAGAIN; + break; + } + } + up_read(&conn_list_lock); + if (ret == -EAGAIN) { + /* Per-IP limit hit: release the just-accepted socket. */ + sock_release(client_sk); continue; } +skip_max_ip_conns_limit: 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", @@ -364,6 +398,7 @@ static int ksmbd_tcp_readv(struct tcp_transport *t, struct kvec *iov_orig, * @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 @@ -403,10 +438,6 @@ static void tcp_destroy_socket(struct socket *ksmbd_socket) 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); @@ -415,6 +446,7 @@ static void tcp_destroy_socket(struct socket *ksmbd_socket) /** * create_socket - create socket for ksmbd/0 + * @iface: interface to bind the created socket to * * Return: 0 on success, error number otherwise */ @@ -426,12 +458,13 @@ static int create_socket(struct interface *iface) struct socket *ksmbd_socket; bool ipv4 = false; - ret = sock_create(PF_INET6, SOCK_STREAM, IPPROTO_TCP, &ksmbd_socket); + ret = sock_create_kern(current->nsproxy->net_ns, 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); + ret = sock_create_kern(current->nsproxy->net_ns, PF_INET, + SOCK_STREAM, IPPROTO_TCP, &ksmbd_socket); if (ret) { pr_err("Can't create socket for ipv4: %d\n", ret); goto out_clear; @@ -445,6 +478,10 @@ static int create_socket(struct interface *iface) 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); @@ -461,19 +498,16 @@ static int create_socket(struct interface *iface) } if (ipv4) - ret = kernel_bind(ksmbd_socket, (struct sockaddr *)&sin, + ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin, sizeof(sin)); else - ret = kernel_bind(ksmbd_socket, (struct sockaddr *)&sin6, + ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&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); @@ -497,52 +531,60 @@ out_clear: return ret; } +struct interface *ksmbd_find_netdev_name_iface_list(char *netdev_name) +{ + struct interface *iface; + + list_for_each_entry(iface, &iface_list, entry) + if (!strcmp(iface->name, netdev_name)) + return iface; + return NULL; +} + 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; + int ret; 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; - } + iface = ksmbd_find_netdev_name_iface_list(netdev->name); + if (iface && iface->state == IFACE_STATE_DOWN) { + ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n", + iface->name); + ret = create_socket(iface); + if (ret) + return NOTIFY_OK; } - if (!found && bind_additional_ifaces) { - iface = alloc_iface(kstrdup(netdev->name, GFP_KERNEL)); + if (!iface && bind_additional_ifaces) { + iface = alloc_iface(kstrdup(netdev->name, KSMBD_DEFAULT_GFP)); if (!iface) return NOTIFY_OK; + ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n", + iface->name); 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; - } + iface = ksmbd_find_netdev_name_iface_list(netdev->name); + if (iface && iface->state == IFACE_STATE_CONFIGURED) { + ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n", + iface->name); + kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR); + tcp_stop_kthread(iface->ksmbd_kthread); + iface->ksmbd_kthread = NULL; + sock_release(iface->ksmbd_socket); + iface->ksmbd_socket = NULL; + + iface->state = IFACE_STATE_DOWN; + break; } break; } @@ -593,7 +635,7 @@ static struct interface *alloc_iface(char *ifname) if (!ifname) return NULL; - iface = kzalloc(sizeof(struct interface), GFP_KERNEL); + iface = kzalloc(sizeof(struct interface), KSMBD_DEFAULT_GFP); if (!iface) { kfree(ifname); return NULL; @@ -602,7 +644,6 @@ static struct interface *alloc_iface(char *ifname) iface->name = ifname; iface->state = IFACE_STATE_DOWN; list_add(&iface->entry, &iface_list); - mutex_init(&iface->sock_release_lock); return iface; } @@ -611,22 +652,12 @@ 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))) + if (!alloc_iface(kstrdup(ifc_list, KSMBD_DEFAULT_GFP))) return -ENOMEM; sz = strlen(ifc_list); @@ -642,8 +673,9 @@ int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz) return 0; } -static struct ksmbd_transport_ops ksmbd_tcp_transport_ops = { +static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = { .read = ksmbd_tcp_read, .writev = ksmbd_tcp_writev, .disconnect = ksmbd_tcp_disconnect, + .free_transport = ksmbd_tcp_free_transport, }; |
