1/*
2 * Copyright (c) 2012, 2015 The Linux Foundation. All rights reserved.
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 */
15
16#include <linux/kernel.h>
17#include <linux/netfilter.h>
18#include <linux/netdevice.h>
19#include <linux/etherdevice.h>
20#include <net/ip.h>
21#include <net/inet_ecn.h>
22#include <linux/netfilter_bridge.h>
23
24#include "mc_snooping.h"
25#include "mc_private.h"
26#include "mc_api.h"
27#include "mc_osdep.h"
28
29
30static struct net_bridge_port *mc_br_port_get(int ifindex)
31{
32	struct net_device *dev = NULL;
33	struct net_bridge_port *bp = NULL;
34
35	dev = dev_get_by_index(&init_net, ifindex);
36
37	if (dev) {
38		bp = os_br_port_get(dev);
39		dev_put(dev);
40	}
41	return bp;
42}
43
44#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0))
45static unsigned int mc_pre_routing_hook(const struct nf_hook_ops *ops, struct sk_buff *skb,
46					const struct net_device *in, const struct net_device *out,
47					int(*okfn)(struct sk_buff *))
48#else
49static unsigned int mc_pre_routing_hook(unsigned int hooknum, struct sk_buff *skb,
50					const struct net_device *in, const struct net_device *out,
51					int(*okfn)(struct sk_buff *))
52#endif
53{
54	struct mc_struct *mc = MC_DEV(in);
55	struct ethhdr *eh = eth_hdr(skb);
56	struct net_bridge_port *port;
57	u8 dscp;
58
59	if (!mc || skb->pkt_type != PACKET_HOST ||
60			(port = os_br_port_get(in)) == NULL)
61		goto out;
62
63	dscp = MC_DSCP(mc->dscp) & (~INET_ECN_MASK);
64
65	switch (ntohs(skb->protocol)) {
66	case ETH_P_IP:
67		{
68			const struct iphdr *iph = ip_hdr(skb);
69			if (ipv4_is_multicast(iph->daddr) && (!mc->enable_retag ||
70						(mc->enable_retag && (ipv4_get_dsfield(iph) == dscp)))) {
71				ip_eth_mc_map(iph->daddr, eh->h_dest);
72
73				if (mc->debug && printk_ratelimit()) {
74					MC_PRINT("Decap the group "MC_IP4_STR" back to "MC_MAC_STR"\n",
75							 MC_IP4_FMT((u8 *)(&iph->daddr)), MC_MAC_FMT(eh->h_dest));
76				}
77				skb->pkt_type = PACKET_MULTICAST;
78			}
79		}
80		break;
81#ifdef MC_SUPPORT_MLD
82	case ETH_P_IPV6:
83		{
84			struct ipv6hdr *iph6 = ipv6_hdr(skb);
85			if (ipv6_addr_is_multicast(&iph6->daddr) && (!mc->enable_retag ||
86						(mc->enable_retag && (ipv6_get_dsfield(iph6) == dscp)))) {
87				ipv6_eth_mc_map(&iph6->daddr, eh->h_dest);
88
89				if (mc->debug && printk_ratelimit()) {
90					MC_PRINT("Decap the group "MC_IP6_STR" back to "MC_MAC_STR"\n",
91							 MC_IP6_FMT((__be16 *)(&iph6->daddr)), MC_MAC_FMT(eh->h_dest));
92				}
93				skb->pkt_type = PACKET_MULTICAST;
94			}
95		}
96		break;
97#endif
98	}
99out:
100	return NF_ACCEPT;
101}
102
103#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0))
104static unsigned int mc_forward_hook(const struct nf_hook_ops *ops, struct sk_buff *skb,
105		const struct net_device *in, const struct net_device *out,
106		int(*okfn)(struct sk_buff *))
107#else
108static unsigned int mc_forward_hook(unsigned int hooknum, struct sk_buff *skb,
109		const struct net_device *in, const struct net_device *out,
110		int(*okfn)(struct sk_buff *))
111#endif
112{
113	struct mc_struct *mc = MC_DEV(out);
114	struct hlist_head *rhead = NULL;
115	struct net_bridge_port *port;
116
117	if (MC_SKB_CB(skb)->type != MC_LEAVE &&
118			MC_SKB_CB(skb)->type != MC_REPORT)
119		goto accept;
120
121	if ((port = os_br_port_get(in)) == NULL || !mc)
122		goto accept;
123
124	/* Report/Leave forward */
125	if (mc->rp.type == MC_RTPORT_DEFAULT) {
126		if (ntohs(skb->protocol) == ETH_P_IP)
127			rhead = &mc->rp.igmp_rlist;
128#ifdef MC_SUPPORT_MLD
129		else
130			rhead = &mc->rp.mld_rlist;
131#endif
132		if (!hlist_empty(rhead)) {
133			struct mc_querier_entry *qe;
134			struct hlist_node *h;
135
136			os_hlist_for_each_entry_rcu(qe, h, rhead, rlist) {
137				if (((struct net_bridge_port *)qe->port)->dev == out)
138					goto accept;
139			}
140			goto drop;
141		}
142	} else if (mc->rp.type == MC_RTPORT_SPECIFY) {
143		port = mc_br_port_get(mc->rp.ifindex);
144		if (!port || port->dev != out)
145			goto drop;
146	} else {
147		goto drop;
148	}
149accept:
150	return NF_ACCEPT;
151drop:
152	return NF_DROP;
153}
154
155static struct nf_hook_ops mc_hook_ops[] __read_mostly = {
156	{
157		.pf = NFPROTO_BRIDGE,
158		.priority = 1,
159		.hooknum = NF_BR_PRE_ROUTING,
160		.hook = mc_pre_routing_hook,
161		.owner = THIS_MODULE,
162	},
163	{
164		.pf = NFPROTO_BRIDGE,
165		.priority = 1,
166		.hooknum = NF_BR_FORWARD,
167		.hook = mc_forward_hook,
168		.owner = THIS_MODULE,
169	}
170};
171
172
173int __init mc_netfilter_init(void)
174{
175	int ret = 0;
176	ret = nf_register_hook(&mc_hook_ops[0]);
177	ret |= nf_register_hook(&mc_hook_ops[1]);
178	return ret;
179}
180
181void mc_netfilter_exit(void)
182{
183	nf_unregister_hook(&mc_hook_ops[0]);
184	nf_unregister_hook(&mc_hook_ops[1]);
185}
186
187