CDC/ECM driver for mbed, based on USBDevice by mbed-official. Uses PicoTCP to access Ethernet USB device. License: GPLv2
Fork of USB_Ethernet by
stack/pico_socket.c
- Committer:
- daniele
- Date:
- 2013-08-03
- Revision:
- 2:540f6e142d59
File content as of revision 2:540f6e142d59:
/********************************************************************* PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. See LICENSE and COPYING for usage. Authors: Daniele Lacamera *********************************************************************/ #include "pico_config.h" #include "pico_queue.h" #include "pico_socket.h" #include "pico_ipv4.h" #include "pico_ipv6.h" #include "pico_udp.h" #include "pico_tcp.h" #include "pico_stack.h" #include "pico_icmp4.h" #include "pico_nat.h" #include "pico_tree.h" #include "pico_device.h" #if defined (PICO_SUPPORT_IPV4) || defined (PICO_SUPPORT_IPV6) #if defined (PICO_SUPPORT_TCP) || defined (PICO_SUPPORT_UDP) #ifdef PICO_SUPPORT_MUTEX static void * Mutex = NULL; #define LOCK(x) {\ if (x == NULL) \ x = pico_mutex_init(); \ pico_mutex_lock(x); \ } #define UNLOCK(x) pico_mutex_unlock(x); #else #define LOCK(x) do{}while(0) #define UNLOCK(x) do{}while(0) #endif #define PROTO(s) ((s)->proto->proto_number) #ifdef PICO_SUPPORT_TCP # define IS_NAGLE_ENABLED(s) (!(!(!(s->opt_flags & (1 << PICO_SOCKET_OPT_TCPNODELAY))))) #endif #define PICO_SOCKET_MTU 1480 /* Ethernet MTU(1500) - IP header size(20) */ #ifdef PICO_SUPPORT_IPV4 # define IS_SOCK_IPV4(s) ((s->net == &pico_proto_ipv4)) #else # define IS_SOCK_IPV4(s) (0) #endif #ifdef PICO_SUPPORT_IPV6 # define IS_SOCK_IPV6(s) ((s->net == &pico_proto_ipv6)) #else # define IS_SOCK_IPV6(s) (0) #endif #ifdef PICO_SUPPORT_IPFRAG # define frag_dbg(...) do{}while(0) #endif #ifdef PICO_SUPPORT_MCAST # define so_mcast_dbg(...) do{}while(0) /* ip_mcast_dbg in pico_ipv4.c */ #endif static struct pico_sockport *sp_udp = NULL ,*sp_tcp = NULL; struct pico_frame *pico_socket_frame_alloc(struct pico_socket *s, int len); static int socket_cmp(void * ka, void * kb) { struct pico_socket *a = ka, *b = kb; int a_is_ip6 = is_sock_ipv6(a); int b_is_ip6 = is_sock_ipv6(b); int diff; /* First, order by network ver */ if (a_is_ip6 < b_is_ip6) return -1; if (a_is_ip6 > b_is_ip6) return 1; /* If either socket is PICO_IPV4_INADDR_ANY mode, skip local address comparison */ /* At this point, sort by local host */ if (0) { #ifdef PICO_SUPPORT_IPV6 } else if (a_is_ip6) { if ((memcmp(a->local_addr.ip6.addr, PICO_IP6_ANY, PICO_SIZE_IP6)==0) || memcmp((b->local_addr.ip6.addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0)) diff = 0; else diff = memcmp(a->local_addr.ip6.addr, b->local_addr.ip6.addr, PICO_SIZE_IP6); #endif } else { if ((a->local_addr.ip4.addr == PICO_IP4_ANY) || (b->local_addr.ip4.addr == PICO_IP4_ANY)) diff = 0; else diff = a->local_addr.ip4.addr - b->local_addr.ip4.addr; } if (diff) return diff; /* Sort by remote host */ if (a_is_ip6) diff = memcmp(a->remote_addr.ip6.addr, b->remote_addr.ip6.addr, PICO_SIZE_IP6); else diff = a->remote_addr.ip4.addr - b->remote_addr.ip4.addr; if (diff) return diff; /* And finally by remote port. The two sockets are coincident if the quad is the same. */ return b->remote_port - a->remote_port; } struct pico_sockport { struct pico_tree socks; // how you make the connection ? uint16_t number; uint16_t proto; }; #define INIT_SOCKPORT { {&LEAF , socket_cmp}, 0, 0 } int sockport_cmp(void * ka, void * kb) { struct pico_sockport *a = ka, *b = kb; if (a->number < b->number) return -1; if (a->number > b->number) return 1; return 0; } PICO_TREE_DECLARE(UDPTable,sockport_cmp); PICO_TREE_DECLARE(TCPTable,sockport_cmp); #ifdef PICO_SUPPORT_MCAST /* socket * | * MCASTListen * | | | * ------------ | ------------ * | | | * MCASTSources MCASTSources MCASTSources * | | | | | | | | | | | | * S S S S S S S S S S S S * * MCASTListen: RBTree(mcast_link, mcast_group) * MCASTSources: RBTree(source) */ struct pico_mcast_listen { uint8_t filter_mode; struct pico_ip4 mcast_link; struct pico_ip4 mcast_group; struct pico_tree MCASTSources; }; static int mcast_listen_cmp(void *ka, void *kb) { struct pico_mcast_listen *a = ka, *b = kb; if (a->mcast_group.addr < b->mcast_group.addr) return -1; if (a->mcast_group.addr > b->mcast_group.addr) return 1; if (a->mcast_link.addr < b->mcast_link.addr) return -1; if (a->mcast_link.addr > b->mcast_link.addr) return 1; return 0; } static int mcast_sources_cmp(void *ka, void *kb) { struct pico_ip4 *a = ka, *b = kb; if (a->addr < b->addr) return -1; if (a->addr > b->addr) return 1; return 0; } static int mcast_socket_cmp(void *ka, void *kb) { struct pico_socket *a = ka, *b = kb; if (a < b) return -1; if (a > b) return 1; return 0; } /* gather all multicast sockets to hasten filter aggregation */ PICO_TREE_DECLARE(MCASTSockets, mcast_socket_cmp); static int mcast_filter_cmp(void *ka, void *kb) { struct pico_ip4 *a = ka, *b = kb; if (a->addr < b->addr) return -1; if (a->addr > b->addr) return 1; return 0; } /* gather sources to be filtered */ PICO_TREE_DECLARE(MCASTFilter, mcast_filter_cmp); /* MCASTFilter will be empty if no socket is listening on mcast_group on mcast_link anymore */ static int pico_socket_aggregate_mcastfilters(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group) { uint8_t filter_mode = PICO_IP_MULTICAST_INCLUDE; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL; struct pico_socket *mcast_sock = NULL; struct pico_tree_node *index = NULL, *_tmp = NULL, *index2 = NULL, *_tmp2 = NULL; ltest.mcast_link = *mcast_link; ltest.mcast_group = *mcast_group; /* cleanup old filter */ pico_tree_foreach_safe(index, &MCASTFilter, _tmp) { pico_tree_delete(&MCASTFilter, index->keyValue); } /* construct new filter */ pico_tree_foreach(index, &MCASTSockets) { mcast_sock = index->keyValue; listen = pico_tree_findKey(mcast_sock->MCASTListen, <est); if (listen) { /* aggregate filter */ switch(filter_mode) { case PICO_IP_MULTICAST_INCLUDE: switch (listen->filter_mode) { case PICO_IP_MULTICAST_INCLUDE: /* filter = summation of INCLUDEs */ /* mode stays INCLUDE, add all sources to filter */ pico_tree_foreach(index2, &listen->MCASTSources) { source = index2->keyValue; pico_tree_insert(&MCASTFilter, source); } break; case PICO_IP_MULTICAST_EXCLUDE: /* filter = EXCLUDE - INCLUDE */ /* delete from the interface INCLUDE filter any source NOT in the socket EXCLUDE filter */ pico_tree_foreach_safe(index2, &MCASTFilter, _tmp2) { source = pico_tree_findKey(&listen->MCASTSources, index2->keyValue); if (!source) pico_tree_delete(&MCASTFilter, index2->keyValue); } /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ filter_mode = PICO_IP_MULTICAST_EXCLUDE; /* add to the interface EXCLUDE filter any socket source NOT in the former interface INCLUDE filter */ pico_tree_foreach(index2, &listen->MCASTSources) { source = pico_tree_insert(&MCASTFilter, index2->keyValue); if (source) pico_tree_delete(&MCASTFilter, source); } break; default: return -1; } break; case PICO_IP_MULTICAST_EXCLUDE: switch (listen->filter_mode) { case PICO_IP_MULTICAST_INCLUDE: /* filter = EXCLUDE - INCLUDE */ /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ /* remove from the interface EXCLUDE filter any source in the socket INCLUDE filter */ pico_tree_foreach(index2, &listen->MCASTSources) { source = pico_tree_findKey(&MCASTFilter, index2->keyValue); if (source) pico_tree_delete(&MCASTFilter, source); } break; case PICO_IP_MULTICAST_EXCLUDE: /* filter = intersection of EXCLUDEs */ /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ /* remove from the interface EXCLUDE filter any source not in the socket EXCLUDE filter */ pico_tree_foreach_safe(index2, &MCASTFilter, _tmp2) { source = pico_tree_findKey(&listen->MCASTSources, index2->keyValue); if (!source) pico_tree_delete(&MCASTFilter, index2->keyValue); } break; default: return -1; } break; default: return -1; } } } return filter_mode; } static int pico_socket_mcast_filter(struct pico_socket *s, struct pico_ip4 *mcast_group, struct pico_ip4 *src) { struct pico_ipv4_link *mcast_link = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_tree_node *index = NULL; /* no multicast enabled on socket */ if (!s->MCASTListen) return 0; mcast_link = pico_ipv4_link_get(&s->local_addr.ip4); if (!mcast_link) return -1; ltest.mcast_link.addr = mcast_link->address.addr; ltest.mcast_group = *mcast_group; listen = pico_tree_findKey(s->MCASTListen, <est); if (!listen) return -1; /* perform source filtering */ switch (listen->filter_mode) { case PICO_IP_MULTICAST_INCLUDE: pico_tree_foreach(index, &listen->MCASTSources) { if (src->addr == ((struct pico_ip4 *)index->keyValue)->addr) { so_mcast_dbg("MCAST: IP %08X in included socket source list\n", src->addr); return 0; } } so_mcast_dbg("MCAST: IP %08X NOT in included socket source list\n", src->addr); return -1; break; case PICO_IP_MULTICAST_EXCLUDE: pico_tree_foreach(index, &listen->MCASTSources) { if (src->addr == ((struct pico_ip4 *)index->keyValue)->addr) { so_mcast_dbg("MCAST: IP %08X in excluded socket source list\n", src->addr); return -1; } } so_mcast_dbg("MCAST: IP %08X NOT in excluded socket source list\n", src->addr); return 0; break; default: return -1; break; } return -1; } static inline struct pico_ipv4_link *pico_socket_setoption_mcastargs_validation(struct pico_ip_mreq *mreq, struct pico_ip_mreq_source *mreq_source) { struct pico_ipv4_link *mcast_link = NULL; if (!mreq && !mreq_source) return NULL; if (mreq) { if (!mreq->mcast_group_addr.addr) return NULL; if (pico_ipv4_is_unicast(mreq->mcast_group_addr.addr)) return NULL; if (!mreq->mcast_link_addr.addr) { mcast_link = pico_ipv4_get_default_mcastlink(); if (!mcast_link) return NULL; } else { mcast_link = pico_ipv4_link_get(&mreq->mcast_link_addr); if (!mcast_link) return NULL; } } if (mreq_source) { if (!mreq_source->mcast_group_addr.addr) return NULL; if (pico_ipv4_is_unicast(mreq_source->mcast_group_addr.addr)) return NULL; if (!mreq_source->mcast_source_addr.addr) return NULL; if (!pico_ipv4_is_unicast(mreq_source->mcast_source_addr.addr)) return NULL; if (!mreq_source->mcast_link_addr.addr) { mcast_link = pico_ipv4_get_default_mcastlink(); if (!mcast_link) return NULL; } else { mcast_link = pico_ipv4_link_get(&mreq_source->mcast_link_addr); if (!mcast_link) return NULL; } } return mcast_link; } #else static int pico_socket_mcast_filter(struct pico_socket *s, struct pico_ip4 *mcast_group, struct pico_ip4 *src) { return 0; } #endif /* PICO_SUPPORT_MCAST */ static struct pico_sockport *pico_get_sockport(uint16_t proto, uint16_t port) { struct pico_sockport test = INIT_SOCKPORT; test.number = port; if (proto == PICO_PROTO_UDP) return pico_tree_findKey(&UDPTable,&test); else if (proto == PICO_PROTO_TCP) return pico_tree_findKey(&TCPTable,&test); else return NULL; } int pico_is_port_free(uint16_t proto, uint16_t port, void *addr, void *net) { struct pico_sockport *sp; struct pico_ip4 ip; sp = pico_get_sockport(proto, port); if (!net) net = &pico_proto_ipv4; /** IPv6 (wip) ***/ if (net != &pico_proto_ipv4) { dbg("IPV6!!!!!\n"); return (!sp); } /* IPv4 */ #ifdef PICO_SUPPORT_NAT if (pico_ipv4_nat_find(port,NULL, 0,proto) == 0) { dbg("In use by nat....\n"); return 0; } #endif if (addr) ip.addr = ((struct pico_ip4 *)addr)->addr; else ip.addr = PICO_IPV4_INADDR_ANY; if (ip.addr == PICO_IPV4_INADDR_ANY) { if (!sp) return 1; else { dbg("In use, and asked for ANY\n"); return 0; } } if (sp) { struct pico_ip4 *s_local; struct pico_tree_node *idx; struct pico_socket *s; pico_tree_foreach(idx, &sp->socks) { s = idx->keyValue; if (s->net == &pico_proto_ipv4) { s_local = (struct pico_ip4*) &s->local_addr; if ((s_local->addr == PICO_IPV4_INADDR_ANY) || (s_local->addr == ip.addr)) return 0; } } } return 1; } static int pico_check_socket(struct pico_socket *s) { struct pico_sockport *test; struct pico_socket *found; struct pico_tree_node * index; test = pico_get_sockport(PROTO(s), s->local_port); if (!test) { return -1; } pico_tree_foreach(index,&test->socks){ found = index->keyValue; if (s == found) { return 0; } } return -1; } int pico_socket_add(struct pico_socket *s) { struct pico_sockport *sp = pico_get_sockport(PROTO(s), s->local_port); LOCK(Mutex); if (!sp) { //dbg("Creating sockport..%04x\n", s->local_port); /* In comment due to spam during test */ sp = pico_zalloc(sizeof(struct pico_sockport)); if (!sp) { pico_err = PICO_ERR_ENOMEM; UNLOCK(Mutex); return -1; } sp->proto = PROTO(s); sp->number = s->local_port; sp->socks.root = &LEAF; sp->socks.compare = socket_cmp; if (PROTO(s) == PICO_PROTO_UDP) { pico_tree_insert(&UDPTable,sp); } else if (PROTO(s) == PICO_PROTO_TCP) { pico_tree_insert(&TCPTable,sp); } } pico_tree_insert(&sp->socks,s); s->state |= PICO_SOCKET_STATE_BOUND; UNLOCK(Mutex); #if DEBUG_SOCKET_TREE { struct pico_tree_node * index; //RB_FOREACH(s, socket_tree, &sp->socks) { pico_tree_foreach(index,&sp->socks){ s = index->keyValue; dbg(">>>> List Socket lc=%hu rm=%hu\n", short_be(s->local_port), short_be(s->remote_port)); } } #endif return 0; } static void socket_garbage_collect(unsigned long now, void *arg) { struct pico_socket *s = (struct pico_socket *) arg; pico_free(s); } int pico_socket_del(struct pico_socket *s) { struct pico_sockport *sp = pico_get_sockport(PROTO(s), s->local_port); if (!sp) { pico_err = PICO_ERR_ENXIO; return -1; } LOCK(Mutex); pico_tree_delete(&sp->socks,s); s->net = NULL; if(pico_tree_empty(&sp->socks)){ if (PROTO(s) == PICO_PROTO_UDP) { pico_tree_delete(&UDPTable,sp); } else if (PROTO(s) == PICO_PROTO_TCP) { pico_tree_delete(&TCPTable,sp); } if(sp_tcp == sp) sp_tcp = NULL; if(sp_udp == sp) sp_udp = NULL; pico_free(sp); } #ifdef PICO_SUPPORT_MCAST do { uint8_t filter_mode = 0; struct pico_tree_node *index = NULL, *_tmp = NULL, *index2 = NULL, *_tmp2 = NULL; struct pico_mcast_listen *listen = NULL; struct pico_ip4 *source = NULL; if (s->MCASTListen) { pico_tree_delete(&MCASTSockets, s); pico_tree_foreach_safe(index, s->MCASTListen, _tmp) { listen = index->keyValue; pico_tree_foreach_safe(index2, &listen->MCASTSources, _tmp2) { source = index->keyValue; pico_tree_delete(&listen->MCASTSources, source); pico_free(source); } filter_mode = pico_socket_aggregate_mcastfilters(&listen->mcast_link, &listen->mcast_group); pico_ipv4_mcast_leave(&listen->mcast_link, &listen->mcast_group, 1, filter_mode, &MCASTFilter); pico_tree_delete(s->MCASTListen, listen); pico_free(listen); } pico_free(s->MCASTListen); } } while (0); #endif s->state = PICO_SOCKET_STATE_CLOSED; pico_timer_add(3000, socket_garbage_collect, s); UNLOCK(Mutex); return 0; } static int pico_socket_alter_state(struct pico_socket *s, uint16_t more_states, uint16_t less_states, uint16_t tcp_state) { struct pico_sockport *sp; if (more_states & PICO_SOCKET_STATE_BOUND) return pico_socket_add(s); if (less_states & PICO_SOCKET_STATE_BOUND) return pico_socket_del(s); sp = pico_get_sockport(PROTO(s), s->local_port); if (!sp) { pico_err = PICO_ERR_ENXIO; return -1; } s->state |= more_states; s->state &= (~less_states); if (tcp_state) { s->state &= 0x00FF; s->state |= tcp_state; } return 0; } static int pico_socket_deliver(struct pico_protocol *p, struct pico_frame *f, uint16_t localport) { struct pico_frame *cpy = NULL; struct pico_sockport *sp = NULL; struct pico_socket *s = NULL, *found = NULL; struct pico_tree_node *index = NULL; struct pico_tree_node *_tmp; struct pico_trans *tr = (struct pico_trans *) f->transport_hdr; #ifdef PICO_SUPPORT_IPV4 struct pico_ipv4_hdr *ip4hdr; #endif #ifdef PICO_SUPPORT_IPV6 struct pico_ipv6_hdr *ip6hdr; #endif if (!tr) return -1; sp = pico_get_sockport(p->proto_number, localport); if (!sp) { dbg("No such port %d\n",short_be(localport)); return -1; } #ifdef PICO_SUPPORT_TCP if (p->proto_number == PICO_PROTO_TCP) { pico_tree_foreach_safe(index,&sp->socks, _tmp){ s = index->keyValue; /* 4-tuple identification of socket (port-IP) */ #ifdef PICO_SUPPORT_IPV4 if (IS_IPV4(f)) { struct pico_ip4 s_local, s_remote, p_src, p_dst; ip4hdr = (struct pico_ipv4_hdr*)(f->net_hdr); s_local.addr = s->local_addr.ip4.addr; s_remote.addr = s->remote_addr.ip4.addr; p_src.addr = ip4hdr->src.addr; p_dst.addr = ip4hdr->dst.addr; if ( (s->remote_port == tr->sport) && /* remote port check */ (s_remote.addr == p_src.addr) && /* remote addr check */ ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr))) { /* Either local socket is ANY, or matches dst */ found = s; break; } else if ( (s->remote_port == 0) && /* not connected... listening */ ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr))) { /* Either local socket is ANY, or matches dst */ /* listen socket */ found = s; } } #endif #ifdef PICO_SUPPORT_IPV6 /* XXX TODO make compare for ipv6 addresses */ if (IS_IPV6(f)) { ip6hdr = (struct pico_ipv6_hdr*)(f->net_hdr); if ( (s->remote_port == localport) ) { // && (((struct pico_ip6) s->remote_addr.ip6).addr == ((struct pico_ip6)(ip6hdr->src)).addr) ) { found = s; break; } else if (s->remote_port == 0) { /* listen socket */ found = s; } } #endif } /* FOREACH */ if (found != NULL) { pico_tcp_input(found,f); if ((found->ev_pending) && found->wakeup) { found->wakeup(found->ev_pending, found); } return 0; } else { dbg("SOCKET> mmm something wrong (prob sockport)\n"); return -1; } } /* TCP CASE */ #endif #ifdef PICO_SUPPORT_UDP if (p->proto_number == PICO_PROTO_UDP) { pico_tree_foreach_safe(index,&sp->socks, _tmp){ s = index->keyValue; if (IS_IPV4(f)) { /* IPV4 */ struct pico_ip4 s_local, p_dst; ip4hdr = (struct pico_ipv4_hdr*)(f->net_hdr); s_local.addr = s->local_addr.ip4.addr; p_dst.addr = ip4hdr->dst.addr; if ((pico_ipv4_is_broadcast(p_dst.addr)) || pico_ipv4_is_multicast(p_dst.addr)) { struct pico_device *dev = pico_ipv4_link_find(&s->local_addr.ip4); if (pico_ipv4_is_multicast(p_dst.addr) && (pico_socket_mcast_filter(s, &ip4hdr->dst, &ip4hdr->src) < 0)) return -1; if ((s_local.addr == PICO_IPV4_INADDR_ANY) || /* If our local ip is ANY, or.. */ (dev == f->dev) ) { /* the source of the bcast packet is a neighbor... */ cpy = pico_frame_copy(f); if (!cpy) return -1; if (pico_enqueue(&s->q_in, cpy) > 0) { if (s->wakeup) s->wakeup(PICO_SOCK_EV_RD, s); } } } else if ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr)) { /* Either local socket is ANY, or matches dst */ cpy = pico_frame_copy(f); if (!cpy) return -1; if (pico_enqueue(&s->q_in, cpy) > 0) { if (s->wakeup) s->wakeup(PICO_SOCK_EV_RD, s); } } } else { /*... IPv6 */ } } /* FOREACH */ pico_frame_discard(f); if (s) return 0; else return -1; } #endif return -1; } struct pico_socket *pico_socket_open(uint16_t net, uint16_t proto, void (*wakeup)(uint16_t ev, struct pico_socket *)) { struct pico_socket *s = NULL; #ifdef PICO_SUPPORT_UDP if (proto == PICO_PROTO_UDP) { s = pico_udp_open(); s->proto = &pico_proto_udp; } #endif #ifdef PICO_SUPPORT_TCP if (proto == PICO_PROTO_TCP) { s = pico_tcp_open(); s->proto = &pico_proto_tcp; /*check if Nagle enabled */ if (!IS_NAGLE_ENABLED(s)) dbg("ERROR Nagle should be enabled here\n\n"); } #endif if (!s) { pico_err = PICO_ERR_EPROTONOSUPPORT; return NULL; } #ifdef PICO_SUPPORT_IPV4 if (net == PICO_PROTO_IPV4) s->net = &pico_proto_ipv4; #endif #ifdef PICO_SUPPORT_IPV6 if (net == PICO_PROTO_IPV6) s->net = &pico_proto_ipv6; #endif s->q_in.max_size = PICO_DEFAULT_SOCKETQ; s->q_out.max_size = PICO_DEFAULT_SOCKETQ; s->wakeup = wakeup; if (!s->net) { pico_free(s); pico_err = PICO_ERR_ENETUNREACH; return NULL; } return s; } struct pico_socket *pico_socket_clone(struct pico_socket *facsimile) { struct pico_socket *s = NULL; #ifdef PICO_SUPPORT_UDP if (facsimile->proto->proto_number == PICO_PROTO_UDP) { s = pico_udp_open(); s->proto = &pico_proto_udp; } #endif #ifdef PICO_SUPPORT_TCP if (facsimile->proto->proto_number == PICO_PROTO_TCP) { s = pico_tcp_open(); s->proto = &pico_proto_tcp; } #endif if (!s) { pico_err = PICO_ERR_EPROTONOSUPPORT; return NULL; } s->local_port = facsimile->local_port; s->remote_port = facsimile->remote_port; s->state = facsimile->state; #ifdef PICO_SUPPORT_IPV4 if (facsimile->net == &pico_proto_ipv4) { s->net = &pico_proto_ipv4; memcpy(&s->local_addr, &facsimile->local_addr, sizeof(struct pico_ip4)); memcpy(&s->remote_addr, &facsimile->remote_addr, sizeof(struct pico_ip4)); } #endif #ifdef PICO_SUPPORT_IPV6 if (net == &pico_proto_ipv6) { s->net = &pico_proto_ipv6; memcpy(&s->local_addr, &facsimile->local_addr, sizeof(struct pico_ip6)); memcpy(&s->remote_addr, &facsimile->remote_addr, sizeof(struct pico_ip6)); } #endif s->q_in.max_size = PICO_DEFAULT_SOCKETQ; s->q_out.max_size = PICO_DEFAULT_SOCKETQ; s->wakeup = NULL; if (!s->net) { pico_free(s); pico_err = PICO_ERR_ENETUNREACH; return NULL; } return s; } int pico_socket_read(struct pico_socket *s, void *buf, int len) { if (!s || buf == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ /* See task #178 */ if (pico_check_socket(s) != 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { pico_err = PICO_ERR_EIO; return -1; } #ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) return pico_udp_recv(s, buf, len, NULL, NULL); #endif #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP){ /* check if in shutdown state and if no more data in tcpq_in */ if ((s->state & PICO_SOCKET_STATE_SHUT_REMOTE) && pico_tcp_queue_in_is_empty(s) ) { pico_err = PICO_ERR_ESHUTDOWN; return -1; } else { return pico_tcp_read(s, buf, len); } } #endif return 0; } int pico_socket_write(struct pico_socket *s, void *buf, int len) { if (!s || buf == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ /* See task #178 */ if (pico_check_socket(s) != 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { pico_err = PICO_ERR_EIO; return -1; } if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { pico_err = PICO_ERR_ENOTCONN; return -1; } else if (s->state & PICO_SOCKET_STATE_SHUT_LOCAL) { /* check if in shutdown state */ pico_err = PICO_ERR_ESHUTDOWN; return -1; } else { return pico_socket_sendto(s, buf, len, &s->remote_addr, s->remote_port); } } uint16_t pico_socket_high_port(uint16_t proto) { uint16_t port; if (0 || #ifdef PICO_SUPPORT_TCP (proto == PICO_PROTO_TCP) || #endif #ifdef PICO_SUPPORT_TCP (proto == PICO_PROTO_UDP) || #endif 0) { do { uint32_t rand = pico_rand(); port = (uint16_t) (rand & 0xFFFFU); port = (uint16_t)(port % (65535 - 1024)) + 1024U; if (pico_is_port_free(proto, port, NULL, NULL)) { return short_be(port); } } while(1); } else return 0U; } int pico_socket_sendto(struct pico_socket *s, void *buf, int len, void *dst, uint16_t remote_port) { struct pico_frame *f; struct pico_remote_duple *remote_duple = NULL; int header_offset = 0; int total_payload_written = 0; #ifdef PICO_SUPPORT_IPV4 struct pico_ip4 *src4; #endif #ifdef PICO_SUPPORT_IPV6 struct pico_ip6 *src6; #endif if (len == 0) { return 0; } else if (len < 0) { pico_err = PICO_ERR_EINVAL; return -1; } if (buf == NULL || s == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } if (!dst || !remote_port) { pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } if ((s->state & PICO_SOCKET_STATE_CONNECTED) != 0) { if (remote_port != s->remote_port) { pico_err = PICO_ERR_EINVAL; return -1; } } #ifdef PICO_SUPPORT_IPV4 if (IS_SOCK_IPV4(s)) { if ((s->state & PICO_SOCKET_STATE_CONNECTED)) { if (s->remote_addr.ip4.addr != ((struct pico_ip4 *)dst)->addr ) { pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } } else { src4 = pico_ipv4_source_find(dst); if (!src4) { pico_err = PICO_ERR_EHOSTUNREACH; return -1; } if (src4->addr != PICO_IPV4_INADDR_ANY) s->local_addr.ip4.addr = src4->addr; # ifdef PICO_SUPPORT_UDP /* socket remote info could change in a consecutive call, make persistent */ if (PROTO(s) == PICO_PROTO_UDP) { remote_duple = pico_zalloc(sizeof(struct pico_remote_duple)); remote_duple->remote_addr.ip4.addr = ((struct pico_ip4 *)dst)->addr; remote_duple->remote_port = remote_port; } # endif } } #endif #ifdef PICO_SUPPORT_IPV6 if (IS_SOCK_IPV6(s)) { if (s->state & PICO_SOCKET_STATE_CONNECTED) { if (memcmp(&s->remote_addr, dst, PICO_SIZE_IP6)) return -1; } else { src6 = pico_ipv6_source_find(dst); if (!src6) { pico_err = PICO_ERR_EHOSTUNREACH; return -1; } memcpy(&s->local_addr, src6, PICO_SIZE_IP6); memcpy(&s->remote_addr, dst, PICO_SIZE_IP6); # ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) { remote_duple = pico_zalloc(sizeof(struct pico_remote_duple)); remote_duple->remote_addr.ip6.addr = ((struct pico_ip6 *)dst)->addr; remote_duple->remote_port = remote_port; } # endif } } #endif if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { s->local_port = pico_socket_high_port(s->proto->proto_number); if (s->local_port == 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { s->remote_port = remote_port; } #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP) header_offset = pico_tcp_overhead(s); #endif #ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) header_offset = sizeof(struct pico_udp_hdr); #endif while (total_payload_written < len) { int transport_len = (len - total_payload_written) + header_offset; if (transport_len > PICO_SOCKET_MTU) transport_len = PICO_SOCKET_MTU; #ifdef PICO_SUPPORT_IPFRAG else { if (total_payload_written) transport_len -= header_offset; /* last fragment, do not allocate memory for transport header */ } #endif /* PICO_SUPPORT_IPFRAG */ f = pico_socket_frame_alloc(s, transport_len); if (!f) { pico_err = PICO_ERR_ENOMEM; return -1; } f->payload += header_offset; f->payload_len -= header_offset; f->sock = s; if (remote_duple) { f->info = pico_zalloc(sizeof(struct pico_remote_duple)); memcpy(f->info, remote_duple, sizeof(struct pico_remote_duple)); } #ifdef PICO_SUPPORT_IPFRAG # ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP && ((len + header_offset) > PICO_SOCKET_MTU)) { /* hacking way to identify fragmentation frames: payload != transport_hdr -> first frame */ if (!total_payload_written) { frag_dbg("FRAG: first fragmented frame %p | len = %u offset = 0\n", f, f->payload_len); /* transport header length field contains total length + header length */ f->transport_len = len + header_offset; f->frag = short_be(PICO_IPV4_MOREFRAG); } else { /* no transport header in fragmented IP */ f->payload = f->transport_hdr; f->payload_len += header_offset; /* set offset in octets */ f->frag = short_be((total_payload_written + header_offset) / 8); if (total_payload_written + f->payload_len < len) { frag_dbg("FRAG: intermediate fragmented frame %p | len = %u offset = %u\n", f, f->payload_len, short_be(f->frag)); f->frag |= short_be(PICO_IPV4_MOREFRAG); } else { frag_dbg("FRAG: last fragmented frame %p | len = %u offset = %u\n", f, f->payload_len, short_be(f->frag)); f->frag &= short_be(PICO_IPV4_FRAG_MASK); } } } else { f->frag = short_be(PICO_IPV4_DONTFRAG); } # endif /* PICO_SUPPORT_UDP */ #endif /* PICO_SUPPORT_IPFRAG */ if (f->payload_len <= 0) { pico_frame_discard(f); if (remote_duple) pico_free(remote_duple); return total_payload_written; } memcpy(f->payload, buf + total_payload_written, f->payload_len); //dbg("Pushing segment, hdr len: %d, payload_len: %d\n", header_offset, f->payload_len); if (s->proto->push(s->proto, f) > 0) { total_payload_written += f->payload_len; } else { pico_frame_discard(f); pico_err = PICO_ERR_EAGAIN; break; } } if (remote_duple) pico_free(remote_duple); return total_payload_written; } int pico_socket_send(struct pico_socket *s, void *buf, int len) { if (!s || buf == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ /* See task #178 */ if (pico_check_socket(s) != 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { pico_err = PICO_ERR_ENOTCONN; return -1; } return pico_socket_sendto(s, buf, len, &s->remote_addr, s->remote_port); } int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, uint16_t *remote_port) { if (!s || buf == NULL) { /// || orig == NULL || remote_port == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ if (pico_check_socket(s) != 0) { pico_err = PICO_ERR_EINVAL; /* See task #178 */ return -1; } } if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } #ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) { return pico_udp_recv(s, buf, len, orig, remote_port); } #endif #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP) { /* check if in shutdown state and if tcpq_in empty */ if ((s->state & PICO_SOCKET_STATE_SHUT_REMOTE) && pico_tcp_queue_in_is_empty(s)) { pico_err = PICO_ERR_ESHUTDOWN; return -1; } else { //dbg("socket tcp recv\n"); return pico_tcp_read(s, buf, len); } } #endif //dbg("socket return 0\n"); return 0; } int pico_socket_recv(struct pico_socket *s, void *buf, int len) { return pico_socket_recvfrom(s, buf, len, NULL, NULL); } int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port) { if (!s || !local_addr || !port) { pico_err = PICO_ERR_EINVAL; return -1; } if (!is_sock_ipv6(s)) { struct pico_ip4 *ip = (struct pico_ip4 *)local_addr; if (ip->addr != PICO_IPV4_INADDR_ANY) { if (!pico_ipv4_link_find(local_addr)) { pico_err = PICO_ERR_EINVAL; return -1; } } } else { /*... IPv6 */ } /* When given port = 0, get a random high port to bind to. */ if (*port == 0) { *port = pico_socket_high_port(PROTO(s)); if (*port == 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if (pico_is_port_free(PROTO(s), *port, local_addr, s->net) == 0) { pico_err = PICO_ERR_EADDRINUSE; return -1; } s->local_port = *port; if (is_sock_ipv6(s)) { struct pico_ip6 *ip = (struct pico_ip6 *) local_addr; memcpy(s->local_addr.ip6.addr, ip, PICO_SIZE_IP6); /* XXX: port ipv4 functionality to ipv6 */ /* Check for port already in use */ if (pico_is_port_free(PROTO(s), *port, &local_addr, s->net)) { pico_err = PICO_ERR_EADDRINUSE; return -1; } } else if (is_sock_ipv4(s)) { struct pico_ip4 *ip = (struct pico_ip4 *) local_addr; s->local_addr.ip4.addr = ip->addr; } return pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); } int pico_socket_connect(struct pico_socket *s, void *remote_addr, uint16_t remote_port) { int ret = -1; pico_err = PICO_ERR_EPROTONOSUPPORT; if (!s || remote_addr == NULL || remote_port == 0) { pico_err = PICO_ERR_EINVAL; return -1; } s->remote_port = remote_port; if (s->local_port == 0) { s->local_port = pico_socket_high_port(PROTO(s)); if (!s->local_port) { pico_err = PICO_ERR_EINVAL; return -1; } } if (is_sock_ipv6(s)) { struct pico_ip6 *ip = (struct pico_ip6 *) remote_addr; memcpy(s->remote_addr.ip6.addr, ip, PICO_SIZE_IP6); } else if (is_sock_ipv4(s)) { struct pico_ip4 *local, *ip = (struct pico_ip4 *) remote_addr; s->remote_addr.ip4.addr = ip->addr; local = pico_ipv4_source_find(ip); if (local) { s->local_addr.ip4.addr = local->addr; } else { pico_err = PICO_ERR_EHOSTUNREACH; return -1; } } pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); #ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) { pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED, 0, 0); pico_err = PICO_ERR_NOERR; ret = 0; } #endif #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP) { if (pico_tcp_initconn(s) == 0) { pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_SENT, 0, 0); pico_err = PICO_ERR_NOERR; ret = 0; } else { pico_err = PICO_ERR_EHOSTUNREACH; } } #endif return ret; } #ifdef PICO_SUPPORT_TCP int pico_socket_listen(struct pico_socket *s, int backlog) { if (!s || backlog < 1) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ /* See task #178 */ if (pico_check_socket(s) != 0) { pico_err = PICO_ERR_EINVAL; return -1; } } if (PROTO(s) == PICO_PROTO_UDP) { pico_err = PICO_ERR_EINVAL; return -1; } if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { pico_err = PICO_ERR_EISCONN; return -1; } if (backlog < 1) { pico_err = PICO_ERR_EINVAL; return -1; } if (PROTO(s) == PICO_PROTO_TCP) pico_socket_alter_state(s, PICO_SOCKET_STATE_TCP_SYN_SENT, 0, PICO_SOCKET_STATE_TCP_LISTEN); s->max_backlog = backlog; return 0; } struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *port) { if (!s || !orig || !port) { pico_err = PICO_ERR_EINVAL; return NULL; } pico_err = PICO_ERR_EINVAL; if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { return NULL; } if (PROTO(s) == PICO_PROTO_UDP) { return NULL; } if (TCPSTATE(s) == PICO_SOCKET_STATE_TCP_LISTEN) { struct pico_sockport *sp = pico_get_sockport(PICO_PROTO_TCP, s->local_port); struct pico_socket *found; /* If at this point no incoming connection socket is found, * the accept call is valid, but no connection is established yet. */ pico_err = PICO_ERR_EAGAIN; if (sp) { struct pico_tree_node * index; //RB_FOREACH(found, socket_tree, &sp->socks) { pico_tree_foreach(index,&sp->socks){ found = index->keyValue; if (s == found->parent) { found->parent = NULL; pico_err = PICO_ERR_NOERR; memcpy(orig, &found->remote_addr, sizeof(struct pico_ip4)); *port = found->remote_port; return found; } } } } return NULL; } #else int pico_socket_listen(struct pico_socket *s, int backlog) { pico_err = PICO_ERR_EINVAL; return -1; } struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *local_port) { pico_err = PICO_ERR_EINVAL; return NULL; } #endif #define PICO_SOCKET_SETOPT_EN(socket,index) (socket->opt_flags |= (1 << index)) #define PICO_SOCKET_SETOPT_DIS(socket,index) (socket->opt_flags &= ~(1 << index)) int pico_socket_setoption(struct pico_socket *s, int option, void *value) // XXX no check against proto (vs setsockopt) or implicit by socket? { if (s == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } pico_err = PICO_ERR_NOERR; switch (option) { #ifdef PICO_SUPPORT_TCP case PICO_TCP_NODELAY: if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } if (s->proto->proto_number == PICO_PROTO_TCP) { int *val = (int*)value; if (*val > 0) { dbg("setsockopt: Nagle algorithm disabled.\n"); PICO_SOCKET_SETOPT_EN(s,PICO_SOCKET_OPT_TCPNODELAY); } else { dbg("setsockopt: Nagle algorithm enabled.\n"); PICO_SOCKET_SETOPT_DIS(s,PICO_SOCKET_OPT_TCPNODELAY); } } else { pico_err = PICO_ERR_EINVAL; } break; #endif #ifdef PICO_SUPPORT_MCAST case PICO_IP_MULTICAST_IF: pico_err = PICO_ERR_EOPNOTSUPP; return -1; break; case PICO_IP_MULTICAST_TTL: if (s->proto->proto_number == PICO_PROTO_UDP) { return pico_udp_set_mc_ttl(s, *((uint8_t *) value)); } break; case PICO_IP_MULTICAST_LOOP: if (s->proto->proto_number == PICO_PROTO_UDP) { switch (*(uint8_t *) value) { case 0: /* do not loop back multicast datagram */ PICO_SOCKET_SETOPT_DIS(s,PICO_SOCKET_OPT_MULTICAST_LOOP); break; case 1: /* do loop back multicast datagram */ PICO_SOCKET_SETOPT_EN(s,PICO_SOCKET_OPT_MULTICAST_LOOP); break; default: pico_err = PICO_ERR_EINVAL; return -1; } } break; case PICO_IP_ADD_MEMBERSHIP: /* EXCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0; struct pico_ip_mreq *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ipv4_link *mcast_link = NULL; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq *) value; mcast_link = pico_socket_setoption_mcastargs_validation(mreq, NULL); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ s->MCASTListen = pico_zalloc(sizeof(struct pico_tree)); if (!s->MCASTListen) { pico_err = PICO_ERR_ENOMEM; return -1; } s->MCASTListen->root = &LEAF; s->MCASTListen->compare = mcast_listen_cmp; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (listen) { if (listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); pico_err = PICO_ERR_EINVAL; return -1; } else { so_mcast_dbg("pico_socket_setoption: ERROR duplicate PICO_IP_ADD_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } } else { listen = pico_zalloc(sizeof(struct pico_mcast_listen)); if (!listen) { pico_err = PICO_ERR_ENOMEM; return -1; } listen->filter_mode = PICO_IP_MULTICAST_EXCLUDE; listen->mcast_link = mreq->mcast_link_addr; listen->mcast_group = mreq->mcast_group_addr; listen->MCASTSources.root = &LEAF; listen->MCASTSources.compare = mcast_sources_cmp; pico_tree_insert(s->MCASTListen, listen); } pico_tree_insert(&MCASTSockets, s); filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_join(&mreq->mcast_link_addr, &mreq->mcast_group_addr, 1, filter_mode, &MCASTFilter); } break; case PICO_IP_DROP_MEMBERSHIP: /* EXCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0; struct pico_ip_mreq *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL; struct pico_ipv4_link *mcast_link = NULL; struct pico_tree_node *index, *_tmp; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq *) value; mcast_link = pico_socket_setoption_mcastargs_validation(mreq, NULL); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ so_mcast_dbg("socket_setoption: ERROR PICO_IP_DROP_MEMBERSHIP before any PICO_IP_ADD_MEMBERSHIP/SOURCE_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (!listen) { so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_DROP_MEMBERSHIP before PICO_IP_ADD_MEMBERSHIP/SOURCE_MEMBERSHIP\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { pico_tree_foreach_safe(index, &listen->MCASTSources, _tmp) { source = index->keyValue; pico_tree_delete(&listen->MCASTSources, source); pico_free(source); } pico_tree_delete(s->MCASTListen, listen); pico_free(listen); if (pico_tree_empty(s->MCASTListen)) { pico_free(s->MCASTListen); s->MCASTListen = NULL; pico_tree_delete(&MCASTSockets, s); } } filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_leave(&mreq->mcast_link_addr, &mreq->mcast_group_addr, 1, filter_mode, &MCASTFilter); } break; case PICO_IP_UNBLOCK_SOURCE: /* EXCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0; struct pico_ip_mreq_source *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL, stest = {0}; struct pico_ipv4_link *mcast_link = NULL; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq_source *) value; mcast_link = pico_socket_setoption_mcastargs_validation(NULL, mreq); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_UNBLOCK_SOURCE before any PICO_IP_ADD_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (!listen) { so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_UNBLOCK_SOURCE before PICO_IP_ADD_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } else { if (listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); pico_err = PICO_ERR_EINVAL; return -1; } stest.addr = mreq->mcast_source_addr.addr; source = pico_tree_findKey(&listen->MCASTSources, &stest); if (!source) { so_mcast_dbg("pico_socket_setoption: ERROR address to unblock not in source list\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { pico_tree_delete(&listen->MCASTSources, source); pico_free(source); } } filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_leave(&mreq->mcast_link_addr, &mreq->mcast_group_addr, 0, filter_mode, &MCASTFilter); } break; case PICO_IP_BLOCK_SOURCE: /* EXCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0; struct pico_ip_mreq_source *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL, stest = {0}; struct pico_ipv4_link *mcast_link = NULL; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq_source *) value; mcast_link = pico_socket_setoption_mcastargs_validation(NULL, mreq); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_BLOCK_SOURCE before any PICO_IP_ADD_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (!listen) { so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_BLOCK_SOURCE before PICO_IP_ADD_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } else { if (listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); pico_err = PICO_ERR_EINVAL; return -1; } stest.addr = mreq->mcast_source_addr.addr; source = pico_tree_findKey(&listen->MCASTSources, &stest); if (source) { so_mcast_dbg("pico_socket_setoption: ERROR address to block already in source list\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { source = pico_zalloc(sizeof(struct pico_ip4)); if (!source) { pico_err = PICO_ERR_ENOMEM; return -1; } source->addr = mreq->mcast_source_addr.addr; pico_tree_insert(&listen->MCASTSources, source); } } filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_join(&mreq->mcast_link_addr, &mreq->mcast_group_addr, 0, filter_mode, &MCASTFilter); } break; case PICO_IP_ADD_SOURCE_MEMBERSHIP: /* INCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0, reference_count = 0; struct pico_ip_mreq_source *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL, stest = {0}; struct pico_ipv4_link *mcast_link = NULL; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq_source *) value; mcast_link = pico_socket_setoption_mcastargs_validation(NULL, mreq); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ s->MCASTListen = pico_zalloc(sizeof(struct pico_tree)); if (!s->MCASTListen) { pico_err = PICO_ERR_ENOMEM; return -1; } s->MCASTListen->root = &LEAF; s->MCASTListen->compare = mcast_listen_cmp; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (listen) { if (listen->filter_mode != PICO_IP_MULTICAST_INCLUDE) { so_mcast_dbg("pico_socket_setoption: ERROR source-specific multicast (include) on any-source multicast (exclude)\n"); pico_err = PICO_ERR_EINVAL; return -1; } stest.addr = mreq->mcast_source_addr.addr; source = pico_tree_findKey(&listen->MCASTSources, &stest); if (source) { so_mcast_dbg("pico_socket_setoption: ERROR source address to allow already in source list\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { source = pico_zalloc(sizeof(struct pico_ip4)); if (!source) { pico_err = PICO_ERR_ENOMEM; return -1; } source->addr = mreq->mcast_source_addr.addr; pico_tree_insert(&listen->MCASTSources, source); } } else { listen = pico_zalloc(sizeof(struct pico_mcast_listen)); if (!listen) { pico_err = PICO_ERR_ENOMEM; return -1; } listen->filter_mode = PICO_IP_MULTICAST_INCLUDE; listen->mcast_link = mreq->mcast_link_addr; listen->mcast_group = mreq->mcast_group_addr; listen->MCASTSources.root = &LEAF; listen->MCASTSources.compare = mcast_sources_cmp; source = pico_zalloc(sizeof(struct pico_ip4)); if (!source) { pico_free(listen); pico_err = PICO_ERR_ENOMEM; return -1; } source->addr = mreq->mcast_source_addr.addr; pico_tree_insert(&listen->MCASTSources, source); pico_tree_insert(s->MCASTListen, listen); reference_count = 1; } pico_tree_insert(&MCASTSockets, s); filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_join(&mreq->mcast_link_addr, &mreq->mcast_group_addr, reference_count, filter_mode, &MCASTFilter); } break; case PICO_IP_DROP_SOURCE_MEMBERSHIP: /* INCLUDE mode */ if (s->proto->proto_number == PICO_PROTO_UDP) { uint8_t filter_mode = 0, reference_count = 0; struct pico_ip_mreq_source *mreq = NULL; struct pico_mcast_listen *listen = NULL, ltest = {0}; struct pico_ip4 *source = NULL, stest = {0}; struct pico_ipv4_link *mcast_link = NULL; if (!value) { pico_err = PICO_ERR_EINVAL; return -1; } mreq = (struct pico_ip_mreq_source *) value; mcast_link = pico_socket_setoption_mcastargs_validation(NULL, mreq); if (!mcast_link) { pico_err = PICO_ERR_EINVAL; return -1; } if (!mreq->mcast_link_addr.addr) mreq->mcast_link_addr.addr = mcast_link->address.addr; if (!s->MCASTListen) { /* No RBTree allocated yet */ so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_DROP_SOURCE_MEMBERSHIP before any PICO_IP_ADD_SOURCE_MEMBERSHIP\n"); pico_err = PICO_ERR_EINVAL; return -1; } ltest.mcast_link = mreq->mcast_link_addr; ltest.mcast_group = mreq->mcast_group_addr; listen = pico_tree_findKey(s->MCASTListen, <est); if (!listen) { so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_DROP_SOURCE_MEMBERSHIP before PICO_IP_ADD_SOURCE_MEMBERSHIP\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { if (listen->filter_mode != PICO_IP_MULTICAST_INCLUDE) { so_mcast_dbg("pico_socket_setoption: ERROR source-specific multicast (include) on any-source multicast (exclude)\n"); pico_err = PICO_ERR_EINVAL; return -1; } stest.addr = mreq->mcast_source_addr.addr; source = pico_tree_findKey(&listen->MCASTSources, &stest); if (!source) { so_mcast_dbg("pico_socket_setoption: ERROR address to drop not in source list\n"); pico_err = PICO_ERR_EADDRNOTAVAIL; return -1; } else { pico_tree_delete(&listen->MCASTSources, source); pico_free(source); if (pico_tree_empty(&listen->MCASTSources)) { /* 1 if empty, 0 otherwise */ reference_count = 1; pico_tree_delete(s->MCASTListen, listen); pico_free(listen); if (pico_tree_empty(s->MCASTListen)) { pico_free(s->MCASTListen); s->MCASTListen = NULL; pico_tree_delete(&MCASTSockets, s); } } } } filter_mode = pico_socket_aggregate_mcastfilters(&mcast_link->address, &mreq->mcast_group_addr); if (filter_mode < 0) return -1; return pico_ipv4_mcast_leave(&mreq->mcast_link_addr, &mreq->mcast_group_addr, reference_count, filter_mode, &MCASTFilter); } break; #endif /* PICO_SUPPORT_MCAST */ default: pico_err = PICO_ERR_EINVAL; return -1; } if (pico_err != PICO_ERR_NOERR) return -1; else return 0; } #define PICO_SOCKET_GETOPT(socket,index) ((socket->opt_flags & (1 << index)) != 0) int pico_socket_getoption(struct pico_socket *s, int option, void *value) { if (!s || !value) { pico_err = PICO_ERR_EINVAL; return -1; } switch (option) { #ifdef PICO_SUPPORT_TCP case PICO_TCP_NODELAY: if (s->proto->proto_number == PICO_PROTO_TCP) /* state of the NODELAY option */ *(int *)value = PICO_SOCKET_GETOPT(s,PICO_SOCKET_OPT_TCPNODELAY); else *(int *)value = 0; break; #endif #ifdef PICO_SUPPORT_MCAST case PICO_IP_MULTICAST_IF: pico_err = PICO_ERR_EOPNOTSUPP; return -1; break; case PICO_IP_MULTICAST_TTL: if (s->proto->proto_number == PICO_PROTO_UDP) { pico_udp_get_mc_ttl(s, (uint8_t *) value); } else { *(uint8_t *)value = 0; pico_err = PICO_ERR_EINVAL; return -1; } break; case PICO_IP_MULTICAST_LOOP: if (s->proto->proto_number == PICO_PROTO_UDP) { *(uint8_t *)value = PICO_SOCKET_GETOPT(s,PICO_SOCKET_OPT_MULTICAST_LOOP); } else { *(uint8_t *)value = 0; pico_err = PICO_ERR_EINVAL; return -1; } break; #endif /* PICO_SUPPORT_MCAST */ default: pico_err = PICO_ERR_EINVAL; return -1; } return 0; } int pico_socket_shutdown(struct pico_socket *s, int mode) { if (!s) { pico_err = PICO_ERR_EINVAL; return -1; } else { /* check if exists in tree */ /* See task #178 */ if (pico_check_socket(s) != 0) { pico_free(s); /* close socket after bind or connect failed */ return 0; } } #ifdef PICO_SUPPORT_UDP if (PROTO(s) == PICO_PROTO_UDP) { if (mode & PICO_SHUT_RDWR) pico_socket_alter_state(s, PICO_SOCKET_STATE_CLOSED, PICO_SOCKET_STATE_CLOSING |PICO_SOCKET_STATE_BOUND | PICO_SOCKET_STATE_CONNECTED, 0); else if (mode & PICO_SHUT_RD) pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); } #endif #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP) { if(mode & PICO_SHUT_RDWR) pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_LOCAL | PICO_SOCKET_STATE_SHUT_REMOTE, 0, 0); else if (mode & PICO_SHUT_WR) pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_LOCAL, 0, 0); else if (mode & PICO_SHUT_RD) pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_REMOTE, 0, 0); } #endif return 0; } int pico_socket_close(struct pico_socket *s) { return pico_socket_shutdown(s, PICO_SHUT_RDWR); } #ifdef PICO_SUPPORT_CRC static inline int pico_transport_crc_check(struct pico_frame *f) { struct pico_ipv4_hdr *net_hdr = (struct pico_ipv4_hdr *) f->net_hdr; struct pico_udp_hdr *udp_hdr = NULL; uint16_t checksum_invalid = 1; switch (net_hdr->proto) { case PICO_PROTO_TCP: checksum_invalid = short_be(pico_tcp_checksum_ipv4(f)); //dbg("TCP CRC validation == %u\n", checksum_invalid); if (checksum_invalid) { //dbg("TCP CRC: validation failed!\n"); pico_frame_discard(f); return 0; } break; case PICO_PROTO_UDP: udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; if (short_be(udp_hdr->crc)) { checksum_invalid = short_be(pico_udp_checksum_ipv4(f)); //dbg("UDP CRC validation == %u\n", checksum_invalid); if (checksum_invalid) { //dbg("UDP CRC: validation failed!\n"); pico_frame_discard(f); return 0; } } break; default: // Do nothing break; } return 1; } #else static inline int pico_transport_crc_check(struct pico_frame *f) { return 1; } #endif /* PICO_SUPPORT_CRC */ int pico_transport_process_in(struct pico_protocol *self, struct pico_frame *f) { struct pico_trans *hdr = (struct pico_trans *) f->transport_hdr; int ret = 0; if (!hdr) { pico_err = PICO_ERR_EFAULT; return -1; } ret = pico_transport_crc_check(f); if (ret < 1) return ret; else ret = 0; if ((hdr) && (pico_socket_deliver(self, f, hdr->dport) == 0)) return ret; if (!IS_BCAST(f)) { dbg("Socket not found... \n"); pico_notify_socket_unreachable(f); #ifdef PICO_SUPPORT_TCP /* if tcp protocol send RST segment */ //if (self->proto_number == PICO_PROTO_TCP) // pico_tcp_reply_rst(f); #endif ret = -1; pico_err = PICO_ERR_ENOENT; } pico_frame_discard(f); return ret; } #define SL_LOOP_MIN 1 int pico_sockets_loop(int loop_score) { static struct pico_tree_node *index_udp, * index_tcp; struct pico_sockport *start; struct pico_socket *s; #ifdef PICO_SUPPORT_UDP struct pico_frame *f; if (sp_udp == NULL) { index_udp = pico_tree_firstNode(UDPTable.root); sp_udp = index_udp->keyValue; } /* init start node */ start = sp_udp; /* round-robin all transport protocols, break if traversed all protocols */ while (loop_score > SL_LOOP_MIN && sp_udp != NULL) { struct pico_tree_node * index; pico_tree_foreach(index,&sp_udp->socks){ s = index->keyValue; f = pico_dequeue(&s->q_out); while (f && (loop_score > 0)) { pico_proto_udp.push(&pico_proto_udp, f); loop_score -= 1; f = pico_dequeue(&s->q_out); } } index_udp = pico_tree_next(index_udp); sp_udp = index_udp->keyValue; if (sp_udp == NULL) { index_udp = pico_tree_firstNode(UDPTable.root); sp_udp = index_udp->keyValue; } if (sp_udp == start) break; } #endif #ifdef PICO_SUPPORT_TCP if (sp_tcp == NULL) { index_tcp = pico_tree_firstNode(TCPTable.root); sp_tcp = index_tcp->keyValue; } /* init start node */ start = sp_tcp; while (loop_score > SL_LOOP_MIN && sp_tcp != NULL) { struct pico_tree_node * index; pico_tree_foreach(index, &sp_tcp->socks){ s = index->keyValue; loop_score = pico_tcp_output(s, loop_score); if ((s->ev_pending) && s->wakeup) { s->wakeup(s->ev_pending, s); } if (loop_score <= 0) { loop_score = 0; break; } } /* check if RB_FOREACH ended, if not, break to keep the cur sp_tcp */ if (s != NULL) break; index_tcp = pico_tree_next(index_tcp); sp_tcp = index_tcp->keyValue; if (sp_tcp == NULL) { index_tcp = pico_tree_firstNode(TCPTable.root); sp_tcp = index_tcp->keyValue; } if (sp_tcp == start) break; } #endif return loop_score; } struct pico_frame *pico_socket_frame_alloc(struct pico_socket *s, int len) { struct pico_frame *f = NULL; #ifdef PICO_SUPPORT_IPV6 if (IS_SOCK_IPV6(s)) f = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); #endif #ifdef PICO_SUPPORT_IPV4 if (IS_SOCK_IPV4(s)) f = pico_proto_ipv4.alloc(&pico_proto_ipv4, len); #endif if (!f) { pico_err = PICO_ERR_ENOMEM; return f; } f->payload = f->transport_hdr; f->payload_len = len; f->sock = s; return f; } int pico_transport_error(struct pico_frame *f, uint8_t proto, int code) { int ret = -1; struct pico_trans *trans = (struct pico_trans*) f->transport_hdr; struct pico_sockport *port = NULL; struct pico_socket *s = NULL; switch (proto) { #ifdef PICO_SUPPORT_UDP case PICO_PROTO_UDP: port = pico_get_sockport(proto, trans->sport); break; #endif #ifdef PICO_SUPPORT_TCP case PICO_PROTO_TCP: port = pico_get_sockport(proto, trans->sport); break; #endif default: /* Protocol not available */ ret = -1; } if (port) { struct pico_tree_node * index; ret = 0; pico_tree_foreach(index,&port->socks) { s = index->keyValue; if (trans->dport == s->remote_port) { if (s->wakeup) { //dbg("SOCKET ERROR FROM ICMP NOTIFICATION. (icmp code= %d)\n\n", code); switch(code) { case PICO_ICMP_UNREACH_PROTOCOL: pico_err = PICO_ERR_EPROTO; break; case PICO_ICMP_UNREACH_PORT: pico_err = PICO_ERR_ECONNREFUSED; break; case PICO_ICMP_UNREACH_NET: case PICO_ICMP_UNREACH_NET_PROHIB: case PICO_ICMP_UNREACH_NET_UNKNOWN: pico_err = PICO_ERR_ENETUNREACH; break; default: pico_err = PICO_ERR_EHOSTUNREACH; } s->wakeup(PICO_SOCK_EV_ERR, s); } break; } } } pico_frame_discard(f); return ret; } #endif #endif