blob: 29b96ab30ff451dcfc6c296c0be881b41c65cf25 [file] [log] [blame]
/*
* Header file describing the BCOL TCPKA interfaces/public functions
*
* Provides type definitions and function prototypes used to link the
* DHD OS, bus, and protocol modules.
*
* Copyright (C) 1999-2017, Broadcom Corporation
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a license
* other than the GPL, without Broadcom's express prior written consent.
*
*
* <<Broadcom-WL-IPTag/Open:>>
*
* $Id:$
*/
#ifndef _DHD_BCOL_TCPKA_PUB_
#define _DHD_BCOL_TCPKA_PUB_
#include <typedefs.h>
#include <osl.h>
#include <dhd_bcol_tcpka.h>
#include <bcmtcp.h>
#include <dhd_ip.h>
#include <dhd.h>
#ifdef TCPKA_DEBUG
#define DHD_TCPKA_INFO(x) printf x
#else
#define DHD_TCPKA_INFO(x)
#endif
enum {
BCOL_TCPKA_SYNC_MODE_OFF = 0,
BCOL_TCPKA_SYNC_MODE_ON
};
enum {
BCOL_TCPKA_SYNC_STATE_NONE = 0,
BCOL_TCPKA_SYNC_STATE_TRAC,
BCOL_TCPKA_SYNC_STATE_READY,
BCOL_TCPKA_SYNC_STATE_BUSY,
BCOL_TCPKA_SYNC_STATE_DONE
};
typedef struct {
uint8 sess_id;
uint8 gpio_slot;
uint8 type;
uint16 len;
uint8 data[1];
} tcpka_noti_cfg_t;
#define TCPKA_NOTI_BLOCK_ALL -2
#ifdef TCPKA_DEBUG
static inline char *
dhd_bcol_ipv6_ntoa(uint8 *ia, char *buf)
{
snprintf(buf, 64, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
"%02x%02x:%02x%02x:%02x%02x:%02x%02x",
ia[0], ia[1], ia[2], ia[3], ia[4], ia[5], ia[6], ia[7],
ia[8], ia[9], ia[10], ia[11], ia[12], ia[13], ia[14], ia[15]);
return (buf);
}
static inline void dhd_bcol_dump_tcpka6_conn(tcpka6_conn_t *tcpka)
{
uint8 ipbuf[64];
printf("%s: dump %s:\n", __func__, CMD_STR_TCPKA6_CONN_ADD);
printf("sess_id=%d, dst_mac=%02x:%02x:%02x:%02x:%02x:%02x\n", tcpka->sess_id,
tcpka->dst_mac.octet[0], tcpka->dst_mac.octet[1],
tcpka->dst_mac.octet[2], tcpka->dst_mac.octet[3],
tcpka->dst_mac.octet[4], tcpka->dst_mac.octet[5]);
printf("srcip=%s dstip=%s", dhd_bcol_ipv6_ntoa((uint8 *)&tcpka->src_ip, ipbuf),
dhd_bcol_ipv6_ntoa((uint8 *)&tcpka->dst_ip, ipbuf));
printf("srcport=%d, dstport=%d, seq=%u, ack=%u\n",
tcpka->srcport, tcpka->dstport, tcpka->seq, tcpka->ack);
printf("window=%u, tsval=%u, tsecr=%u, last_payload_len=%u\n",
tcpka->tcpwin, tcpka->tsval, tcpka->tsecr, tcpka->last_payload_len);
printf("ka_payload_len=%d\n", tcpka->ka_payload_len);
}
static inline void dhd_bcol_dump_tcpka_conn(tcpka_conn_t *tcpka)
{
printf("%s: dump %s:\n", __func__, CMD_STR_TCPKA_CONN_ADD);
printf("sess_id=%d, dst_mac=%02x:%02x:%02x:%02x:%02x:%02x\n", tcpka->sess_id,
tcpka->dst_mac.octet[0], tcpka->dst_mac.octet[1],
tcpka->dst_mac.octet[2], tcpka->dst_mac.octet[3],
tcpka->dst_mac.octet[4], tcpka->dst_mac.octet[5]);
printf("ipid=%u, srcport=%d, dstport=%d, seq=%u, ack=%u\n",
tcpka->ipid, tcpka->srcport, tcpka->dstport, tcpka->seq, tcpka->ack);
printf("window=%u, tsval=%u, tsecr=%u, last_payload_len=%u\n",
tcpka->tcpwin, tcpka->tsval, tcpka->tsecr, tcpka->last_payload_len);
printf("ka_payload_len=%d\n", tcpka->ka_payload_len);
}
static inline void dhd_bcol_dump_tcpka_conn_sess(tcpka_conn_sess_t *tcpka)
{
printf("%s: dump %s:\n", __func__, CMD_STR_TCPKA_CONN_EN);
printf("sess_id=%d, flag=%x\n", tcpka->sess_id, tcpka->flag);
if (tcpka->flag) {
printf("tcp keepalive timers:interval=%d\n", tcpka->tcp_keepalive_timers.interval);
printf("retry_interval=%d. retry_count=%d\n",
tcpka->tcp_keepalive_timers.retry_interval,
tcpka->tcp_keepalive_timers.retry_count);
}
}
#else
#define dhd_bcol_dump_tcpka_conn(x)
#define dhd_bcol_dump_tcpka6_conn(x)
#define dhd_bcol_dump_tcpka_conn_sess(x)
#endif /* TCPKA_DEBUG */
static inline int dhd_bcol_tcpka_check_xmit(dhd_pub_t *dhdp, void *pkt)
{
uint8 *new_ether_hdr; /* Ethernet header of the new packet */
uint16 new_ether_type; /* Ethernet type of the new packet */
uint8 *new_ip_hdr; /* IP header of the new packet */
uint8 *new_tcp_hdr; /* TCP header of the new packet */
uint32 new_ip_hdr_len; /* IP header length of the new packet */
uint32 cur_framelen;
uint16 new_ip_total_len; /* Total length of IP packet for the new packet */
uint16 new_ip_payload_len; /* Total payload length of IPv6 packet for the new packet */
uint32 new_tcp_hdr_len; /* TCP header length of the new packet */
uint32 tcp_option_len;
tcpka_conn_t *tcpka, tcpka_orig;
tcpka6_conn_t *tcpka6, tcpka6_orig;
struct tcphdr *tcph; /* TCP header */
uint8 *payload; /* TCP data begin pointer */
uint16 last_payload_len = 0;
cur_framelen = PKTLEN(dhdp->osh, pkt);
if (tcpka_sync.tcpka_sync_mode == BCOL_TCPKA_SYNC_MODE_OFF ||
(!tcpka_sync.tcpka_conn && !tcpka_sync.tcpka6_conn)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: mode %d, tcpka_conn %p, tcpka6_conn %p, cur_framelen %d\n", __func__, tcpka_sync.tcpka_sync_mode, tcpka_sync.tcpka_conn, tcpka_sync.tcpka6_conn, cur_framelen));
}
return BCME_NOTFOUND;
}
/* config is going */
if (tcpka_sync.tcpka_sync_state == BCOL_TCPKA_SYNC_STATE_BUSY) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: sync state busy, cur_framelen: %d\n", __func__, cur_framelen));
}
return BCME_BUSY;
}
if (tcpka_sync.tcpka_sync_state == BCOL_TCPKA_SYNC_STATE_NONE) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: sync_state %d, cur_framelen %d\n", __func__, tcpka_sync.tcpka_sync_state, cur_framelen));
}
return BCME_NOTFOUND;
}
/* get the right tcpka_conn for IPv6 or IPv4 */
if (tcpka_sync.sess_type) {
tcpka6 = tcpka_sync.tcpka6_conn;
if (!tcpka6 || !tcpka6->sess_id) {
DHD_ERROR(("%s: tcpka6 config has no vaild session id\n", __func__));
return BCME_NOTREADY;
}
memcpy(&tcpka6_orig, tcpka6, sizeof(tcpka6_conn_t));
} else {
tcpka = tcpka_sync.tcpka_conn;
if (!tcpka || !tcpka->sess_id) {
DHD_ERROR(("%s: tcpka config has no vaild session id\n", __func__));
return BCME_NOTREADY;
}
memcpy(&tcpka_orig, tcpka, sizeof(tcpka_conn_t));
}
new_ether_hdr = PKTDATA(dhdp->osh, pkt);
if (cur_framelen < TCPACKSZMIN) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: cur_framelen: %d\n", __func__, cur_framelen));
}
return BCME_NOTFOUND;
}
new_ether_type = new_ether_hdr[12] << 8 | new_ether_hdr[13];
if ((new_ether_type != ETHER_TYPE_IP) && (new_ether_type != ETHER_TYPE_IPV6)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: new_ether_type: %d, cur_framelen: %d\n", __func__, new_ether_type, cur_framelen));
}
return BCME_NOTFOUND;
}
new_ip_hdr = new_ether_hdr + ETHER_HDR_LEN;
if (((IP_VER(new_ip_hdr) != IP_VER_4) || (IPV4_PROT(new_ip_hdr) != IP_PROT_TCP)) &&
((IP_VER(new_ip_hdr) != IP_VER_6) || (IPV6_PROT(new_ip_hdr) != IP_PROT_TCP))) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: wrong ip header, cur_framelen: %d\n", __func__, cur_framelen));
}
return BCME_NOTFOUND;
}
new_tcp_hdr = (uint8 *)tcp_hdr((struct sk_buff *)pkt);
/* if session type is IPV4 and packet is IPv4 */
if ((IP_VER(new_ip_hdr) == IP_VER_4) && (tcpka_sync.sess_type == 0)) {
new_ip_hdr_len = IPV4_HLEN(new_ip_hdr);
#ifdef BCOL_TCPKA_SYNC_MATCHED_BY_PORT
/* If TCP port number does not match, skip. */
if (tcpka->srcport == 0) {
/* skip the src port match */
uint16 srcport = ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]);
uint16 dstport = ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]);
if (dstport != tcpka->dstport) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: wrong port dst(%d,%d), cur_framelen: %d\n", __func__, dstport, tcpka->dstport, cur_framelen));
}
return BCME_NOTFOUND;
}
tcpka->srcport = srcport;
} else {
uint16 srcport = ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]);
uint16 dstport = ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]);
if ((srcport != tcpka->srcport) || (dstport != tcpka->dstport)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: wrong port src(%d, %d), dst(%d, %d), cur_framelen: %d\n", __func__, srcport, tcpka->srcport, dstport, tcpka->dstport, cur_framelen));
}
return BCME_NOTFOUND;
}
}
memcpy(tcpka->src_ip.addr, &new_ip_hdr[IPV4_SRC_IP_OFFSET], IPV4_ADDR_LEN);
memcpy(tcpka->dst_ip.addr, &new_ip_hdr[IPV4_DEST_IP_OFFSET], IPV4_ADDR_LEN);
#else
/* If either of IP address or TCP port number does not match, skip. */
if (memcmp(&new_ip_hdr[IPV4_SRC_IP_OFFSET], tcpka->src_ip.addr, IPV4_ADDR_LEN) ||
memcmp(&new_ip_hdr[IPV4_DEST_IP_OFFSET], tcpka->dst_ip.addr, IPV4_ADDR_LEN) ||
(ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]) != tcpka->srcport) ||
(ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]) != tcpka->dstport)) {
return BCME_NOTFOUND;
}
#endif /* BCOL_TCPKA_SYNC_MATCHED_BY_PORT */
/* update the destination mac */
memcpy(tcpka->dst_mac.octet, new_ether_hdr, ETHER_ADDR_LEN);
/* update the ID of IP */
tcpka->ipid = ntoh16_ua(&new_ip_hdr[IPV4_ID_OFFSET]);
DHD_TCPKA_INFO(("%s: before\n", __func__));
dhd_bcol_dump_tcpka_conn(tcpka);
tcpka->seq = ntoh32_ua(&new_tcp_hdr[TCP_SEQ_NUM_OFFSET]);
tcpka->ack = ntoh32_ua(&new_tcp_hdr[TCP_ACK_NUM_OFFSET]);
tcpka->tcpwin = ntoh16_ua(&new_tcp_hdr[TCP_WINDOW_OFFSET]);
#ifdef TCPKA_BYPASS
tcpka->bypass = TRUE;
#endif
}
else if ((IP_VER(new_ip_hdr) == IP_VER_6) && (tcpka_sync.sess_type == 1)) {
#ifdef BCOL_TCPKA_SYNC_MATCHED_BY_PORT
/* If TCP port number does not match, skip. */
if (tcpka6->srcport == 0) {
/* skip the src port match */
if ((ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]) != tcpka6->dstport)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: %d, wrong port, cur_framelen: %d\n", __func__, __LINE__, cur_framelen));
}
return BCME_NOTFOUND;
}
tcpka6->srcport = ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]);
} else {
if ((ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]) != tcpka6->srcport) ||
(ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]) != tcpka6->dstport)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: %d, wrong port, cur_framelen: %d\n", __func__, __LINE__, cur_framelen));
}
return BCME_NOTFOUND;
}
}
/* update the dst mac, src ip, dst ip */
memcpy(tcpka6->src_ip, &new_ip_hdr[IPV6_SRC_IP_OFFSET], IPV6_ADDR_LEN);
memcpy(tcpka6->dst_ip, &new_ip_hdr[IPV6_DEST_IP_OFFSET], IPV6_ADDR_LEN);
#else
/* If either of IP address or TCP port number does not match, skip. */
if (memcmp(&new_ip_hdr[IPV6_SRC_IP_OFFSET], tcpka6->src_ip, IPV6_ADDR_LEN) ||
memcmp(&new_ip_hdr[IPV6_DEST_IP_OFFSET], tcpka6->dst_ip, IPV6_ADDR_LEN) ||
(ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]) != tcpka6->srcport) ||
(ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET]) != tcpka6->dstport)) {
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: %d, wrong port, cur_framelen: %d\n", __func__, __LINE__, cur_framelen));
}
return BCME_NOTFOUND;
}
#endif /* BCOL_TCPKA_SYNC_MATCHED_BY_PORT */
memcpy(tcpka6->dst_mac.octet, new_ether_hdr, ETHER_ADDR_LEN);
DHD_TCPKA_INFO(("%s: before\n", __func__));
dhd_bcol_dump_tcpka6_conn(tcpka6);
tcpka6->seq = ntoh32_ua(&new_tcp_hdr[TCP_SEQ_NUM_OFFSET]);
tcpka6->ack = ntoh32_ua(&new_tcp_hdr[TCP_ACK_NUM_OFFSET]);
tcpka6->tcpwin = ntoh16_ua(&new_tcp_hdr[TCP_WINDOW_OFFSET]);
#ifdef TCPKA_BYPASS
tcpka6->bypass = TRUE;
#endif
}
else {
DHD_TCPKA_INFO(("%s: curr sess type %d, pkt type %d. Skip it.\n", __func__,
tcpka_sync.sess_type, IP_VER(new_ip_hdr)));
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: wrong pkt type %d, cur_framelen: %d\n", __func__, IP_VER(new_ip_hdr), cur_framelen));
}
return BCME_NOTFOUND;
}
new_tcp_hdr_len = 4 * TCP_HDRLEN(new_tcp_hdr[TCP_HLEN_OFFSET]);
/* TCP options */
tcp_option_len = new_tcp_hdr_len - TCP_MIN_HEADER_LEN;
if (tcp_option_len) {
uint8 *new_tcp_opt_hdr = new_tcp_hdr + TCP_MIN_HEADER_LEN;
uint32 i = 0;
uint8 kind, length;
bool opt_end = false;
do {
kind = new_tcp_opt_hdr[i];
i++;
switch (kind) {
case 0: /* End of option list. */
opt_end = true;
break;
case 1: /* No operation. */
break;
case 8: /* Timestamp */
length = new_tcp_opt_hdr[i];
i++;
if (length != 10) {
DHD_ERROR(("%s: tcp opt timestamp has wrong length(%d).\n",
__func__, length));
opt_end = true;
break;
}
if (tcpka_sync.sess_type == 0) {
tcpka->tsval = ntoh32_ua(&new_tcp_opt_hdr[i]);
i+=4;
tcpka->tsecr = ntoh32_ua(&new_tcp_opt_hdr[i]);
i+=4;
}
else if (tcpka_sync.sess_type == 1) {
tcpka6->tsval = ntoh32_ua(&new_tcp_opt_hdr[i]);
i+=4;
tcpka6->tsecr = ntoh32_ua(&new_tcp_opt_hdr[i]);
i+=4;
}
break;
default: /* just skip */
length = new_tcp_opt_hdr[i];
i = i + 1 + length;
break;
}
if (opt_end) break;
} while(i < tcp_option_len);
}
/* check the sess type for IPv4 or IPv6 and set the payload len */
if (!tcpka_sync.sess_type) {
new_ip_total_len = ntoh16_ua(&new_ip_hdr[IPV4_PKTLEN_OFFSET]);
tcpka->last_payload_len = new_ip_total_len - (new_ip_hdr_len + new_tcp_hdr_len);
last_payload_len = tcpka->last_payload_len;
dhd_bcol_dump_tcpka_conn(tcpka);
} else if (tcpka_sync.sess_type) {
new_ip_payload_len = IPV6_PAYLOAD_LEN(new_ip_hdr);
tcpka6->last_payload_len = new_ip_payload_len - new_tcp_hdr_len;
last_payload_len = tcpka6->last_payload_len;
dhd_bcol_dump_tcpka6_conn(tcpka6);
}
DHD_TCPKA_INFO(("%s: last_payload_len %d\n", __func__, last_payload_len));
if (tcpka_sync.tcpka_sync_state == BCOL_TCPKA_SYNC_STATE_DONE) {
/* free all packets from target session after sync done */
DHD_ERROR(("%s: FREE post-sync packet, payload %d\n", __func__, last_payload_len));
return BCME_BUSY;
}
if (tcpka_sync.tcpka_noti_capture.type != -1) {
DHD_ERROR(("%s: got len %d v%s pkt, capture_type %d\n",
__func__,
last_payload_len,
tcpka_sync.sess_type ? "6" : "4",
tcpka_sync.tcpka_noti_capture.type));
}
if ((last_payload_len >= 2) &&
(tcpka_sync.tcpka_noti_capture.min_len <= last_payload_len) &&
(tcpka_sync.tcpka_noti_capture.max_len >= last_payload_len) &&
(tcpka_sync.tcpka_noti_capture.type >= 0)) {
int buflen = 0;
tcpka_noti_cfg_t *np = NULL;
uint16 weave_pkt_len = 0;
tcph = tcp_hdr((struct sk_buff *)pkt);
/* Find tcp payload */
payload = (unsigned char *)((unsigned char *)tcph + (tcph->doff * 4));
//prhex("TCPDATA", payload, last_payload_len);
weave_pkt_len = *(uint16 *)payload;
DHD_ERROR(("%s: tcpka_noti, weave_pkt_len %d, payload_len %d\n", __func__, weave_pkt_len, last_payload_len));
if ((weave_pkt_len + 2) != last_payload_len) {
/* This TCP segment is not complete weave msg */
goto done;
}
buflen = sizeof(tcpka_noti_cfg_t) + last_payload_len - 1;
if (tcpka_sync.tcpka_noti_buf) {
MFREE(dhdp->osh, tcpka_sync.tcpka_noti_buf,
tcpka_sync.tcpka_noti_buf_len);
tcpka_sync.tcpka_noti_buf = NULL;
}
if (tcpka_sync.tcpka_noti_buf == NULL)
tcpka_sync.tcpka_noti_buf_len = 0;
if ((tcpka_sync.tcpka_noti_buf =
(unsigned char *)MALLOCZ(dhdp->osh, buflen)) != NULL) {
tcpka_sync.tcpka_noti_buf_len = buflen;
np = (tcpka_noti_cfg_t *)tcpka_sync.tcpka_noti_buf;
memset(np, 0, buflen);
np->len = last_payload_len;
memcpy(np->data, payload, last_payload_len);
np->sess_id = TCPKA_DEFAULT_SESS_ID;
np->gpio_slot = 0;
np->type = tcpka_sync.tcpka_noti_capture.type;
DHD_ERROR(("%s: Captured tcpka_noti pyaload %d bytes\n",
__func__, last_payload_len));
} else {
DHD_ERROR(("%s: Failed to allocate memory %d bytes "
"for tcpka noti buf\n", __func__, buflen));
}
}
done:
if (tcpka_sync.tcpka_drop && last_payload_len > 0) {
DHD_ERROR(("%s: Free len %d v%s pkt\n",
__func__, last_payload_len, tcpka_sync.sess_type ? "6" : "4"));
if (tcpka_sync.sess_type) {
memcpy(tcpka6, &tcpka6_orig, sizeof(tcpka6_conn_t));
} else {
memcpy(tcpka, &tcpka_orig, sizeof(tcpka_conn_t));
}
return BCME_BUSY;
}
tcpka_sync.tcpka_sync_state = BCOL_TCPKA_SYNC_STATE_READY;
tcpka_sync.tcpka_sk = ((struct sk_buff *)pkt)->sk;
if (tcpka_sync.tcpka_drop) {
DHD_ERROR(("%s: packet sent, last_payload_len: %d\n", __func__, last_payload_len));
}
return BCME_OK;
}
#endif /* _DHD_BCOL_TCPKA_PUB_ */