📄 proto.c
字号:
/* * net/dccp/proto.c * * An implementation of the DCCP protocol * Arnaldo Carvalho de Melo <acme@conectiva.com.br> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */#include <linux/dccp.h>#include <linux/module.h>#include <linux/types.h>#include <linux/sched.h>#include <linux/kernel.h>#include <linux/skbuff.h>#include <linux/netdevice.h>#include <linux/in.h>#include <linux/if_arp.h>#include <linux/init.h>#include <linux/random.h>#include <net/checksum.h>#include <net/inet_sock.h>#include <net/sock.h>#include <net/xfrm.h>#include <asm/ioctls.h>#include <asm/semaphore.h>#include <linux/spinlock.h>#include <linux/timer.h>#include <linux/delay.h>#include <linux/poll.h>#include "ccid.h"#include "dccp.h"#include "feat.h"DEFINE_SNMP_STAT(struct dccp_mib, dccp_statistics) __read_mostly;EXPORT_SYMBOL_GPL(dccp_statistics);atomic_t dccp_orphan_count = ATOMIC_INIT(0);EXPORT_SYMBOL_GPL(dccp_orphan_count);struct inet_hashinfo __cacheline_aligned dccp_hashinfo = { .lhash_lock = RW_LOCK_UNLOCKED, .lhash_users = ATOMIC_INIT(0), .lhash_wait = __WAIT_QUEUE_HEAD_INITIALIZER(dccp_hashinfo.lhash_wait),};EXPORT_SYMBOL_GPL(dccp_hashinfo);/* the maximum queue length for tx in packets. 0 is no limit */int sysctl_dccp_tx_qlen __read_mostly = 5;void dccp_set_state(struct sock *sk, const int state){ const int oldstate = sk->sk_state; dccp_pr_debug("%s(%p) %-10.10s -> %s\n", dccp_role(sk), sk, dccp_state_name(oldstate), dccp_state_name(state)); WARN_ON(state == oldstate); switch (state) { case DCCP_OPEN: if (oldstate != DCCP_OPEN) DCCP_INC_STATS(DCCP_MIB_CURRESTAB); break; case DCCP_CLOSED: if (oldstate == DCCP_CLOSING || oldstate == DCCP_OPEN) DCCP_INC_STATS(DCCP_MIB_ESTABRESETS); sk->sk_prot->unhash(sk); if (inet_csk(sk)->icsk_bind_hash != NULL && !(sk->sk_userlocks & SOCK_BINDPORT_LOCK)) inet_put_port(&dccp_hashinfo, sk); /* fall through */ default: if (oldstate == DCCP_OPEN) DCCP_DEC_STATS(DCCP_MIB_CURRESTAB); } /* Change state AFTER socket is unhashed to avoid closed * socket sitting in hash tables. */ sk->sk_state = state;}EXPORT_SYMBOL_GPL(dccp_set_state);void dccp_done(struct sock *sk){ dccp_set_state(sk, DCCP_CLOSED); dccp_clear_xmit_timers(sk); sk->sk_shutdown = SHUTDOWN_MASK; if (!sock_flag(sk, SOCK_DEAD)) sk->sk_state_change(sk); else inet_csk_destroy_sock(sk);}EXPORT_SYMBOL_GPL(dccp_done);const char *dccp_packet_name(const int type){ static const char *dccp_packet_names[] = { [DCCP_PKT_REQUEST] = "REQUEST", [DCCP_PKT_RESPONSE] = "RESPONSE", [DCCP_PKT_DATA] = "DATA", [DCCP_PKT_ACK] = "ACK", [DCCP_PKT_DATAACK] = "DATAACK", [DCCP_PKT_CLOSEREQ] = "CLOSEREQ", [DCCP_PKT_CLOSE] = "CLOSE", [DCCP_PKT_RESET] = "RESET", [DCCP_PKT_SYNC] = "SYNC", [DCCP_PKT_SYNCACK] = "SYNCACK", }; if (type >= DCCP_NR_PKT_TYPES) return "INVALID"; else return dccp_packet_names[type];}EXPORT_SYMBOL_GPL(dccp_packet_name);const char *dccp_state_name(const int state){ static char *dccp_state_names[] = { [DCCP_OPEN] = "OPEN", [DCCP_REQUESTING] = "REQUESTING", [DCCP_PARTOPEN] = "PARTOPEN", [DCCP_LISTEN] = "LISTEN", [DCCP_RESPOND] = "RESPOND", [DCCP_CLOSING] = "CLOSING", [DCCP_TIME_WAIT] = "TIME_WAIT", [DCCP_CLOSED] = "CLOSED", }; if (state >= DCCP_MAX_STATES) return "INVALID STATE!"; else return dccp_state_names[state];}EXPORT_SYMBOL_GPL(dccp_state_name);void dccp_hash(struct sock *sk){ inet_hash(&dccp_hashinfo, sk);}EXPORT_SYMBOL_GPL(dccp_hash);void dccp_unhash(struct sock *sk){ inet_unhash(&dccp_hashinfo, sk);}EXPORT_SYMBOL_GPL(dccp_unhash);int dccp_init_sock(struct sock *sk, const __u8 ctl_sock_initialized){ struct dccp_sock *dp = dccp_sk(sk); struct dccp_minisock *dmsk = dccp_msk(sk); struct inet_connection_sock *icsk = inet_csk(sk); dccp_minisock_init(&dp->dccps_minisock); /* * FIXME: We're hardcoding the CCID, and doing this at this point makes * the listening (master) sock get CCID control blocks, which is not * necessary, but for now, to not mess with the test userspace apps, * lets leave it here, later the real solution is to do this in a * setsockopt(CCIDs-I-want/accept). -acme */ if (likely(ctl_sock_initialized)) { int rc = dccp_feat_init(dmsk); if (rc) return rc; if (dmsk->dccpms_send_ack_vector) { dp->dccps_hc_rx_ackvec = dccp_ackvec_alloc(GFP_KERNEL); if (dp->dccps_hc_rx_ackvec == NULL) return -ENOMEM; } dp->dccps_hc_rx_ccid = ccid_hc_rx_new(dmsk->dccpms_rx_ccid, sk, GFP_KERNEL); dp->dccps_hc_tx_ccid = ccid_hc_tx_new(dmsk->dccpms_tx_ccid, sk, GFP_KERNEL); if (unlikely(dp->dccps_hc_rx_ccid == NULL || dp->dccps_hc_tx_ccid == NULL)) { ccid_hc_rx_delete(dp->dccps_hc_rx_ccid, sk); ccid_hc_tx_delete(dp->dccps_hc_tx_ccid, sk); if (dmsk->dccpms_send_ack_vector) { dccp_ackvec_free(dp->dccps_hc_rx_ackvec); dp->dccps_hc_rx_ackvec = NULL; } dp->dccps_hc_rx_ccid = dp->dccps_hc_tx_ccid = NULL; return -ENOMEM; } } else { /* control socket doesn't need feat nego */ INIT_LIST_HEAD(&dmsk->dccpms_pending); INIT_LIST_HEAD(&dmsk->dccpms_conf); } dccp_init_xmit_timers(sk); icsk->icsk_rto = DCCP_TIMEOUT_INIT; icsk->icsk_syn_retries = sysctl_dccp_request_retries; sk->sk_state = DCCP_CLOSED; sk->sk_write_space = dccp_write_space; icsk->icsk_sync_mss = dccp_sync_mss; dp->dccps_mss_cache = 536; dp->dccps_rate_last = jiffies; dp->dccps_role = DCCP_ROLE_UNDEFINED; dp->dccps_service = DCCP_SERVICE_CODE_IS_ABSENT; dp->dccps_l_ack_ratio = dp->dccps_r_ack_ratio = 1; return 0;}EXPORT_SYMBOL_GPL(dccp_init_sock);int dccp_destroy_sock(struct sock *sk){ struct dccp_sock *dp = dccp_sk(sk); struct dccp_minisock *dmsk = dccp_msk(sk); /* * DCCP doesn't use sk_write_queue, just sk_send_head * for retransmissions */ if (sk->sk_send_head != NULL) { kfree_skb(sk->sk_send_head); sk->sk_send_head = NULL; } /* Clean up a referenced DCCP bind bucket. */ if (inet_csk(sk)->icsk_bind_hash != NULL) inet_put_port(&dccp_hashinfo, sk); kfree(dp->dccps_service_list); dp->dccps_service_list = NULL; if (dmsk->dccpms_send_ack_vector) { dccp_ackvec_free(dp->dccps_hc_rx_ackvec); dp->dccps_hc_rx_ackvec = NULL; } ccid_hc_rx_delete(dp->dccps_hc_rx_ccid, sk); ccid_hc_tx_delete(dp->dccps_hc_tx_ccid, sk); dp->dccps_hc_rx_ccid = dp->dccps_hc_tx_ccid = NULL; /* clean up feature negotiation state */ dccp_feat_clean(dmsk); return 0;}EXPORT_SYMBOL_GPL(dccp_destroy_sock);static inline int dccp_listen_start(struct sock *sk, int backlog){ struct dccp_sock *dp = dccp_sk(sk); dp->dccps_role = DCCP_ROLE_LISTEN; return inet_csk_listen_start(sk, backlog);}int dccp_disconnect(struct sock *sk, int flags){ struct inet_connection_sock *icsk = inet_csk(sk); struct inet_sock *inet = inet_sk(sk); int err = 0; const int old_state = sk->sk_state; if (old_state != DCCP_CLOSED) dccp_set_state(sk, DCCP_CLOSED); /* ABORT function of RFC793 */ if (old_state == DCCP_LISTEN) { inet_csk_listen_stop(sk); /* FIXME: do the active reset thing */ } else if (old_state == DCCP_REQUESTING) sk->sk_err = ECONNRESET; dccp_clear_xmit_timers(sk); __skb_queue_purge(&sk->sk_receive_queue); if (sk->sk_send_head != NULL) { __kfree_skb(sk->sk_send_head); sk->sk_send_head = NULL; } inet->dport = 0; if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK)) inet_reset_saddr(sk); sk->sk_shutdown = 0; sock_reset_flag(sk, SOCK_DONE); icsk->icsk_backoff = 0; inet_csk_delack_init(sk); __sk_dst_reset(sk); BUG_TRAP(!inet->num || icsk->icsk_bind_hash); sk->sk_error_report(sk); return err;}EXPORT_SYMBOL_GPL(dccp_disconnect);/* * Wait for a DCCP event. * * Note that we don't need to lock the socket, as the upper poll layers * take care of normal races (between the test and the event) and we don't * go look at any of the socket buffers directly. */unsigned int dccp_poll(struct file *file, struct socket *sock, poll_table *wait){ unsigned int mask; struct sock *sk = sock->sk; poll_wait(file, sk->sk_sleep, wait); if (sk->sk_state == DCCP_LISTEN) return inet_csk_listen_poll(sk); /* Socket is not locked. We are protected from async events by poll logic and correct handling of state changes made by another threads is impossible in any case. */ mask = 0; if (sk->sk_err) mask = POLLERR; if (sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == DCCP_CLOSED) mask |= POLLHUP; if (sk->sk_shutdown & RCV_SHUTDOWN) mask |= POLLIN | POLLRDNORM | POLLRDHUP; /* Connected? */ if ((1 << sk->sk_state) & ~(DCCPF_REQUESTING | DCCPF_RESPOND)) { if (atomic_read(&sk->sk_rmem_alloc) > 0) mask |= POLLIN | POLLRDNORM; if (!(sk->sk_shutdown & SEND_SHUTDOWN)) { if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk)) { mask |= POLLOUT | POLLWRNORM; } else { /* send SIGIO later */ set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags); set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); /* Race breaker. If space is freed after * wspace test but before the flags are set, * IO signal will be lost. */ if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk)) mask |= POLLOUT | POLLWRNORM; } } } return mask;}EXPORT_SYMBOL_GPL(dccp_poll);int dccp_ioctl(struct sock *sk, int cmd, unsigned long arg){ int rc = -ENOTCONN; lock_sock(sk); if (sk->sk_state == DCCP_LISTEN) goto out; switch (cmd) { case SIOCINQ: { struct sk_buff *skb; unsigned long amount = 0; skb = skb_peek(&sk->sk_receive_queue); if (skb != NULL) { /* * We will only return the amount of this packet since * that is all that will be read. */ amount = skb->len; } rc = put_user(amount, (int __user *)arg); } break; default: rc = -ENOIOCTLCMD; break; }out: release_sock(sk); return rc;}EXPORT_SYMBOL_GPL(dccp_ioctl);static int dccp_setsockopt_service(struct sock *sk, const __be32 service, char __user *optval, int optlen){ struct dccp_sock *dp = dccp_sk(sk); struct dccp_service_list *sl = NULL; if (service == DCCP_SERVICE_INVALID_VALUE || optlen > DCCP_SERVICE_LIST_MAX_LEN * sizeof(u32)) return -EINVAL; if (optlen > sizeof(service)) { sl = kmalloc(optlen, GFP_KERNEL); if (sl == NULL) return -ENOMEM; sl->dccpsl_nr = optlen / sizeof(u32) - 1; if (copy_from_user(sl->dccpsl_list, optval + sizeof(service), optlen - sizeof(service)) || dccp_list_has_service(sl, DCCP_SERVICE_INVALID_VALUE)) { kfree(sl); return -EFAULT; } } lock_sock(sk); dp->dccps_service = service; kfree(dp->dccps_service_list); dp->dccps_service_list = sl; release_sock(sk); return 0;}/* byte 1 is feature. the rest is the preference list */static int dccp_setsockopt_change(struct sock *sk, int type, struct dccp_so_feat __user *optval){ struct dccp_so_feat opt; u8 *val; int rc; if (copy_from_user(&opt, optval, sizeof(opt))) return -EFAULT; val = kmalloc(opt.dccpsf_len, GFP_KERNEL); if (!val) return -ENOMEM; if (copy_from_user(val, opt.dccpsf_val, opt.dccpsf_len)) { rc = -EFAULT; goto out_free_val; } rc = dccp_feat_change(dccp_msk(sk), type, opt.dccpsf_feat, val, opt.dccpsf_len, GFP_KERNEL); if (rc) goto out_free_val;out: return rc;out_free_val: kfree(val); goto out;}static int do_dccp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, int optlen){ struct dccp_sock *dp = dccp_sk(sk); int val, err = 0; if (optlen < sizeof(int)) return -EINVAL; if (get_user(val, (int __user *)optval)) return -EFAULT; if (optname == DCCP_SOCKOPT_SERVICE) return dccp_setsockopt_service(sk, val, optval, optlen); lock_sock(sk); switch (optname) { case DCCP_SOCKOPT_PACKET_SIZE: DCCP_WARN("sockopt(PACKET_SIZE) is deprecated: fix your app\n"); err = 0; break; case DCCP_SOCKOPT_CHANGE_L: if (optlen != sizeof(struct dccp_so_feat)) err = -EINVAL; else err = dccp_setsockopt_change(sk, DCCPO_CHANGE_L, (struct dccp_so_feat __user *) optval); break; case DCCP_SOCKOPT_CHANGE_R: if (optlen != sizeof(struct dccp_so_feat)) err = -EINVAL; else err = dccp_setsockopt_change(sk, DCCPO_CHANGE_R, (struct dccp_so_feat __user *) optval); break; case DCCP_SOCKOPT_SEND_CSCOV: /* sender side, RFC 4340, sec. 9.2 */ if (val < 0 || val > 15) err = -EINVAL; else dp->dccps_pcslen = val; break; case DCCP_SOCKOPT_RECV_CSCOV: /* receiver side, RFC 4340 sec. 9.2.1 */ if (val < 0 || val > 15) err = -EINVAL; else { dp->dccps_pcrlen = val; /* FIXME: add feature negotiation, * ChangeL(MinimumChecksumCoverage, val) */ } break; default: err = -ENOPROTOOPT; break; } release_sock(sk); return err;}int dccp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, int optlen){ if (level != SOL_DCCP) return inet_csk(sk)->icsk_af_ops->setsockopt(sk, level, optname, optval, optlen); return do_dccp_setsockopt(sk, level, optname, optval, optlen);}EXPORT_SYMBOL_GPL(dccp_setsockopt);#ifdef CONFIG_COMPATint compat_dccp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, int optlen){ if (level != SOL_DCCP) return inet_csk_compat_setsockopt(sk, level, optname, optval, optlen); return do_dccp_setsockopt(sk, level, optname, optval, optlen);}EXPORT_SYMBOL_GPL(compat_dccp_setsockopt);#endifstatic int dccp_getsockopt_service(struct sock *sk, int len, __be32 __user *optval, int __user *optlen){ const struct dccp_sock *dp = dccp_sk(sk); const struct dccp_service_list *sl; int err = -ENOENT, slen = 0, total_len = sizeof(u32); lock_sock(sk); if ((sl = dp->dccps_service_list) != NULL) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -