zephyr/subsys/net/ip/connection.c
Jukka Rissanen 1443ff0f5e net: stats: Make statistics collection per network interface
Instead of one global statistics, collect statistics information
separately for each network interface. This per interface statistics
collection is optional but turned on by default. It can be turned
off if needed, in which case only global statistics are collected.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
2018-04-10 13:29:39 +03:00

1030 lines
23 KiB
C

/** @file
* @brief Generic connection related functions
*/
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_CONN)
#define SYS_LOG_DOMAIN "net/conn"
#define NET_LOG_ENABLED 1
#endif
#include <errno.h>
#include <misc/util.h>
#include <net/net_core.h>
#include <net/net_pkt.h>
#include <net/udp.h>
#include "net_private.h"
#include "icmpv6.h"
#include "icmpv4.h"
#include "udp_internal.h"
#include "tcp.h"
#include "connection.h"
#include "net_stats.h"
/** Is this connection used or not */
#define NET_CONN_IN_USE BIT(0)
/** Remote address set */
#define NET_CONN_REMOTE_ADDR_SET BIT(1)
/** Local address set */
#define NET_CONN_LOCAL_ADDR_SET BIT(2)
/** Rank bits */
#define NET_RANK_LOCAL_PORT BIT(0)
#define NET_RANK_REMOTE_PORT BIT(1)
#define NET_RANK_LOCAL_UNSPEC_ADDR BIT(2)
#define NET_RANK_REMOTE_UNSPEC_ADDR BIT(3)
#define NET_RANK_LOCAL_SPEC_ADDR BIT(4)
#define NET_RANK_REMOTE_SPEC_ADDR BIT(5)
static struct net_conn conns[CONFIG_NET_MAX_CONN];
#if defined(CONFIG_NET_CONN_CACHE)
/* Cache the connection so that we do not have to go
* through the full list of connections when receiving
* a network packet. The cache contains an index to
* corresponding entry in conns array.
*
* Hash value is constructed like this:
*
* bit description
* 0 - 3 bits from remote port
* 4 - 7 bits from local port
* 8 - 18 bits from remote address
* 19 - 29 bits from local address
* 30 family
* 31 protocol
*/
struct conn_hash {
u32_t value;
s32_t idx;
};
struct conn_hash_neg {
u32_t value;
};
/** Connection cache */
static struct conn_hash conn_cache[CONFIG_NET_MAX_CONN];
/** Negative cache, we definitely do not have a connection
* to these hosts.
*/
static struct conn_hash_neg conn_cache_neg[CONFIG_NET_MAX_CONN];
#define TAKE_BIT(val, bit, max, used) \
(((val & BIT(bit)) >> bit) << (max - used))
static inline u8_t ports_to_hash(u16_t remote_port,
u16_t local_port)
{
/* Note that we do not convert port value to network byte order */
return (remote_port & BIT(0)) |
((remote_port & BIT(4)) >> 3) |
((remote_port & BIT(8)) >> 6) |
((remote_port & BIT(15)) >> 12) |
(((local_port & BIT(0)) |
((local_port & BIT(4)) >> 3) |
((local_port & BIT(8)) >> 6) |
((local_port & BIT(15)) >> 12)) << 4);
}
static inline u16_t ipv6_to_hash(struct in6_addr *addr)
{
/* There is 11 bits available for IPv6 address */
/* Use more bits from the lower part of address space */
return
/* Take 3 bits from higher values */
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[0]), 31, 11, 1) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[0]), 15, 11, 2) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[0]), 7, 11, 3) |
/* Take 2 bits from higher middle values */
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[1]), 31, 11, 4) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[1]), 15, 11, 5) |
/* Take 2 bits from lower middle values */
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[2]), 31, 11, 6) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[2]), 15, 11, 7) |
/* Take 4 bits from lower values */
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[3]), 31, 11, 8) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[3]), 15, 11, 9) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[3]), 7, 11, 10) |
TAKE_BIT(UNALIGNED_GET(&addr->s6_addr32[3]), 0, 11, 11);
}
static inline u16_t ipv4_to_hash(struct in_addr *addr)
{
/* There is 11 bits available for IPv4 address */
/* Use more bits from the lower part of address space */
return
TAKE_BIT(addr->s_addr, 31, 11, 1) |
TAKE_BIT(addr->s_addr, 27, 11, 2) |
TAKE_BIT(addr->s_addr, 21, 11, 3) |
TAKE_BIT(addr->s_addr, 17, 11, 4) |
TAKE_BIT(addr->s_addr, 14, 11, 5) |
TAKE_BIT(addr->s_addr, 11, 11, 6) |
TAKE_BIT(addr->s_addr, 8, 11, 7) |
TAKE_BIT(addr->s_addr, 5, 11, 8) |
TAKE_BIT(addr->s_addr, 3, 11, 9) |
TAKE_BIT(addr->s_addr, 2, 11, 10) |
TAKE_BIT(addr->s_addr, 0, 11, 11);
}
/* Return either the first free position in the cache (idx < 0) or
* the existing cached position (idx >= 0)
*/
static s32_t check_hash(enum net_ip_protocol proto,
sa_family_t family,
void *remote_addr,
void *local_addr,
u16_t remote_port,
u16_t local_port,
u32_t *cache_value)
{
int i, free_pos = -1;
u32_t value = 0;
value = ports_to_hash(remote_port, local_port);
#if defined(CONFIG_NET_UDP)
if (proto == IPPROTO_UDP) {
value |= BIT(31);
}
#endif
#if defined(CONFIG_NET_TCP)
if (proto == IPPROTO_TCP) {
value &= ~BIT(31);
}
#endif
#if defined(CONFIG_NET_IPV6)
if (family == AF_INET6) {
value |= BIT(30);
value |= ipv6_to_hash((struct in6_addr *)remote_addr) << 8;
value |= ipv6_to_hash((struct in6_addr *)local_addr) << 19;
}
#endif
#if defined(CONFIG_NET_IPV4)
if (family == AF_INET) {
value &= ~BIT(30);
value |= ipv4_to_hash((struct in_addr *)remote_addr) << 8;
value |= ipv4_to_hash((struct in_addr *)local_addr) << 19;
}
#endif
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (conn_cache[i].value == value) {
return i;
}
if (conn_cache[i].idx < 0 && free_pos < 0) {
free_pos = i;
}
}
if (free_pos >= 0) {
conn_cache[free_pos].value = value;
return free_pos;
}
*cache_value = value;
return -ENOENT;
}
static inline s32_t get_conn(enum net_ip_protocol proto,
sa_family_t family,
struct net_pkt *pkt,
u32_t *cache_value)
{
struct net_udp_hdr hdr, *udp_hdr;
udp_hdr = net_udp_get_hdr(pkt, &hdr);
if (!udp_hdr) {
return NET_DROP;
}
#if defined(CONFIG_NET_IPV4)
if (family == AF_INET) {
return check_hash(proto, family,
&NET_IPV4_HDR(pkt)->src,
&NET_IPV4_HDR(pkt)->dst,
udp_hdr->src_port,
udp_hdr->dst_port,
cache_value);
}
#endif
#if defined(CONFIG_NET_IPV6)
if (family == AF_INET6) {
return check_hash(proto, family,
&NET_IPV6_HDR(pkt)->src,
&NET_IPV6_HDR(pkt)->dst,
udp_hdr->src_port,
udp_hdr->dst_port,
cache_value);
}
#endif
return -1;
}
static inline void cache_add_neg(u32_t cache_value)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN && cache_value > 0; i++) {
if (conn_cache_neg[i].value) {
continue;
}
NET_DBG("Add to neg cache value 0x%x", cache_value);
conn_cache_neg[i].value = cache_value;
break;
}
}
static inline bool cache_check_neg(u32_t cache_value)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN && cache_value > 0; i++) {
if (conn_cache_neg[i].value == cache_value) {
NET_DBG("Cache neg [%d] value 0x%x found",
i, cache_value);
return true;
}
}
return false;
}
static void cache_clear(void)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
conn_cache[i].idx = -1;
conn_cache_neg[i].value = 0;
}
}
static inline enum net_verdict cache_check(enum net_ip_protocol proto,
struct net_pkt *pkt,
u32_t *cache_value,
s32_t *pos)
{
*pos = get_conn(proto, net_pkt_family(pkt), pkt, cache_value);
if (*pos >= 0) {
if (conn_cache[*pos].idx >= 0) {
/* Connection is in the cache */
struct net_conn *conn;
struct net_udp_hdr hdr, *udp_hdr;
udp_hdr = net_udp_get_hdr(pkt, &hdr);
if (!udp_hdr) {
return NET_CONTINUE;
}
conn = &conns[conn_cache[*pos].idx];
NET_DBG("Cache %s listener for pkt %p src port %u "
"dst port %u family %d cache[%d] 0x%x",
net_proto2str(proto), pkt,
ntohs(udp_hdr->src_port),
ntohs(udp_hdr->dst_port),
net_pkt_family(pkt), *pos,
conn_cache[*pos].value);
return conn->cb(conn, pkt, conn->user_data);
}
} else if (*cache_value > 0) {
if (cache_check_neg(*cache_value)) {
NET_DBG("Drop by cache");
return NET_DROP;
}
}
return NET_CONTINUE;
}
static inline void cache_remove(struct net_conn *conn)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (conn_cache[i].idx < 0 ||
conn_cache[i].idx >= CONFIG_NET_MAX_CONN) {
continue;
}
if (&conns[conn_cache[i].idx] == conn) {
conn_cache[i].idx = -1;
break;
}
}
}
#else
#define cache_clear(...)
#define cache_add_neg(...)
#define cache_check(...) NET_CONTINUE
#define cache_remove(...)
#endif /* CONFIG_NET_CONN_CACHE */
int net_conn_unregister(struct net_conn_handle *handle)
{
struct net_conn *conn = (struct net_conn *)handle;
if (conn < &conns[0] || conn > &conns[CONFIG_NET_MAX_CONN]) {
return -EINVAL;
}
if (!(conn->flags & NET_CONN_IN_USE)) {
return -ENOENT;
}
cache_remove(conn);
NET_DBG("[%zu] connection handler %p removed",
(conn - conns) / sizeof(*conn), conn);
memset(conn, 0, sizeof(*conn));
return 0;
}
int net_conn_change_callback(struct net_conn_handle *handle,
net_conn_cb_t cb, void *user_data)
{
struct net_conn *conn = (struct net_conn *)handle;
if (conn < &conns[0] || conn > &conns[CONFIG_NET_MAX_CONN]) {
return -EINVAL;
}
if (!(conn->flags & NET_CONN_IN_USE)) {
return -ENOENT;
}
NET_DBG("[%zu] connection handler %p changed callback",
(conn - conns) / sizeof(*conn), conn);
conn->cb = cb;
conn->user_data = user_data;
return 0;
}
#if defined(CONFIG_NET_DEBUG_CONN)
static inline
void prepare_register_debug_print(char *dst, int dst_len,
char *src, int src_len,
const struct sockaddr *remote_addr,
const struct sockaddr *local_addr)
{
if (remote_addr && remote_addr->sa_family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
snprintk(dst, dst_len, "%s",
net_sprint_ipv6_addr(&net_sin6(remote_addr)->
sin6_addr));
#else
snprintk(dst, dst_len, "%s", "?");
#endif
} else if (remote_addr && remote_addr->sa_family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
snprintk(dst, dst_len, "%s",
net_sprint_ipv4_addr(&net_sin(remote_addr)->
sin_addr));
#else
snprintk(dst, dst_len, "%s", "?");
#endif
} else {
snprintk(dst, dst_len, "%s", "-");
}
if (local_addr && local_addr->sa_family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
snprintk(src, src_len, "%s",
net_sprint_ipv6_addr(&net_sin6(local_addr)->
sin6_addr));
#else
snprintk(src, src_len, "%s", "?");
#endif
} else if (local_addr && local_addr->sa_family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
snprintk(src, src_len, "%s",
net_sprint_ipv4_addr(&net_sin(local_addr)->
sin_addr));
#else
snprintk(src, src_len, "%s", "?");
#endif
} else {
snprintk(src, src_len, "%s", "-");
}
}
#endif /* CONFIG_NET_DEBUG_CONN */
/* Check if we already have identical connection handler installed. */
static int find_conn_handler(enum net_ip_protocol proto,
const struct sockaddr *remote_addr,
const struct sockaddr *local_addr,
u16_t remote_port,
u16_t local_port)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (!(conns[i].flags & NET_CONN_IN_USE)) {
continue;
}
if (conns[i].proto != proto) {
continue;
}
if (remote_addr) {
if (!(conns[i].flags & NET_CONN_REMOTE_ADDR_SET)) {
continue;
}
#if defined(CONFIG_NET_IPV6)
if (remote_addr->sa_family == AF_INET6 &&
remote_addr->sa_family ==
conns[i].remote_addr.sa_family) {
if (!net_ipv6_addr_cmp(
&net_sin6(remote_addr)->sin6_addr,
&net_sin6(&conns[i].remote_addr)->
sin6_addr)) {
continue;
}
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (remote_addr->sa_family == AF_INET &&
remote_addr->sa_family ==
conns[i].remote_addr.sa_family) {
if (!net_ipv4_addr_cmp(
&net_sin(remote_addr)->sin_addr,
&net_sin(&conns[i].remote_addr)->
sin_addr)) {
continue;
}
} else
#endif
{
continue;
}
} else {
if (conns[i].flags & NET_CONN_REMOTE_ADDR_SET) {
continue;
}
}
if (local_addr) {
if (!(conns[i].flags & NET_CONN_LOCAL_ADDR_SET)) {
continue;
}
#if defined(CONFIG_NET_IPV6)
if (local_addr->sa_family == AF_INET6 &&
local_addr->sa_family ==
conns[i].local_addr.sa_family) {
if (!net_ipv6_addr_cmp(
&net_sin6(local_addr)->sin6_addr,
&net_sin6(&conns[i].local_addr)->
sin6_addr)) {
continue;
}
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (local_addr->sa_family == AF_INET &&
local_addr->sa_family ==
conns[i].local_addr.sa_family) {
if (!net_ipv4_addr_cmp(
&net_sin(local_addr)->sin_addr,
&net_sin(&conns[i].local_addr)->
sin_addr)) {
continue;
}
} else
#endif
{
continue;
}
} else {
if (conns[i].flags & NET_CONN_LOCAL_ADDR_SET) {
continue;
}
}
if (net_sin(&conns[i].remote_addr)->sin_port !=
htons(remote_port)) {
continue;
}
if (net_sin(&conns[i].local_addr)->sin_port !=
htons(local_port)) {
continue;
}
return i;
}
return -ENOENT;
}
int net_conn_register(enum net_ip_protocol proto,
const struct sockaddr *remote_addr,
const struct sockaddr *local_addr,
u16_t remote_port,
u16_t local_port,
net_conn_cb_t cb,
void *user_data,
struct net_conn_handle **handle)
{
int i;
u8_t rank = 0;
i = find_conn_handler(proto, remote_addr, local_addr, remote_port,
local_port);
if (i != -ENOENT) {
NET_ERR("Identical connection handler %p already found.",
&conns[i]);
return -EALREADY;
}
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (conns[i].flags & NET_CONN_IN_USE) {
continue;
}
if (remote_addr) {
#if defined(CONFIG_NET_IPV6)
if (remote_addr->sa_family == AF_INET6) {
memcpy(&conns[i].remote_addr, remote_addr,
sizeof(struct sockaddr_in6));
if (net_is_ipv6_addr_unspecified(
&net_sin6(remote_addr)->
sin6_addr)) {
rank |= NET_RANK_REMOTE_UNSPEC_ADDR;
} else {
rank |= NET_RANK_REMOTE_SPEC_ADDR;
}
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (remote_addr->sa_family == AF_INET) {
memcpy(&conns[i].remote_addr, remote_addr,
sizeof(struct sockaddr_in));
if (!net_sin(remote_addr)->
sin_addr.s_addr) {
rank |= NET_RANK_REMOTE_UNSPEC_ADDR;
} else {
rank |= NET_RANK_REMOTE_SPEC_ADDR;
}
} else
#endif
{
NET_ERR("Remote address family not set");
return -EINVAL;
}
conns[i].flags |= NET_CONN_REMOTE_ADDR_SET;
}
if (local_addr) {
#if defined(CONFIG_NET_IPV6)
if (local_addr->sa_family == AF_INET6) {
memcpy(&conns[i].local_addr, local_addr,
sizeof(struct sockaddr_in6));
if (net_is_ipv6_addr_unspecified(
&net_sin6(local_addr)->
sin6_addr)) {
rank |= NET_RANK_LOCAL_UNSPEC_ADDR;
} else {
rank |= NET_RANK_LOCAL_SPEC_ADDR;
}
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (local_addr->sa_family == AF_INET) {
memcpy(&conns[i].local_addr, local_addr,
sizeof(struct sockaddr_in));
if (!net_sin(local_addr)->sin_addr.s_addr) {
rank |= NET_RANK_LOCAL_UNSPEC_ADDR;
} else {
rank |= NET_RANK_LOCAL_SPEC_ADDR;
}
} else
#endif
{
NET_ERR("Local address family not set");
return -EINVAL;
}
conns[i].flags |= NET_CONN_LOCAL_ADDR_SET;
}
if (remote_addr && local_addr) {
if (remote_addr->sa_family != local_addr->sa_family) {
NET_ERR("Address families different");
return -EINVAL;
}
}
if (remote_port) {
rank |= NET_RANK_REMOTE_PORT;
net_sin(&conns[i].remote_addr)->sin_port =
htons(remote_port);
}
if (local_port) {
rank |= NET_RANK_LOCAL_PORT;
net_sin(&conns[i].local_addr)->sin_port =
htons(local_port);
}
conns[i].flags |= NET_CONN_IN_USE;
conns[i].cb = cb;
conns[i].user_data = user_data;
conns[i].rank = rank;
conns[i].proto = proto;
/* Cache needs to be cleared if new entries are added. */
cache_clear();
#if defined(CONFIG_NET_DEBUG_CONN)
do {
char dst[NET_IPV6_ADDR_LEN];
char src[NET_IPV6_ADDR_LEN];
prepare_register_debug_print(dst, sizeof(dst),
src, sizeof(src),
remote_addr,
local_addr);
NET_DBG("[%d/%d/%u/0x%02x] remote %p/%s/%u "
"local %p/%s/%u cb %p ud %p",
i, local_addr ? local_addr->sa_family : AF_UNSPEC,
proto, rank, remote_addr, dst, remote_port,
local_addr, src, local_port,
cb, user_data);
} while (0);
#endif /* CONFIG_NET_DEBUG_CONN */
if (handle) {
*handle = (struct net_conn_handle *)&conns[i];
}
return 0;
}
return -ENOENT;
}
static bool check_addr(struct net_pkt *pkt,
struct sockaddr *addr,
bool is_remote)
{
if (addr->sa_family != net_pkt_family(pkt)) {
return false;
}
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6 && addr->sa_family == AF_INET6) {
struct in6_addr *addr6;
if (is_remote) {
addr6 = &NET_IPV6_HDR(pkt)->src;
} else {
addr6 = &NET_IPV6_HDR(pkt)->dst;
}
if (!net_is_ipv6_addr_unspecified(
&net_sin6(addr)->sin6_addr)) {
if (!net_ipv6_addr_cmp(&net_sin6(addr)->sin6_addr,
addr6)) {
return false;
}
}
return true;
}
#endif /* CONFIG_NET_IPV6 */
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET && addr->sa_family == AF_INET) {
struct in_addr *addr4;
if (is_remote) {
addr4 = &NET_IPV4_HDR(pkt)->src;
} else {
addr4 = &NET_IPV4_HDR(pkt)->dst;
}
if (net_sin(addr)->sin_addr.s_addr) {
if (!net_ipv4_addr_cmp(&net_sin(addr)->sin_addr,
addr4)) {
return false;
}
}
}
#endif /* CONFIG_NET_IPV4 */
return true;
}
static inline void send_icmp_error(struct net_pkt *pkt)
{
if (net_pkt_family(pkt) == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
net_icmpv6_send_error(pkt, NET_ICMPV6_DST_UNREACH,
NET_ICMPV6_DST_UNREACH_NO_PORT, 0);
#endif /* CONFIG_NET_IPV6 */
} else {
#if defined(CONFIG_NET_IPV4)
net_icmpv4_send_error(pkt, NET_ICMPV4_DST_UNREACH,
NET_ICMPV4_DST_UNREACH_NO_PORT);
#endif /* CONFIG_NET_IPV4 */
}
}
enum net_verdict net_conn_input(enum net_ip_protocol proto, struct net_pkt *pkt)
{
int i, best_match = -1;
s16_t best_rank = -1;
u16_t src_port, dst_port;
u16_t chksum;
#if defined(CONFIG_NET_CONN_CACHE)
enum net_verdict verdict;
u32_t cache_value = 0;
s32_t pos;
verdict = cache_check(proto, pkt, &cache_value, &pos);
if (verdict != NET_CONTINUE) {
return verdict;
}
#endif
/* This is only used for getting source and destination ports.
* Because both TCP and UDP header have these in the same
* location, we can check them both using the UDP struct.
*/
if (IS_ENABLED(CONFIG_NET_UDP) && proto == IPPROTO_UDP) {
struct net_udp_hdr hdr, *udp_hdr;
ARG_UNUSED(hdr);
udp_hdr = net_udp_get_hdr(pkt, &hdr);
if (!udp_hdr) {
return NET_DROP;
}
src_port = udp_hdr->src_port;
dst_port = udp_hdr->dst_port;
chksum = udp_hdr->chksum;
} else if (IS_ENABLED(CONFIG_NET_TCP) && proto == IPPROTO_TCP) {
struct net_tcp_hdr hdr, *tcp_hdr;
ARG_UNUSED(hdr);
tcp_hdr = net_tcp_get_hdr(pkt, &hdr);
if (!tcp_hdr) {
return NET_DROP;
}
src_port = tcp_hdr->src_port;
dst_port = tcp_hdr->dst_port;
chksum = tcp_hdr->chksum;
} else {
NET_DBG("No UDP or TCP configured, dropping packet.");
return NET_DROP;
}
if (IS_ENABLED(CONFIG_NET_DEBUG_CONN)) {
int data_len = -1;
if (IS_ENABLED(CONFIG_NET_IPV4) &&
net_pkt_family(pkt) == AF_INET) {
data_len = NET_IPV4_HDR(pkt)->len[0] * 256 +
NET_IPV4_HDR(pkt)->len[1];
} else if (IS_ENABLED(CONFIG_NET_IPV6) &&
net_pkt_family(pkt) == AF_INET6) {
data_len = NET_IPV6_HDR(pkt)->len[0] * 256 +
NET_IPV6_HDR(pkt)->len[1];
}
NET_DBG("Check %s listener for pkt %p src port %u dst port %u "
"family %d chksum 0x%04x len %d", net_proto2str(proto),
pkt,
ntohs(src_port),
ntohs(dst_port),
net_pkt_family(pkt), ntohs(chksum), data_len);
}
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (!(conns[i].flags & NET_CONN_IN_USE)) {
continue;
}
if (conns[i].proto != proto) {
continue;
}
if (net_sin(&conns[i].remote_addr)->sin_port) {
if (net_sin(&conns[i].remote_addr)->sin_port !=
src_port) {
continue;
}
}
if (net_sin(&conns[i].local_addr)->sin_port) {
if (net_sin(&conns[i].local_addr)->sin_port !=
dst_port) {
continue;
}
}
if (conns[i].flags & NET_CONN_REMOTE_ADDR_SET) {
if (!check_addr(pkt, &conns[i].remote_addr, true)) {
continue;
}
}
if (conns[i].flags & NET_CONN_LOCAL_ADDR_SET) {
if (!check_addr(pkt, &conns[i].local_addr, false)) {
continue;
}
}
/* If we have an existing best_match, and that one
* specifies a remote port, then we've matched to a
* LISTENING connection that should not override.
*/
if (best_match >= 0 &&
net_sin(&conns[best_match].remote_addr)->sin_port) {
continue;
}
if (best_rank < conns[i].rank) {
best_rank = conns[i].rank;
best_match = i;
}
}
if (best_match >= 0) {
/* If packet has a listener configured, then check also the
* protocol checksum if that checking is enabled.
* If the checksum calculation fails, then discard the message.
*/
if (IS_ENABLED(CONFIG_NET_UDP_CHECKSUM) &&
proto == IPPROTO_UDP &&
net_if_need_calc_rx_checksum(net_pkt_iface(pkt))) {
u16_t chksum_calc;
net_udp_set_chksum(pkt, pkt->frags);
chksum_calc = net_udp_get_chksum(pkt, pkt->frags);
if (chksum != chksum_calc) {
net_stats_update_udp_chkerr(net_pkt_iface(pkt));
NET_DBG("UDP checksum mismatch "
"expected 0x%04x got 0x%04x, dropping packet.",
ntohs(chksum_calc), ntohs(chksum));
goto drop;
}
} else if (IS_ENABLED(CONFIG_NET_TCP_CHECKSUM) &&
proto == IPPROTO_TCP &&
net_if_need_calc_rx_checksum(net_pkt_iface(pkt))) {
u16_t chksum_calc;
net_tcp_set_chksum(pkt, pkt->frags);
chksum_calc = net_tcp_get_chksum(pkt, pkt->frags);
if (chksum != chksum_calc) {
net_stats_update_tcp_seg_chkerr(
net_pkt_iface(pkt));
NET_DBG("TCP checksum mismatch "
"expected 0x%04x got 0x%04x, dropping packet.",
ntohs(chksum_calc), ntohs(chksum));
goto drop;
}
}
#if defined(CONFIG_NET_CONN_CACHE)
NET_DBG("[%d] match found cb %p ud %p rank 0x%02x cache 0x%x",
best_match,
conns[best_match].cb,
conns[best_match].user_data,
conns[best_match].rank,
pos < 0 ? 0 : conn_cache[pos].value);
if (pos >= 0) {
conn_cache[pos].idx = best_match;
}
#else
NET_DBG("[%d] match found cb %p ud %p rank 0x%02x",
best_match,
conns[best_match].cb,
conns[best_match].user_data,
conns[best_match].rank);
#endif /* CONFIG_NET_CONN_CACHE */
if (conns[best_match].cb(&conns[best_match], pkt,
conns[best_match].user_data) == NET_DROP) {
goto drop;
}
net_stats_update_per_proto_recv(net_pkt_iface(pkt), proto);
return NET_OK;
}
NET_DBG("No match found.");
cache_add_neg(cache_value);
#if defined(CONFIG_NET_IPV6)
/* If the destination address is multicast address,
* we do not send ICMP error as that makes no sense.
*/
if (net_pkt_family(pkt) == AF_INET6 &&
net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst)) {
;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET &&
net_is_ipv4_addr_mcast(&NET_IPV4_HDR(pkt)->dst)) {
;
} else
#endif
{
send_icmp_error(pkt);
if (IS_ENABLED(CONFIG_NET_TCP) && proto == IPPROTO_TCP) {
net_stats_update_tcp_seg_connrst(net_pkt_iface(pkt));
}
}
drop:
net_stats_update_per_proto_drop(net_pkt_iface(pkt), proto);
return NET_DROP;
}
void net_conn_foreach(net_conn_foreach_cb_t cb, void *user_data)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
if (!(conns[i].flags & NET_CONN_IN_USE)) {
continue;
}
cb(&conns[i], user_data);
}
}
void net_conn_init(void)
{
#if defined(CONFIG_NET_CONN_CACHE)
do {
int i;
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
conn_cache[i].idx = -1;
}
} while (0);
#endif /* CONFIG_NET_CONN_CACHE */
}