wifidog-ng: Update to 2.0.2
[feed/packages.git] / net / wifidog-ng / src / main.c
1 /*
2 * Copyright (C) 2017 jianhui zhao <zhaojh329@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9 #include <linux/init.h>
10 #include <linux/module.h>
11 #include <linux/version.h>
12
13 #include <linux/ip.h>
14 #include <linux/tcp.h>
15 #include <linux/udp.h>
16 #include <net/netfilter/nf_nat.h>
17 #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0)
18 #include <net/netfilter/nf_nat_l3proto.h>
19 #endif
20
21 #include "utils.h"
22 #include "config.h"
23
24 #define IPS_HIJACKED (1 << 31)
25 #define IPS_ALLOWED (1 << 30)
26
27 #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 17, 19)
28 static u32 wd_nat_setup_info(struct sk_buff *skb, struct nf_conn *ct)
29 #else
30 static u32 wd_nat_setup_info(void *priv, struct sk_buff *skb,
31 const struct nf_hook_state *state, struct nf_conn *ct)
32 #endif
33 {
34 struct config *conf = get_config();
35 struct tcphdr *tcph = tcp_hdr(skb);
36 union nf_conntrack_man_proto proto;
37 #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 17, 19)
38 struct nf_nat_range2 newrange = {};
39 #else
40 struct nf_nat_range newrange = {};
41 #endif
42 static uint16_t PORT_80 = htons(80);
43
44 proto.tcp.port = (tcph->dest == PORT_80) ? htons(conf->port) : htons(conf->ssl_port);
45 newrange.flags = NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED;
46 newrange.min_addr.ip = conf->interface_ipaddr;
47 newrange.max_addr.ip = conf->interface_ipaddr;
48 newrange.min_proto = proto;
49 newrange.max_proto = proto;
50
51 ct->status |= IPS_HIJACKED;
52
53 return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
54 }
55
56 static u32 wifidog_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
57 {
58 struct config *conf = get_config();
59 struct iphdr *iph = ip_hdr(skb);
60 struct nf_conn *ct;
61 struct tcphdr *tcph;
62 struct udphdr *udph;
63 enum ip_conntrack_info ctinfo;
64 static uint16_t PORT_80 = htons(80); /* http */
65 static uint16_t PORT_443 = htons(443); /* https */
66 static uint16_t PORT_67 = htons(67); /* dhcp */
67 static uint16_t PORT_53 = htons(53); /* dns */
68
69 if (unlikely(!conf->enabled))
70 return NF_ACCEPT;
71
72 if (state->in->ifindex != conf->interface_ifindex)
73 return NF_ACCEPT;
74
75 /* Accept broadcast */
76 if (skb->pkt_type == PACKET_BROADCAST || skb->pkt_type == PACKET_MULTICAST)
77 return NF_ACCEPT;
78
79 /* Accept all to local area networks */
80 if ((iph->daddr | ~conf->interface_mask) == conf->interface_broadcast)
81 return NF_ACCEPT;
82
83 ct = nf_ct_get(skb, &ctinfo);
84 if (!ct || (ct->status & IPS_ALLOWED))
85 return NF_ACCEPT;
86
87 if (ct->status & IPS_HIJACKED) {
88 if (is_allowed_mac(skb, state)) {
89 /* Avoid duplication of authentication */
90 #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)
91 nf_reset(skb);
92 #else
93 nf_reset_ct(skb);
94 #endif
95 nf_ct_kill(ct);
96 }
97 return NF_ACCEPT;
98 } else if (ctinfo == IP_CT_NEW && (is_allowed_dest_ip(skb, state) || is_allowed_mac(skb, state))) {
99 ct->status |= IPS_ALLOWED;
100 return NF_ACCEPT;
101 }
102
103 switch (iph->protocol) {
104 case IPPROTO_TCP:
105 tcph = tcp_hdr(skb);
106 if(tcph->dest == PORT_53 || tcph->dest == PORT_67) {
107 ct->status |= IPS_ALLOWED;
108 return NF_ACCEPT;
109 }
110
111 if (tcph->dest == PORT_80 || tcph->dest == PORT_443)
112 goto redirect;
113 else
114 return NF_DROP;
115
116 case IPPROTO_UDP:
117 udph = udp_hdr(skb);
118 if(udph->dest == PORT_53 || udph->dest == PORT_67) {
119 ct->status |= IPS_ALLOWED;
120 return NF_ACCEPT;
121 }
122 return NF_DROP;
123
124 default:
125 ct->status |= IPS_ALLOWED;
126 return NF_ACCEPT;
127 }
128
129 redirect:
130 /* all packets from unknown client are dropped */
131 if (ctinfo != IP_CT_NEW || (ct->status & IPS_DST_NAT_DONE)) {
132 pr_debug("dropping packets of suspect stream, src:%pI4, dst:%pI4\n", &iph->saddr, &iph->daddr);
133 return NF_DROP;
134 }
135
136 #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 17, 19)
137 return wd_nat_setup_info(skb, ct);
138 #else
139 return nf_nat_ipv4_in(priv, skb, state, wd_nat_setup_info);
140 #endif
141 }
142
143 static struct nf_hook_ops wifidog_ops __read_mostly = {
144 .hook = wifidog_hook,
145 .pf = PF_INET,
146 .hooknum = NF_INET_PRE_ROUTING,
147 .priority = NF_IP_PRI_NAT_DST
148 };
149
150 static int __init wifidog_init(void)
151 {
152 int ret;
153
154 ret = init_config();
155 if (ret)
156 return ret;
157
158 #if LINUX_VERSION_CODE > KERNEL_VERSION(5, 0, 21)
159 ret = nf_nat_ipv4_register_fn(&init_net, &wifidog_ops);
160 #elif LINUX_VERSION_CODE > KERNEL_VERSION(4, 17, 19)
161 ret = nf_nat_l3proto_ipv4_register_fn(&init_net, &wifidog_ops);
162 #elif LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14)
163 ret = nf_register_net_hook(&init_net, &wifidog_ops);
164 #else
165 ret = nf_register_hook(&wifidog_ops);
166 #endif
167 if (ret < 0) {
168 pr_err("can't register hook\n");
169 goto remove_config;
170 }
171
172 pr_info("kmod of wifidog is started\n");
173
174 return 0;
175
176 remove_config:
177 deinit_config();
178 return ret;
179 }
180
181 static void __exit wifidog_exit(void)
182 {
183 deinit_config();
184
185 #if LINUX_VERSION_CODE > KERNEL_VERSION(5, 0, 21)
186 nf_nat_ipv4_unregister_fn(&init_net, &wifidog_ops);
187 #elif LINUX_VERSION_CODE > KERNEL_VERSION(4, 17, 19)
188 nf_nat_l3proto_ipv4_unregister_fn(&init_net, &wifidog_ops);
189 #elif LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14)
190 nf_unregister_net_hook(&init_net, &wifidog_ops);
191 #else
192 nf_unregister_hook(&wifidog_ops);
193 #endif
194
195 pr_info("kmod of wifidog-ng is stop\n");
196 }
197
198 module_init(wifidog_init);
199 module_exit(wifidog_exit);
200
201 MODULE_AUTHOR("jianhui zhao <zhaojh329@gmail.com>");
202 MODULE_LICENSE("GPL");