blob: d2af6200feacec988e70546b1c082ebd0e0bf8a8 [file] [log] [blame]
/** @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) {
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_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) {
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_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(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();
}
}
drop:
net_stats_update_per_proto_drop(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 */
}