blob: 1c0a6d0ef45c5b83a5277e8a71b543fe2d2621fb [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-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.
**************************************************************************
*/
#ifndef __ECM_FRONT_END_COMMON_H
#define __ECM_FRONT_END_COMMON_H
#include <linux/if_pppox.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_acct.h>
#include "ecm_bond_notifier.h"
#define ECM_FRONT_END_SYSCTL_PATH "/net/ecm"
/*
* Flag to limit the number of DB connections at any point to the maximum number
* that can be accelerated by NSS. This may need to be enabled for low memory
* platforms to control memory allocated by ECM databases.
*/
extern unsigned int ecm_front_end_conn_limit;
/*
* ecm_front_end_l2_encap_header_len()
* Return length of encapsulating L2 header
*/
static inline uint32_t ecm_front_end_l2_encap_header_len(uint16_t protocol)
{
switch (protocol) {
case ETH_P_PPP_SES:
return PPPOE_SES_HLEN;
default:
return 0;
}
}
/*
* ecm_front_end_pull_l2_encap_header()
* Pull encapsulating L2 header
*/
static inline void ecm_front_end_pull_l2_encap_header(struct sk_buff *skb, uint32_t len)
{
skb->data += len;
skb->network_header += len;
}
/*
* ecm_front_end_push_l2_encap_header()
* Push encapsulating L2 header
*/
static inline void ecm_front_end_push_l2_encap_header(struct sk_buff *skb, uint32_t len)
{
skb->data -= len;
skb->network_header -= len;
}
/*
* ecm_front_end_acceleration_rejected()
* Check if the acceleration of a flow could be rejected quickly.
*/
static inline bool ecm_front_end_acceleration_rejected(struct sk_buff *skb)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_counter *acct;
ct = nf_ct_get(skb, &ctinfo);
if (unlikely(!ct)) {
/*
* Maybe bridged traffic, no decision here.
*/
return false;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0))
if (unlikely(nf_ct_is_untracked(ct))) {
#else
if (unlikely(ctinfo == IP_CT_UNTRACKED)) {
#endif
/*
* Untracked traffic certainly can't be accelerated.
*/
return true;
}
acct = nf_conn_acct_find(ct)->counter;
if (acct) {
long long packets = atomic64_read(&acct[CTINFO2DIR(ctinfo)].packets);
if ((packets > 0xff) && (packets & 0xff)) {
/*
* Connection hits slow path at least 256 times, so it must be not able to accelerate.
* But we also give it a chance to walk through ECM every 256 packets
*/
return true;
}
}
return false;
}
/*
* ecm_front_end_flow_and_return_directions_get()
* Gets the flow and return flows directions respect to conntrack entry.
*/
static inline void ecm_front_end_flow_and_return_directions_get(struct nf_conn *ct, ip_addr_t flow_ip, int ip_version, int *flow_dir, int *return_dir)
{
ip_addr_t ct_src_ip;
if (ip_version == 4) {
uint32_t flow_ip_32 = 0;
uint32_t ct_src_ip_32 = 0;
ECM_NIN4_ADDR_TO_IP_ADDR(ct_src_ip, ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip);
/*
* Print the IP addresses for debug purpose.
*/
ECM_IP_ADDR_TO_HIN4_ADDR(flow_ip_32, flow_ip);
ECM_IP_ADDR_TO_HIN4_ADDR(ct_src_ip_32, ct_src_ip);
DEBUG_TRACE("flow_ip: %pI4h ct_src_ip: %pI4h\n", &flow_ip_32, &ct_src_ip_32);
} else if (ip_version == 6) {
ECM_NIN6_ADDR_TO_IP_ADDR(ct_src_ip, ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.in6);
/*
* Print the IP addresses for debug purpose.
*/
DEBUG_TRACE("flow_ip: " ECM_IP_ADDR_OCTAL_FMT " ct_src_ip: " ECM_IP_ADDR_OCTAL_FMT "\n",
ECM_IP_ADDR_TO_OCTAL(flow_ip), ECM_IP_ADDR_TO_OCTAL(ct_src_ip));
} else {
DEBUG_ASSERT(NULL, "Invalid ip version");
}
if (ECM_IP_ADDR_MATCH(ct_src_ip, flow_ip)) {
*flow_dir = IP_CT_DIR_ORIGINAL;
*return_dir = IP_CT_DIR_REPLY;
DEBUG_TRACE("flow_ip and ct_src_ip match\n");
} else {
*flow_dir = IP_CT_DIR_REPLY;
*return_dir = IP_CT_DIR_ORIGINAL;
DEBUG_TRACE("flow_ip and ct_src_ip do not match\n");
}
DEBUG_TRACE("flow_dir: %d return_dir: %d\n", *flow_dir, *return_dir);
}
/*
* ecm_front_end_common_connection_defunct_check()
* Checks if the connection can be defuncted.
* The return value indicates that the caller is allowed to send a defunct message.
*/
static inline bool ecm_front_end_common_connection_defunct_check(struct ecm_front_end_connection_instance *feci)
{
DEBUG_ASSERT(spin_is_locked(&feci->lock), "%px: feci lock is not held\n", feci);
/*
* If we have not completed the destroy failure handling, do nothing.
*/
if (feci->destroy_fail_handle_pending) {
return false;
}
/*
* If connection has already become defunct, do nothing.
*/
if (feci->is_defunct) {
return false;
}
feci->is_defunct = true;
/*
* If the connection is already in one of the fail modes, do nothing, keep the current accel_mode.
*/
if (ECM_FRONT_END_ACCELERATION_FAILED(feci->accel_mode)) {
return false;
}
/*
* If the connection is decel then ensure it will not attempt accel while defunct.
*/
if (feci->accel_mode == ECM_FRONT_END_ACCELERATION_MODE_DECEL) {
feci->accel_mode = ECM_FRONT_END_ACCELERATION_MODE_FAIL_DEFUNCT;
return false;
}
/*
* If the connection is decel pending then decel operation is in progress anyway.
*/
if (feci->accel_mode == ECM_FRONT_END_ACCELERATION_MODE_DECEL_PENDING) {
return false;
}
return true;
}
/*
* ecm_front_end_common_connection_decelerate_accel_mode_check()
* Checks the accel mode of the connection to see it is ok for deceleration.
* The return value indicates that the caller is allowed to send a decelerate message.
*/
static inline bool ecm_front_end_common_connection_decelerate_accel_mode_check(struct ecm_front_end_connection_instance *feci)
{
DEBUG_ASSERT(spin_is_locked(&feci->lock), "%px: feci lock is not held\n", feci);
/*
* If decelerate is in error or already pending then ignore
*/
if (feci->stats.decelerate_pending) {
return false;
}
/*
* If acceleration is pending then we cannot decelerate right now or we will race with it
* Set a decelerate pending flag that will be actioned when the acceleration command is complete.
*/
if (feci->accel_mode == ECM_FRONT_END_ACCELERATION_MODE_ACCEL_PENDING) {
feci->stats.decelerate_pending = true;
return false;
}
/*
* Can only decelerate if accelerated
* NOTE: This will also deny accel when the connection is in fail condition too.
*/
if (feci->accel_mode != ECM_FRONT_END_ACCELERATION_MODE_ACCEL) {
return false;
}
return true;
}
/*
* ecm_front_end_destroy_failure_handle()
* Destroy request failure handler.
*/
static inline bool ecm_front_end_destroy_failure_handle(struct ecm_front_end_connection_instance *feci)
{
spin_lock_bh(&feci->lock);
feci->stats.driver_fail_total++;
feci->stats.driver_fail++;
if (feci->stats.driver_fail >= feci->stats.driver_fail_limit) {
/*
* Reached to the driver failure limit. ECM no longer allows
* re-trying deceleration.
*/
feci->accel_mode = ECM_FRONT_END_ACCELERATION_MODE_FAIL_DRIVER;
spin_unlock_bh(&feci->lock);
DEBUG_WARN("%px: Decel failed - driver fail limit\n", feci);
return true;
}
/*
* Destroy request failed. The accelerated connection couldn't be destroyed
* in the acceleration engine. Revert back the accel_mode, unset the is_defunct
* flag just in case this request has come through the defunct process.
*/
feci->accel_mode = ECM_FRONT_END_ACCELERATION_MODE_ACCEL;
feci->is_defunct = false;
feci->destroy_fail_handle_pending = true;
spin_unlock_bh(&feci->lock);
/*
* Set the defunct timer to a smaller timeout value so that the connection will be
* tried to be defuncted again, when the timeout expires (its value is 5 seconds).
*/
ecm_db_connection_defunct_timer_remove_and_set(feci->ci, ECM_DB_TIMER_GROUPS_CONNECTION_DEFUNCT_RETRY_TIMEOUT);
spin_lock_bh(&feci->lock);
feci->destroy_fail_handle_pending = false;
spin_unlock_bh(&feci->lock);
return false;
}
extern void ecm_front_end_bond_notifier_stop(int num);
extern int ecm_front_end_bond_notifier_init(struct dentry *dentry);
extern void ecm_front_end_bond_notifier_exit(void);
#ifdef ECM_STATE_OUTPUT_ENABLE
extern int ecm_front_end_common_connection_state_get(struct ecm_front_end_connection_instance *feci,
struct ecm_state_file_instance *sfi,
char *conn_type);
#endif
extern bool ecm_front_end_gre_proto_is_accel_allowed(struct net_device *indev,
struct net_device *outdev,
struct sk_buff *skb,
struct nf_conntrack_tuple *tuple,
int ip_version);
extern uint64_t ecm_front_end_get_slow_packet_count(struct ecm_front_end_connection_instance *feci);
#ifdef ECM_CLASSIFIER_DSCP_ENABLE
void ecm_front_end_tcp_set_dscp_ext(struct nf_conn *ct,
struct ecm_tracker_ip_header *iph,
struct sk_buff *skb,
ecm_tracker_sender_type_t sender);
#endif
void ecm_front_end_fill_ovs_params(struct ecm_front_end_ovs_params ovs_params[],
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,
int src_port, int src_port_nat,
int dest_port, int dest_port_nat, ecm_db_direction_t ecm_dir);
void ecm_front_end_common_sysctl_register(void);
void ecm_front_end_common_sysctl_unregister(void);
int ecm_sfe_sysctl_tbl_init(void);
void ecm_sfe_sysctl_tbl_exit(void);
#endif /* __ECM_FRONT_END_COMMON_H */