| /* |
| * wl tko command module - TCP Keepalive Offload |
| * |
| * Broadcom Proprietary and Confidential. Copyright (C) 2017, |
| * All Rights Reserved. |
| * |
| * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom; |
| * the contents of this file may not be disclosed to third parties, copied |
| * or duplicated in any form, in whole or in part, without the prior |
| * written permission of Broadcom. |
| * |
| * |
| * <<Broadcom-WL-IPTag/Proprietary:>> |
| * |
| * $Id:$ |
| */ |
| |
| #ifdef WIN32 |
| #include <windows.h> |
| #endif |
| #if defined(linux) |
| #include <unistd.h> |
| #include <sys/socket.h> |
| #include <sys/ioctl.h> |
| #include <netinet/in.h> |
| #include <net/if.h> |
| #include <linux/if_packet.h> |
| #endif /* linux */ |
| |
| #include <wlioctl.h> |
| |
| #if defined(DONGLEBUILD) |
| #include <typedefs.h> |
| #include <osl.h> |
| #endif |
| |
| #include <sys/stat.h> |
| #include <errno.h> |
| |
| /* Because IL_BIGENDIAN was removed there are few warnings that need |
| * to be fixed. Windows was not compiled earlier with IL_BIGENDIAN. |
| * Hence these warnings were not seen earlier. |
| * For now ignore the following warnings |
| */ |
| #ifdef WIN32 |
| #pragma warning(push) |
| #pragma warning(disable : 4244) |
| #pragma warning(disable : 4761) |
| #endif |
| |
| #include <bcmutils.h> |
| #include <bcmendian.h> |
| #include <proto/802.3.h> |
| #include <proto/bcmip.h> |
| #include <proto/bcmipv6.h> |
| #include <proto/bcmtcp.h> |
| #include "wlu_common.h" |
| #include "wlu.h" |
| |
| static cmd_func_t wl_tko; |
| #if defined(linux) |
| static cmd_func_t wl_tko_auto_config; |
| static cmd_func_t wl_tko_event_check; |
| #endif /* linux */ |
| |
| static cmd_t wl_tko_cmds[] = { |
| { "tko", wl_tko, WLC_GET_VAR, WLC_SET_VAR, |
| "TCP keepalive offload subcommands:\n" |
| "\ttko max_tcp\n" |
| "\ttko param <interval> <retry interval> <retry count>\n" |
| "\ttko connect <index> [<ip_addr_type> <src MAC> <dst MAC>\n" |
| "<src IP> <dst IP> <src port> <dst port>\n" |
| "<seq num> <ack num> <TCP window>]\n" |
| "\ttko enable <0|1>\n" |
| "\ttko status\n"}, |
| #if defined(linux) |
| { "tko_auto_config", wl_tko_auto_config, -1, WLC_SET_VAR, |
| "Listens for TCP and automatically configures TKO.\n"}, |
| { "tko_event_check", wl_tko_event_check, -1, WLC_SET_VAR, |
| "Listens for TKO event.\n"}, |
| #endif /* linux */ |
| { NULL, NULL, 0, 0, NULL } |
| }; |
| |
| static char *buf; |
| |
| /* module initialization */ |
| void |
| wluc_tko_module_init(void) |
| { |
| /* get the global buf */ |
| buf = wl_get_buf(); |
| |
| /* register tko commands */ |
| wl_module_cmds_register(wl_tko_cmds); |
| } |
| |
| /* encode IPv6 TCP keepalive packet |
| * input 'buf' size = (ETHER_HDR_LEN + sizeof(struct ipv6_hdr) + |
| * sizeof(struct bcmtcp_hdr)) |
| * returns the size of the encoded packet |
| */ |
| static int |
| wl_ipv6_tcp_keepalive_pkt(uint8 *buf, |
| struct ether_addr *daddr, struct ether_addr *saddr, |
| struct ipv6_addr *dipv6addr, struct ipv6_addr *sipv6addr, |
| uint16 sport, uint16 dport, uint32 seq, uint32 ack, uint16 tcpwin) |
| { |
| struct ether_header *eth; |
| struct ipv6_hdr *ipv6; |
| struct bcmtcp_hdr *tcp; |
| |
| if (buf == NULL) { |
| return 0; |
| } |
| |
| /* encode ethernet header */ |
| eth = (struct ether_header *)buf; |
| memcpy(eth->ether_dhost, daddr, ETHER_ADDR_LEN); |
| memcpy(eth->ether_shost, saddr, ETHER_ADDR_LEN); |
| eth->ether_type = hton16(ETHER_TYPE_IPV6); |
| |
| /* encode IPv6 header */ |
| ipv6 = (struct ipv6_hdr *)(buf + sizeof(struct ether_header)); |
| memset(ipv6, 0, sizeof(*ipv6)); |
| ipv6->version = IPV6_VERSION; |
| ipv6->payload_len = hton16(sizeof(struct bcmtcp_hdr)); |
| ipv6->nexthdr = IP_PROT_TCP; |
| ipv6->hop_limit = IPV6_HOP_LIMIT; |
| memcpy(&ipv6->daddr, dipv6addr, sizeof(ipv6->daddr)); |
| memcpy(&ipv6->saddr, sipv6addr, sizeof(ipv6->saddr)); |
| |
| /* encode TCP header */ |
| tcp = (struct bcmtcp_hdr *)(buf + sizeof(struct ether_header) + sizeof(struct ipv6_hdr)); |
| memset(tcp, 0, sizeof(*tcp)); |
| tcp->src_port = hton16(sport); |
| tcp->dst_port = hton16(dport); |
| tcp->seq_num = hton32(seq); |
| tcp->ack_num = hton32(ack); |
| tcp->tcpwin = hton16(tcpwin); |
| tcp->chksum = 0; |
| tcp->urg_ptr = 0; |
| tcp->hdrlen_rsvd_flags = (TCP_FLAG_ACK << 8) | |
| ((sizeof(struct bcmtcp_hdr) >> 2) << TCP_HDRLEN_SHIFT); |
| |
| /* calculate checksum */ |
| tcp->chksum = ipv6_tcp_hdr_cksum((uint8 *)ipv6, (uint8 *)tcp, sizeof(*tcp)); |
| |
| return (sizeof(struct ether_header) + sizeof(struct ipv6_hdr) + |
| sizeof(struct bcmtcp_hdr)); |
| } |
| |
| /* encode TCP keepalive packet |
| * input 'buf' size = (ETHER_HDR_LEN + sizeof(struct ipv4_hdr) + |
| * sizeof(struct bcmtcp_hdr)) |
| * returns the size of the encoded packet |
| */ |
| static int |
| wl_tcp_keepalive_pkt(uint8 *buf, |
| struct ether_addr *daddr, struct ether_addr *saddr, |
| struct ipv4_addr *dipaddr, struct ipv4_addr *sipaddr, |
| uint16 sport, uint16 dport, uint32 seq, uint32 ack, uint16 tcpwin) |
| { |
| struct ether_header *eth; |
| struct ipv4_hdr *ip; |
| struct bcmtcp_hdr *tcp; |
| |
| if (buf == NULL) { |
| return 0; |
| } |
| eth = (struct ether_header *)buf; |
| memcpy(eth->ether_dhost, daddr, ETHER_ADDR_LEN); |
| memcpy(eth->ether_shost, saddr, ETHER_ADDR_LEN); |
| eth->ether_type = hton16(ETHER_TYPE_IP); |
| ip = (struct ipv4_hdr *)(buf + sizeof(struct ether_header)); |
| memcpy(ip->dst_ip, dipaddr, IPV4_ADDR_LEN); |
| memcpy(ip->src_ip, sipaddr, IPV4_ADDR_LEN); |
| ip->version_ihl = (IP_VER_4 << IPV4_VER_SHIFT) | (IPV4_MIN_HEADER_LEN / 4); |
| ip->tos = 0; |
| ip->tot_len = hton16(sizeof(struct ipv4_hdr) + sizeof(struct bcmtcp_hdr)); |
| ip->id = hton16(0); |
| ip->frag = 0; |
| ip->ttl = 32; |
| ip->prot = IP_PROT_TCP; |
| ip->hdr_chksum = 0; |
| ip->hdr_chksum = ipv4_hdr_cksum((uint8 *)ip, IPV4_HLEN(ip)); |
| tcp = (struct bcmtcp_hdr *)(buf + sizeof(struct ether_header) + |
| sizeof(struct ipv4_hdr)); |
| tcp->src_port = hton16(sport); |
| tcp->dst_port = hton16(dport); |
| tcp->seq_num = hton32(seq); |
| tcp->ack_num = hton32(ack); |
| tcp->tcpwin = hton16(tcpwin); |
| tcp->chksum = 0; |
| tcp->urg_ptr = 0; |
| tcp->hdrlen_rsvd_flags = (TCP_FLAG_ACK << 8) | |
| ((sizeof(struct bcmtcp_hdr) >> 2) << TCP_HDRLEN_SHIFT); |
| |
| /* calculate TCP header checksum */ |
| tcp->chksum = ipv4_tcp_hdr_cksum((uint8 *)ip, (uint8 *)tcp, sizeof(*tcp)); |
| |
| return (sizeof(struct ether_header) + sizeof(struct ipv4_hdr) + |
| sizeof(struct bcmtcp_hdr)); |
| } |
| |
| /* intialize TKO connect struct and returns the length of connect struct */ |
| static int |
| wl_tko_connect_init(wl_tko_connect_t *connect, uint8 index, uint8 ip_addr_type, |
| struct ether_addr *saddr, struct ether_addr *daddr, void *sipaddr, void *dipaddr, |
| uint16 local_port, uint16 remote_port, uint32 local_seq, uint32 remote_seq, uint16 tcpwin) |
| { |
| int data_len = 0; |
| |
| connect->index = index; |
| connect->ip_addr_type = ip_addr_type; |
| connect->local_port = htod16(local_port); |
| connect->remote_port = htod16(remote_port); |
| connect->local_seq = htod32(local_seq); |
| connect->remote_seq = htod32(remote_seq); |
| |
| if (ip_addr_type) { |
| /* IPv6 */ |
| struct ipv6_addr *sipv6addr = sipaddr; |
| struct ipv6_addr *dipv6addr = dipaddr; |
| |
| memcpy(&connect->data[data_len], sipv6addr, sizeof(*sipv6addr)); |
| data_len += sizeof(*sipv6addr); |
| memcpy(&connect->data[data_len], dipv6addr, sizeof(*dipv6addr)); |
| data_len += sizeof(*dipv6addr); |
| /* IPv6 TCP keepalive request */ |
| connect->request_len = wl_ipv6_tcp_keepalive_pkt( |
| &connect->data[data_len], daddr, saddr, |
| dipv6addr, sipv6addr, local_port, remote_port, |
| local_seq - 1, remote_seq, tcpwin); |
| data_len += connect->request_len; |
| /* IPv6 TCP keepalive response */ |
| connect->response_len = wl_ipv6_tcp_keepalive_pkt( |
| &connect->data[data_len], daddr, saddr, |
| dipv6addr, sipv6addr, local_port, remote_port, |
| local_seq, remote_seq, tcpwin); |
| data_len += connect->response_len; |
| } else { |
| /* IPv4 */ |
| struct ipv4_addr *sipv4addr = sipaddr; |
| struct ipv4_addr *dipv4addr = dipaddr; |
| |
| memcpy(&connect->data[data_len], sipv4addr, sizeof(*sipv4addr)); |
| data_len += sizeof(*sipv4addr); |
| memcpy(&connect->data[data_len], dipv4addr, sizeof(*dipv4addr)); |
| data_len += sizeof(*dipv4addr); |
| /* TCP keepalive request */ |
| connect->request_len = wl_tcp_keepalive_pkt( |
| &connect->data[data_len], daddr, saddr, |
| dipv4addr, sipv4addr, local_port, remote_port, |
| local_seq - 1, remote_seq, tcpwin); |
| data_len += connect->request_len; |
| /* TCP keepalive response */ |
| connect->response_len = wl_tcp_keepalive_pkt( |
| &connect->data[data_len], daddr, saddr, |
| dipv4addr, sipv4addr, local_port, remote_port, |
| local_seq, remote_seq, tcpwin); |
| data_len += connect->response_len; |
| } |
| |
| return (OFFSETOF(wl_tko_connect_t, data) + data_len); |
| } |
| |
| static int |
| wl_tko(void *wl, cmd_t *cmd, char **argv) |
| { |
| int err = -1; |
| char *subcmd; |
| int subcmd_len; |
| |
| /* skip iovar */ |
| argv++; |
| |
| /* must have subcommand */ |
| subcmd = *argv++; |
| if (!subcmd) { |
| return BCME_USAGE_ERROR; |
| } |
| subcmd_len = strlen(subcmd); |
| |
| /* GET for "connect" has 1 parameter */ |
| if ((!*argv && strncmp(subcmd, "connect", subcmd_len)) || |
| (!strncmp(subcmd, "connect", subcmd_len) && argv[0] && !argv[1])) { |
| /* get */ |
| uint8 buffer[OFFSETOF(wl_tko_t, data) + sizeof(wl_tko_get_connect_t)]; |
| wl_tko_t *tko = (wl_tko_t *)buffer; |
| int len = OFFSETOF(wl_tko_t, data); |
| |
| memset(tko, 0, len); |
| if (!strncmp(subcmd, "max_tcp", subcmd_len)) { |
| tko->subcmd_id = WL_TKO_SUBCMD_MAX_TCP; |
| } else if (!strncmp(subcmd, "param", subcmd_len)) { |
| tko->subcmd_id = WL_TKO_SUBCMD_PARAM; |
| } else if (!strncmp(subcmd, "connect", subcmd_len)) { |
| wl_tko_get_connect_t *get_connect = (wl_tko_get_connect_t *)tko->data; |
| tko->subcmd_id = WL_TKO_SUBCMD_CONNECT; |
| tko->len = sizeof(*get_connect); |
| get_connect->index = atoi(argv[0]); |
| } else if (!strncmp(subcmd, "enable", subcmd_len)) { |
| tko->subcmd_id = WL_TKO_SUBCMD_ENABLE; |
| } else if (!strncmp(subcmd, "status", subcmd_len)) { |
| tko->subcmd_id = WL_TKO_SUBCMD_STATUS; |
| } else { |
| return BCME_USAGE_ERROR; |
| } |
| len += tko->len; |
| |
| /* invoke GET iovar */ |
| tko->subcmd_id = htod16(tko->subcmd_id); |
| tko->len = htod16(tko->len); |
| if ((err = wlu_iovar_getbuf(wl, cmd->name, tko, len, buf, WLC_IOCTL_MEDLEN)) < 0) { |
| return err; |
| } |
| |
| /* process and print GET results */ |
| tko = (wl_tko_t *)buf; |
| tko->subcmd_id = dtoh16(tko->subcmd_id); |
| tko->len = dtoh16(tko->len); |
| |
| switch (tko->subcmd_id) { |
| case WL_TKO_SUBCMD_MAX_TCP: |
| { |
| wl_tko_max_tcp_t *max_tcp = |
| (wl_tko_max_tcp_t *)tko->data; |
| if (tko->len >= sizeof(*max_tcp)) { |
| printf("max TCP: %d\n", max_tcp->max); |
| } else { |
| err = BCME_BADLEN; |
| } |
| break; |
| |
| } |
| case WL_TKO_SUBCMD_PARAM: |
| { |
| wl_tko_param_t *param = (wl_tko_param_t *)tko->data; |
| if (tko->len >= sizeof(*param)) { |
| param->interval = dtoh16(param->interval); |
| param->retry_interval = dtoh16(param->retry_interval); |
| param->retry_count = dtoh16(param->retry_count); |
| printf("interval: %d\n", param->interval); |
| printf("retry interval: %d\n", param->retry_interval); |
| printf("retry count: %d\n", param->retry_count); |
| } else { |
| err = BCME_BADLEN; |
| } |
| break; |
| } |
| case WL_TKO_SUBCMD_CONNECT: |
| { |
| wl_tko_connect_t *connect = (wl_tko_connect_t *)tko->data; |
| if (tko->len >= sizeof(*connect)) { |
| connect->local_port = dtoh16(connect->local_port); |
| connect->remote_port = dtoh16(connect->remote_port); |
| connect->local_seq = dtoh32(connect->local_seq); |
| connect->remote_seq = dtoh32(connect->remote_seq); |
| printf("index: %d\n", connect->index); |
| printf("IP addr type: %d\n", connect->ip_addr_type); |
| printf("local port: %u\n", connect->local_port); |
| printf("remote port: %u\n", connect->remote_port); |
| printf("local seq: %u\n", connect->local_seq); |
| printf("remote seq: %u\n", connect->remote_seq); |
| if (connect->ip_addr_type) { |
| /* IPv6 */ |
| printf("src IPv6: %s\n", |
| wl_ipv6toa((struct ipv6_addr *)&connect->data[0])); |
| printf("dst IPv6: %s\n", |
| wl_ipv6toa((struct ipv6_addr *) |
| &connect->data[IPV6_ADDR_LEN])); |
| printf("IPv6 TCP keepalive request\n"); |
| wl_hexdump(&connect->data[2 * IPV6_ADDR_LEN], |
| connect->request_len); |
| printf("IPv6 TCP keepalive response\n"); |
| wl_hexdump(&connect->data[2 * IPV6_ADDR_LEN + |
| connect->request_len], connect->response_len); |
| } else { |
| /* IPv4 */ |
| printf("src IP: %s\n", |
| wl_iptoa((struct ipv4_addr *)&connect->data[0])); |
| printf("dst IP: %s\n", |
| wl_iptoa((struct ipv4_addr *) |
| &connect->data[IPV4_ADDR_LEN])); |
| printf("TCP keepalive request\n"); |
| wl_hexdump(&connect->data[2 * IPV4_ADDR_LEN], |
| connect->request_len); |
| printf("TCP keepalive response\n"); |
| wl_hexdump(&connect->data[2 * IPV4_ADDR_LEN + |
| connect->request_len], connect->response_len); |
| } |
| } else { |
| err = BCME_BADLEN; |
| } |
| break; |
| } |
| case WL_TKO_SUBCMD_ENABLE: |
| { |
| wl_tko_enable_t *tko_enable = (wl_tko_enable_t *)tko->data; |
| if (tko->len >= sizeof(*tko_enable)) { |
| printf("%d\n", tko_enable->enable); |
| } else { |
| err = BCME_BADLEN; |
| } |
| break; |
| } |
| case WL_TKO_SUBCMD_STATUS: |
| { |
| wl_tko_status_t *tko_status = (wl_tko_status_t *)tko->data; |
| if (tko->len >= sizeof(*tko_status)) { |
| int i; |
| for (i = 0; i < tko_status->count; i++) { |
| uint8 status = tko_status->status[i]; |
| printf("%d: %s\n", i, |
| status == TKO_STATUS_NORMAL ? "normal" : |
| status == TKO_STATUS_NO_RESPONSE ? "no response" : |
| status == TKO_STATUS_NO_TCP_ACK_FLAG ? "no TCP ACK flag" : |
| status == TKO_STATUS_UNEXPECT_TCP_FLAG ? |
| "unexpected TCP flag" : |
| status == TKO_STATUS_SEQ_NUM_INVALID ? "seq num invalid" : |
| status == TKO_STATUS_REMOTE_SEQ_NUM_INVALID ? |
| "remote seq num invalid" : |
| status == TKO_STATUS_TCP_DATA ? "TCP data" : |
| status == TKO_STATUS_UNAVAILABLE ? "unavailable" : |
| "unknown"); |
| } |
| } else { |
| err = BCME_BADLEN; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| else { |
| /* set */ |
| wl_tko_t *tko = (wl_tko_t *)buf; |
| int len; |
| |
| if (!strncmp(subcmd, "param", subcmd_len) && |
| argv[0] && argv[1] && argv[2]) { |
| wl_tko_param_t *param = |
| (wl_tko_param_t *)tko->data; |
| tko->subcmd_id = WL_TKO_SUBCMD_PARAM; |
| tko->len = sizeof(*param); |
| param->interval = htod16(atoi(argv[0])); |
| param->retry_interval = htod16(atoi(argv[1])); |
| param->retry_count = htod16(atoi(argv[2])); |
| } else if (!strncmp(subcmd, "connect", subcmd_len) && |
| argv[0] && argv[1] && argv[2] && argv[3] && argv[4] && |
| argv[5] && argv[6] && argv[7] && argv[8] && argv[9] && argv[10]) { |
| wl_tko_connect_t *connect = (wl_tko_connect_t *)tko->data; |
| uint8 index; |
| uint8 ip_addr_type; |
| struct ether_addr saddr; |
| struct ether_addr daddr; |
| struct ipv4_addr sipaddr; |
| struct ipv4_addr dipaddr; |
| struct ipv6_addr sipv6addr; |
| struct ipv6_addr dipv6addr; |
| uint16 local_port; |
| uint16 remote_port; |
| uint32 local_seq; |
| uint32 remote_seq; |
| uint16 tcpwin; |
| |
| index = atoi(argv[0]); |
| ip_addr_type = atoi(argv[1]); |
| if (!wl_ether_atoe(argv[2], &saddr)) { |
| return BCME_USAGE_ERROR; |
| } |
| if (!wl_ether_atoe(argv[3], &daddr)) { |
| return BCME_USAGE_ERROR; |
| } |
| if (ip_addr_type) { |
| /* IPv6 */ |
| if (!wl_atoipv6(argv[4], &sipv6addr)) { |
| return BCME_USAGE_ERROR; |
| } |
| if (!wl_atoipv6(argv[5], &dipv6addr)) { |
| return BCME_USAGE_ERROR; |
| } |
| } else { |
| /* IPv4 */ |
| if (!wl_atoip(argv[4], &sipaddr)) { |
| return BCME_USAGE_ERROR; |
| } |
| if (!wl_atoip(argv[5], &dipaddr)) { |
| return BCME_USAGE_ERROR; |
| } |
| } |
| local_port = atoi(argv[6]); |
| remote_port = atoi(argv[7]); |
| local_seq = bcm_strtoul(argv[8], NULL, 0); |
| remote_seq = bcm_strtoul(argv[9], NULL, 0); |
| tcpwin = atoi(argv[10]); |
| |
| tko->subcmd_id = WL_TKO_SUBCMD_CONNECT; |
| tko->len = wl_tko_connect_init(connect, index, ip_addr_type, |
| &saddr, &daddr, |
| ip_addr_type ? (void *)&sipv6addr : (void *)&sipaddr, |
| ip_addr_type ? (void *)&dipv6addr : (void *)&dipaddr, |
| local_port, remote_port, local_seq, remote_seq, tcpwin); |
| |
| } else if (!strncmp(subcmd, "enable", subcmd_len) && |
| (!strcmp(argv[0], "0") || !strcmp(argv[0], "1"))) { |
| wl_tko_enable_t *tko_enable = (wl_tko_enable_t *)tko->data; |
| tko->subcmd_id = WL_TKO_SUBCMD_ENABLE; |
| tko->len = sizeof(*tko_enable); |
| tko_enable->enable = atoi(argv[0]); |
| |
| } else { |
| return BCME_USAGE_ERROR; |
| } |
| |
| /* invoke SET iovar */ |
| len = OFFSETOF(wl_tko_t, data) + tko->len; |
| tko->subcmd_id = htod16(tko->subcmd_id); |
| tko->len = htod16(tko->len); |
| err = wlu_iovar_set(wl, cmd->name, tko, len); |
| } |
| |
| return err; |
| } |
| |
| #if defined(linux) |
| |
| #define ETH_P_ALL 0x0003 |
| #define TKO_BUFFER_SIZE 2048 |
| #define TKO_TCP_WINDOW_SIZE 400 |
| |
| /* automatically configures TKO by monitoring TCP connections */ |
| static int |
| wl_tko_auto_config(void *wl, cmd_t *cmd, char **argv) |
| { |
| /* 802.3 llc/snap header */ |
| static const uint8 llc_snap_hdr[SNAP_HDR_LEN] = |
| {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; |
| bool is_checksum = TRUE; |
| int err; |
| int fd = -1; |
| int i; |
| wl_tko_t *tko; |
| wl_tko_max_tcp_t *tko_max; |
| uint8 cur_tcp = 0; |
| uint8 max_tcp = 0; |
| uint8 **tko_buf = NULL; |
| uint8 *frame = NULL; |
| struct ether_addr ea; |
| struct sockaddr_ll sll; |
| struct ifreq ifr; |
| char ifnames[IFNAMSIZ] = {"eth1"}; |
| |
| UNUSED_PARAMETER(cmd); |
| |
| argv++; |
| if (*argv) { |
| if (!stricmp(*argv, "-nochecksum")) { |
| /* note: ssh on FC15 has TCP checksum failures on packets read |
| * from socket but checksum correct over-the-air |
| */ |
| printf("no checksum check\n"); |
| is_checksum = FALSE; |
| } |
| } |
| |
| memset(&ifr, 0, sizeof(ifr)); |
| if (wl) { |
| strncpy(ifr.ifr_name, ((struct ifreq *)wl)->ifr_name, (IFNAMSIZ - 1)); |
| } else { |
| strncpy(ifr.ifr_name, ifnames, (IFNAMSIZ - 1)); |
| } |
| |
| if ((err = wlu_iovar_get(wl, "cur_etheraddr", &ea, ETHER_ADDR_LEN)) < 0) { |
| printf("Failed to get current ether address\n"); |
| goto exit; |
| } |
| |
| fd = socket(PF_PACKET, SOCK_RAW, hton16(ETH_P_ALL)); |
| if (fd < 0) { |
| printf("Cannot create socket %d\n", fd); |
| err = -1; |
| goto exit; |
| } |
| |
| err = ioctl(fd, SIOCGIFINDEX, &ifr); |
| if (err < 0) { |
| printf("Cannot get index %d\n", err); |
| goto exit; |
| } |
| |
| /* bind the socket first before starting so we won't miss any event */ |
| memset(&sll, 0, sizeof(sll)); |
| sll.sll_family = AF_PACKET; |
| sll.sll_protocol = hton16(ETH_P_ALL); |
| sll.sll_ifindex = ifr.ifr_ifindex; |
| err = bind(fd, (struct sockaddr *)&sll, sizeof(sll)); |
| if (err < 0) { |
| printf("Cannot bind %d\n", err); |
| goto exit; |
| } |
| |
| /* get the max TCP connections supported */ |
| tko = (wl_tko_t *)buf; |
| memset(tko, 0, sizeof(*tko)); |
| tko->subcmd_id = htod16(WL_TKO_SUBCMD_MAX_TCP); |
| tko->len = htod16(tko->len); |
| if ((err = wlu_iovar_getbuf(wl, "tko", tko, sizeof(*tko), buf, WLC_IOCTL_SMLEN)) < 0) { |
| goto exit; |
| } |
| tko = (wl_tko_t *)buf; |
| tko_max = (wl_tko_max_tcp_t *)tko->data; |
| max_tcp = tko_max->max; |
| |
| /* allocate TKO buffers */ |
| tko_buf = (uint8 **)malloc(sizeof(uint8 *) * max_tcp); |
| if (tko_buf == NULL) { |
| printf("Cannot not allocate %d bytes for TKO buffer\n", |
| (int)(sizeof(uint8 *) * max_tcp)); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| for (i = 0; i < max_tcp; i++) { |
| int size = OFFSETOF(wl_tko_t, data) + OFFSETOF(wl_tko_connect_t, data) + 1024; |
| tko_buf[i] = malloc(size); |
| if (tko_buf[i] == NULL) { |
| printf("Cannot not allocate %d bytes for TKO buffer\n", size); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| memset(tko_buf[i], 0, size); |
| } |
| |
| /* frame buffer */ |
| frame = (uint8 *)malloc(TKO_BUFFER_SIZE); |
| if (frame == NULL) { |
| printf("Cannot not allocate %d bytes for receive buffer\n", |
| TKO_BUFFER_SIZE); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| |
| /* receive result */ |
| while (1) { |
| int length; |
| uint8 *end, *pt; |
| uint16 ethertype; |
| struct bcmtcp_hdr *tcp_hdr = NULL; |
| uint16 tcp_len = 0; |
| uint8 ip_addr_type = 0; |
| uint8 *src_ip = NULL; |
| uint8 *dst_ip = NULL; |
| |
| length = recv(fd, frame, TKO_BUFFER_SIZE, 0); |
| end = frame + length; |
| |
| if (length < ETHER_HDR_LEN) { |
| continue; |
| } |
| |
| /* process only tx'ed packets */ |
| if (memcmp(ea.octet, &frame[ETHER_SRC_OFFSET], sizeof(ea)) != 0) { |
| continue; |
| } |
| |
| /* process Ethernet II or SNAP-encapsulated 802.3 frames */ |
| if (ntoh16_ua((const void *)(frame + ETHER_TYPE_OFFSET)) >= ETHER_TYPE_MIN) { |
| /* Frame is Ethernet II */ |
| pt = frame + ETHER_TYPE_OFFSET; |
| } else if (length >= ETHER_HDR_LEN + SNAP_HDR_LEN + ETHER_TYPE_LEN && |
| !memcmp(llc_snap_hdr, frame + ETHER_HDR_LEN, SNAP_HDR_LEN)) { |
| pt = frame + ETHER_HDR_LEN + SNAP_HDR_LEN; |
| } else { |
| continue; |
| } |
| |
| ethertype = ntoh16_ua((const void *)pt); |
| pt += ETHER_TYPE_LEN; |
| |
| /* check ethertype */ |
| if ((ethertype != ETHER_TYPE_IP) && (ethertype != ETHER_TYPE_IPV6)) { |
| continue; |
| } |
| |
| if (ethertype == ETHER_TYPE_IP) { |
| struct ipv4_hdr *ipv4_hdr; |
| uint16 iphdr_len; |
| uint16 cksum; |
| |
| ipv4_hdr = (struct ipv4_hdr *)pt; |
| iphdr_len = IPV4_HLEN(ipv4_hdr); |
| if (pt + iphdr_len > end) { |
| continue; |
| } |
| pt += iphdr_len; |
| |
| if (ipv4_hdr->prot != IP_PROT_TCP) { |
| continue; |
| } |
| |
| if (ntoh16(ipv4_hdr->frag) & IPV4_FRAG_OFFSET_MASK) { |
| continue; |
| } |
| |
| if (ntoh16(ipv4_hdr->frag) & IPV4_FRAG_MORE) { |
| continue; |
| } |
| |
| tcp_len = ntoh16(ipv4_hdr->tot_len) - iphdr_len; |
| if (pt + tcp_len > end) { |
| continue; |
| } |
| tcp_hdr = (struct bcmtcp_hdr *)pt; |
| |
| /* verify IP header checksum */ |
| cksum = ipv4_hdr_cksum((uint8 *)ipv4_hdr, iphdr_len); |
| if (cksum != ipv4_hdr->hdr_chksum) { |
| continue; |
| } |
| |
| /* verify TCP header checksum */ |
| if (is_checksum) { |
| cksum = ipv4_tcp_hdr_cksum((uint8 *)ipv4_hdr, |
| (uint8 *)tcp_hdr, tcp_len); |
| if (cksum != tcp_hdr->chksum) { |
| printf("TCP checksum failed: 0x%04x 0x%04x\n", |
| ntoh16(cksum), ntoh16(tcp_hdr->chksum)); |
| continue; |
| } |
| } |
| |
| ip_addr_type = 0; |
| src_ip = ipv4_hdr->src_ip; |
| dst_ip = ipv4_hdr->dst_ip; |
| |
| } else if (ethertype == ETHER_TYPE_IPV6) { |
| struct ipv6_hdr *ipv6_hdr; |
| uint16 cksum; |
| |
| if (pt + sizeof(struct ipv6_hdr) > end) { |
| continue; |
| } |
| ipv6_hdr = (struct ipv6_hdr *)pt; |
| pt += sizeof(struct ipv6_hdr); |
| |
| if (ipv6_hdr->nexthdr != IP_PROT_TCP) { |
| continue; |
| } |
| |
| tcp_len = ntoh16(ipv6_hdr->payload_len); |
| if (pt + tcp_len > end) { |
| continue; |
| } |
| tcp_hdr = (struct bcmtcp_hdr *)pt; |
| |
| /* verify TCP checksum */ |
| if (is_checksum) { |
| cksum = ipv6_tcp_hdr_cksum((uint8 *)ipv6_hdr, |
| (uint8 *)tcp_hdr, tcp_len); |
| if (cksum != tcp_hdr->chksum) { |
| printf("IPv6 TCP checksum failed: 0x%04x 0x%04x\n", |
| ntoh16(cksum), ntoh16(tcp_hdr->chksum)); |
| continue; |
| } |
| } |
| |
| ip_addr_type = 1; |
| src_ip = ipv6_hdr->saddr.addr; |
| dst_ip = ipv6_hdr->daddr.addr; |
| } |
| |
| if (src_ip != NULL && dst_ip != NULL && tcp_hdr != NULL) { |
| uint16 src_port = ntoh16(tcp_hdr->src_port); |
| uint16 dst_port = ntoh16(tcp_hdr->dst_port); |
| uint32 seq_num = ntoh32(tcp_hdr->seq_num); |
| uint32 ack_num = ntoh32(tcp_hdr->ack_num); |
| uint8 tcp_flags[2]; |
| uint16 tcp_data_len; |
| wl_tko_connect_t *connect; |
| uint8 index; |
| int len; |
| |
| /* find existing TCP connection */ |
| for (i = 0; i < cur_tcp; i++) { |
| int ip_addr_size = ip_addr_type ? sizeof(struct ipv6_addr) : |
| sizeof(struct ipv4_addr); |
| |
| tko = (wl_tko_t *)tko_buf[i]; |
| connect = (wl_tko_connect_t *)tko->data; |
| |
| /* existing TCP connection if src/dst IP and ports match */ |
| if (connect->ip_addr_type == ip_addr_type && |
| memcmp(src_ip, &connect->data[0], ip_addr_size) == 0 && |
| memcmp(dst_ip, &connect->data[ip_addr_size], |
| ip_addr_size) == 0 && |
| connect->local_port == src_port && |
| connect->remote_port == dst_port) { |
| /* found existing TCP connection */ |
| index = connect->index; |
| break; |
| } |
| } |
| /* TCP connection not found */ |
| if (i == cur_tcp) { |
| if (cur_tcp < max_tcp) { |
| /* new TCP connection */ |
| index = cur_tcp; |
| cur_tcp++; |
| } else { |
| /* no more */ |
| printf("max TCP connections supported %d\n", max_tcp); |
| continue; |
| } |
| } |
| |
| /* TCP data length */ |
| memcpy(tcp_flags, &tcp_hdr->hdrlen_rsvd_flags, sizeof(tcp_flags)); |
| tcp_data_len = tcp_len - (TCP_HDRLEN(tcp_flags[0]) << 2); |
| if (tcp_data_len != 0) { |
| printf("TCP data len=%d\n", tcp_data_len); |
| } |
| |
| /* adjust the sequence num with the TCP data length */ |
| seq_num += tcp_data_len; |
| |
| printf("index: %d\n", index); |
| if (ip_addr_type) { |
| /* IPv6 */ |
| printf("src IPv6: %s\n", |
| wl_ipv6toa((struct ipv6_addr *)src_ip)); |
| printf("dst IPv6: %s\n", |
| wl_ipv6toa((struct ipv6_addr *)dst_ip)); |
| } else { |
| /* IPv4 */ |
| printf("src IP: %s\n", |
| wl_iptoa((struct ipv4_addr *)src_ip)); |
| printf("dst IP: %s\n", |
| wl_iptoa((struct ipv4_addr *)dst_ip)); |
| } |
| printf("local port: %u\n", src_port); |
| printf("remote port: %u\n", dst_port); |
| printf("local seq: %u\n", seq_num); |
| printf("remote seq: %u\n", ack_num); |
| |
| tko = (wl_tko_t *)tko_buf[index]; |
| connect = (wl_tko_connect_t *)tko->data; |
| |
| tko->len = wl_tko_connect_init(connect, index, ip_addr_type, |
| (struct ether_addr *)&frame[ETHER_SRC_OFFSET], |
| (struct ether_addr *)&frame[ETHER_DEST_OFFSET], |
| src_ip, dst_ip, src_port, dst_port, seq_num, |
| ack_num, TKO_TCP_WINDOW_SIZE); |
| |
| /* invoke SET iovar */ |
| tko->subcmd_id = WL_TKO_SUBCMD_CONNECT; |
| len = OFFSETOF(wl_tko_t, data) + tko->len; |
| tko->subcmd_id = htod16(tko->subcmd_id); |
| tko->len = htod16(tko->len); |
| err = wlu_iovar_set(wl, "tko", tko, len); |
| if (err < 0) { |
| printf("TKO connect failed\n"); |
| break; |
| } |
| } |
| } |
| |
| exit: |
| if (tko_buf != NULL) { |
| for (i = 0; i < max_tcp; i++) { |
| if (tko_buf[i] != NULL) { |
| free(tko_buf[i]); |
| } |
| } |
| free(tko_buf); |
| } |
| if (frame != NULL) { |
| free(frame); |
| } |
| if (fd >= 0) { |
| close(fd); |
| } |
| return err; |
| } |
| |
| static void |
| wl_tko_event_cb(int event_type, bcm_event_t *bcm_event) |
| { |
| wl_event_tko_t *data = (wl_event_tko_t *) (bcm_event + 1); |
| |
| if (event_type == WLC_E_TKO) { |
| printf("WLC_E_TKO(%d): status=%d index=%d\n", |
| WLC_E_TKO, ntoh32(bcm_event->event.status), data->index); |
| } |
| } |
| |
| static int |
| wl_tko_event_check(void *wl, cmd_t *cmd, char **argv) |
| { |
| if (argv[1] && argv[1][0] == '-') { |
| wl_cmd_usage(stderr, cmd); |
| return -1; |
| } |
| return wl_wait_for_event(wl, argv, WLC_E_TKO, 256, wl_tko_event_cb); |
| } |
| |
| #endif /* linux */ |