blob: 2d72164a1e7ac32c498bceaa82063a013dbbe5ee [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-2016, 2020-2021 The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/debugfs.h>
#include <linux/inet.h>
#include <linux/etherdevice.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/route.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_FRONT_END_IPV4_DEBUG_LEVEL
#include "ecm_types.h"
#include "ecm_db_types.h"
#include "ecm_state.h"
#include "ecm_tracker.h"
#include "ecm_classifier.h"
#include "ecm_front_end_types.h"
#include "ecm_tracker_datagram.h"
#include "ecm_tracker_udp.h"
#include "ecm_tracker_tcp.h"
#include "ecm_db.h"
#include "ecm_front_end_ipv4.h"
#include "ecm_interface.h"
#include "ecm_ipv4.h"
/*
* General operational control
*/
int ecm_front_end_ipv4_stopped = 0; /* When non-zero further traffic will not be processed */
/*
* ecm_front_end_ipv4_interface_construct_ip_addr_set()
* Sets the IP addresses.
*
* Sets the ip address fields of the ecm_front_end_interface_construct_instance
* with the given ip addresses.
*/
static void ecm_front_end_ipv4_interface_construct_ip_addr_set(struct ecm_front_end_interface_construct_instance *efeici,
ip_addr_t from_mac_lookup, ip_addr_t to_mac_lookup,
ip_addr_t from_nat_mac_lookup, ip_addr_t to_nat_mac_lookup)
{
ECM_IP_ADDR_COPY(efeici->from_mac_lookup_ip_addr, from_mac_lookup);
ECM_IP_ADDR_COPY(efeici->to_mac_lookup_ip_addr, to_mac_lookup);
ECM_IP_ADDR_COPY(efeici->from_nat_mac_lookup_ip_addr, from_nat_mac_lookup);
ECM_IP_ADDR_COPY(efeici->to_nat_mac_lookup_ip_addr, to_nat_mac_lookup);
}
/*
* ecm_front_end_ipv4_interface_construct_netdev_set()
* Sets the net devices.
*
* Sets the net device fields of the ecm_front_end_interface_construct_instance
* with the given net devices.
*/
static void ecm_front_end_ipv4_interface_construct_netdev_set(struct ecm_front_end_interface_construct_instance *efeici,
struct net_device *from, struct net_device *from_other,
struct net_device *to, struct net_device *to_other,
struct net_device *from_nat, struct net_device *from_nat_other,
struct net_device *to_nat, struct net_device *to_nat_other)
{
efeici->from_dev = from;
efeici->from_other_dev = from_other;
efeici->to_dev = to;
efeici->to_other_dev = to_other;
efeici->from_nat_dev = from_nat;
efeici->from_nat_other_dev = from_nat_other;
efeici->to_nat_dev = to_nat;
efeici->to_nat_other_dev = to_nat_other;
}
/*
* ecm_front_end_ipv4_interface_construct_netdev_put()
* Release the references of the net devices.
*/
void ecm_front_end_ipv4_interface_construct_netdev_put(struct ecm_front_end_interface_construct_instance *efeici)
{
dev_put(efeici->from_dev);
dev_put(efeici->from_other_dev);
dev_put(efeici->to_dev);
dev_put(efeici->to_other_dev);
dev_put(efeici->from_nat_dev);
dev_put(efeici->from_nat_other_dev);
dev_put(efeici->to_nat_dev);
dev_put(efeici->to_nat_other_dev);
}
/*
* ecm_front_end_ipv4_interface_construct_netdev_hold()
* Holds the references of the netdevices.
*/
void ecm_front_end_ipv4_interface_construct_netdev_hold(struct ecm_front_end_interface_construct_instance *efeici)
{
dev_hold(efeici->from_dev);
dev_hold(efeici->from_other_dev);
dev_hold(efeici->to_dev);
dev_hold(efeici->to_other_dev);
dev_hold(efeici->from_nat_dev);
dev_hold(efeici->from_nat_other_dev);
dev_hold(efeici->to_nat_dev);
dev_hold(efeici->to_nat_other_dev);
}
/*
* ecm_front_end_ipv4_interface_construct_set_and_hold()
* Sets the IPv4 ECM front end interface construct instance,
* and holds the net devices.
*/
bool ecm_front_end_ipv4_interface_construct_set_and_hold(struct sk_buff *skb, ecm_tracker_sender_type_t sender, ecm_db_direction_t ecm_dir, bool is_routed,
struct net_device *in_dev, struct net_device *out_dev,
ip_addr_t ip_src_addr, ip_addr_t ip_src_addr_nat,
ip_addr_t ip_dest_addr, ip_addr_t ip_dest_addr_nat,
struct ecm_front_end_interface_construct_instance *efeici)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *rt_iif_dev = NULL;
ip_addr_t rt_dst_addr;
struct net_device *from = NULL;
struct net_device *from_other = NULL;
struct net_device *to = NULL;
struct net_device *to_other = NULL;
struct net_device *from_nat = NULL;
struct net_device *from_nat_other = NULL;
struct net_device *to_nat = NULL;
struct net_device *to_nat_other = NULL;
struct net_device *dst_dev = NULL;
ip_addr_t from_mac_lookup;
ip_addr_t to_mac_lookup;
ip_addr_t from_nat_mac_lookup;
ip_addr_t to_nat_mac_lookup;
bool gateway = false;
bool dst_dev_override = false;
/*
* Set the rt_dst_addr with the destination IP address by default.
* If there is a gateway IP address, this will be overwritten with the gateway IP address.
*/
ECM_IP_ADDR_COPY(rt_dst_addr, ip_dest_addr);
if (!is_routed) {
/*
* Bridged
*/
from = in_dev;
from_other = in_dev;
to = out_dev;
to_other = out_dev;
} else {
__be32 rt_gw4;
if (!rt) {
DEBUG_WARN("rtable is NULL\n");
return false;
}
/*
* If the flow is routed, extract the route information from the skb.
* Print the extracted information for debug purpose.
*/
rt_iif_dev = dev_get_by_index(&init_net, skb->skb_iif);
if (!rt_iif_dev) {
DEBUG_WARN("No rt_iif dev\n");
return false;
}
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(5, 2, 0))
rt_gw4 = rt->rt_gateway;
#else
rt_gw4 = rt->rt_gw4;
#endif
dst_dev = dst->dev;
#ifdef ECM_XFRM_ENABLE
/*
* If the dst is an xfrm dst, then override the dst_dev.
*/
if (dst_xfrm(dst)) {
int32_t if_type;
struct net_device *xfrm_dst_dev = ecm_interface_get_and_hold_ipsec_tun_netdev(NULL, skb, &if_type);
/*
* If we reach here and are unable to find the tunnel netdevice,
* then return failure.
*/
if (!xfrm_dst_dev) {
return false;
}
dst_dev = xfrm_dst_dev;
dst_dev_override = true;
}
#endif
DEBUG_TRACE("dst->dev: %s dst_dev: %s\n", dst->dev->name, dst_dev->name);
DEBUG_TRACE("%px: rt gateway: %pI4\n", rt, &rt_gw4);
DEBUG_INFO("ip_src_addr" ECM_IP_ADDR_DOT_FMT "\n", ECM_IP_ADDR_TO_DOT(ip_src_addr));
DEBUG_INFO("ip_src_addr_nat" ECM_IP_ADDR_DOT_FMT "\n", ECM_IP_ADDR_TO_DOT(ip_src_addr_nat));
DEBUG_INFO("ip_dest_addr" ECM_IP_ADDR_DOT_FMT "\n", ECM_IP_ADDR_TO_DOT(ip_dest_addr));
DEBUG_INFO("ip_dest_addr_nat" ECM_IP_ADDR_DOT_FMT "\n", ECM_IP_ADDR_TO_DOT(ip_dest_addr_nat));
if (rt->rt_uses_gateway || (rt->rt_flags & RTF_GATEWAY)) {
/*
* Overwrite the rt_dst_addr with the gateway IP address. The destination host is
* behind a gateway.
*/
DEBUG_TRACE("Gateway address will be looked up overwrite the rt_dst_addr\n");
ECM_NIN4_ADDR_TO_IP_ADDR(rt_dst_addr, rt_gw4)
gateway = true;
}
DEBUG_INFO("rt_dst_addr" ECM_IP_ADDR_DOT_FMT "\n", ECM_IP_ADDR_TO_DOT(rt_dst_addr));
/*
* Initialize the interfaces with defaults.
* rt_iif_dev is the interface which the packet comes in to the system,
* dst->dev is the interface which the packet goes out from the system.
*/
from = rt_iif_dev;
from_other = dst_dev;
to = dst_dev;
to_other = rt_iif_dev;
}
/*
* Just in case the is_routed flag comes as 0, but the ecm_dir is different than
* bridge flow, we check the netdevices here before setting the efeici fields.
*
* TODO: Why ecm_dir comes as non-bridge flow, even though the is_routed flag is bridged?
*/
if (ecm_dir != ECM_DB_DIRECTION_BRIDGED) {
if (!dst || !dst_dev || !rt_iif_dev) {
DEBUG_WARN("Traffic is not bridged but the netdevs are not valid\n");
if (rt_iif_dev) {
dev_put(rt_iif_dev);
}
if (dst_dev_override) {
dev_put(dst_dev);
}
return false;
}
}
/*
* Initialize the mac lookup ip addresses with defaults.
*/
ECM_IP_ADDR_COPY(from_mac_lookup, ip_src_addr);
ECM_IP_ADDR_COPY(to_mac_lookup, rt_dst_addr);
ECM_IP_ADDR_COPY(from_nat_mac_lookup, ip_src_addr_nat);
/*
* If we have a gateway IP address we should use it for the
* to_nat_mac_lookup IP address.
* Note that in hairpin NAT the destination IP address and the destination
* NAT IP addresses are different than each other. Because of this we cannot
* use the rt_dst_addr for to_nat_mac_lookup as well. In a normal routing
* traffic they are equal.
*/
if (gateway) {
ECM_IP_ADDR_COPY(to_nat_mac_lookup, rt_dst_addr);
} else {
ECM_IP_ADDR_COPY(to_nat_mac_lookup, ip_dest_addr_nat);
}
/*
* Based on the flow and connection direction, set the NAT'd net devices.
* The above IP address settings are valid for each flow and connection direction case.
*/
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
if (ecm_dir == ECM_DB_DIRECTION_EGRESS_NAT) {
from_nat = dst_dev;
from_nat_other = rt_iif_dev;
to_nat = dst_dev;
to_nat_other = rt_iif_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_NON_NAT) {
from_nat = rt_iif_dev;
from_nat_other = dst_dev;
to_nat = dst_dev;
to_nat_other = rt_iif_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_INGRESS_NAT) {
from_nat = rt_iif_dev;
from_nat_other = dst_dev;
to_nat = rt_iif_dev;
to_nat_other = dst_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_BRIDGED) {
from_nat = in_dev;
from_nat_other = in_dev;
to_nat = out_dev;
to_nat_other = out_dev;
} else {
DEBUG_ASSERT(false, "Unhandled ecm_dir: %d\n", ecm_dir);
}
} else {
if (ecm_dir == ECM_DB_DIRECTION_EGRESS_NAT) {
from_nat = rt_iif_dev;
from_nat_other = dst_dev;
to_nat = rt_iif_dev;
to_nat_other = dst_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_NON_NAT) {
from_nat = rt_iif_dev;
from_nat_other = dst_dev;
to_nat = dst_dev;
to_nat_other = rt_iif_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_INGRESS_NAT) {
from_nat = dst_dev;
from_nat_other = rt_iif_dev;
to_nat = dst_dev;
to_nat_other = rt_iif_dev;
} else if (ecm_dir == ECM_DB_DIRECTION_BRIDGED) {
from_nat = in_dev;
from_nat_other = in_dev;
to_nat = out_dev;
to_nat_other = out_dev;
} else {
DEBUG_ASSERT(false, "Unhandled ecm_dir: %d\n", ecm_dir);
}
}
ecm_front_end_ipv4_interface_construct_netdev_set(efeici, from, from_other,
to, to_other,
from_nat, from_nat_other,
to_nat, to_nat_other);
ecm_front_end_ipv4_interface_construct_netdev_hold(efeici);
ecm_front_end_ipv4_interface_construct_ip_addr_set(efeici, from_mac_lookup, to_mac_lookup,
from_nat_mac_lookup, to_nat_mac_lookup);
if (dst_dev_override) {
dev_put(dst_dev);
}
/*
* Release the iff_dev which was hold by the dev_get_by_index() call.
*/
if (rt_iif_dev) {
dev_put(rt_iif_dev);
}
return true;
}
/*
* ecm_front_end_ipv4_stop()
*/
void ecm_front_end_ipv4_stop(int num)
{
ecm_front_end_ipv4_stopped = num;
}
/*
* ecm_front_end_ipv4_init()
*/
int ecm_front_end_ipv4_init(struct dentry *dentry)
{
if (!debugfs_create_u32("front_end_ipv4_stop", S_IRUGO | S_IWUSR, dentry,
(u32 *)&ecm_front_end_ipv4_stopped)) {
DEBUG_ERROR("Failed to create ecm front end ipv4 stop file in debugfs\n");
return -1;
}
return ecm_ipv4_init(dentry);
}
/*
* ecm_front_end_ipv4_exit()
*/
void ecm_front_end_ipv4_exit(void)
{
ecm_ipv4_exit();
}