/* * Copyright (C) 2017 jianhui zhao * * 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 #include #include #include #include #include #include #include #include "utils.h" #include "config.h" #define IPS_HIJACKED (1 << 31) #define IPS_ALLOWED (1 << 30) static u32 wd_nf_nat_setup_info(void *priv, struct sk_buff *skb, const struct nf_hook_state *state, struct nf_conn *ct) { struct config *conf = get_config(); struct tcphdr *tcph = tcp_hdr(skb); union nf_conntrack_man_proto proto; struct nf_nat_range newrange; static uint16_t PORT_80 = htons(80); proto.tcp.port = (tcph->dest == PORT_80) ? htons(conf->port) : htons(conf->ssl_port); newrange.flags = NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED; newrange.min_addr.ip = newrange.max_addr.ip = conf->interface_ipaddr; newrange.min_proto = newrange.max_proto = proto; ct->status |= IPS_HIJACKED; return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); } static u32 wifidog_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct config *conf = get_config(); struct iphdr *iph = ip_hdr(skb); struct nf_conn *ct; struct tcphdr *tcph; struct udphdr *udph; enum ip_conntrack_info ctinfo; static uint16_t PORT_80 = htons(80); /* http */ static uint16_t PORT_443 = htons(443); /* https */ static uint16_t PORT_67 = htons(67); /* dhcp */ static uint16_t PORT_53 = htons(53); /* dns */ if (unlikely(!conf->enabled)) return NF_ACCEPT; if (state->in->ifindex != conf->interface_ifindex) return NF_ACCEPT; /* Accept broadcast */ if (skb->pkt_type == PACKET_BROADCAST || skb->pkt_type == PACKET_MULTICAST) return NF_ACCEPT; /* Accept all to local area networks */ if ((iph->daddr | ~conf->interface_mask) == conf->interface_broadcast) return NF_ACCEPT; ct = nf_ct_get(skb, &ctinfo); if (!ct || (ct->status & IPS_ALLOWED)) return NF_ACCEPT; if (ct->status & IPS_HIJACKED) { if (is_allowed_mac(skb, state)) { /* Avoid duplication of authentication */ nf_reset(skb); nf_ct_kill(ct); } return NF_ACCEPT; } else if (ctinfo == IP_CT_NEW && (is_allowed_dest_ip(skb, state) || is_allowed_mac(skb, state))) { ct->status |= IPS_ALLOWED; return NF_ACCEPT; } switch (iph->protocol) { case IPPROTO_TCP: tcph = tcp_hdr(skb); if(tcph->dest == PORT_53 || tcph->dest == PORT_67) { ct->status |= IPS_ALLOWED; return NF_ACCEPT; } if (tcph->dest == PORT_80 || tcph->dest == PORT_443) goto redirect; else return NF_DROP; case IPPROTO_UDP: udph = udp_hdr(skb); if(udph->dest == PORT_53 || udph->dest == PORT_67) { ct->status |= IPS_ALLOWED; return NF_ACCEPT; } return NF_DROP; default: ct->status |= IPS_ALLOWED; return NF_ACCEPT; } redirect: /* all packets from unknown client are dropped */ if (ctinfo != IP_CT_NEW || (ct->status & IPS_DST_NAT_DONE)) { pr_debug("dropping packets of suspect stream, src:%pI4, dst:%pI4\n", &iph->saddr, &iph->daddr); return NF_DROP; } return nf_nat_ipv4_in(priv, skb, state, wd_nf_nat_setup_info); } static struct nf_hook_ops wifidog_ops[] __read_mostly = { { .hook = wifidog_hook, .pf = PF_INET, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_CONNTRACK + 1 /* after conntrack */ } }; static int __init wifidog_init(void) { int ret; ret = init_config(); if (ret) return ret; #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) ret = nf_register_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); #else ret = nf_register_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); #endif if (ret < 0) { pr_err("can't register hook\n"); goto remove_config; } pr_info("kmod of wifidog is started\n"); return 0; remove_config: deinit_config(); return ret; } static void __exit wifidog_exit(void) { deinit_config(); #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) nf_unregister_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); #else nf_unregister_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); #endif pr_info("kmod of wifidog-ng is stop\n"); } module_init(wifidog_init); module_exit(wifidog_exit); MODULE_AUTHOR("jianhui zhao "); MODULE_LICENSE("GPL");