1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * xt_ipvs - kernel module to match IPVS connection properties |
4 | * |
5 | * Author: Hannes Eder <heder@google.com> |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/moduleparam.h> |
12 | #include <linux/spinlock.h> |
13 | #include <linux/skbuff.h> |
14 | #ifdef CONFIG_IP_VS_IPV6 |
15 | #include <net/ipv6.h> |
16 | #endif |
17 | #include <linux/ip_vs.h> |
18 | #include <linux/types.h> |
19 | #include <linux/netfilter/x_tables.h> |
20 | #include <linux/netfilter/xt_ipvs.h> |
21 | #include <net/netfilter/nf_conntrack.h> |
22 | |
23 | #include <net/ip_vs.h> |
24 | |
25 | MODULE_AUTHOR("Hannes Eder <heder@google.com>" ); |
26 | MODULE_DESCRIPTION("Xtables: match IPVS connection properties" ); |
27 | MODULE_LICENSE("GPL" ); |
28 | MODULE_ALIAS("ipt_ipvs" ); |
29 | MODULE_ALIAS("ip6t_ipvs" ); |
30 | |
31 | /* borrowed from xt_conntrack */ |
32 | static bool ipvs_mt_addrcmp(const union nf_inet_addr *kaddr, |
33 | const union nf_inet_addr *uaddr, |
34 | const union nf_inet_addr *umask, |
35 | unsigned int l3proto) |
36 | { |
37 | if (l3proto == NFPROTO_IPV4) |
38 | return ((kaddr->ip ^ uaddr->ip) & umask->ip) == 0; |
39 | #ifdef CONFIG_IP_VS_IPV6 |
40 | else if (l3proto == NFPROTO_IPV6) |
41 | return ipv6_masked_addr_cmp(a1: &kaddr->in6, m: &umask->in6, |
42 | a2: &uaddr->in6) == 0; |
43 | #endif |
44 | else |
45 | return false; |
46 | } |
47 | |
48 | static bool |
49 | ipvs_mt(const struct sk_buff *skb, struct xt_action_param *par) |
50 | { |
51 | const struct xt_ipvs_mtinfo *data = par->matchinfo; |
52 | struct netns_ipvs *ipvs = net_ipvs(net: xt_net(par)); |
53 | /* ipvs_mt_check ensures that family is only NFPROTO_IPV[46]. */ |
54 | const u_int8_t family = xt_family(par); |
55 | struct ip_vs_iphdr iph; |
56 | struct ip_vs_protocol *pp; |
57 | struct ip_vs_conn *cp; |
58 | bool match = true; |
59 | |
60 | if (data->bitmask == XT_IPVS_IPVS_PROPERTY) { |
61 | match = skb->ipvs_property ^ |
62 | !!(data->invert & XT_IPVS_IPVS_PROPERTY); |
63 | goto out; |
64 | } |
65 | |
66 | /* other flags than XT_IPVS_IPVS_PROPERTY are set */ |
67 | if (!skb->ipvs_property) { |
68 | match = false; |
69 | goto out; |
70 | } |
71 | |
72 | ip_vs_fill_iph_skb(af: family, skb, inverse: true, iphdr: &iph); |
73 | |
74 | if (data->bitmask & XT_IPVS_PROTO) |
75 | if ((iph.protocol == data->l4proto) ^ |
76 | !(data->invert & XT_IPVS_PROTO)) { |
77 | match = false; |
78 | goto out; |
79 | } |
80 | |
81 | pp = ip_vs_proto_get(proto: iph.protocol); |
82 | if (unlikely(!pp)) { |
83 | match = false; |
84 | goto out; |
85 | } |
86 | |
87 | /* |
88 | * Check if the packet belongs to an existing entry |
89 | */ |
90 | cp = pp->conn_out_get(ipvs, family, skb, &iph); |
91 | if (unlikely(cp == NULL)) { |
92 | match = false; |
93 | goto out; |
94 | } |
95 | |
96 | /* |
97 | * We found a connection, i.e. ct != 0, make sure to call |
98 | * __ip_vs_conn_put before returning. In our case jump to out_put_con. |
99 | */ |
100 | |
101 | if (data->bitmask & XT_IPVS_VPORT) |
102 | if ((cp->vport == data->vport) ^ |
103 | !(data->invert & XT_IPVS_VPORT)) { |
104 | match = false; |
105 | goto out_put_cp; |
106 | } |
107 | |
108 | if (data->bitmask & XT_IPVS_VPORTCTL) |
109 | if ((cp->control != NULL && |
110 | cp->control->vport == data->vportctl) ^ |
111 | !(data->invert & XT_IPVS_VPORTCTL)) { |
112 | match = false; |
113 | goto out_put_cp; |
114 | } |
115 | |
116 | if (data->bitmask & XT_IPVS_DIR) { |
117 | enum ip_conntrack_info ctinfo; |
118 | struct nf_conn *ct = nf_ct_get(skb, ctinfo: &ctinfo); |
119 | |
120 | if (ct == NULL) { |
121 | match = false; |
122 | goto out_put_cp; |
123 | } |
124 | |
125 | if ((ctinfo >= IP_CT_IS_REPLY) ^ |
126 | !!(data->invert & XT_IPVS_DIR)) { |
127 | match = false; |
128 | goto out_put_cp; |
129 | } |
130 | } |
131 | |
132 | if (data->bitmask & XT_IPVS_METHOD) |
133 | if (((cp->flags & IP_VS_CONN_F_FWD_MASK) == data->fwd_method) ^ |
134 | !(data->invert & XT_IPVS_METHOD)) { |
135 | match = false; |
136 | goto out_put_cp; |
137 | } |
138 | |
139 | if (data->bitmask & XT_IPVS_VADDR) { |
140 | if (ipvs_mt_addrcmp(kaddr: &cp->vaddr, uaddr: &data->vaddr, |
141 | umask: &data->vmask, l3proto: family) ^ |
142 | !(data->invert & XT_IPVS_VADDR)) { |
143 | match = false; |
144 | goto out_put_cp; |
145 | } |
146 | } |
147 | |
148 | out_put_cp: |
149 | __ip_vs_conn_put(cp); |
150 | out: |
151 | pr_debug("match=%d\n" , match); |
152 | return match; |
153 | } |
154 | |
155 | static int ipvs_mt_check(const struct xt_mtchk_param *par) |
156 | { |
157 | if (par->family != NFPROTO_IPV4 |
158 | #ifdef CONFIG_IP_VS_IPV6 |
159 | && par->family != NFPROTO_IPV6 |
160 | #endif |
161 | ) { |
162 | pr_info_ratelimited("protocol family %u not supported\n" , |
163 | par->family); |
164 | return -EINVAL; |
165 | } |
166 | |
167 | return 0; |
168 | } |
169 | |
170 | static struct xt_match xt_ipvs_mt_reg __read_mostly = { |
171 | .name = "ipvs" , |
172 | .revision = 0, |
173 | .family = NFPROTO_UNSPEC, |
174 | .match = ipvs_mt, |
175 | .checkentry = ipvs_mt_check, |
176 | .matchsize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)), |
177 | .me = THIS_MODULE, |
178 | }; |
179 | |
180 | static int __init ipvs_mt_init(void) |
181 | { |
182 | return xt_register_match(target: &xt_ipvs_mt_reg); |
183 | } |
184 | |
185 | static void __exit ipvs_mt_exit(void) |
186 | { |
187 | xt_unregister_match(target: &xt_ipvs_mt_reg); |
188 | } |
189 | |
190 | module_init(ipvs_mt_init); |
191 | module_exit(ipvs_mt_exit); |
192 | |