| /* |
| ************************************************************************** |
| * Copyright (c) 2014-2016, 2019-2021 The Linux Foundation. All rights reserved. |
| * Copyright (c) 2022, Qualcomm Innovation Center, Inc. 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/types.h> |
| #include <linux/ip.h> |
| #include <linux/tcp.h> |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/icmp.h> |
| #include <linux/debugfs.h> |
| #include <linux/kthread.h> |
| #include <linux/pkt_sched.h> |
| #include <linux/string.h> |
| #include <net/route.h> |
| #include <net/ip.h> |
| #include <net/tcp.h> |
| #include <asm/unaligned.h> |
| #include <asm/uaccess.h> /* for put_user */ |
| #include <net/ipv6.h> |
| #include <linux/inet.h> |
| #include <linux/in.h> |
| #include <linux/udp.h> |
| #include <linux/tcp.h> |
| |
| #include <linux/netfilter_ipv4.h> |
| #include <linux/netfilter_bridge.h> |
| #include <net/netfilter/nf_conntrack.h> |
| #include <net/netfilter/nf_conntrack_helper.h> |
| #include <net/netfilter/nf_conntrack_l4proto.h> |
| #include <net/netfilter/nf_conntrack_core.h> |
| #include <net/netfilter/ipv4/nf_conntrack_ipv4.h> |
| #include <net/netfilter/ipv4/nf_defrag_ipv4.h> |
| #include <linux/netfilter/xt_dscp.h> |
| #include <net/netfilter/nf_conntrack_dscpremark_ext.h> |
| |
| /* |
| * Debug output levels |
| * 0 = OFF |
| * 1 = ASSERTS / ERRORS |
| * 2 = 1 + WARN |
| * 3 = 2 + INFO |
| * 4 = 3 + TRACE |
| */ |
| #define DEBUG_LEVEL ECM_CLASSIFIER_DSCP_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_udp.h" |
| #include "ecm_tracker_tcp.h" |
| #include "ecm_db.h" |
| #include "ecm_classifier_dscp.h" |
| #include "ecm_front_end_common.h" |
| |
| /* |
| * Magic numbers |
| */ |
| #define ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC 0xFA43 |
| |
| /* |
| * struct ecm_classifier_dscp_instance |
| * State to allow tracking of dynamic qos for a connection |
| */ |
| struct ecm_classifier_dscp_instance { |
| struct ecm_classifier_instance base; /* Base type */ |
| |
| struct ecm_classifier_dscp_instance *next; /* Next classifier state instance (for accouting and reporting purposes) */ |
| struct ecm_classifier_dscp_instance *prev; /* Next classifier state instance (for accouting and reporting purposes) */ |
| |
| uint32_t ci_serial; /* RO: Serial of the connection */ |
| struct ecm_classifier_process_response process_response;/* Last process response computed */ |
| bool packet_seen[ECM_CONN_DIR_MAX]; /* Per-direction packet seen flag */ |
| int refs; /* Integer to trap we never go negative */ |
| #if (DEBUG_LEVEL > 0) |
| uint16_t magic; |
| #endif |
| }; |
| |
| /* |
| * Operational control |
| */ |
| static int ecm_classifier_dscp_enabled = 1; /* Operational behaviour */ |
| |
| /* |
| * Management thread control |
| */ |
| static bool ecm_classifier_dscp_terminate_pending = false; /* True when the user wants us to terminate */ |
| |
| /* |
| * Debugfs dentry object. |
| */ |
| static struct dentry *ecm_classifier_dscp_dentry; |
| |
| /* |
| * Locking of the classifier structures |
| */ |
| static DEFINE_SPINLOCK(ecm_classifier_dscp_lock); /* Protect SMP access. */ |
| |
| /* |
| * List of our classifier instances |
| */ |
| static struct ecm_classifier_dscp_instance *ecm_classifier_dscp_instances = NULL; |
| /* list of all active instances */ |
| static int ecm_classifier_dscp_count = 0; /* Tracks number of instances allocated */ |
| |
| /* |
| * ecm_classifier_dscp_ref() |
| * Ref |
| */ |
| static void ecm_classifier_dscp_ref(struct ecm_classifier_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->refs++; |
| DEBUG_TRACE("%px: cdscpi ref %d\n", cdscpi, cdscpi->refs); |
| DEBUG_ASSERT(cdscpi->refs > 0, "%px: ref wrap\n", cdscpi); |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| } |
| |
| /* |
| * ecm_classifier_dscp_deref() |
| * Deref |
| */ |
| static int ecm_classifier_dscp_deref(struct ecm_classifier_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->refs--; |
| DEBUG_ASSERT(cdscpi->refs >= 0, "%px: refs wrapped\n", cdscpi); |
| DEBUG_TRACE("%px: DSCP classifier deref %d\n", cdscpi, cdscpi->refs); |
| if (cdscpi->refs) { |
| int refs = cdscpi->refs; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| return refs; |
| } |
| |
| /* |
| * Object to be destroyed |
| */ |
| ecm_classifier_dscp_count--; |
| DEBUG_ASSERT(ecm_classifier_dscp_count >= 0, "%px: ecm_classifier_dscp_count wrap\n", cdscpi); |
| |
| /* |
| * UnLink the instance from our list |
| */ |
| if (cdscpi->next) { |
| cdscpi->next->prev = cdscpi->prev; |
| } |
| if (cdscpi->prev) { |
| cdscpi->prev->next = cdscpi->next; |
| } else { |
| DEBUG_ASSERT(ecm_classifier_dscp_instances == cdscpi, "%px: list bad %px\n", cdscpi, ecm_classifier_dscp_instances); |
| ecm_classifier_dscp_instances = cdscpi->next; |
| } |
| cdscpi->next = NULL; |
| cdscpi->prev = NULL; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| |
| /* |
| * Final |
| */ |
| DEBUG_INFO("%px: Final DSCP classifier instance\n", cdscpi); |
| kfree(cdscpi); |
| |
| return 0; |
| } |
| |
| /* |
| * ecm_classifier_dscp_is_bidi_packet_seen() |
| * Return true if both direction packets are seen. |
| */ |
| static inline bool ecm_classifier_dscp_is_bidi_packet_seen(struct ecm_classifier_dscp_instance *cdscpi) |
| { |
| return ((cdscpi->packet_seen[ECM_CONN_DIR_FLOW] == true) && (cdscpi->packet_seen[ECM_CONN_DIR_RETURN] == true)); |
| } |
| |
| /* |
| * ecm_classifier_dscp_fill_info() |
| * Save the QoS and DSCP values in the classifier instance. |
| */ |
| static void ecm_classifier_dscp_fill_info(struct ecm_classifier_dscp_instance *cdscpi, |
| ecm_tracker_sender_type_t sender, |
| struct ecm_tracker_ip_header *ip_hdr, |
| struct sk_buff *skb) |
| { |
| if (sender == ECM_TRACKER_SENDER_TYPE_SRC) { |
| cdscpi->process_response.flow_qos_tag = skb->priority; |
| cdscpi->process_response.flow_mark = skb->mark; |
| cdscpi->process_response.flow_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| cdscpi->packet_seen[ECM_CONN_DIR_FLOW] = true; |
| } else { |
| cdscpi->process_response.return_qos_tag = skb->priority; |
| cdscpi->process_response.return_mark = skb->mark; |
| cdscpi->process_response.return_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| cdscpi->packet_seen[ECM_CONN_DIR_RETURN] = true; |
| } |
| } |
| |
| /* |
| * ecm_classifier_dscp_update() |
| * Called from the frontend files to update the classifier instance. |
| */ |
| void ecm_classifier_dscp_update(struct ecm_classifier_instance *aci, enum ecm_rule_update_type type, void *arg) |
| { |
| struct nf_ct_dscpremark_ext *dscpcte; |
| struct nf_conn *ct = (struct nf_conn *)arg; |
| struct ecm_classifier_dscp_instance *cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| |
| if (type != ECM_RULE_UPDATE_TYPE_CONNMARK) { |
| DEBUG_WARN("%px: unsupported update type: %d\n", aci, type); |
| return; |
| } |
| |
| /* |
| * Since we set the mark values from the DSCP extension, we need to update this |
| * extension as well. Because in case of a rule flush, the updated value should be read. |
| * UDP flows should also overwrite the classifier's response mark field which is set with the |
| * skb->mark. |
| */ |
| spin_lock_bh(&ct->lock); |
| dscpcte = nf_ct_dscpremark_ext_find(ct); |
| if (!dscpcte) { |
| spin_unlock_bh(&ct->lock); |
| DEBUG_WARN("%px: no dscp extension\n", aci); |
| return; |
| } |
| dscpcte->flow_mark = ct->mark; |
| dscpcte->reply_mark = ct->mark; |
| dscpcte->flow_set_flags |= NF_CT_DSCPREMARK_EXT_MARK; |
| dscpcte->return_set_flags |= NF_CT_DSCPREMARK_EXT_MARK; |
| spin_unlock_bh(&ct->lock); |
| |
| cdscpi->process_response.flow_mark = ct->mark; |
| cdscpi->process_response.return_mark = ct->mark; |
| } |
| |
| /* |
| * ecm_classifier_dscp_process() |
| * Process new data for connection |
| */ |
| static void ecm_classifier_dscp_process(struct ecm_classifier_instance *aci, ecm_tracker_sender_type_t sender, |
| struct ecm_tracker_ip_header *ip_hdr, struct sk_buff *skb, |
| struct ecm_classifier_process_response *process_response) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| ecm_classifier_relevence_t relevance; |
| struct ecm_db_connection_instance *ci = NULL; |
| struct ecm_front_end_connection_instance *feci; |
| ecm_front_end_acceleration_mode_t accel_mode; |
| int protocol; |
| uint32_t became_relevant = 0; |
| struct nf_conn *ct; |
| enum ip_conntrack_info ctinfo; |
| struct nf_ct_dscpremark_ext *dscpcte; |
| bool dscp_marked = false; |
| uint64_t slow_pkts; |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| |
| /* |
| * Are we yet to decide if this instance is relevant to the connection? |
| */ |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| relevance = cdscpi->process_response.relevance; |
| |
| /* |
| * Are we relevant? |
| */ |
| if (relevance == ECM_CLASSIFIER_RELEVANCE_NO) { |
| *process_response = cdscpi->process_response; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| return; |
| } |
| |
| /* |
| * Yes or maybe relevant. |
| * |
| * Need to decide our relevance to this connection. |
| * We are only relevent to a connection iff: |
| * 1. We are enabled. |
| * 2. Connection can be accelerated. |
| * 3. Connection has a ct, ct has a dscp remark extension and the rule is validated. |
| * Any other condition and we are not and will stop analysing this connection. |
| */ |
| if (!ecm_classifier_dscp_enabled) { |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO; |
| *process_response = cdscpi->process_response; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| return; |
| } |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| |
| /* |
| * Can we accelerate? |
| */ |
| ci = ecm_db_connection_serial_find_and_ref(cdscpi->ci_serial); |
| if (!ci) { |
| DEBUG_TRACE("%px: No ci found for %u\n", cdscpi, cdscpi->ci_serial); |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO; |
| goto dscp_classifier_out; |
| } |
| |
| feci = ecm_db_connection_front_end_get_and_ref(ci); |
| accel_mode = feci->accel_state_get(feci); |
| slow_pkts = ecm_front_end_get_slow_packet_count(feci); |
| feci->deref(feci); |
| protocol = ecm_db_connection_protocol_get(ci); |
| ecm_db_connection_deref(ci); |
| if (ECM_FRONT_END_ACCELERATION_NOT_POSSIBLE(accel_mode)) { |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO; |
| goto dscp_classifier_out; |
| } |
| |
| /* |
| * Is there a valid conntrack? |
| */ |
| ct = nf_ct_get(skb, &ctinfo); |
| if (!ct) { |
| DEBUG_WARN("%px: no conntrack found\n", cdscpi); |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO; |
| goto dscp_classifier_out; |
| } |
| |
| /* |
| * Is there a DSCPREMARK extension? |
| */ |
| spin_lock_bh(&ct->lock); |
| dscpcte = nf_ct_dscpremark_ext_find(ct); |
| if (!dscpcte) { |
| spin_unlock_bh(&ct->lock); |
| DEBUG_WARN("%px: no DSCP conntrack extension found\n", cdscpi); |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO; |
| goto dscp_classifier_out; |
| } |
| |
| /* |
| * Was a DSCP rule enabled for the flow using the iptables 'DSCP' |
| * target? |
| */ |
| if (nf_conntrack_dscpremark_ext_get_dscp_rule_validity(ct) |
| == NF_CT_DSCPREMARK_EXT_RULE_VALID) { |
| DEBUG_TRACE("%px: DSCP remark extension is valid\n", cdscpi); |
| dscp_marked = true; |
| } |
| spin_unlock_bh(&ct->lock); |
| |
| /* |
| * We are relevant to the connection |
| */ |
| became_relevant = ecm_db_time_get(); |
| |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES; |
| cdscpi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE; |
| cdscpi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL; |
| cdscpi->process_response.became_relevant = became_relevant; |
| |
| if (protocol == IPPROTO_TCP) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) |
| /* |
| * If DSCP conntrack extension is filled in the frontend, use those values |
| * instead of waiting both direction traffic again. |
| */ |
| if ((dscpcte->flow_set_flags == (NF_CT_DSCPREMARK_EXT_PRIO | NF_CT_DSCPREMARK_EXT_DSCP | NF_CT_DSCPREMARK_EXT_MARK)) |
| && (dscpcte->return_set_flags == (NF_CT_DSCPREMARK_EXT_PRIO | NF_CT_DSCPREMARK_EXT_DSCP | NF_CT_DSCPREMARK_EXT_MARK))) { |
| /* |
| * If sender and the conntrack info direction are consistent, fill the response field |
| * with the flow/return values as it is. Otherwise reverse the assignments. |
| */ |
| if (((sender == ECM_TRACKER_SENDER_TYPE_SRC) && (IP_CT_DIR_ORIGINAL == CTINFO2DIR(ctinfo))) || |
| ((sender == ECM_TRACKER_SENDER_TYPE_DEST) && (IP_CT_DIR_REPLY == CTINFO2DIR(ctinfo)))) { |
| cdscpi->process_response.flow_qos_tag = dscpcte->flow_priority; |
| cdscpi->process_response.return_qos_tag = dscpcte->reply_priority; |
| cdscpi->process_response.flow_mark = dscpcte->flow_mark; |
| cdscpi->process_response.return_mark = dscpcte->reply_mark; |
| cdscpi->process_response.flow_dscp = dscpcte->flow_dscp; |
| cdscpi->process_response.return_dscp = dscpcte->reply_dscp; |
| } else { |
| cdscpi->process_response.flow_qos_tag = dscpcte->reply_priority; |
| cdscpi->process_response.return_qos_tag = dscpcte->flow_priority; |
| cdscpi->process_response.flow_mark = dscpcte->reply_mark; |
| cdscpi->process_response.return_mark = dscpcte->flow_mark; |
| cdscpi->process_response.flow_dscp = dscpcte->reply_dscp; |
| cdscpi->process_response.return_dscp = dscpcte->flow_dscp; |
| } |
| DEBUG_TRACE("%px: DSCP extension is used to set the QoS values\n", cdscpi); |
| goto done; |
| } |
| #endif |
| |
| /* |
| * Stop the processing if both side packets are already seen. |
| * Above the process response is already set to allow the acceleration. |
| */ |
| if (ecm_classifier_dscp_is_bidi_packet_seen(cdscpi)) { |
| DEBUG_TRACE("%px: TCP bi-di packets seen\n", cdscpi); |
| goto done; |
| } |
| |
| /* |
| * Store the QoS and DSCP info in the classifier instance and deny the |
| * acceleration if both side info is not yet available. |
| * |
| * This setting is a backup setting for the QoS values, in case the DSCP |
| * conntrack extension is not filled. This situation happens when the conntrack |
| * table is flushed by the user with the echo f > /proc/net/nf_conntrack command. |
| * After this flush, the conntarck entry and the ECM connection entry are destroyed, |
| * but the TCP connection remains established. When the next packet comes to ECM, |
| * since there is no TCP handshake, the DSCP conntarck extension is not set. So, |
| * we need to read these values from the packet's IP header. |
| */ |
| ecm_classifier_dscp_fill_info(cdscpi, sender, ip_hdr, skb); |
| if (!ecm_classifier_dscp_is_bidi_packet_seen(cdscpi)) { |
| DEBUG_TRACE("%px: TCP both side info is not yet picked\n", cdscpi); |
| cdscpi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO; |
| goto dscp_classifier_out; |
| } |
| } else { |
| /* |
| * If the acceleration delay option is enabled, we will wait |
| * until seeing both side traffic. |
| * |
| * There are 2 options: |
| * Option 1: Wait forever until to see the reply direction traffic |
| * Option 2: Wait for seeing N number of packets. If we still don't see reply, |
| * set the uni-directional values. |
| */ |
| if (ecm_classifier_accel_delay_pkts) { |
| /* |
| * Stop the processing if both side packets are already seen. |
| * Above the process response is already set to allow the |
| * acceleration. |
| */ |
| if (ecm_classifier_dscp_is_bidi_packet_seen(cdscpi)) { |
| DEBUG_TRACE("%px: UDP bi-di packets seen\n", cdscpi); |
| goto done; |
| } |
| |
| /* |
| * Store the QoS and DSCP info in the classifier instance and allow the |
| * acceleration if both side info is not yet available. |
| */ |
| ecm_classifier_dscp_fill_info(cdscpi, sender, ip_hdr, skb); |
| if (ecm_classifier_dscp_is_bidi_packet_seen(cdscpi)) { |
| DEBUG_TRACE("%px: UDP both side info is picked\n", cdscpi); |
| goto done; |
| } |
| |
| /* |
| * Deny the acceleration if any of the below options holds true. |
| * For option 1, we wait forever |
| * For option 2, we wait until seeing ecm_classifier_accel_delay_pkts. |
| */ |
| if ((ecm_classifier_accel_delay_pkts == 1) || (slow_pkts < ecm_classifier_accel_delay_pkts)) { |
| DEBUG_TRACE("%px: accel_delay_pkts: %d slow_pkts: %llu accel is not allowed yet\n", |
| cdscpi, ecm_classifier_accel_delay_pkts, slow_pkts); |
| cdscpi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO; |
| goto dscp_classifier_out; |
| } |
| } |
| |
| /* |
| * If we didn't see both direction traffic during the acceleration |
| * delay time, we can allow the acceleration by setting the uni-directional |
| * values to both flow and return QoS and DSCP. |
| */ |
| if (sender == ECM_TRACKER_SENDER_TYPE_SRC) { |
| cdscpi->process_response.flow_qos_tag = skb->priority; |
| cdscpi->process_response.flow_mark = skb->mark; |
| cdscpi->process_response.flow_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| |
| /* |
| * If UDP bi-di traffic is being run, it is possible that other direction's |
| * QoS and DSCP values are also set by the subsequent packets before we push |
| * the rule to NSS. So, let's update them, if they are not set. |
| */ |
| if (cdscpi->process_response.return_qos_tag == 0) { |
| cdscpi->process_response.return_qos_tag = skb->priority; |
| } |
| |
| if (cdscpi->process_response.return_mark == 0) { |
| cdscpi->process_response.return_mark = skb->mark; |
| } |
| |
| if (cdscpi->process_response.return_dscp == 0) { |
| cdscpi->process_response.return_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| } |
| |
| } else { |
| cdscpi->process_response.return_qos_tag = skb->priority; |
| cdscpi->process_response.return_mark = skb->mark; |
| cdscpi->process_response.return_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| |
| /* |
| * If UDP bi-di traffic is being run, it is possible that other direction's |
| * QoS and DSCP values are also set by the subsequent packets before we push |
| * the rule to NSS. So, let's update them, if they are not set. |
| */ |
| if (cdscpi->process_response.flow_qos_tag == 0) { |
| cdscpi->process_response.flow_qos_tag = skb->priority; |
| } |
| |
| if (cdscpi->process_response.flow_mark == 0) { |
| cdscpi->process_response.flow_mark = skb->mark; |
| } |
| |
| if (cdscpi->process_response.flow_dscp == 0) { |
| cdscpi->process_response.flow_dscp = ip_hdr->ds >> XT_DSCP_SHIFT; |
| } |
| } |
| |
| /* |
| * If the flow and return set flags are set for the MARK, we overwrite the mark field. |
| * These values are stored in the dscp extentension in the update callback. |
| */ |
| if ((dscpcte->flow_set_flags & NF_CT_DSCPREMARK_EXT_MARK) && |
| (dscpcte->return_set_flags & NF_CT_DSCPREMARK_EXT_MARK)) { |
| cdscpi->process_response.flow_mark = dscpcte->flow_mark; |
| cdscpi->process_response.return_mark = dscpcte->reply_mark; |
| } |
| } |
| done: |
| cdscpi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_QOS_TAG; |
| cdscpi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_MARK; |
| |
| #ifdef ECM_CLASSIFIER_DSCP_IGS |
| /* |
| * IGS qostag values in conntrack are stored as per the direction of the flow. |
| * But ECM always create an acceleration connection rule treating packet's source |
| * address as the source of the connection irrespective of the CT's direction. |
| * So, the IGS qostag values should be appropriately filled in ECM acceleration |
| * connection rule. |
| * Scenario's example: For WAN to LAN traffic for tunnel, the CT will be created from |
| * WAN to LAN but the CI will not be created as ECM will not get the packet in this |
| * direction. CI will be created for packet from LAN to WAN. |
| */ |
| cdscpi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_IGS_QOS_TAG; |
| if (((sender == ECM_TRACKER_SENDER_TYPE_SRC) && (IP_CT_DIR_ORIGINAL == CTINFO2DIR(ctinfo))) || |
| ((sender == ECM_TRACKER_SENDER_TYPE_DEST) && (IP_CT_DIR_REPLY == CTINFO2DIR(ctinfo)))) { |
| cdscpi->process_response.igs_flow_qos_tag = dscpcte->igs_flow_qos_tag; |
| cdscpi->process_response.igs_return_qos_tag = dscpcte->igs_reply_qos_tag; |
| } else { |
| cdscpi->process_response.igs_return_qos_tag = dscpcte->igs_flow_qos_tag; |
| cdscpi->process_response.igs_flow_qos_tag = dscpcte->igs_reply_qos_tag; |
| } |
| #endif |
| /* |
| * Check if we need to set DSCP |
| */ |
| if (dscp_marked) { |
| cdscpi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_DSCP; |
| } |
| |
| dscp_classifier_out: |
| |
| /* |
| * Return our process response |
| */ |
| *process_response = cdscpi->process_response; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| } |
| |
| /* |
| * ecm_classifier_dscp_sync_to_v4() |
| * Front end is pushing accel engine state to us |
| */ |
| static void ecm_classifier_dscp_sync_to_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi __attribute__((unused)); |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed", cdscpi); |
| } |
| |
| /* |
| * ecm_classifier_dscp_sync_from_v4() |
| * Front end is retrieving accel engine state from us |
| */ |
| static void ecm_classifier_dscp_sync_from_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi __attribute__((unused)); |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed", cdscpi); |
| } |
| |
| /* |
| * ecm_classifier_dscp_sync_to_v6() |
| * Front end is pushing accel engine state to us |
| */ |
| static void ecm_classifier_dscp_sync_to_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi __attribute__((unused)); |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed", cdscpi); |
| } |
| |
| /* |
| * ecm_classifier_dscp_sync_from_v6() |
| * Front end is retrieving accel engine state from us |
| */ |
| static void ecm_classifier_dscp_sync_from_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi __attribute__((unused)); |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)aci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed", cdscpi); |
| } |
| |
| /* |
| * ecm_classifier_dscp_type_get() |
| * Get type of classifier this is |
| */ |
| static ecm_classifier_type_t ecm_classifier_dscp_type_get(struct ecm_classifier_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| return ECM_CLASSIFIER_TYPE_DSCP; |
| } |
| |
| /* |
| * ecm_classifier_dscp_last_process_response_get() |
| * Get result code returned by the last process call |
| */ |
| static void ecm_classifier_dscp_last_process_response_get(struct ecm_classifier_instance *ci, |
| struct ecm_classifier_process_response *process_response) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| *process_response = cdscpi->process_response; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| } |
| |
| /* |
| * ecm_classifier_dscp_reclassify_allowed() |
| * Indicate if reclassify is allowed |
| */ |
| static bool ecm_classifier_dscp_reclassify_allowed(struct ecm_classifier_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| |
| return true; |
| } |
| |
| /* |
| * ecm_classifier_dscp_reclassify() |
| * Reclassify |
| */ |
| static void ecm_classifier_dscp_reclassify(struct ecm_classifier_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed\n", cdscpi); |
| |
| /* |
| * Revert back to MAYBE relevant - we will evaluate when we get the next process() call. |
| */ |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| } |
| |
| #ifdef ECM_STATE_OUTPUT_ENABLE |
| /* |
| * ecm_classifier_dscp_state_get() |
| * Return state |
| */ |
| static int ecm_classifier_dscp_state_get(struct ecm_classifier_instance *ci, struct ecm_state_file_instance *sfi) |
| { |
| int result; |
| struct ecm_classifier_dscp_instance *cdscpi; |
| struct ecm_classifier_process_response process_response; |
| |
| cdscpi = (struct ecm_classifier_dscp_instance *)ci; |
| DEBUG_CHECK_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC, "%px: magic failed", cdscpi); |
| |
| if ((result = ecm_state_prefix_add(sfi, "dscp"))) { |
| return result; |
| } |
| |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| process_response = cdscpi->process_response; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| |
| /* |
| * Output our last process response |
| */ |
| if ((result = ecm_classifier_process_response_state_get(sfi, &process_response))) { |
| return result; |
| } |
| |
| return ecm_state_prefix_remove(sfi); |
| } |
| #endif |
| |
| /* |
| * ecm_classifier_dscp_instance_alloc() |
| * Allocate an instance of the DSCP classifier |
| */ |
| struct ecm_classifier_dscp_instance *ecm_classifier_dscp_instance_alloc(struct ecm_db_connection_instance *ci) |
| { |
| struct ecm_classifier_dscp_instance *cdscpi; |
| |
| /* |
| * Allocate the instance |
| */ |
| cdscpi = (struct ecm_classifier_dscp_instance *)kzalloc(sizeof(struct ecm_classifier_dscp_instance), GFP_ATOMIC | __GFP_NOWARN); |
| if (!cdscpi) { |
| DEBUG_WARN("Failed to allocate DSCP instance\n"); |
| return NULL; |
| } |
| |
| DEBUG_SET_MAGIC(cdscpi, ECM_CLASSIFIER_DSCP_INSTANCE_MAGIC); |
| cdscpi->refs = 1; |
| cdscpi->base.process = ecm_classifier_dscp_process; |
| cdscpi->base.sync_from_v4 = ecm_classifier_dscp_sync_from_v4; |
| cdscpi->base.sync_to_v4 = ecm_classifier_dscp_sync_to_v4; |
| cdscpi->base.sync_from_v6 = ecm_classifier_dscp_sync_from_v6; |
| cdscpi->base.sync_to_v6 = ecm_classifier_dscp_sync_to_v6; |
| cdscpi->base.type_get = ecm_classifier_dscp_type_get; |
| cdscpi->base.last_process_response_get = ecm_classifier_dscp_last_process_response_get; |
| cdscpi->base.reclassify_allowed = ecm_classifier_dscp_reclassify_allowed; |
| cdscpi->base.reclassify = ecm_classifier_dscp_reclassify; |
| #ifdef ECM_STATE_OUTPUT_ENABLE |
| cdscpi->base.state_get = ecm_classifier_dscp_state_get; |
| #endif |
| cdscpi->base.ref = ecm_classifier_dscp_ref; |
| cdscpi->base.deref = ecm_classifier_dscp_deref; |
| cdscpi->base.update = ecm_classifier_dscp_update; |
| cdscpi->ci_serial = ecm_db_connection_serial_get(ci); |
| cdscpi->process_response.process_actions = 0; |
| cdscpi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE; |
| |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| |
| /* |
| * Final check if we are pending termination |
| */ |
| if (ecm_classifier_dscp_terminate_pending) { |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| DEBUG_INFO("%px: Terminating\n", ci); |
| kfree(cdscpi); |
| return NULL; |
| } |
| |
| /* |
| * Link the new instance into our list at the head |
| */ |
| cdscpi->next = ecm_classifier_dscp_instances; |
| if (ecm_classifier_dscp_instances) { |
| ecm_classifier_dscp_instances->prev = cdscpi; |
| } |
| ecm_classifier_dscp_instances = cdscpi; |
| |
| /* |
| * Increment stats |
| */ |
| ecm_classifier_dscp_count++; |
| DEBUG_ASSERT(ecm_classifier_dscp_count > 0, "%px: ecm_classifier_dscp_count wrap\n", cdscpi); |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| |
| DEBUG_INFO("DSCP instance alloc: %px\n", cdscpi); |
| return cdscpi; |
| } |
| EXPORT_SYMBOL(ecm_classifier_dscp_instance_alloc); |
| |
| /* |
| * ecm_classifier_dscp_init() |
| */ |
| int ecm_classifier_dscp_init(struct dentry *dentry) |
| { |
| DEBUG_INFO("DSCP classifier Module init\n"); |
| |
| ecm_classifier_dscp_dentry = debugfs_create_dir("ecm_classifier_dscp", dentry); |
| if (!ecm_classifier_dscp_dentry) { |
| DEBUG_ERROR("Failed to create ecm dscp directory in debugfs\n"); |
| return -1; |
| } |
| |
| if (!debugfs_create_u32("enabled", S_IRUGO | S_IWUSR, ecm_classifier_dscp_dentry, |
| (u32 *)&ecm_classifier_dscp_enabled)) { |
| DEBUG_ERROR("Failed to create dscp enabled file in debugfs\n"); |
| debugfs_remove_recursive(ecm_classifier_dscp_dentry); |
| return -1; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ecm_classifier_dscp_init); |
| |
| /* |
| * ecm_classifier_dscp_exit() |
| */ |
| void ecm_classifier_dscp_exit(void) |
| { |
| DEBUG_INFO("DSCP classifier Module exit\n"); |
| |
| spin_lock_bh(&ecm_classifier_dscp_lock); |
| ecm_classifier_dscp_terminate_pending = true; |
| spin_unlock_bh(&ecm_classifier_dscp_lock); |
| |
| /* |
| * Remove the debugfs files recursively. |
| */ |
| if (ecm_classifier_dscp_dentry) { |
| debugfs_remove_recursive(ecm_classifier_dscp_dentry); |
| } |
| } |
| EXPORT_SYMBOL(ecm_classifier_dscp_exit); |