Skip to content
Snippets Groups Projects
af_inet.c 36.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out_permanent;
    
    	/* Add the new entry after the last permanent entry if any, so that
    	 * the new entry does not override a permanent entry when matched with
    	 * a wild-card protocol. But it is allowed to override any existing
    
    	 * non-permanent entry.  This means that when we remove this entry, the
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 * system automatically returns to the old behavior.
    	 */
    	list_add_rcu(&p->list, last_perm);
    out:
    	spin_unlock_bh(&inetsw_lock);
    
    	synchronize_net();
    
    	return;
    
    out_permanent:
    	printk(KERN_ERR "Attempt to override permanent protocol %d.\n",
    	       protocol);
    	goto out;
    
    out_illegal:
    	printk(KERN_ERR
    	       "Ignoring attempt to register invalid socket type %d.\n",
    	       p->type);
    	goto out;
    }
    
    void inet_unregister_protosw(struct inet_protosw *p)
    {
    	if (INET_PROTOSW_PERMANENT & p->flags) {
    		printk(KERN_ERR
    		       "Attempt to unregister permanent protocol %d.\n",
    		       p->protocol);
    	} else {
    		spin_lock_bh(&inetsw_lock);
    		list_del_rcu(&p->list);
    		spin_unlock_bh(&inetsw_lock);
    
    		synchronize_net();
    	}
    }
    
    
    /*
     *      Shall we try to damage output packets if routing dev changes?
     */
    
    
    int sysctl_ip_dynaddr __read_mostly;
    
    
    static int inet_sk_reselect_saddr(struct sock *sk)
    {
    	struct inet_sock *inet = inet_sk(sk);
    	int err;
    	struct rtable *rt;
    
    	__be32 old_saddr = inet->saddr;
    	__be32 new_saddr;
    
    	__be32 daddr = inet->daddr;
    
    
    	if (inet->opt && inet->opt->srr)
    		daddr = inet->opt->faddr;
    
    	/* Query new route. */
    	err = ip_route_connect(&rt, daddr, 0,
    			       RT_CONN_FLAGS(sk),
    			       sk->sk_bound_dev_if,
    			       sk->sk_protocol,
    
    	if (err)
    		return err;
    
    	sk_setup_caps(sk, &rt->u.dst);
    
    	new_saddr = rt->rt_src;
    
    	if (new_saddr == old_saddr)
    		return 0;
    
    	if (sysctl_ip_dynaddr > 1) {
    		printk(KERN_INFO "%s(): shifting inet->"
    
    				 "saddr from " NIPQUAD_FMT " to " NIPQUAD_FMT "\n",
    
    		       NIPQUAD(old_saddr),
    		       NIPQUAD(new_saddr));
    	}
    
    	inet->saddr = inet->rcv_saddr = new_saddr;
    
    	/*
    	 * XXX The only one ugly spot where we need to
    	 * XXX really change the sockets identity after
    	 * XXX it has entered the hashes. -DaveM
    	 *
    	 * Besides that, it does not check for connection
    	 * uniqueness. Wait for troubles.
    	 */
    	__sk_prot_rehash(sk);
    	return 0;
    }
    
    int inet_sk_rebuild_header(struct sock *sk)
    {
    	struct inet_sock *inet = inet_sk(sk);
    	struct rtable *rt = (struct rtable *)__sk_dst_check(sk, 0);
    
    	__be32 daddr;
    
    	int err;
    
    	/* Route is OK, nothing to do. */
    	if (rt)
    		return 0;
    
    	/* Reroute. */
    	daddr = inet->daddr;
    	if (inet->opt && inet->opt->srr)
    		daddr = inet->opt->faddr;
    {
    	struct flowi fl = {
    		.oif = sk->sk_bound_dev_if,
    		.nl_u = {
    			.ip4_u = {
    				.daddr	= daddr,
    				.saddr	= inet->saddr,
    				.tos	= RT_CONN_FLAGS(sk),
    			},
    		},
    		.proto = sk->sk_protocol,
    		.uli_u = {
    			.ports = {
    				.sport = inet->sport,
    				.dport = inet->dport,
    			},
    		},
    	};
    
    	security_sk_classify_flow(sk, &fl);
    
    	err = ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0);
    
    }
    	if (!err)
    		sk_setup_caps(sk, &rt->u.dst);
    	else {
    		/* Routing failed... */
    		sk->sk_route_caps = 0;
    		/*
    		 * Other protocols have to map its equivalent state to TCP_SYN_SENT.
    		 * DCCP maps its DCCP_REQUESTING state to TCP_SYN_SENT. -acme
    		 */
    		if (!sysctl_ip_dynaddr ||
    		    sk->sk_state != TCP_SYN_SENT ||
    		    (sk->sk_userlocks & SOCK_BINDADDR_LOCK) ||
    		    (err = inet_sk_reselect_saddr(sk)) != 0)
    			sk->sk_err_soft = -err;
    	}
    
    	return err;
    }
    
    EXPORT_SYMBOL(inet_sk_rebuild_header);
    
    
    static int inet_gso_send_check(struct sk_buff *skb)
    {
    	struct iphdr *iph;
    	struct net_protocol *ops;
    	int proto;
    	int ihl;
    	int err = -EINVAL;
    
    	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
    		goto out;
    
    
    	ihl = iph->ihl * 4;
    	if (ihl < sizeof(*iph))
    		goto out;
    
    	if (unlikely(!pskb_may_pull(skb, ihl)))
    		goto out;
    
    
    	__skb_pull(skb, ihl);
    	skb_reset_transport_header(skb);
    
    	proto = iph->protocol & (MAX_INET_PROTOS - 1);
    	err = -EPROTONOSUPPORT;
    
    	rcu_read_lock();
    	ops = rcu_dereference(inet_protos[proto]);
    	if (likely(ops && ops->gso_send_check))
    		err = ops->gso_send_check(skb);
    	rcu_read_unlock();
    
    out:
    	return err;
    }
    
    
    static struct sk_buff *inet_gso_segment(struct sk_buff *skb, int features)
    
    Herbert Xu's avatar
    Herbert Xu committed
    {
    	struct sk_buff *segs = ERR_PTR(-EINVAL);
    	struct iphdr *iph;
    	struct net_protocol *ops;
    	int proto;
    	int ihl;
    	int id;
    
    
    	if (!(features & NETIF_F_V4_CSUM))
    		features &= ~NETIF_F_SG;
    
    
    	if (unlikely(skb_shinfo(skb)->gso_type &
    		     ~(SKB_GSO_TCPV4 |
    		       SKB_GSO_UDP |
    		       SKB_GSO_DODGY |
    		       SKB_GSO_TCP_ECN |
    		       0)))
    		goto out;
    
    	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
    
    Herbert Xu's avatar
    Herbert Xu committed
    		goto out;
    
    
    Herbert Xu's avatar
    Herbert Xu committed
    	ihl = iph->ihl * 4;
    	if (ihl < sizeof(*iph))
    		goto out;
    
    
    	if (unlikely(!pskb_may_pull(skb, ihl)))
    
    Herbert Xu's avatar
    Herbert Xu committed
    		goto out;
    
    
    	__skb_pull(skb, ihl);
    	skb_reset_transport_header(skb);
    
    Herbert Xu's avatar
    Herbert Xu committed
    	id = ntohs(iph->id);
    	proto = iph->protocol & (MAX_INET_PROTOS - 1);
    	segs = ERR_PTR(-EPROTONOSUPPORT);
    
    	rcu_read_lock();
    	ops = rcu_dereference(inet_protos[proto]);
    
    	if (likely(ops && ops->gso_segment))
    
    		segs = ops->gso_segment(skb, features);
    
    Herbert Xu's avatar
    Herbert Xu committed
    	rcu_read_unlock();
    
    
    	if (!segs || unlikely(IS_ERR(segs)))
    
    Herbert Xu's avatar
    Herbert Xu committed
    		goto out;
    
    	skb = segs;
    	do {
    
    Herbert Xu's avatar
    Herbert Xu committed
    		iph->id = htons(id++);
    		iph->tot_len = htons(skb->len - skb->mac_len);
    		iph->check = 0;
    
    		iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);
    
    Herbert Xu's avatar
    Herbert Xu committed
    	} while ((skb = skb->next));
    
    out:
    	return segs;
    }
    
    
    int inet_ctl_sock_create(struct sock **sk, unsigned short family,
    
    			 unsigned short type, unsigned char protocol,
    			 struct net *net)
    
    	struct socket *sock;
    	int rc = sock_create_kern(family, type, protocol, &sock);
    
    		*sk = sock->sk;
    		(*sk)->sk_allocation = GFP_ATOMIC;
    
    		/*
    		 * Unhash it so that IP input processing does not even see it,
    		 * we do not wish this socket to see incoming packets.
    		 */
    
    unsigned long snmp_fold_field(void *mib[], int offt)
    {
    	unsigned long res = 0;
    	int i;
    
    	for_each_possible_cpu(i) {
    		res += *(((unsigned long *) per_cpu_ptr(mib[0], i)) + offt);
    		res += *(((unsigned long *) per_cpu_ptr(mib[1], i)) + offt);
    	}
    	return res;
    }
    EXPORT_SYMBOL_GPL(snmp_fold_field);
    
    
    int snmp_mib_init(void *ptr[2], size_t mibsize)
    
    {
    	BUG_ON(ptr == NULL);
    	ptr[0] = __alloc_percpu(mibsize);
    	if (!ptr[0])
    		goto err0;
    	ptr[1] = __alloc_percpu(mibsize);
    	if (!ptr[1])
    		goto err1;
    	return 0;
    err1:
    	free_percpu(ptr[0]);
    	ptr[0] = NULL;
    err0:
    	return -ENOMEM;
    }
    EXPORT_SYMBOL_GPL(snmp_mib_init);
    
    void snmp_mib_free(void *ptr[2])
    {
    	BUG_ON(ptr == NULL);
    	free_percpu(ptr[0]);
    	free_percpu(ptr[1]);
    	ptr[0] = ptr[1] = NULL;
    }
    EXPORT_SYMBOL_GPL(snmp_mib_free);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IP_MULTICAST
    static struct net_protocol igmp_protocol = {
    	.handler =	igmp_rcv,
    };
    #endif
    
    static struct net_protocol tcp_protocol = {
    	.handler =	tcp_v4_rcv,
    	.err_handler =	tcp_v4_err,
    
    	.gso_send_check = tcp_v4_gso_send_check,
    
    Herbert Xu's avatar
    Herbert Xu committed
    	.gso_segment =	tcp_tso_segment,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.no_policy =	1,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    static struct net_protocol udp_protocol = {
    	.handler =	udp_rcv,
    	.err_handler =	udp_err,
    	.no_policy =	1,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    static struct net_protocol icmp_protocol = {
    	.handler =	icmp_rcv,
    
    	.no_policy =	1,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    static int __init init_ipv4_mibs(void)
    {
    
    	if (snmp_mib_init((void **)net_statistics,
    
    		goto err_net_mib;
    	if (snmp_mib_init((void **)ip_statistics,
    
    			  sizeof(struct ipstats_mib)) < 0)
    
    		goto err_ip_mib;
    	if (snmp_mib_init((void **)icmp_statistics,
    
    	if (snmp_mib_init((void **)icmpmsg_statistics,
    
    			  sizeof(struct icmpmsg_mib)) < 0)
    
    		goto err_icmpmsg_mib;
    
    	if (snmp_mib_init((void **)tcp_statistics,
    
    		goto err_tcp_mib;
    	if (snmp_mib_init((void **)udp_statistics,
    
    		goto err_udp_mib;
    	if (snmp_mib_init((void **)udplite_statistics,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    
    
    err_udplite_mib:
    	snmp_mib_free((void **)udp_statistics);
    err_udp_mib:
    	snmp_mib_free((void **)tcp_statistics);
    err_tcp_mib:
    
    	snmp_mib_free((void **)icmpmsg_statistics);
    err_icmpmsg_mib:
    
    	snmp_mib_free((void **)icmp_statistics);
    err_icmp_mib:
    	snmp_mib_free((void **)ip_statistics);
    err_ip_mib:
    	snmp_mib_free((void **)net_statistics);
    err_net_mib:
    	return -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int ipv4_proc_init(void);
    
    
    /*
     *	IP protocol layer initialiser
     */
    
    static struct packet_type ip_packet_type = {
    	.type = __constant_htons(ETH_P_IP),
    	.func = ip_rcv,
    
    	.gso_send_check = inet_gso_send_check,
    
    Herbert Xu's avatar
    Herbert Xu committed
    	.gso_segment = inet_gso_segment,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int __init inet_init(void)
    {
    	struct sk_buff *dummy_skb;
    	struct inet_protosw *q;
    	struct list_head *r;
    	int rc = -EINVAL;
    
    
    	BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	rc = proto_register(&tcp_prot, 1);
    	if (rc)
    		goto out;
    
    	rc = proto_register(&udp_prot, 1);
    	if (rc)
    		goto out_unregister_tcp_proto;
    
    	rc = proto_register(&raw_prot, 1);
    	if (rc)
    		goto out_unregister_udp_proto;
    
    	/*
    
    	 *	Tell SOCKET that we are alive...
    
    	(void)sock_register(&inet_family_ops);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	Add all the base protocols.
    	 */
    
    	if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
    	if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
    	if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
    #ifdef CONFIG_IP_MULTICAST
    	if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
    #endif
    
    	/* Register the socket-side information for inet_create. */
    	for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
    		INIT_LIST_HEAD(r);
    
    	for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
    		inet_register_protosw(q);
    
    	/*
    	 *	Set the ARP module up
    	 */
    
    	arp_init();
    
    
    	/*
    	 *	Set the IP module up
    	 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	ip_init();
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Setup TCP slab cache for open requests. */
    	tcp_init();
    
    
    	/* Setup UDP memory threshold */
    	udp_init();
    
    
    	/* Add UDP-Lite (RFC 3828) */
    	udplite4_register();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	Set the ICMP layer up
    	 */
    
    
    	if (icmp_init() < 0)
    		panic("Failed to create the ICMP control socket.\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	Initialise the multicast router
    	 */
    #if defined(CONFIG_IP_MROUTE)
    	ip_mr_init();
    #endif
    	/*
    	 *	Initialise per-cpu ipv4 mibs
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	if (init_ipv4_mibs())
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs\n"); ;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	ipv4_proc_init();
    
    	ipfrag_init();
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	rc = 0;
    out:
    	return rc;
    out_unregister_udp_proto:
    	proto_unregister(&udp_prot);
    
    out_unregister_tcp_proto:
    	proto_unregister(&tcp_prot);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	goto out;
    }
    
    
    fs_initcall(inet_init);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* ------------------------------------------------------------------------ */
    
    #ifdef CONFIG_PROC_FS
    static int __init ipv4_proc_init(void)
    {
    	int rc = 0;
    
    	if (raw_proc_init())
    		goto out_raw;
    	if (tcp4_proc_init())
    		goto out_tcp;
    	if (udp4_proc_init())
    		goto out_udp;
    	if (ip_misc_proc_init())
    		goto out_misc;
    out:
    	return rc;
    out_misc:
    	udp4_proc_exit();
    out_udp:
    	tcp4_proc_exit();
    out_tcp:
    	raw_proc_exit();
    out_raw:
    	rc = -ENOMEM;
    	goto out;
    }
    
    #else /* CONFIG_PROC_FS */
    static int __init ipv4_proc_init(void)
    {
    	return 0;
    }
    #endif /* CONFIG_PROC_FS */
    
    MODULE_ALIAS_NETPROTO(PF_INET);
    
    EXPORT_SYMBOL(inet_accept);
    EXPORT_SYMBOL(inet_bind);
    EXPORT_SYMBOL(inet_dgram_connect);
    EXPORT_SYMBOL(inet_dgram_ops);
    EXPORT_SYMBOL(inet_getname);
    EXPORT_SYMBOL(inet_ioctl);
    EXPORT_SYMBOL(inet_listen);
    EXPORT_SYMBOL(inet_register_protosw);
    EXPORT_SYMBOL(inet_release);
    EXPORT_SYMBOL(inet_sendmsg);
    EXPORT_SYMBOL(inet_shutdown);
    EXPORT_SYMBOL(inet_sock_destruct);
    EXPORT_SYMBOL(inet_stream_connect);
    EXPORT_SYMBOL(inet_stream_ops);
    EXPORT_SYMBOL(inet_unregister_protosw);
    EXPORT_SYMBOL(net_statistics);
    
    EXPORT_SYMBOL(sysctl_ip_nonlocal_bind);