POSIX requires struct sockaddr's field to be named "sa_family" (not just "family"): http://pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/socket.h.html This change allows to port POSIX apps easier (including writing portable apps using BSD Sockets compatible API). Signed-off-by: Paul Sokolovsky <paul.sokolovsky@linaro.org>
1005 lines
22 KiB
C
1005 lines
22 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);
|
|
|
|
conn->flags = 0;
|
|
|
|
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 (remote_addr->sa_family != AF_INET &&
|
|
remote_addr->sa_family != AF_INET6) {
|
|
NET_ERR("Remote address family not set.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
conns[i].flags |= NET_CONN_REMOTE_ADDR_SET;
|
|
|
|
memcpy(&conns[i].remote_addr, remote_addr,
|
|
sizeof(struct sockaddr));
|
|
|
|
#if defined(CONFIG_NET_IPV6)
|
|
if (remote_addr->sa_family == AF_INET6) {
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
if (remote_addr->sa_family == AF_INET) {
|
|
if (!net_sin(remote_addr)->
|
|
sin_addr.s_addr) {
|
|
rank |= NET_RANK_REMOTE_UNSPEC_ADDR;
|
|
} else {
|
|
rank |= NET_RANK_REMOTE_SPEC_ADDR;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (local_addr) {
|
|
if (local_addr->sa_family != AF_INET &&
|
|
local_addr->sa_family != AF_INET6) {
|
|
NET_ERR("Local address family not set.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
conns[i].flags |= NET_CONN_LOCAL_ADDR_SET;
|
|
|
|
memcpy(&conns[i].local_addr, local_addr,
|
|
sizeof(struct sockaddr));
|
|
|
|
#if defined(CONFIG_NET_IPV6)
|
|
if (local_addr->sa_family == AF_INET6) {
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
if (local_addr->sa_family == AF_INET) {
|
|
if (!net_sin(local_addr)->sin_addr.s_addr) {
|
|
rank |= NET_RANK_LOCAL_UNSPEC_ADDR;
|
|
} else {
|
|
rank |= NET_RANK_LOCAL_SPEC_ADDR;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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)) {
|
|
NET_DBG("Check %s listener for pkt %p src port %u dst port %u "
|
|
"family %d chksum 0x%04x", net_proto2str(proto), pkt,
|
|
ntohs(src_port),
|
|
ntohs(dst_port),
|
|
net_pkt_family(pkt), ntohs(chksum));
|
|
}
|
|
|
|
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();
|
|
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();
|
|
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 */
|
|
}
|