| /* |
| ************************************************************************** |
| * Copyright (c) 2014-2017, 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/ip6_route.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_IPV6_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_ipv6.h" |
| #include "ecm_interface.h" |
| #include "ecm_ipv6.h" |
| |
| /* |
| * General operational control |
| */ |
| int ecm_front_end_ipv6_stopped = 0; /* When non-zero further traffic will not be processed */ |
| |
| /* |
| * ecm_front_end_ipv6_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_ipv6_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) |
| { |
| 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_front_end_ipv6_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_ipv6_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) |
| { |
| efeici->from_dev = from; |
| efeici->from_other_dev = from_other; |
| efeici->to_dev = to; |
| efeici->to_other_dev = to_other; |
| } |
| |
| /* |
| * ecm_front_end_ipv6_interface_construct_netdev_put() |
| * Release the references of the net devices. |
| */ |
| void ecm_front_end_ipv6_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); |
| } |
| |
| /* |
| * ecm_front_end_ipv6_interface_construct_netdev_hold() |
| * Holds the references of the netdevices. |
| */ |
| void ecm_front_end_ipv6_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); |
| } |
| |
| /* |
| * ecm_front_end_ipv6_interface_construct_set_and_hold() |
| * Sets the IPv6 ECM front end interface construct instance, |
| * and holds the net devices. |
| */ |
| bool ecm_front_end_ipv6_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_dest_addr, |
| struct ecm_front_end_interface_construct_instance *efeici) |
| { |
| struct dst_entry *dst = skb_dst(skb); |
| struct rt6_info *rt = (struct rt6_info *)dst; |
| ip_addr_t rt_dst_addr; |
| struct net_device *rt_iif_dev = NULL; |
| 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 *dst_dev = NULL; |
| ip_addr_t from_mac_lookup; |
| ip_addr_t to_mac_lookup; |
| bool dst_dev_override = false; |
| |
| /* |
| * Set the rt_dst_addr with the destination IP address by default. |
| */ |
| 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; |
| |
| ECM_IP_ADDR_COPY(from_mac_lookup, ip_src_addr); |
| ECM_IP_ADDR_COPY(to_mac_lookup, ip_dest_addr); |
| } else { |
| /* |
| * Routed |
| */ |
| if (!rt) { |
| DEBUG_WARN("rt6_info 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; |
| } |
| |
| 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("in_dev: %s\n", in_dev->name); |
| DEBUG_TRACE("out_dev: %s\n", out_dev->name); |
| DEBUG_TRACE("dst->dev: %s dst_dev: %s\n", dst_dev->name, dst_dev->name); |
| DEBUG_TRACE("rt_iif_dev: %s\n", rt_iif_dev->name); |
| DEBUG_TRACE("%px: rt6i_dst.addr: %pi6\n", rt, &rt->rt6i_dst.addr); |
| DEBUG_TRACE("%px: rt6i_src.addr: %pi6\n", rt, &rt->rt6i_src.addr); |
| DEBUG_TRACE("%px: rt6i_gateway: %pi6\n", rt, &rt->rt6i_gateway); |
| DEBUG_TRACE("%px: rt6i_idev: %s\n", rt, rt->rt6i_idev->dev->name); |
| DEBUG_TRACE("%px: skb->dev: %s\n", rt, skb->dev->name); |
| |
| DEBUG_INFO("ip_src_addr: " ECM_IP_ADDR_OCTAL_FMT "\n", ECM_IP_ADDR_TO_OCTAL(ip_src_addr)); |
| DEBUG_INFO("ip_dest_addr: " ECM_IP_ADDR_OCTAL_FMT "\n", ECM_IP_ADDR_TO_OCTAL(ip_dest_addr)); |
| |
| /* |
| * If the destination host is behind a gateway, use the gateway address as destination |
| * for routed connections. |
| */ |
| if (!ECM_IP_ADDR_MATCH(rt->rt6i_dst.addr.in6_u.u6_addr32, rt->rt6i_gateway.in6_u.u6_addr32) || (rt->rt6i_flags & RTF_GATEWAY)) { |
| if (!ECM_IP_ADDR_IS_NULL(rt->rt6i_gateway.in6_u.u6_addr32)) { |
| ECM_NIN6_ADDR_TO_IP_ADDR(rt_dst_addr, rt->rt6i_gateway); |
| } |
| } |
| |
| from = rt_iif_dev; |
| from_other = dst_dev; |
| to = dst_dev; |
| to_other = rt_iif_dev; |
| |
| ECM_IP_ADDR_COPY(from_mac_lookup, ip_src_addr); |
| ECM_IP_ADDR_COPY(to_mac_lookup, rt_dst_addr); |
| } |
| |
| ecm_front_end_ipv6_interface_construct_netdev_set(efeici, from, from_other, |
| to, to_other); |
| |
| ecm_front_end_ipv6_interface_construct_netdev_hold(efeici); |
| |
| ecm_front_end_ipv6_interface_construct_ip_addr_set(efeici, from_mac_lookup, to_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_ipv6_stop() |
| */ |
| void ecm_front_end_ipv6_stop(int num) |
| { |
| ecm_front_end_ipv6_stopped = num; |
| } |
| |
| /* |
| * ecm_front_end_ipv6_init() |
| */ |
| int ecm_front_end_ipv6_init(struct dentry *dentry) |
| { |
| if (!debugfs_create_u32("front_end_ipv6_stop", S_IRUGO | S_IWUSR, dentry, |
| (u32 *)&ecm_front_end_ipv6_stopped)) { |
| DEBUG_ERROR("Failed to create ecm front end ipv6 stop file in debugfs\n"); |
| return -1; |
| } |
| |
| return ecm_ipv6_init(dentry); |
| } |
| |
| /* |
| * ecm_front_end_ipv6_exit() |
| */ |
| void ecm_front_end_ipv6_exit(void) |
| { |
| ecm_ipv6_exit(); |
| } |