Free (GPLv2) TCP/IP stack developed by TASS Belgium
Fork of PicoTCP by
Revision 51:18637a3d071f, committed 2013-08-03
- Comitter:
- daniele
- Date:
- Sat Aug 03 08:50:27 2013 +0000
- Parent:
- 50:c3b337c38feb
- Commit message:
- Branch for CDC-ECM: Work in progress
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dev_loop.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,68 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Daniele Lacamera +*********************************************************************/ + + +#include "pico_device.h" +#include "pico_dev_loop.h" +#include "pico_stack.h" + + +#define LOOP_MTU 1500 +static uint8_t l_buf[LOOP_MTU]; +static int l_bufsize = 0; + + +static int pico_loop_send(struct pico_device *dev, void *buf, int len) +{ + if (len > LOOP_MTU) + return 0; + + if (l_bufsize == 0) { + memcpy(l_buf, buf, len); + l_bufsize+=len; + return len; + } + return 0; +} + +static int pico_loop_poll(struct pico_device *dev, int loop_score) +{ + if (loop_score <= 0) + return 0; + + if (l_bufsize > 0) { + pico_stack_recv(dev, l_buf, l_bufsize); + l_bufsize = 0; + loop_score--; + } + return loop_score; +} + +/* Public interface: create/destroy. */ + +void pico_loop_destroy(struct pico_device *dev) +{ +} + +struct pico_device *pico_loop_create(void) +{ + struct pico_device *loop = pico_zalloc(sizeof(struct pico_device)); + if (!loop) + return NULL; + + if( 0 != pico_device_init((struct pico_device *)loop, "loop", NULL)) { + dbg ("Loop init failed.\n"); + pico_loop_destroy((struct pico_device *)loop); + return NULL; + } + loop->send = pico_loop_send; + loop->poll = pico_loop_poll; + loop->destroy = pico_loop_destroy; + dbg("Device %s created.\n", loop->name); + return (struct pico_device *)loop; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dev_loop.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,15 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_LOOP +#define _INCLUDE_PICO_LOOP +#include "pico_config.h" +#include "pico_device.h" + +void pico_loop_destroy(struct pico_device *loop); +struct pico_device *pico_loop_create(void); + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dev_mbed.cpp Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,118 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. +Do not redistribute without a written permission by the Copyright +holders. + +Authors: Toon Peters, Maxime Vincent +*********************************************************************/ +#include "mbed.h" +extern "C" { +#include "pico_device.h" +#include "pico_dev_mbed.h" +#include "pico_stack.h" +#include "ethernet_api.h" +} + +struct pico_device_mbed { + struct pico_device dev; + int bytes_left_in_frame; +}; + +#define ETH_MTU 1514 +uint8_t buf[ETH_MTU]; + +Serial pc(p9, p10, "Serial port"); // tx, rx + +extern "C" { + +static int pico_mbed_send(struct pico_device *dev, void *buf, int len) +{ + int ret, sent; + struct pico_device_mbed *mb = (struct pico_device_mbed *) dev; + + if (len > ETH_MTU) + return -1; + + /* Write buf content to dev and return amount written */ + ret = ethernet_write((const char *)buf, len); + sent = ethernet_send(); + + pc.printf("ETH> sent %d bytes\r\n",ret); + if (len != ret || sent != ret) + return -1; + else + return ret; +} + +static int pico_mbed_poll(struct pico_device *dev, int loop_score) +{ + int len; + struct pico_device_mbed *mb = (struct pico_device_mbed *) dev; + + while(loop_score > 0) + { + /* check for new frame(s) */ + len = (int) ethernet_receive(); + + /* return if no frame has arrived */ + if (!len) + return loop_score; + + /* read and process frame */ + len = ethernet_read((char*)buf, ETH_MTU); + pc.printf("ETH> recv %d bytes: %x:%x\r\n", len, buf[0],buf[1]); + pico_stack_recv(dev, buf, len); + loop_score--; + } + return loop_score; +} + +/* Public interface: create/destroy. */ +void pico_mbed_destroy(struct pico_device *dev) +{ + ethernet_free(); + pico_device_destroy(dev); +} + +struct pico_device *pico_mbed_create(char *name) +{ + std::uint8_t mac[PICO_SIZE_ETH]; + struct pico_device_mbed *mb = (struct pico_device_mbed*) pico_zalloc(sizeof(struct pico_device_mbed)); + + if (!mb) + return NULL; + + ethernet_address((char *)mac); + pc.printf("ETH> Set MAC address to: %x:%x:%x:%x:%x:%x\r\n", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); + + if(0 != pico_device_init((struct pico_device *)mb, name, mac)) { + pc.printf ("ETH> Loop init failed.\n"); + //pico_loop_destroy(mb); + return NULL; + } + + mb->dev.send = pico_mbed_send; + mb->dev.poll = pico_mbed_poll; + mb->dev.destroy = pico_mbed_destroy; + mb->bytes_left_in_frame = 0; + + if(0 != ethernet_init()) { + pc.printf("ETH> Failed to initialize hardware.\r\n"); + pico_device_destroy((struct pico_device *)mb); + return NULL; + } + + // future work: make the mac address configurable + + pc.printf("ETH> Device %s created.\r\n", mb->dev.name); + + return (struct pico_device *)mb; +} + +void pico_mbed_get_address(char *mac) +{ + ethernet_address(mac); +} + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dev_mbed.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,21 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. +Do not redistribute without a written permission by the Copyright +holders. + +File: pico_dev_mbed.h +Author: Toon Peters +*********************************************************************/ + +#ifndef PICO_DEV_MBED_H +#define PICO_DEV_MBED_H + +#include "pico_config.h" +#include "pico_device.h" + +void pico_mbed_destroy(struct pico_device *vde); +struct pico_device *pico_mbed_create(char *name); + +#endif /* PICO_DEV_MBED_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_client.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,731 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Frederik Van Slycken, Kristof Roelants +*********************************************************************/ + +#include "pico_dhcp_client.h" +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_device.h" +#include "pico_ipv4.h" +#include "pico_socket.h" + +#ifdef PICO_SUPPORT_DHCPC + +/*********** + * structs * + ***********/ + +static uint8_t dhcp_client_mutex = 1; /* to serialize client negotations if multiple devices */ + +struct dhcp_timer_param{ + uint16_t type; + struct pico_dhcp_client_cookie* cli; + int valid; +}; + +struct pico_dhcp_client_cookie +{ + uint32_t xid; + uint32_t *xid_user; + struct pico_ip4 address; + struct pico_ip4 netmask; + struct pico_ip4 gateway; + struct pico_ip4 nameserver; + struct pico_ip4 server_id; + uint32_t lease_time; + uint32_t T1; + uint32_t T2; + struct pico_socket* socket; + int connected; + struct pico_device* device; + unsigned long start_time; + int attempt; + enum dhcp_negotiation_state state; + void (*cb)(void* cli, int code); + struct dhcp_timer_param* timer_param_1; + struct dhcp_timer_param* timer_param_2; + struct dhcp_timer_param* timer_param_lease; + struct dhcp_timer_param* timer_param_retransmit; + int link_added; +}; + +static int dhcp_cookies_cmp(void *ka, void *kb) +{ + struct pico_dhcp_client_cookie *a = ka, *b = kb; + if (a->xid < b->xid) + return -1; + else if (a->xid > b->xid) + return 1; + else + return 0; +} +PICO_TREE_DECLARE(DHCPCookies, dhcp_cookies_cmp); + +/************************* + * function declarations * + *************************/ +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len); +static void pico_dhcp_reinitiate_negotiation(unsigned long now, void *arg); + +//cb +static void pico_dhcp_wakeup(uint16_t ev, struct pico_socket *s); +static void dhcp_timer_cb(unsigned long tick, void* param); + +//util +static void pico_dhcp_retry(struct pico_dhcp_client_cookie *client); +static int dhclient_send(struct pico_dhcp_client_cookie *cli, uint8_t msg_type); +static int pico_dhcp_verify_and_identify_type(uint8_t* data, int len, struct pico_dhcp_client_cookie *cli); +static int init_cookie(struct pico_dhcp_client_cookie* cli); +static struct pico_dhcp_client_cookie* get_cookie_by_xid(uint32_t xid); +static uint32_t get_xid(uint8_t* data); + +//fsm functions +static int recv_offer(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int recv_ack(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int renew(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int reset(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int retransmit(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + +//fsm implementation +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len); + +/*************** + * entry point * + ***************/ + +static uint32_t pico_dhcp_execute_init(struct pico_dhcp_client_cookie *cli) +{ + if (!dhcp_client_mutex) { + pico_timer_add(3000, pico_dhcp_reinitiate_negotiation, cli); + return 0; + } + dhcp_client_mutex--; + + if (init_cookie(cli) < 0) + return -1; + + dbg("DHCPC: cookie with xid %u\n", cli->xid); + + if (pico_tree_insert(&DHCPCookies, cli)) { + pico_err = PICO_ERR_EAGAIN; + if(cli->cb != NULL) { + cli->cb(cli, PICO_DHCP_ERROR); + } + pico_free(cli); + return -1; /* Element key already exists */ + } + + if (dhclient_send(cli, PICO_DHCP_MSG_DISCOVER) < 0) + return -1; + + return 0; +} + +/* returns a pointer to the client cookie. The user should pass this pointer every time he calls a dhcp-function. This is so that we can (one day) support dhcp on multiple interfaces */ +int pico_dhcp_initiate_negotiation(struct pico_device *device, void (*callback)(void *cli, int code), uint32_t *xid) +{ + struct pico_dhcp_client_cookie *cli; + + if(!device || !callback || !xid){ + pico_err = PICO_ERR_EINVAL; + return -1; + } + cli = pico_zalloc(sizeof(struct pico_dhcp_client_cookie)); + if(!cli){ + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + cli->device = device; + cli->cb = callback; + cli->xid_user = xid; + *(cli->xid_user) = 0; + + return pico_dhcp_execute_init(cli); +} + +static void pico_dhcp_reinitiate_negotiation(unsigned long now, void *arg) +{ + struct pico_dhcp_client_cookie *cli = (struct pico_dhcp_client_cookie *) arg; + + pico_dhcp_execute_init(cli); + + return; +} + +/******************** + * access functions * + ********************/ + +struct pico_ip4 pico_dhcp_get_address(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->address; +} + +struct pico_ip4 pico_dhcp_get_gateway(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->gateway; +} + +struct pico_ip4 pico_dhcp_get_nameserver(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->nameserver; +} + +/************* + * callbacks * + *************/ + +static void pico_dhcp_wakeup(uint16_t ev, struct pico_socket *s) +{ + uint8_t buf[DHCPC_DATAGRAM_SIZE]; + int r=0; + uint32_t peer; + uint16_t port; + int type; + + struct pico_dhcp_client_cookie *cli; + dbg("DHCPC: called dhcp_wakeup\n"); + if (ev == PICO_SOCK_EV_RD) { + do { + r = pico_socket_recvfrom(s, buf, DHCPC_DATAGRAM_SIZE, &peer, &port); + cli = get_cookie_by_xid(get_xid(buf)); + if(cli == NULL) + return; + if (r > 0 && port == PICO_DHCPD_PORT) { + type = pico_dhcp_verify_and_identify_type(buf, r, cli); + pico_dhcp_state_machine(type, cli, buf, r); + } + } while(r>0); + } +} + +static void dhcp_timer_cb(unsigned long tick, void* param) +{ + struct dhcp_timer_param* param2 = (struct dhcp_timer_param*) param; + if(param2->valid == 1){ + //dbg("called timer cb on active timer type %d\n",param2->type); + pico_dhcp_state_machine(param2->type, param2->cli, NULL, 0); + } + if(param2->cli->timer_param_1 == param){ + param2->cli->timer_param_1 = NULL; + } + if(param2->cli->timer_param_2 == param){ + param2->cli->timer_param_2 = NULL; + } + if(param2->cli->timer_param_lease == param){ + param2->cli->timer_param_lease = NULL; + } + if(param2->cli->timer_param_retransmit == param){ + param2->cli->timer_param_retransmit = NULL; + } + + pico_free(param); + +} +/***************** + * fsm functions * + *****************/ + +static int recv_offer(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + uint8_t msg_type = 0xFF; + int T1_set = 0; + int T2_set = 0; + + cli->address.addr = dhdr->yiaddr; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != PICO_DHCPOPT_END) { + if (opt_type == PICO_DHCPOPT_MSGTYPE) + msg_type = opt_data[0]; + if ((opt_type == PICO_DHCPOPT_LEASETIME) && (opt_len == 4)){ + memcpy(&cli->lease_time, opt_data, 4); + cli->lease_time = long_be(cli->lease_time); + } + if ((opt_type == PICO_DHCPOPT_RENEWALTIME) && (opt_len == 4)){ + memcpy(&cli->T1, opt_data, 4); + cli->T1 = long_be(cli->T1); + T1_set =1; + } + if ((opt_type == PICO_DHCPOPT_REBINDINGTIME) && (opt_len == 4)){ + memcpy(&cli->T2, opt_data, 4); + cli->T2 = long_be(cli->T2); + T2_set =1; + } + if ((opt_type == PICO_DHCPOPT_ROUTER) && (opt_len == 4)) //XXX assuming only one router will be advertised... + memcpy(&cli->gateway.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_DNS) && (opt_len == 4)) + memcpy(&cli->nameserver.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_NETMASK) && (opt_len == 4)) + memcpy(&cli->netmask.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_SERVERID) && (opt_len == 4)) + memcpy(&cli->server_id.addr, opt_data, 4); + if (opt_type == PICO_DHCPOPT_OPTIONOVERLOAD) + dbg("DHCPC: WARNING: option overload present (not processed)"); + + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + + /* default values for T1 and T2 if necessary */ + if(T1_set != 1) + cli->T1 = cli->lease_time >> 1; + if(T2_set != 1) + cli->T2 = (cli->lease_time * 875) / 1000; + + + + if ((msg_type != PICO_DHCP_MSG_OFFER) || !cli->lease_time || !cli->netmask.addr || !cli->server_id.addr ) + return 0; + + + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + cli->state = DHCPSTATE_REQUEST; + return 1; +} + +static int recv_ack(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct pico_ip4 address = {0}; + + if(cli->link_added == 0){ + /* close the socket bound on address 0.0.0.0 */ + pico_socket_close(cli->socket); + cli->socket = NULL; + pico_ipv4_link_del(cli->device, address); + pico_ipv4_link_add(cli->device, cli->address, cli->netmask); + cli->link_added = 1; + } + cli->state = DHCPSTATE_BOUND; + + dbg("DHCPC: T1: %d\n",cli->T1); + dbg("DHCPC: T2: %d\n",cli->T2); + dbg("DHCPC: lease time: %d\n",cli->lease_time); + + if(cli->timer_param_1) + cli->timer_param_1->valid = 0; + if(cli->timer_param_2) + cli->timer_param_2->valid = 0; + if(cli->timer_param_lease) + cli->timer_param_lease->valid = 0; + if(cli->timer_param_retransmit) + cli->timer_param_retransmit->valid = 0; + + + cli->timer_param_1 = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_1){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_2 = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_2){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_lease = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_lease){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_1->valid = 1; + cli->timer_param_2->valid = 1; + cli->timer_param_lease->valid = 1; + + cli->timer_param_1->cli = cli; + cli->timer_param_2->cli = cli; + cli->timer_param_lease->cli = cli; + + cli->timer_param_1->type = PICO_DHCP_EVENT_T1; + cli->timer_param_2->type = PICO_DHCP_EVENT_T2; + cli->timer_param_lease->type = PICO_DHCP_EVENT_LEASE; + //add timer + pico_timer_add(cli->T1*1000, dhcp_timer_cb, cli->timer_param_1); + pico_timer_add(cli->T2*1000, dhcp_timer_cb, cli->timer_param_2); + pico_timer_add(cli->lease_time*1000, dhcp_timer_cb, cli->timer_param_lease); + + *(cli->xid_user) = cli->xid; + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_SUCCESS); + else + dbg("DHCPC: no callback\n"); + + dhcp_client_mutex++; + cli->state = DHCPSTATE_BOUND; + return 0; +} + +static int renew(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + uint16_t port = PICO_DHCP_CLIENT_PORT; + + /* open and bind to currently acquired address */ + if (!cli->socket){ + cli->socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_wakeup); + if (!cli->socket) { + dbg("DHCPC: error opening socket on renew: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + if (pico_socket_bind(cli->socket, &cli->address, &port) != 0){ + dbg("DHCPC: error binding socket on renew: %s\n", strerror(pico_err)); + pico_socket_close(cli->socket); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + } + cli->state = DHCPSTATE_RENEWING; + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + + return 0; +} + +static int reset(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_RESET); + //reset pretty much everything + + if(cli->timer_param_1) + cli->timer_param_1->valid = 0; + if(cli->timer_param_2) + cli->timer_param_2->valid = 0; + if(cli->timer_param_lease) + cli->timer_param_lease->valid = 0; + if(cli->timer_param_retransmit) + cli->timer_param_retransmit->valid = 0; + + pico_socket_close(cli->socket); + pico_ipv4_link_del(cli->device, cli->address); + + //initiate negotiations again + init_cookie(cli); + pico_dhcp_retry(cli); + dhclient_send(cli, PICO_DHCP_MSG_DISCOVER); + + return 0; + +} + +static int retransmit(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + pico_dhcp_retry(cli); + + if(cli->state == DHCPSTATE_DISCOVER) + dhclient_send(cli, PICO_DHCP_MSG_DISCOVER); + else if(cli->state == DHCPSTATE_RENEWING) + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + else + dbg("DHCPC: WARNING: should not get here in state %d!\n", cli->state); + + return 0; + +} + +/********************** + * fsm implementation * + **********************/ + +struct dhcp_action_entry { + uint16_t tcpstate; + int (*offer)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*ack)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*nak)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer1)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer_lease)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer_retransmit)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +}; + +static struct dhcp_action_entry dhcp_fsm[] = { + /* State offer ack nak timer1 timer_lease timer_retransmit*/ + { DHCPSTATE_DISCOVER, recv_offer, NULL, NULL, NULL, reset, retransmit}, + { DHCPSTATE_OFFER, NULL, NULL, NULL, NULL, reset, NULL}, + { DHCPSTATE_REQUEST, NULL, recv_ack, reset, NULL, reset, retransmit}, + { DHCPSTATE_BOUND, NULL, NULL, reset, renew, reset, NULL}, + { DHCPSTATE_RENEWING, NULL, recv_ack, reset, NULL, reset, retransmit}, +}; + + +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len) +{ + dbg("DHCPC: received incoming event of type %d\n", type); + switch(type){ + case PICO_DHCP_MSG_OFFER: + if(dhcp_fsm[cli->state].offer != NULL) + dhcp_fsm[cli->state].offer(cli, data, len); + break; + case PICO_DHCP_MSG_ACK: + if(dhcp_fsm[cli->state].ack != NULL){ + dhcp_fsm[cli->state].ack(cli, data, len); + } + break; + case PICO_DHCP_MSG_NAK: + if(dhcp_fsm[cli->state].nak!= NULL){ + dhcp_fsm[cli->state].nak(cli, data, len); + } + break; + case PICO_DHCP_EVENT_T1: + if(dhcp_fsm[cli->state].timer1!= NULL){ + dhcp_fsm[cli->state].timer1(cli, NULL, 0); + } + break; + case PICO_DHCP_EVENT_LEASE: + if(dhcp_fsm[cli->state].timer_lease!= NULL){ + dhcp_fsm[cli->state].timer_lease(cli, NULL, 0); + } + break; + case PICO_DHCP_EVENT_RETRANSMIT: + if(dhcp_fsm[cli->state].timer_retransmit!= NULL){ + dhcp_fsm[cli->state].timer_retransmit(cli, NULL, 0); + } + break; + default: + dbg("DHCPC: event not supported yet!!\n"); + break; + } +} + + +/********************* + * utility functions * + *********************/ + +static void pico_dhcp_retry(struct pico_dhcp_client_cookie *cli) +{ + const int MAX_RETRY = 3; + uint32_t new_xid; + if (++cli->attempt > MAX_RETRY) { + cli->start_time = pico_tick; + cli->attempt = 0; + new_xid = pico_rand(); + while(get_cookie_by_xid(new_xid) != NULL){ + new_xid = pico_rand(); + } + cli->xid = new_xid; + cli->state = DHCPSTATE_DISCOVER; + } +} + +static int dhclient_send(struct pico_dhcp_client_cookie *cli, uint8_t msg_type) +{ + uint8_t buf_out[DHCPC_DATAGRAM_SIZE] = {0}; + struct pico_dhcphdr *dh_out = (struct pico_dhcphdr *) buf_out; + int sent = 0; + int i = 0; + struct pico_ip4 destination; + uint16_t port = PICO_DHCPD_PORT; + if(cli->state == DHCPSTATE_BOUND || cli->state == DHCPSTATE_RENEWING){ + destination.addr = cli->server_id.addr; + }else{ + destination.addr = long_be(0xFFFFFFFF); + } + + if(cli->device->eth == NULL){ + pico_err = PICO_ERR_EOPNOTSUPP; + if(cli->cb != NULL){ + cli->cb(cli, PICO_DHCP_ERROR); + } + return -1; + } + memcpy(dh_out->hwaddr, &cli->device->eth->mac, PICO_HLEN_ETHER); + dh_out->op = PICO_DHCP_OP_REQUEST; + dh_out->htype = PICO_HTYPE_ETHER; + dh_out->hlen = PICO_HLEN_ETHER; + dh_out->xid = cli->xid; + dh_out->secs = (msg_type == PICO_DHCP_MSG_REQUEST)?0:short_be((pico_tick - cli->start_time)/1000); + dh_out->dhcp_magic = PICO_DHCPD_MAGIC_COOKIE; + if (cli->state == DHCPSTATE_RENEWING) + dh_out->ciaddr = cli->address.addr; + + /* Option: msg type, len 1 */ + dh_out->options[i++] = PICO_DHCPOPT_MSGTYPE; + dh_out->options[i++] = 1; + dh_out->options[i++] = msg_type; + + if (( msg_type == PICO_DHCP_MSG_REQUEST) && ( cli->state != DHCPSTATE_RENEWING )) { + dh_out->options[i++] = PICO_DHCPOPT_REQIP; + dh_out->options[i++] = 4; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF000000) >> 24; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF0000) >> 16; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF00) >> 8; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF); + dh_out->options[i++] = PICO_DHCPOPT_SERVERID; + dh_out->options[i++] = 4; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF000000) >> 24; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF0000) >> 16; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF00) >> 8; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF); + } + + /* Option: req list, len 4 */ + dh_out->options[i++] = PICO_DHCPOPT_PARMLIST; + dh_out->options[i++] = 7; + dh_out->options[i++] = PICO_DHCPOPT_NETMASK; + dh_out->options[i++] = PICO_DHCPOPT_BCAST; + dh_out->options[i++] = PICO_DHCPOPT_TIME; + dh_out->options[i++] = PICO_DHCPOPT_ROUTER; + dh_out->options[i++] = PICO_DHCPOPT_HOSTNAME; + dh_out->options[i++] = PICO_DHCPOPT_RENEWALTIME; + dh_out->options[i++] = PICO_DHCPOPT_REBINDINGTIME; + + /* Option : max message size */ + if( msg_type == PICO_DHCP_MSG_REQUEST || msg_type == PICO_DHCP_MSG_DISCOVER){ + uint16_t dds = DHCPC_DATAGRAM_SIZE; + dh_out->options[i++] = PICO_DHCPOPT_MAXMSGSIZE; + dh_out->options[i++] = 2; + dh_out->options[i++] = (dds & 0xFF00) >> 8; + dh_out->options[i++] = (dds & 0xFF); + } + + + + dh_out->options[i] = PICO_DHCPOPT_END; + sent = pico_socket_sendto(cli->socket, buf_out, DHCPC_DATAGRAM_SIZE, &destination, port); + if (sent < 0) { + dbg("DHCPC: sendto failed: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + } + + + //resend-timer : + if(cli->timer_param_retransmit != NULL) + cli->timer_param_retransmit->valid=0; + + cli->timer_param_retransmit = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_retransmit){ + if(cli->cb != NULL) + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + cli->timer_param_retransmit->valid = 1; + cli->timer_param_retransmit->cli = cli; + cli->timer_param_retransmit->type = PICO_DHCP_EVENT_RETRANSMIT; + pico_timer_add(4000, dhcp_timer_cb, cli->timer_param_retransmit); + + return 0; +} + +//identifies type & does some preprocessing : checking if everything is valid +static int pico_dhcp_verify_and_identify_type(uint8_t* data, int len, struct pico_dhcp_client_cookie *cli) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + + if (dhdr->xid != cli->xid) + return 0; + + if (!is_options_valid(dhdr->options, len - sizeof(struct pico_dhcphdr))) + return 0; + + if( dhdr->dhcp_magic != PICO_DHCPD_MAGIC_COOKIE) + return 0; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != PICO_DHCPOPT_END) { + /* parse interesting options here */ + if (opt_type == PICO_DHCPOPT_MSGTYPE) { + return *opt_data; + } + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + return 0; + +} + +static int init_cookie(struct pico_dhcp_client_cookie* cli) +{ + uint8_t n = 3; + uint16_t port = PICO_DHCP_CLIENT_PORT; + struct pico_ip4 address, netmask; + + address.addr = long_be(0x00000000); + netmask.addr = long_be(0x00000000); + + cli->state = DHCPSTATE_DISCOVER; + cli->start_time = pico_tick; + cli->attempt = 0; + + cli->socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_wakeup); + if (!cli->socket) { + dbg("DHCPC: error opening socket: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + if (pico_socket_bind(cli->socket, &address, &port) != 0){ + dbg("DHCPC: error binding socket: %s\n", strerror(pico_err)); + pico_socket_close(cli->socket); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + cli->socket->dev = cli->device; + + if(pico_ipv4_link_add(cli->device, address, netmask) != 0){ + dbg("DHCPC: error adding link: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + + /* attempt to generate a correct xid 3 times, then fail */ + do { + cli->xid = pico_rand(); + } while (!cli->xid && --n); + if (!cli->xid) { + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + + return 0; +} + +static struct pico_dhcp_client_cookie *get_cookie_by_xid(uint32_t xid) +{ + struct pico_dhcp_client_cookie test = { }, *cookie = NULL; + + if (!xid) + return NULL; + + test.xid = xid; + cookie = pico_tree_findKey(&DHCPCookies, &test); + if (!cookie) + return NULL; + else + return cookie; +} + +void *pico_dhcp_get_identifier(uint32_t xid) +{ + return (void *) get_cookie_by_xid(xid); +} + +static uint32_t get_xid(uint8_t* data) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + return dhdr->xid; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_client.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,36 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_DHCP_CLIENT +#define _INCLUDE_PICO_DHCP_CLIENT + + +#include "pico_dhcp_common.h" +#include "pico_addressing.h" +#include "pico_protocol.h" + + +int pico_dhcp_initiate_negotiation(struct pico_device *device, void (*callback)(void* cli, int code), uint32_t *xid); +void pico_dhcp_process_incoming_message(uint8_t *data, int len); +void *pico_dhcp_get_identifier(uint32_t xid); +struct pico_ip4 pico_dhcp_get_address(void *cli); +struct pico_ip4 pico_dhcp_get_gateway(void *cli); +struct pico_ip4 pico_dhcp_get_nameserver(void* cli); + +/* possible codes for the callback */ +#define PICO_DHCP_SUCCESS 0 +#define PICO_DHCP_ERROR 1 +#define PICO_DHCP_RESET 2 + +/* DHCP EVENT TYPE + * these come after the message types, used for the state machine*/ +#define PICO_DHCP_EVENT_T1 9 +#define PICO_DHCP_EVENT_T2 10 +#define PICO_DHCP_EVENT_LEASE 11 +#define PICO_DHCP_EVENT_RETRANSMIT 12 + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_common.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,67 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Frederik Van Slycken +*********************************************************************/ + +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_dhcp_common.h" + +#if defined (PICO_SUPPORT_DHCPC) || defined (PICO_SUPPORT_DHCPD) +//this function should only be used after you checked if the options are valid! otherwise it could read from bad memory! +uint8_t dhcp_get_next_option(uint8_t *begin, uint8_t *data, int *len, uint8_t **nextopt) +{ + uint8_t *p; + uint8_t type; + uint8_t opt_len; + + if (!begin) + p = *nextopt; + else + p = begin; + + type = *p; + *nextopt = ++p; + if ((type == PICO_DHCPOPT_END) || (type == PICO_DHCPOPT_PAD)) { + memset(data, 0, *len); + len = 0; + return type; + } + opt_len = *p; + p++; + if (*len > opt_len) + *len = opt_len; + memcpy(data, p, *len); + *nextopt = p + opt_len; + return type; +} + +int is_options_valid(uint8_t *opt_buffer, int len) +{ + uint8_t *p = opt_buffer; + while (len > 0) { + if (*p == PICO_DHCPOPT_END) + return 1; + else if (*p == PICO_DHCPOPT_PAD) { + p++; + len--; + } else { + uint8_t opt_len; + p++; + len--; + if(len > 0) { + opt_len = *p; + p += opt_len + 1; + len -= opt_len; + }else + return 0; + } + } + return 0; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_common.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,102 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_DHCP_COMMON +#define _INCLUDE_PICO_DHCP_COMMON + + +#include <stdint.h> + +//minimum size is 576, cfr RFC +#define DHCPC_DATAGRAM_SIZE 576 +#define DHCPD_DATAGRAM_SIZE 576 + + +#define PICO_DHCPD_PORT (short_be(67)) +#define PICO_DHCP_CLIENT_PORT (short_be(68)) + +#define PICO_DHCP_OP_REQUEST 1 +#define PICO_DHCP_OP_REPLY 2 + +#define PICO_HTYPE_ETHER 1 +#define PICO_HLEN_ETHER 6 + +#define PICO_DHCPD_MAGIC_COOKIE (long_be(0x63825363)) + +/* DHCP OPTIONS, RFC2132 */ +#define PICO_DHCPOPT_PAD 0x00 +#define PICO_DHCPOPT_NETMASK 0x01 +#define PICO_DHCPOPT_TIME 0x02 +#define PICO_DHCPOPT_ROUTER 0x03 +#define PICO_DHCPOPT_DNS 0x06 +#define PICO_DHCPOPT_HOSTNAME 0x0c +#define PICO_DHCPOPT_DOMAINNAME 0x0f +#define PICO_DHCPOPT_MTU 0x1a +#define PICO_DHCPOPT_BCAST 0x1c +#define PICO_DHCPOPT_NETBIOSNS 0x2c +#define PICO_DHCPOPT_NETBIOSSCOPE 0x2f + +#define PICO_DHCPOPT_REQIP 0x32 +#define PICO_DHCPOPT_LEASETIME 0x33 +#define PICO_DHCPOPT_OPTIONOVERLOAD 0x34 +#define PICO_DHCPOPT_MSGTYPE 0x35 +#define PICO_DHCPOPT_SERVERID 0x36 +#define PICO_DHCPOPT_PARMLIST 0x37 +#define PICO_DHCPOPT_MAXMSGSIZE 0x39 +#define PICO_DHCPOPT_RENEWALTIME 0x3a +#define PICO_DHCPOPT_REBINDINGTIME 0x3b +#define PICO_DHCPOPT_DOMAINSEARCH 0x77 +#define PICO_DHCPOPT_STATICROUTE 0x79 +#define PICO_DHCPOPT_END 0xFF + +/* DHCP MESSAGE TYPE */ +#define PICO_DHCP_MSG_DISCOVER 1 +#define PICO_DHCP_MSG_OFFER 2 +#define PICO_DHCP_MSG_REQUEST 3 +#define PICO_DHCP_MSG_DECLINE 4 +#define PICO_DHCP_MSG_ACK 5 +#define PICO_DHCP_MSG_NAK 6 +#define PICO_DHCP_MSG_RELEASE 7 +#define PICO_DHCP_MSG_INFORM 8 + + +enum dhcp_negotiation_state { + DHCPSTATE_DISCOVER = 0, + DHCPSTATE_OFFER, + DHCPSTATE_REQUEST, + DHCPSTATE_BOUND, + DHCPSTATE_RENEWING +}; + + +struct __attribute__((packed)) pico_dhcphdr +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; //zero + uint32_t xid; //store this in the request + uint16_t secs; // ignore + uint16_t flags; + uint32_t ciaddr; // client address - if asking for renewal + uint32_t yiaddr; // your address (client) + uint32_t siaddr; // dhcp offered address + uint32_t giaddr; // relay agent, bootp. + uint8_t hwaddr[6]; + uint8_t hwaddr_padding[10]; + char hostname[64]; + char bootp_filename[128]; + uint32_t dhcp_magic; + uint8_t options[0]; +}; + + +//common functions for client and server + +uint8_t dhcp_get_next_option(uint8_t *begin, uint8_t *data, int *len, uint8_t **nextopt); +int is_options_valid(uint8_t *opt_buffer, int len); +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_server.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,339 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + + +Authors: Frederik Van Slycken, Kristof Roelants +*********************************************************************/ + +#ifdef PICO_SUPPORT_DHCPD + +#include "pico_dhcp_server.h" +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_arp.h" +#include <stdlib.h> + +# define dhcpd_dbg(...) do{}while(0) +//# define dhcpd_dbg dbg + +#define dhcpd_make_offer(x) dhcpd_make_reply(x, PICO_DHCP_MSG_OFFER) +#define dhcpd_make_ack(x) dhcpd_make_reply(x, PICO_DHCP_MSG_ACK) +#define ip_inrange(x) ((long_be(x) >= long_be(dn->settings->pool_start)) && (long_be(x) <= long_be(dn->settings->pool_end))) + +static int dhcp_settings_cmp(void *ka, void *kb) +{ + struct pico_dhcpd_settings *a = ka, *b = kb; + if (a->dev < b->dev) + return -1; + else if (a->dev > b->dev) + return 1; + else + return 0; +} +PICO_TREE_DECLARE(DHCPSettings, dhcp_settings_cmp); + +static int dhcp_negotiations_cmp(void *ka, void *kb) +{ + struct pico_dhcp_negotiation *a = ka, *b = kb; + if (a->xid < b->xid) + return -1; + else if (a->xid > b->xid) + return 1; + else + return 0; +} +PICO_TREE_DECLARE(DHCPNegotiations, dhcp_negotiations_cmp); + +static struct pico_dhcp_negotiation *get_negotiation_by_xid(uint32_t xid) +{ + struct pico_dhcp_negotiation test = { }, *neg = NULL; + + test.xid = xid; + neg = pico_tree_findKey(&DHCPNegotiations, &test); + if (!neg) + return NULL; + else + return neg; +} + +static void dhcpd_make_reply(struct pico_dhcp_negotiation *dn, uint8_t reply_type) +{ + uint8_t buf_out[DHCPD_DATAGRAM_SIZE] = {0}; + struct pico_dhcphdr *dh_out = (struct pico_dhcphdr *) buf_out; + struct pico_ip4 destination = { }; + uint32_t bcast = dn->settings->my_ip.addr | ~(dn->settings->netmask.addr); + uint32_t dns_server = OPENDNS; + uint16_t port = PICO_DHCP_CLIENT_PORT; + int sent = 0; + + memcpy(dh_out->hwaddr, dn->eth.addr, PICO_HLEN_ETHER); + dh_out->op = PICO_DHCP_OP_REPLY; + dh_out->htype = PICO_HTYPE_ETHER; + dh_out->hlen = PICO_HLEN_ETHER; + dh_out->xid = dn->xid; + dh_out->yiaddr = dn->ipv4.addr; + dh_out->siaddr = dn->settings->my_ip.addr; + dh_out->dhcp_magic = PICO_DHCPD_MAGIC_COOKIE; + + /* Option: msg type, len 1 */ + dh_out->options[0] = PICO_DHCPOPT_MSGTYPE; + dh_out->options[1] = 1; + dh_out->options[2] = reply_type; + + /* Option: server id, len 4 */ + dh_out->options[3] = PICO_DHCPOPT_SERVERID; + dh_out->options[4] = 4; + memcpy(dh_out->options + 5, &dn->settings->my_ip.addr, 4); + + /* Option: Lease time, len 4 */ + dh_out->options[9] = PICO_DHCPOPT_LEASETIME; + dh_out->options[10] = 4; + memcpy(dh_out->options + 11, &dn->settings->lease_time, 4); + + /* Option: Netmask, len 4 */ + dh_out->options[15] = PICO_DHCPOPT_NETMASK; + dh_out->options[16] = 4; + memcpy(dh_out->options + 17, &dn->settings->netmask.addr, 4); + + /* Option: Router, len 4 */ + dh_out->options[21] = PICO_DHCPOPT_ROUTER; + dh_out->options[22] = 4; + memcpy(dh_out->options + 23, &dn->settings->my_ip.addr, 4); + + /* Option: Broadcast, len 4 */ + dh_out->options[27] = PICO_DHCPOPT_BCAST; + dh_out->options[28] = 4; + memcpy(dh_out->options + 29, &bcast, 4); + + /* Option: DNS, len 4 */ + dh_out->options[33] = PICO_DHCPOPT_DNS; + dh_out->options[34] = 4; + memcpy(dh_out->options + 35, &dns_server, 4); + + dh_out->options[40] = PICO_DHCPOPT_END; + + destination.addr = dh_out->yiaddr; + + sent = pico_socket_sendto(dn->settings->s, buf_out, DHCPD_DATAGRAM_SIZE, &destination, port); + if (sent < 0) { + dhcpd_dbg("DHCPD: sendto failed with code %d!\n", pico_err); + } +} + +static void dhcp_recv(struct pico_socket *s, uint8_t *buffer, int len) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) buffer; + struct pico_dhcp_negotiation *dn = get_negotiation_by_xid(dhdr->xid); + struct pico_ip4* ipv4 = NULL; + struct pico_dhcpd_settings test, *settings = NULL; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + uint8_t msg_type; + uint32_t msg_reqIP = 0; + uint32_t msg_servID = 0; + + if (!is_options_valid(dhdr->options, len - sizeof(struct pico_dhcphdr))) { + dhcpd_dbg("DHCPD WARNING: invalid options in dhcp message\n"); + return; + } + + if (!dn) { + dn = pico_zalloc(sizeof(struct pico_dhcp_negotiation)); + if (!dn) { + pico_err = PICO_ERR_ENOMEM; + return; + } + dn->xid = dhdr->xid; + dn->state = DHCPSTATE_DISCOVER; + memcpy(dn->eth.addr, dhdr->hwaddr, PICO_HLEN_ETHER); + + test.dev = pico_ipv4_link_find(&s->local_addr.ip4); + settings = pico_tree_findKey(&DHCPSettings, &test); + if (settings) { + dn->settings = settings; + } else { + dhcpd_dbg("DHCPD WARNING: received DHCP message on unconfigured link %s\n", test.dev->name); + pico_free(dn); + return; + } + + ipv4 = pico_arp_reverse_lookup(&dn->eth); + if (!ipv4) { + dn->ipv4.addr = settings->pool_next; + pico_arp_create_entry(dn->eth.addr, dn->ipv4, settings->dev); + settings->pool_next = long_be(long_be(settings->pool_next) + 1); + } else { + dn->ipv4.addr = ipv4->addr; + } + + if (pico_tree_insert(&DHCPNegotiations, dn)) { + dhcpd_dbg("DHCPD WARNING: tried creating new negotation for existing xid %u\n", dn->xid); + pico_free(dn); + return; /* Element key already exists */ + } + } + + if (!ip_inrange(dn->ipv4.addr)) + return; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != PICO_DHCPOPT_END) { + /* parse interesting options here */ + //dhcpd_dbg("DHCPD sever: opt_type %x, opt_data[0]%d\n", opt_type, opt_data[0]); + switch(opt_type){ + case PICO_DHCPOPT_MSGTYPE: + msg_type = opt_data[0]; + break; + case PICO_DHCPOPT_REQIP: + //dhcpd_dbg("DHCPD sever: opt_type %x, opt_len%d\n", opt_type, opt_len); + if( opt_len == 4) + { + msg_reqIP = ( opt_data[0] << 24 ); + msg_reqIP |= ( opt_data[1] << 16 ); + msg_reqIP |= ( opt_data[2] << 8 ); + msg_reqIP |= ( opt_data[3] ); + //dhcpd_dbg("DHCPD sever: msg_reqIP %x, opt_data[0] %x,[1] %x,[2] %x,[3] %x\n", msg_reqIP, opt_data[0],opt_data[1],opt_data[2],opt_data[3]); + }; + break; + case PICO_DHCPOPT_SERVERID: + //dhcpd_dbg("DHCPD sever: opt_type %x, opt_len%d\n", opt_type, opt_len); + if( opt_len == 4) + { + msg_servID = ( opt_data[0] << 24 ); + msg_servID |= ( opt_data[1] << 16 ); + msg_servID |= ( opt_data[2] << 8 ); + msg_servID |= ( opt_data[3] ); + //dhcpd_dbg("DHCPD sever: msg_servID %x, opt_data[0] %x,[1] %x,[2] %x,[3] %x\n", msg_servID, opt_data[0],opt_data[1],opt_data[2],opt_data[3]); + }; + break; + default: + break; + } + + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + + //dhcpd_dbg("DHCPD sever: msg_type %d, dn->state %d\n", msg_type, dn->state); + //dhcpd_dbg("DHCPD sever: msg_reqIP %x, dn->msg_servID %x\n", msg_reqIP, msg_servID); + //dhcpd_dbg("DHCPD sever: dhdr->ciaddr %x, dhdr->yiaddr %x, dn->ipv4.addr %x\n", dhdr->ciaddr,dhdr->yiaddr,dn->ipv4.addr); + + if (msg_type == PICO_DHCP_MSG_DISCOVER) + { + dhcpd_make_offer(dn); + dn->state = DHCPSTATE_OFFER; + return; + } + else if ((msg_type == PICO_DHCP_MSG_REQUEST)&&( dn->state == DHCPSTATE_OFFER)) + { + dhcpd_make_ack(dn); + dn->state = DHCPSTATE_BOUND; + return; + } + else if ((msg_type == PICO_DHCP_MSG_REQUEST)&&( dn->state == DHCPSTATE_BOUND)) + { + if( ( msg_servID == 0 ) + &&( msg_reqIP == 0 ) + &&( dhdr->ciaddr == dn->ipv4.addr) + ) + { + dhcpd_make_ack(dn); + return; + } + } +} + +static void pico_dhcpd_wakeup(uint16_t ev, struct pico_socket *s) +{ + uint8_t buf[DHCPD_DATAGRAM_SIZE] = { }; + int r = 0; + uint32_t peer = 0; + uint16_t port = 0; + + dhcpd_dbg("DHCPD: called dhcpd_wakeup\n"); + if (ev == PICO_SOCK_EV_RD) { + do { + r = pico_socket_recvfrom(s, buf, DHCPD_DATAGRAM_SIZE, &peer, &port); + if (r > 0 && port == PICO_DHCP_CLIENT_PORT) { + dhcp_recv(s, buf, r); + } + } while(r>0); + } +} + +int pico_dhcp_server_initiate(struct pico_dhcpd_settings *setting) +{ + struct pico_dhcpd_settings *settings = NULL; + struct pico_ipv4_link *link = NULL; + uint16_t port = PICO_DHCPD_PORT; + + if (!setting) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (!setting->my_ip.addr) { + pico_err = PICO_ERR_EINVAL; + dhcpd_dbg("DHCPD: IP address of interface was not supplied\n"); + return -1; + } + + link = pico_ipv4_link_get(&setting->my_ip); + if (!link) { + pico_err = PICO_ERR_EINVAL; + dhcpd_dbg("DHCPD: no link with IP %X found\n", setting->my_ip.addr); + return -1; + } + + settings = pico_zalloc(sizeof(struct pico_dhcpd_settings)); + if (!settings) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + memcpy(settings, setting, sizeof(struct pico_dhcpd_settings)); + + settings->dev = link->dev; + dhcpd_dbg("DHCPD: configuring DHCP server for link %s\n", link->dev->name); + settings->my_ip.addr = link->address.addr; + dhcpd_dbg("DHCPD: using server addr %X\n", long_be(settings->my_ip.addr)); + settings->netmask.addr = link->netmask.addr; + dhcpd_dbg("DHCPD: using netmask %X\n", long_be(settings->netmask.addr)); + + /* default values if not provided */ + if (settings->pool_start == 0) + settings->pool_start = (settings->my_ip.addr & settings->netmask.addr) | POOL_START; + dhcpd_dbg("DHCPD: using pool_start %X\n", long_be(settings->pool_start)); + if (settings->pool_end == 0) + settings->pool_end = (settings->my_ip.addr & settings->netmask.addr) | POOL_END; + dhcpd_dbg("DHCPD: using pool_end %x\n", long_be(settings->pool_end)); + if (settings->lease_time == 0) + settings->lease_time = LEASE_TIME; + dhcpd_dbg("DHCPD: using lease time %x\n", long_be(settings->lease_time)); + settings->pool_next = settings->pool_start; + + settings->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcpd_wakeup); + if (!settings->s) { + dhcpd_dbg("DHCP: could not open client socket\n"); + pico_free(settings); + return -1; + } + if (pico_socket_bind(settings->s, &settings->my_ip, &port) != 0) { + dhcpd_dbg("DHCP: could not bind server socket (%s)\n", strerror(pico_err)); + pico_free(settings); + return -1; + } + + if (pico_tree_insert(&DHCPSettings, settings)) { + dhcpd_dbg("DHCPD ERROR: link %s already configured\n", link->dev->name); + pico_err = PICO_ERR_EINVAL; + pico_free(settings); + return -1; /* Element key already exists */ + } + dhcpd_dbg("DHCPD: configured DHCP server for link %s\n", link->dev->name); + + return 0; +} +#endif /* PICO_SUPPORT_DHCP */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_server.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,43 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_DHCP_SERVER +#define _INCLUDE_PICO_DHCP_SERVER + +#include "pico_dhcp_common.h" +#include "pico_addressing.h" + +/* default configuration */ +#define OPENDNS (long_be(0xd043dede)) /* OpenDNS DNS server 208.67.222.222 */ +#define POOL_START long_be(0x00000064) +#define POOL_END long_be(0x000000fe) +#define LEASE_TIME long_be(0x00000078) + +struct pico_dhcpd_settings +{ + struct pico_device *dev; + struct pico_socket *s; + struct pico_ip4 my_ip; + struct pico_ip4 netmask; + uint32_t pool_start; + uint32_t pool_next; + uint32_t pool_end; + uint32_t lease_time; + uint8_t flags; /* unused atm */ +}; + +struct pico_dhcp_negotiation { + struct pico_dhcpd_settings *settings; + struct pico_ip4 ipv4; + struct pico_eth eth; + enum dhcp_negotiation_state state; + uint32_t xid; + uint32_t assigned_address; +}; + +/* required settings field: IP address of the interface to serve, only IPs of this network will be served. */ +int pico_dhcp_server_initiate(struct pico_dhcpd_settings *setting); + +#endif /* _INCLUDE_PICO_DHCP_SERVER */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dns_client.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,767 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Kristof Roelants +*********************************************************************/ +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_dns_client.h" +#include "pico_tree.h" + +#ifdef PICO_SUPPORT_DNS_CLIENT + +#define dns_dbg(...) do{}while(0) +//#define dns_dbg dbg + +/* DNS response length */ +#define PICO_DNS_MAX_RESPONSE_LEN 256 + +/* DNS client retransmission time (msec) + frequency */ +#define PICO_DNS_CLIENT_RETRANS 4000 +#define PICO_DNS_CLIENT_MAX_RETRANS 3 + +/* Default nameservers */ +#define PICO_DNS_NS_GOOGLE "8.8.8.8" + +/* Nameserver port */ +#define PICO_DNS_NS_PORT 53 + +/* FLAG values */ +#define PICO_DNS_QR_QUERY 0 +#define PICO_DNS_QR_RESPONSE 1 +#define PICO_DNS_OPCODE_QUERY 0 +#define PICO_DNS_OPCODE_IQUERY 1 +#define PICO_DNS_OPCODE_STATUS 2 +#define PICO_DNS_AA_NO_AUTHORITY 0 +#define PICO_DNS_AA_IS_AUTHORITY 1 +#define PICO_DNS_TC_NO_TRUNCATION 0 +#define PICO_DNS_TC_IS_TRUNCATED 1 +#define PICO_DNS_RD_NO_DESIRE 0 +#define PICO_DNS_RD_IS_DESIRED 1 +#define PICO_DNS_RA_NO_SUPPORT 0 +#define PICO_DNS_RA_IS_SUPPORTED 1 +#define PICO_DNS_RCODE_NO_ERROR 0 +#define PICO_DNS_RCODE_EFORMAT 1 +#define PICO_DNS_RCODE_ESERVER 2 +#define PICO_DNS_RCODE_ENAME 3 +#define PICO_DNS_RCODE_ENOIMP 4 +#define PICO_DNS_RCODE_EREFUSED 5 + +/* QTYPE values */ +#define PICO_DNS_TYPE_A 1 +#define PICO_DNS_TYPE_PTR 12 + +/* QCLASS values */ +#define PICO_DNS_CLASS_IN 1 + +/* Compression values */ +#define PICO_DNS_LABEL 0 +#define PICO_DNS_POINTER 3 + +/* TTL values */ +#define PICO_DNS_MAX_TTL 604800 /* one week */ + +/* Header flags */ +#define FLAG_QR(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x1 << 15)) | (x << 15)) +#define FLAG_OPCODE(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0xF << 11)) | (x << 11)) +#define FLAG_AA(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x1 << 10)) | (x << 10)) +#define FLAG_TC(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x1 << 9)) | (x << 9)) +#define FLAG_RD(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x1 << 8)) | (x << 8)) +#define FLAG_RA(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x1 << 7)) | (x << 7)) +#define FLAG_Z(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0x7 << 4)) | (x << 4)) +#define FLAG_RCODE(hdr, x) ((hdr)->flags = ((hdr)->flags & ~(0xF)) | x) + +#define GET_FLAG_QR(hdr) ((((hdr)->flags) & (1 << 15)) != 0) +#define GET_FLAG_OPCODE(hdr) ((((hdr)->flags) & (0xF << 11)) >> 11) +#define GET_FLAG_AA(hdr) ((((hdr)->flags) & (1 << 10)) != 0) +#define GET_FLAG_TC(hdr) ((((hdr)->flags) & (1 << 9)) != 0) +#define GET_FLAG_RD(hdr) ((((hdr)->flags) & (1 << 8)) != 0) +#define GET_FLAG_RA(hdr) ((((hdr)->flags) & (1 << 7)) != 0) +#define GET_FLAG_Z(hdr) ((((hdr)->flags) & (0x7 << 4)) >> 4) +#define GET_FLAG_RCODE(hdr) (((hdr)->flags) & (0x0F)) + +/* RFC 1025 section 4. MESSAGES */ +struct __attribute__((packed)) dns_message_hdr +{ + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct __attribute__((packed)) dns_query_suffix +{ + /* NAME - domain name to which this resource record pertains */ + uint16_t qtype; + uint16_t qclass; +}; + +struct __attribute__((packed)) dns_answer_suffix +{ + /* NAME - domain name to which this resource record pertains */ + uint16_t qtype; + uint16_t qclass; + uint32_t ttl; + uint16_t rdlength; + /* RDATA - variable length string of octets that describes the resource */ +}; + +struct pico_dns_ns +{ + struct pico_ip4 ns; /* Nameserver */ +}; + +static int dns_ns_cmp(void *ka, void *kb) +{ + struct pico_dns_ns *a = ka, *b = kb; + if (a->ns.addr < b->ns.addr) + return -1; + else if (a->ns.addr > b->ns.addr) + return 1; + else + return 0; +} + +PICO_TREE_DECLARE(NSTable,dns_ns_cmp); + +int pico_dns_client_nameserver(struct pico_ip4 *ns, uint8_t flag) +{ + struct pico_dns_ns test, *key = NULL; + + if (!ns) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (flag) + { + case PICO_DNS_NS_ADD: + key = pico_zalloc(sizeof(struct pico_dns_ns)); + if (!key) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + key->ns = *ns; + + if(pico_tree_insert(&NSTable,key)){ + dns_dbg("DNS WARNING: nameserver %08X already added\n",ns->addr); + pico_err = PICO_ERR_EINVAL; + pico_free(key); + return -1; /* Element key already exists */ + } + dns_dbg("DNS: nameserver %08X added\n", ns->addr); + /* If default NS found, remove it */ + pico_string_to_ipv4(PICO_DNS_NS_GOOGLE, &test.ns.addr); + if (ns->addr != test.ns.addr) { + + key = pico_tree_findKey(&NSTable,&test); + if (key) { + if(pico_tree_delete(&NSTable,key)) { + dns_dbg("DNS: default nameserver %08X removed\n", test.ns.addr); + pico_free(key); + } else { + pico_err = PICO_ERR_EAGAIN; + return -1; + } + } + } + break; + + case PICO_DNS_NS_DEL: + test.ns = *ns; + + key = pico_tree_findKey(&NSTable,&test); + if (!key) { + dns_dbg("DNS WARNING: nameserver %08X not found\n", ns->addr); + pico_err = PICO_ERR_EINVAL; + return -1; + } + /* RB_REMOVE returns pointer to removed element, NULL to indicate error */ + + if(pico_tree_delete(&NSTable,key)) { + dns_dbg("DNS: nameserver %08X removed\n",key->ns.addr); + pico_free(key); + } else { + pico_err = PICO_ERR_EAGAIN; + return -1; + } + /* If no NS left, add default NS */ + if(pico_tree_first(&NSTable) == NULL){ + dns_dbg("DNS: add default nameserver\n"); + return pico_dns_client_init(); + } + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + return 0; +} + +int pico_dns_client_init() +{ + struct pico_ip4 default_ns; + if (pico_string_to_ipv4(PICO_DNS_NS_GOOGLE, &default_ns.addr) != 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + return pico_dns_client_nameserver(&default_ns, PICO_DNS_NS_ADD); +} + +struct pico_dns_key +{ + char *q_hdr; + uint16_t len; + uint16_t id; + uint16_t qtype; + uint16_t qclass; + uint8_t retrans; + struct pico_dns_ns q_ns; + struct pico_socket *s; + void (*callback)(char *, void *); + void *arg; +}; + +static int dns_cmp(void *ka, void *kb) +{ + struct pico_dns_key *a = ka,*b = kb; + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return 1; + else + return 0; +} + +PICO_TREE_DECLARE(DNSTable,dns_cmp); + +static int pico_dns_client_strlen(const char *url) +{ + uint16_t len = 0; + int p; + + if (!url) + return -1; + + while ((p = *url++) != 0) { + len++; + } + return len; +} + +/* Replace '.' by the label length */ +static int pico_dns_client_label(char *ptr) +{ + char *l; + uint8_t lbl_len = 0; + int p; + + if (!ptr) + return -1; + + l = ptr++; + while ((p = *ptr++) != 0){ + if (p == '.') { + *l = lbl_len; + l = ptr - 1; + lbl_len = 0; + } else { + lbl_len++; + } + } + *l = lbl_len; + return 0; +} + +/* Replace the label length by '.' */ +static int pico_dns_client_reverse_label(char *ptr) +{ + char *l; + int p; + + if(!ptr) + return -1; + + l = ptr; + while ((p = *ptr++) != 0){ + ptr += p; + *l = '.'; + l = ptr; + } + return 0; +} + +/* Seek the end of a string */ +static char *pico_dns_client_seek(char *ptr) +{ + int p; + + if (!ptr) + return NULL; + + while ((p = *ptr++) != 0); + + return ptr++; +} + +static inline void pico_dns_client_construct_hdr(struct dns_message_hdr *hdr, uint16_t id) +{ + hdr->id = short_be(id); + FLAG_QR(hdr, PICO_DNS_QR_QUERY); + FLAG_OPCODE(hdr, PICO_DNS_OPCODE_QUERY); + FLAG_AA(hdr, PICO_DNS_AA_NO_AUTHORITY); + FLAG_TC(hdr, PICO_DNS_TC_NO_TRUNCATION); + FLAG_RD(hdr, PICO_DNS_RD_IS_DESIRED); + FLAG_RA(hdr, PICO_DNS_RA_NO_SUPPORT); + FLAG_Z(hdr, 0); + FLAG_RCODE(hdr, PICO_DNS_RCODE_NO_ERROR); + hdr->flags = short_be(hdr->flags); + hdr->qdcount = short_be(1); + hdr->ancount = short_be(0); + hdr->nscount = short_be(0); + hdr->arcount = short_be(0); +} + +static inline void pico_dns_client_hdr_ntoh(struct dns_message_hdr *hdr) +{ + hdr->id = short_be(hdr->id); + hdr->flags = short_be(hdr->flags); + hdr->qdcount = short_be(hdr->qdcount); + hdr->ancount = short_be(hdr->ancount); + hdr->nscount = short_be(hdr->nscount); + hdr->arcount = short_be(hdr->arcount); +} + + +static int pico_dns_client_mirror(char *ptr) +{ + unsigned char buf[4] = {0}; + char *m; + int cnt = 0; + int p, i; + + if (!ptr) + return -1; + + m = ptr; + while ((p = *ptr++) != 0) + { + if (pico_is_digit(p)) { + buf[cnt] = (10 * buf[cnt]) + (p - '0'); + } else if (p == '.') { + cnt++; + } else { + return -1; + } + } + + /* Handle short notation */ + if(cnt == 1){ + buf[3] = buf[1]; + buf[1] = 0; + buf[2] = 0; + }else if (cnt == 2){ + buf[3] = buf[2]; + buf[2] = 0; + }else if(cnt != 3){ + /* String could not be parsed, return error */ + return -1; + } + + ptr = m; + for(i = 3; i >= 0; i--) + { + if(buf[i] > 99){ + *ptr++ = '0' + (buf[i] / 100); + *ptr++ = '0' + ((buf[i] % 100) / 10); + *ptr++ = '0' + ((buf[i] % 100) % 10); + }else if(buf[i] > 9){ + *ptr++ = '0' + (buf[i] / 10); + *ptr++ = '0' + (buf[i] % 10); + }else{ + *ptr++ = '0' + buf[i]; + } + if(i > 0) + *ptr++ = '.'; + } + + return 0; +} + +static struct pico_dns_key *pico_dns_client_idcheck(uint16_t id) +{ + struct pico_dns_key test; + + test.id = id; + return pico_tree_findKey(&DNSTable,&test); +} + +static void pico_dns_client_callback(uint16_t ev, struct pico_socket *s); + +static int pico_dns_client_send(struct pico_dns_key *key) +{ + struct pico_socket *s; + int w = 0; + + dns_dbg("DNS: sending query to %08X\n", key->q_ns.ns.addr); + s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dns_client_callback); + if (!s) + return -1; + key->s = s; + if (pico_socket_connect(s, &key->q_ns.ns, short_be(PICO_DNS_NS_PORT)) != 0) + return -1; + w = pico_socket_send(s, key->q_hdr, key->len); + if (w <= 0) + return -1; + + return 0; +} + +static void pico_dns_client_retransmission(unsigned long now, void *arg) +{ + struct pico_dns_key *key = (struct pico_dns_key *)arg; + struct pico_dns_ns *q_ns = NULL; + + if (!key->retrans) { + dns_dbg("DNS: no retransmission!\n"); + pico_free(key->q_hdr); + + if(pico_tree_delete(&DNSTable,key)) + pico_free(key); + } + else if (key->retrans <= PICO_DNS_CLIENT_MAX_RETRANS) { + key->retrans++; + dns_dbg("DNS: retransmission! (%u attempts)\n", key->retrans); + // ugly hack + q_ns = pico_tree_next(pico_tree_findNode(&NSTable,&key->q_ns))->keyValue; + if (q_ns) + key->q_ns = *q_ns; + else + key->q_ns = *((struct pico_dns_ns *)pico_tree_first(&NSTable)); + pico_dns_client_send(key); + pico_timer_add(PICO_DNS_CLIENT_RETRANS, pico_dns_client_retransmission, key); + } else { + dns_dbg("DNS ERROR: no reply from nameservers! (%u attempts)\n", key->retrans); + pico_socket_close(key->s); + pico_err = PICO_ERR_EIO; + key->callback(NULL, key->arg); + pico_free(key->q_hdr); + /* RB_REMOVE returns pointer to removed element, NULL to indicate error */ + + if(pico_tree_delete(&DNSTable,key)) + pico_free(key); + } +} + +static void pico_dns_client_callback(uint16_t ev, struct pico_socket *s) +{ + char *q_qname, *q_suf, *a_hdr, *a_qname, *a_suf, *a_rdata; + struct dns_message_hdr *hdr; + struct dns_query_suffix query_suf; + struct dns_answer_suffix answer_suf; + struct pico_dns_key test, *key; + char *answer; + char dns_answer[PICO_DNS_MAX_RESPONSE_LEN] = {0}; + uint8_t valid_suffix = 0; + uint16_t compression = 0; + int i = 0, r = 0; + + if (ev & PICO_SOCK_EV_RD) { + r = pico_socket_read(s, dns_answer, PICO_DNS_MAX_RESPONSE_LEN); + pico_socket_close(s); + if (r == PICO_DNS_MAX_RESPONSE_LEN || r < (int)sizeof(struct dns_message_hdr)) { + dns_dbg("DNS ERROR: received incorrect number(%d) of bytes\n", r); + return; + } + + /* Check header validity */ + a_hdr = dns_answer; + hdr = (struct dns_message_hdr *) a_hdr; + pico_dns_client_hdr_ntoh(hdr); + if (GET_FLAG_QR(hdr) != PICO_DNS_QR_RESPONSE || GET_FLAG_OPCODE(hdr) != PICO_DNS_OPCODE_QUERY + || GET_FLAG_TC(hdr) == PICO_DNS_TC_IS_TRUNCATED || GET_FLAG_RCODE(hdr) != PICO_DNS_RCODE_NO_ERROR) { + dns_dbg("DNS ERROR: OPCODE %d | TC %d | RCODE %d\n", GET_FLAG_OPCODE(hdr), GET_FLAG_TC(hdr), GET_FLAG_RCODE(hdr)); + return; + } + + if (hdr->ancount < 1 || r < (int)(sizeof(struct dns_message_hdr) + hdr->qdcount * sizeof(struct dns_query_suffix) + + hdr->ancount * sizeof(struct dns_answer_suffix))) { + dns_dbg("DNS ERROR: ancount < 1 OR received number(%d) of bytes too low\n", r); + return; + } + + /* Find DNS key */ + test.id = hdr->id; + + key = pico_tree_findKey(&DNSTable,&test); + if (!key) { + dns_dbg("DNS WARNING: key with id %u not found\n", hdr->id); + return; + } + key->retrans = 0; + + /* Check query suffix validity */ + q_qname = a_hdr + sizeof(struct dns_message_hdr); + q_suf = pico_dns_client_seek(q_qname); + query_suf = *(struct dns_query_suffix *) q_suf; + if (short_be(query_suf.qtype) != key->qtype || short_be(query_suf.qclass) != key->qclass) { + dns_dbg("DNS ERROR: received qtype (%u) or qclass (%u) incorrect\n", short_be(query_suf.qtype), short_be(query_suf.qclass)); + return; + } + + /* Seek answer suffix */ + a_qname = q_suf + sizeof(struct dns_query_suffix); + a_suf = a_qname; + while(i++ < hdr->ancount) { + uint16_t comp_h = short_from(a_suf); + compression = short_be(comp_h); + switch (compression >> 14) + { + case PICO_DNS_POINTER: + while (compression >> 14 == PICO_DNS_POINTER) { + dns_dbg("DNS: pointer\n"); + a_suf += sizeof(uint16_t); + comp_h = short_from(a_suf); + compression = short_be(comp_h); + } + break; + + case PICO_DNS_LABEL: + dns_dbg("DNS: label\n"); + a_suf = pico_dns_client_seek(a_qname); + break; + + default: + dns_dbg("DNS ERROR: incorrect compression (%u) value\n", compression); + return; + } + + /* Check answer suffix validity */ + answer_suf = *(struct dns_answer_suffix *)a_suf; + if (short_be(answer_suf.qtype) != key->qtype || short_be(answer_suf.qclass) != key->qclass) { + dns_dbg("DNS WARNING: received qtype (%u) or qclass (%u) incorrect\n", short_be(answer_suf.qtype), short_be(answer_suf.qclass)); + a_suf = a_suf + sizeof(struct dns_answer_suffix) + short_be(answer_suf.rdlength); + continue; + } + + if (short_be(answer_suf.ttl) > PICO_DNS_MAX_TTL) { + dns_dbg("DNS WARNING: received TTL (%u) > MAX (%u)\n", short_be(answer_suf.ttl), PICO_DNS_MAX_TTL); + a_suf = a_suf + sizeof(struct dns_answer_suffix) + short_be(answer_suf.rdlength); + continue; + } + + valid_suffix = 1; + break; + } + + if (!valid_suffix) { + dns_dbg("DNS ERROR: invalid dns answer suffix\n"); + return; + } + + a_rdata = a_suf + sizeof(struct dns_answer_suffix); + if (key->qtype == PICO_DNS_TYPE_A) { + uint32_t ip_h = long_from(a_rdata); + dns_dbg("DNS: length %u | ip %08X\n", short_be(answer_suf.rdlength), long_be(ip_h)); + answer = pico_zalloc(16); + pico_ipv4_to_string(answer, ip_h); + key->callback(answer, key->arg); + } else if (key->qtype == PICO_DNS_TYPE_PTR) { + pico_dns_client_reverse_label((char *) a_rdata); + dns_dbg("DNS: length %u | name %s\n", short_be(answer_suf.rdlength), (char *)a_rdata + 1); + answer = pico_zalloc(answer_suf.rdlength - 1); + memcpy(answer, (char *)a_rdata + 1, short_be(answer_suf.rdlength) - 1); + key->callback(answer, key->arg); + } else { + dns_dbg("DNS ERROR: incorrect qtype (%u)\n", key->qtype); + return; + } + } + + if (ev == PICO_SOCK_EV_ERR) { + dns_dbg("DNS: socket error received\n"); + } +} + +int pico_dns_client_getaddr(const char *url, void (*callback)(char *, void *), void *arg) +{ + char *q_hdr, *q_qname, *q_suf; + struct dns_message_hdr *hdr; + struct dns_query_suffix query_suf; + struct pico_dns_key *key; + uint16_t url_len = 0; + uint16_t id = 0; + + if (!url || !callback) { + dns_dbg("DNS ERROR: NULL parameters\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + url_len = pico_dns_client_strlen(url); + /* 2 extra bytes for url_len to account for 2 extra label length octets */ + q_hdr = pico_zalloc(sizeof(struct dns_message_hdr) + (1 + url_len + 1) + sizeof(struct dns_query_suffix)); + if (!q_hdr) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + q_qname = q_hdr + sizeof(struct dns_message_hdr); + q_suf = q_qname + (1 + url_len + 1); + + /* Construct query header */ + hdr = (struct dns_message_hdr *) q_hdr; + do { + id = (uint16_t) (pico_rand() & 0xFFFFU); + dns_dbg("DNS: generated id %u\n", id); + } while (pico_dns_client_idcheck(id)); + pico_dns_client_construct_hdr(hdr, id); + /* Add and manipulate domain name */ + memcpy(q_qname + 1, url, url_len + 1); + pico_dns_client_label(q_qname); + /* Add type and class of query */ + query_suf.qtype = short_be(PICO_DNS_TYPE_A); + query_suf.qclass = short_be(PICO_DNS_CLASS_IN); + memcpy(q_suf, &query_suf, sizeof(struct dns_query_suffix)); + /* Create RB entry */ + key = pico_zalloc(sizeof(struct pico_dns_key)); + if (!key) { + pico_free(q_hdr); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + key->q_hdr = q_hdr; + key->len = sizeof(struct dns_message_hdr) + (1 + url_len + 1) + sizeof(struct dns_query_suffix); + key->id = id; + key->qtype = PICO_DNS_TYPE_A; + key->qclass = PICO_DNS_CLASS_IN; + key->retrans = 1; + + key->q_ns = *((struct pico_dns_ns *)pico_tree_first(&NSTable)); + key->s = NULL; + key->callback = callback; + key->arg = arg; + /* Send query */ + if (pico_dns_client_send(key) < 0) { + pico_free(q_hdr); + if (key->s) + pico_socket_close(key->s); + pico_free(key); + pico_err = PICO_ERR_EAGAIN; + return -1; + } + /* Insert RB entry */ + + if(pico_tree_insert(&DNSTable,key)) { + pico_free(q_hdr); + pico_free(key); + pico_err = PICO_ERR_EAGAIN; + return -1; /* Element key already exists */ + } + + pico_timer_add(PICO_DNS_CLIENT_RETRANS, pico_dns_client_retransmission, key); + return 0; +} + +int pico_dns_client_getname(const char *ip, void (*callback)(char *, void *), void *arg) +{ + char *q_hdr, *q_qname, *q_suf; + struct dns_message_hdr *hdr; + struct dns_query_suffix query_suf; + struct pico_dns_key *key; + uint16_t ip_len = 0; + uint16_t arpa_len = 0; + uint16_t id = 0; + + if (!ip || !callback) { + dns_dbg("DNS ERROR: NULL parameters\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + ip_len = pico_dns_client_strlen(ip); + arpa_len = pico_dns_client_strlen(".in-addr.arpa"); + /* 2 extra bytes for ip_len and arpa_len to account for 2 extra length octets */ + q_hdr = pico_zalloc(sizeof(struct dns_message_hdr) + (1 + ip_len + arpa_len + 1) + sizeof(struct dns_query_suffix)); + if (!q_hdr) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + q_qname = q_hdr + sizeof(struct dns_message_hdr); + q_suf = q_qname + (1 + ip_len + arpa_len + 1); + + /* Construct query header */ + hdr = (struct dns_message_hdr *)q_hdr; + do { + id = (uint16_t) (pico_rand() & 0xFFFFU); + dns_dbg("DNS: generated id %u\n", id); + } while (pico_dns_client_idcheck(id)); + pico_dns_client_construct_hdr(hdr, id); + /* Add and manipulate domain name */ + memcpy(q_qname + 1, ip, ip_len + 1); + pico_dns_client_mirror(q_qname + 1); + memcpy(q_qname + 1 + ip_len, ".in-addr.arpa", arpa_len); + pico_dns_client_label(q_qname); + /* Add type and class of query */ + query_suf.qtype = short_be(PICO_DNS_TYPE_PTR); + query_suf.qclass = short_be(PICO_DNS_CLASS_IN); + memcpy(q_suf, &query_suf, sizeof(struct dns_query_suffix)); + /* Create RB entry */ + key = pico_zalloc(sizeof(struct pico_dns_key)); + if (!key) { + pico_free(q_hdr); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + key->q_hdr = q_hdr; + key->len = sizeof(struct dns_message_hdr) + (1 + ip_len + arpa_len + 1) + sizeof(struct dns_query_suffix); + key->id = id; + key->qtype = PICO_DNS_TYPE_PTR; + key->qclass = PICO_DNS_CLASS_IN; + key->retrans = 1; + key->q_ns = *((struct pico_dns_ns *)pico_tree_first(&NSTable)); + key->s = NULL; + key->callback = callback; + key->arg = arg; + /* Send query */ + if (pico_dns_client_send(key) < 0) { + pico_free(q_hdr); + if (key->s) + pico_socket_close(key->s); + pico_free(key); + pico_err = PICO_ERR_EAGAIN; + return -1; + } + /* Insert RB entry */ + + if(pico_tree_insert(&DNSTable,key)) { + pico_free(q_hdr); + pico_free(key); + pico_err = PICO_ERR_EAGAIN; + return -1; /* Element key already exists */ + } + + pico_timer_add(PICO_DNS_CLIENT_RETRANS, pico_dns_client_retransmission, key); + return 0; +} + +#ifdef PICO_DNS_CLIENT_MAIN +int main(int argc, char *argv[]) +{ + dns_dbg(">>>>> DNS GET ADDR\n"); + pico_dns_client_getaddr("www.google.be"); + dns_dbg(">>>>> DNS GET NAME\n"); + pico_dns_client_getname("173.194.67.94"); + + return 0; +} +#endif /* PICO_DNS_CLIENT_MAIN */ +#endif /* PICO_SUPPORT_DNS_CLIENT */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dns_client.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,23 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Kristof Roelants +*********************************************************************/ + +#ifndef _INCLUDE_PICO_DNS_CLIENT +#define _INCLUDE_PICO_DNS_CLIENT + +#define PICO_DNS_NS_DEL 0 +#define PICO_DNS_NS_ADD 1 +#include <stdint.h> + +int pico_dns_client_init(); +/* flag is PICO_DNS_NS_DEL or PICO_DNS_NS_ADD */ +int pico_dns_client_nameserver(struct pico_ip4 *ns, uint8_t flag); +int pico_dns_client_getaddr(const char *url, void (*callback)(char *ip, void *arg), void *arg); +int pico_dns_client_getname(const char *ip, void (*callback)(char *url, void *arg), void *arg); + +#endif /* _INCLUDE_PICO_DNS_CLIENT */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_client.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,701 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ +#include <string.h> +#include <stdint.h> +#include "pico_tree.h" +#include "pico_config.h" +#include "pico_socket.h" +#include "pico_tcp.h" +#include "pico_dns_client.h" +#include "pico_http_client.h" +#include "pico_ipv4.h" +#include "pico_stack.h" + +/* + * This is the size of the following header + * + * GET <resource> HTTP/1.1<CRLF> + * Host: <host>:<port><CRLF> + * User-Agent: picoTCP<CRLF> + * Connection: close<CRLF> + * <CRLF> + * + * where <resource>,<host> and <port> will be added later. + */ + +#ifdef PICO_SUPPORT_HTTP_CLIENT + +#define HTTP_GET_BASIC_SIZE 63u +#define HTTP_HEADER_LINE_SIZE 50u +#define RESPONSE_INDEX 9u + +#define HTTP_CHUNK_ERROR 0xFFFFFFFFu + +#ifdef dbg + #undef dbg + #define dbg(...) do{}while(0); +#endif + +#define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) +#define isLocation(line) (memcmp(line,"Location",8u) == 0) +#define isContentLength(line) (memcmp(line,"Content-Length",14u) == 0u) +#define isTransferEncoding(line) (memcmp(line,"Transfer-Encoding",17u) == 0u) +#define isChunked(line) (memcmp(line," chunked",8u) == 0u) +#define isNotHTTPv1(line) (memcmp(line,"HTTP/1.",7u)) +#define is_hex_digit(x) ( ('0' <= x && x <= '9') || ('a' <= x && x <= 'f') ) +#define hex_digit_to_dec(x) ( ('0' <= x && x <= '9') ? x-'0' : ( ('a' <= x && x <= 'f') ? x-'a' + 10 : -1) ) + +struct pico_http_client +{ + uint16_t connectionID; + uint8_t state; + struct pico_socket * sck; + void (*wakeup)(uint16_t ev, uint16_t conn); + struct pico_ip4 ip; + struct pico_http_uri * uriKey; + struct pico_http_header * header; +}; + +// HTTP Client internal states +#define HTTP_READING_HEADER 0 +#define HTTP_READING_BODY 1 +#define HTTP_READING_CHUNK_VALUE 2 +#define HTTP_READING_CHUNK_TRAIL 3 + + +static int compareClients(void * ka, void * kb) +{ + return ((struct pico_http_client *)ka)->connectionID - ((struct pico_http_client *)kb)->connectionID; +} + +PICO_TREE_DECLARE(pico_client_list,compareClients); + +// Local functions +int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header); +int readChunkLine(struct pico_http_client * client); + +void tcpCallback(uint16_t ev, struct pico_socket *s) +{ + + struct pico_http_client * client = NULL; + struct pico_tree_node * index; + + // find httpClient + pico_tree_foreach(index,&pico_client_list) + { + if( ((struct pico_http_client *)index->keyValue)->sck == s ) + { + client = (struct pico_http_client *)index->keyValue; + break; + } + } + + if(!client) + { + dbg("Client not found...Something went wrong !\n"); + return; + } + + if(ev & PICO_SOCK_EV_CONN) + client->wakeup(EV_HTTP_CON,client->connectionID); + + if(ev & PICO_SOCK_EV_RD) + { + + // read the header, if not read + if(client->state == HTTP_READING_HEADER) + { + // wait for header + client->header = pico_zalloc(sizeof(struct pico_http_header)); + if(!client->header) + { + pico_err = PICO_ERR_ENOMEM; + return; + } + + // wait for header + if(parseHeaderFromServer(client,client->header) < 0) + { + client->wakeup(EV_HTTP_ERROR,client->connectionID); + } + else + { + // call wakeup + if(client->header->responseCode != HTTP_CONTINUE) + { + client->wakeup( + client->header->responseCode == HTTP_OK ? + EV_HTTP_REQ | EV_HTTP_BODY : // data comes for sure only when 200 is received + EV_HTTP_REQ + ,client->connectionID); + } + } + } + else + { + // just let the user know that data has arrived, if chunked data comes, will be treated in the + // read api. + client->wakeup(EV_HTTP_BODY,client->connectionID); + } + } + + if(ev & PICO_SOCK_EV_ERR) + { + client->wakeup(EV_HTTP_ERROR,client->connectionID); + } + + if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) + { + client->wakeup(EV_HTTP_CLOSE,client->connectionID); + } + +} + +// used for getting a response from DNS servers +static void dnsCallback(char *ip, void * ptr) +{ + struct pico_http_client * client = (struct pico_http_client *)ptr; + + if(!client) + { + dbg("Who made the request ?!\n"); + return; + } + + if(ip) + { + client->wakeup(EV_HTTP_DNS,client->connectionID); + + // add the ip address to the client, and start a tcp connection socket + pico_string_to_ipv4(ip,&client->ip.addr); + pico_free(ip); + client->sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcpCallback); + if(!client->sck) + { + client->wakeup(EV_HTTP_ERROR,client->connectionID); + return; + } + + if(pico_socket_connect(client->sck,&client->ip,short_be(client->uriKey->port)) < 0) + { + client->wakeup(EV_HTTP_ERROR,client->connectionID); + return; + } + + } + else + { + // wakeup client and let know error occured + client->wakeup(EV_HTTP_ERROR,client->connectionID); + + // close the client (free used heap) + pico_http_client_close(client->connectionID); + } +} + +/* + * API used for opening a new HTTP Client. + * + * The accepted uri's are [http://]hostname[:port]/resource + * no relative uri's are accepted. + * + * The function returns a connection ID >= 0 if successful + * -1 if an error occured. + */ +int pico_http_client_open(char * uri, void (*wakeup)(uint16_t ev, uint16_t conn)) +{ + struct pico_http_client * client; + + if(!wakeup) + { + pico_err = PICO_ERR_EINVAL; + return HTTP_RETURN_ERROR; + } + + client = pico_zalloc(sizeof(struct pico_http_client)); + if(!client) + { + // memory error + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + + client->wakeup = wakeup; + client->connectionID = (uint16_t)pico_rand() & 0x7FFFu; // negative values mean error, still not good generation + + client->uriKey = pico_zalloc(sizeof(struct pico_http_uri)); + + if(!client->uriKey) + { + pico_err = PICO_ERR_ENOMEM; + pico_free(client); + return HTTP_RETURN_ERROR; + } + + pico_processURI(uri,client->uriKey); + + if(pico_tree_insert(&pico_client_list,client)) + { + // already in + pico_err = PICO_ERR_EEXIST; + pico_free(client->uriKey); + pico_free(client); + return HTTP_RETURN_ERROR; + } + + // dns query + dbg("Querying : %s \n",client->uriKey->host); + pico_dns_client_getaddr(client->uriKey->host, dnsCallback,client); + + // return the connection ID + return client->connectionID; +} + +/* + * API for sending a header to the client. + * + * if hdr == HTTP_HEADER_RAW , then the parameter header + * is sent as it is to client. + * + * if hdr == HTTP_HEADER_DEFAULT, then the parameter header + * is ignored and the library will build the response header + * based on the uri passed when opening the client. + * + */ +int pico_http_client_sendHeader(uint16_t conn, char * header, int hdr) +{ + struct pico_http_client search = {.connectionID = conn}; + struct pico_http_client * http = pico_tree_findKey(&pico_client_list,&search); + int length ; + if(!http) + { + dbg("Client not found !\n"); + return HTTP_RETURN_ERROR; + } + + // the api gives the possibility to the user to build the GET header + // based on the uri passed when opening the client, less headache for the user + if(hdr == HTTP_HEADER_DEFAULT) + { + header = pico_http_client_buildHeader(http->uriKey); + + if(!header) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + } + + length = pico_socket_write(http->sck,(void *)header,strlen(header)+1); + + if(hdr == HTTP_HEADER_DEFAULT) + pico_free(header); + + return length; +} + +/* + * API for reading received data. + * + * This api hides from the user if the transfer-encoding + * was chunked or a full length was provided, in case of + * a chunked transfer encoding will "de-chunk" the data + * and pass it to the user. + */ +int pico_http_client_readData(uint16_t conn, char * data, uint16_t size) +{ + struct pico_http_client dummy = {.connectionID = conn}; + struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); + + if(!client) + { + dbg("Wrong connection id !\n"); + pico_err = PICO_ERR_EINVAL; + return HTTP_RETURN_ERROR; + } + + // for the moment just read the data, do not care if it's chunked or not + if(client->header->transferCoding == HTTP_TRANSFER_FULL) + return pico_socket_read(client->sck,(void *)data,size); + else + { + int lenRead = 0; + + // read the chunk line + if(readChunkLine(client) == HTTP_RETURN_ERROR) + { + dbg("Probably the chunk is malformed or parsed wrong...\n"); + client->wakeup(EV_HTTP_ERROR,client->connectionID); + return HTTP_RETURN_ERROR; + } + + // nothing to read, no use to try + if(client->state != HTTP_READING_BODY) + { + pico_err = PICO_ERR_EAGAIN; + return HTTP_RETURN_OK; + } + + // check if we need more than one chunk + if(size >= client->header->contentLengthOrChunk) + { + // read the rest of the chunk, if chunk is done, proceed to the next chunk + while(lenRead <= size) + { + int tmpLenRead = 0; + + if(client->state == HTTP_READING_BODY) + { + + // if needed truncate the data + tmpLenRead = pico_socket_read(client->sck,data + lenRead, + client->header->contentLengthOrChunk < size-lenRead ? client->header->contentLengthOrChunk : size-lenRead); + + if(tmpLenRead > 0) + { + client->header->contentLengthOrChunk -= tmpLenRead; + } + else if(tmpLenRead < 0) + { + // error on reading + dbg(">>> Error returned pico_socket_read\n"); + pico_err = PICO_ERR_EBUSY; + // return how much data was read until now + return lenRead; + } + } + + lenRead += tmpLenRead; + if(readChunkLine(client) == HTTP_RETURN_ERROR) + { + dbg("Probably the chunk is malformed or parsed wrong...\n"); + client->wakeup(EV_HTTP_ERROR,client->connectionID); + return HTTP_RETURN_ERROR; + } + + if(client->state != HTTP_READING_BODY || !tmpLenRead) break; + + } + } + else + { + // read the data from the chunk + lenRead = pico_socket_read(client->sck,(void *)data,size); + + if(lenRead) + client->header->contentLengthOrChunk -= lenRead; + } + + return lenRead; + } +} + +/* + * API for reading received data. + * + * Reads out the header struct received from server. + */ +struct pico_http_header * pico_http_client_readHeader(uint16_t conn) +{ + struct pico_http_client dummy = {.connectionID = conn}; + struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); + + if(client) + { + return client->header; + } + else + { + // not found + dbg("Wrong connection id !\n"); + pico_err = PICO_ERR_EINVAL; + return NULL; + } +} + +/* + * API for reading received data. + * + * Reads out the uri struct after was processed. + */ +struct pico_http_uri * pico_http_client_readUriData(uint16_t conn) +{ + struct pico_http_client dummy = {.connectionID = conn}; + struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); + // + if(client) + return client->uriKey; + else + { + // not found + dbg("Wrong connection id !\n"); + pico_err = PICO_ERR_EINVAL; + return NULL; + } +} + +/* + * API for reading received data. + * + * Close the client. + */ +int pico_http_client_close(uint16_t conn) +{ + struct pico_http_client * toBeRemoved = NULL; + struct pico_http_client dummy = {}; + dummy.connectionID = conn; + + dbg("Closing the client...\n"); + toBeRemoved = pico_tree_delete(&pico_client_list,&dummy); + if(!toBeRemoved) + { + dbg("Warning ! Element not found ..."); + return HTTP_RETURN_ERROR; + } + + // close socket + if(toBeRemoved->sck) + pico_socket_close(toBeRemoved->sck); + + + if(toBeRemoved->header) + { + // free space used + if(toBeRemoved->header->location) + pico_free(toBeRemoved->header->location); + + pico_free(toBeRemoved->header); + } + + if(toBeRemoved->uriKey) + { + if(toBeRemoved->uriKey->host) + pico_free(toBeRemoved->uriKey->host); + + if(toBeRemoved->uriKey->resource) + pico_free(toBeRemoved->uriKey->resource); + pico_free(toBeRemoved->uriKey); + } + pico_free(toBeRemoved); + + return 0; +} + +/* + * API for reading received data. + * + * Builds a GET header based on the fields on the uri. + */ +char * pico_http_client_buildHeader(const struct pico_http_uri * uriData) +{ + char * header; + char port[6u]; // 6 = max length of a uint16 + \0 + + uint16_t headerSize = HTTP_GET_BASIC_SIZE; + + if(!uriData->host || !uriData->resource || !uriData->port) + { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + // + headerSize += strlen(uriData->host) + strlen(uriData->resource) + pico_itoa(uriData->port,port) + 4u; // 3 = size(CRLF + \0) + header = pico_zalloc(headerSize); + + if(!header) + { + // not enought memory + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + // build the actual header + strcpy(header,"GET "); + strcat(header,uriData->resource); + strcat(header," HTTP/1.1\r\n"); + strcat(header,"Host: "); + strcat(header,uriData->host); + strcat(header,":"); + strcat(header,port); + strcat(header,"\r\n"); + strcat(header,"User-Agent: picoTCP\r\nConnection: close\r\n\r\n"); //? + + return header; +} + +int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header) +{ + char line[HTTP_HEADER_LINE_SIZE]; + char c; + int index = 0; + + // read the first line of the header + while(consumeChar(c)>0 && c!='\r') + { + if(index < HTTP_HEADER_LINE_SIZE) // truncate if too long + line[index++] = c; + } + + consumeChar(c); // consume \n + + // check the integrity of the response + // make sure we have enough characters to include the response code + // make sure the server response starts with HTTP/1. + if(index < RESPONSE_INDEX+2 || isNotHTTPv1(line)) + { + // wrong format of the the response + pico_err = PICO_ERR_EINVAL; + return HTTP_RETURN_ERROR; + } + + // extract response code + header->responseCode = (line[RESPONSE_INDEX] - '0') * 100u + + (line[RESPONSE_INDEX+1] - '0') * 10u + + (line[RESPONSE_INDEX+2] - '0'); + + + if(header->responseCode/100u > 5u) + { + // invalid response type + header->responseCode = 0; + return HTTP_RETURN_ERROR; + } + + dbg("Server response : %d \n",header->responseCode); + + // parse the rest of the header + while(consumeChar(c)>0) + { + if(c==':') + { + // check for interesting fields + + // Location: + if(isLocation(line)) + { + index = 0; + while(consumeChar(c)>0 && c!='\r') + { + line[index++] = c; + } + + // allocate space for the field + header->location = pico_zalloc(index+1u); + if(!header->location) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + + memcpy(header->location,line,index); + + }// Content-Length: + else if(isContentLength(line)) + { + header->contentLengthOrChunk = 0u; + header->transferCoding = HTTP_TRANSFER_FULL; + // consume the first space + consumeChar(c); + while(consumeChar(c)>0 && c!='\r') + { + header->contentLengthOrChunk = header->contentLengthOrChunk*10u + (c-'0'); + } + + }// Transfer-Encoding: chunked + else if(isTransferEncoding(line)) + { + index = 0; + while(consumeChar(c)>0 && c!='\r') + { + line[index++] = c; + } + + if(isChunked(line)) + { + header->contentLengthOrChunk = 0u; + header->transferCoding = HTTP_TRANSFER_CHUNKED; + } + + }// just ignore the line + else + { + while(consumeChar(c)>0 && c!='\r'); + } + + // consume the next one + consumeChar(c); + // reset the index + index = 0u; + } + else if(c=='\r' && !index) + { + // consume the \n + consumeChar(c); + break; + } + else + { + line[index++] = c; + } + } + + if(header->transferCoding == HTTP_TRANSFER_CHUNKED) + { + // read the first chunk + header->contentLengthOrChunk = 0; + + client->state = HTTP_READING_CHUNK_VALUE; + readChunkLine(client); + + } + else + client->state = HTTP_READING_BODY; + + dbg("End of header\n"); + return HTTP_RETURN_OK; + +} + +// an async read of the chunk part, since in theory a chunk can be split in 2 packets +int readChunkLine(struct pico_http_client * client) +{ + char c = 0; + + if(client->header->contentLengthOrChunk==0 && client->state == HTTP_READING_BODY) + { + client->state = HTTP_READING_CHUNK_VALUE; + } + + if(client->state == HTTP_READING_CHUNK_VALUE) + { + while(consumeChar(c)>0 && c!='\r' && c!=';') + { + if(is_hex_digit(c)) + client->header->contentLengthOrChunk = (client->header->contentLengthOrChunk << 4u) + hex_digit_to_dec(c); + else + { + pico_err = PICO_ERR_EINVAL; + // something went wrong + return HTTP_RETURN_ERROR; + } + } + + if(c=='\r' || c==';') client->state = HTTP_READING_CHUNK_TRAIL; + } + + if(client->state == HTTP_READING_CHUNK_TRAIL) + { + + while(consumeChar(c)>0 && c!='\n'); + + if(c=='\n') client->state = HTTP_READING_BODY; + } + + return HTTP_RETURN_OK; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_client.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,49 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + + +#ifndef PICO_HTTP_CLIENT_H_ +#define PICO_HTTP_CLIENT_H_ + +#include "pico_http_util.h" + +/* + * Transfer encodings + */ +#define HTTP_TRANSFER_CHUNKED 1u +#define HTTP_TRANSFER_FULL 0u + +/* + * Parameters for the send header function + */ +#define HTTP_HEADER_RAW 0u +#define HTTP_HEADER_DEFAULT 1u + +/* + * Data types + */ + +struct pico_http_header +{ + uint16_t responseCode; // http response + char * location; // if redirect is reported + uint32_t contentLengthOrChunk; // size of the message + uint8_t transferCoding; // chunked or full + +}; + +int pico_http_client_open(char * uri, void (*wakeup)(uint16_t ev, uint16_t conn)); +int pico_http_client_sendHeader(uint16_t conn, char * header, int hdr); + +struct pico_http_header * pico_http_client_readHeader(uint16_t conn); +struct pico_http_uri * pico_http_client_readUriData(uint16_t conn); +char * pico_http_client_buildHeader(const struct pico_http_uri * uriData); + +int pico_http_client_readData(uint16_t conn, char * data, uint16_t size); +int pico_http_client_close(uint16_t conn); + +#endif /* PICO_HTTP_CLIENT_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_server.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,636 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#include "pico_stack.h" +#include "pico_http_server.h" +#include "pico_tcp.h" +#include "pico_tree.h" +#include "pico_socket.h" + +#ifdef PICO_SUPPORT_HTTP_SERVER + +#define BACKLOG 10 + +#define HTTP_SERVER_CLOSED 0 +#define HTTP_SERVER_LISTEN 1 + +#define HTTP_HEADER_MAX_LINE 256u + +#define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) + +static char returnOkHeader[] = +"HTTP/1.1 200 OK\r\n\ +Host: localhost\r\n\ +Transfer-Encoding: chunked\r\n\ +Connection: close\r\n\ +\r\n"; + +static char returnFailHeader[] = +"HTTP/1.1 404 Not Found\r\n\ +Host: localhost\r\n\ +Connection: close\r\n\ +\r\n\ +<html><body>The resource you requested cannot be found !</body></html>"; + +static char errorHeader[] = +"HTTP/1.1 400 Bad Request\r\n\ +Host: localhost\r\n\ +Connection: close\r\n\ +\r\n\ +<html><body>There was a problem with your request !</body></html>"; + +struct httpServer +{ + uint16_t state; + struct pico_socket * sck; + uint16_t port; + void (*wakeup)(uint16_t ev, uint16_t param); + uint8_t accepted; +}; + +struct httpClient +{ + uint16_t connectionID; + struct pico_socket * sck; + void * buffer; + uint16_t bufferSize; + uint16_t bufferSent; + char * resource; + uint16_t state; +}; + +/* Local states for clients */ +#define HTTP_WAIT_HDR 0 +#define HTTP_WAIT_EOF_HDR 1 +#define HTTP_EOF_HDR 2 +#define HTTP_WAIT_RESPONSE 3 +#define HTTP_WAIT_DATA 4 +#define HTTP_SENDING_DATA 5 +#define HTTP_ERROR 6 +#define HTTP_CLOSED 7 + +static struct httpServer server = {}; + +/* + * Private functions + */ +static int parseRequest(struct httpClient * client); +static int readRemainingHeader(struct httpClient * client); +static void sendData(struct httpClient * client); +static inline int readData(struct httpClient * client); // used only in a place +static inline struct httpClient * findClient(uint16_t conn); + +static int compareClients(void * ka, void * kb) +{ + return ((struct httpClient *)ka)->connectionID - ((struct httpClient *)kb)->connectionID; +} + +PICO_TREE_DECLARE(pico_http_clients,compareClients); + +void httpServerCbk(uint16_t ev, struct pico_socket *s) +{ + struct pico_tree_node * index; + struct httpClient * client = NULL; + uint8_t serverEvent = FALSE; + + // determine the client for the socket + if( s == server.sck) + { + serverEvent = TRUE; + } + else + { + pico_tree_foreach(index,&pico_http_clients) + { + client = index->keyValue; + if(client->sck == s) break; + client = NULL; + } + } + + if(!client && !serverEvent) + { + return; + } + + if (ev & PICO_SOCK_EV_RD) + { + + if(readData(client) == HTTP_RETURN_ERROR) + { + // send out error + client->state = HTTP_ERROR; + pico_socket_write(client->sck,errorHeader,sizeof(errorHeader)-1); + server.wakeup(EV_HTTP_ERROR,client->connectionID); + } + } + + if(ev & PICO_SOCK_EV_WR) + { + if(client->state == HTTP_SENDING_DATA) + { + sendData(client); + } + } + + if(ev & PICO_SOCK_EV_CONN) + { + server.accepted = FALSE; + server.wakeup(EV_HTTP_CON,HTTP_SERVER_ID); + if(!server.accepted) + { + pico_socket_close(s); // reject socket + } + } + + if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) + { + server.wakeup(EV_HTTP_CLOSE,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); + } + + if(ev & PICO_SOCK_EV_ERR) + { + server.wakeup(EV_HTTP_ERROR,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); + } +} + +/* + * API for starting the server. If 0 is passed as a port, the port 80 + * will be used. + */ +int pico_http_server_start(uint16_t port, void (*wakeup)(uint16_t ev, uint16_t conn)) +{ + struct pico_ip4 anything = {}; + + server.port = port ? short_be(port) : short_be(80u); + + if(!wakeup) + { + pico_err = PICO_ERR_EINVAL; + return HTTP_RETURN_ERROR; + } + + server.sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &httpServerCbk); + + if(!server.sck) + { + pico_err = PICO_ERR_EFAULT; + return HTTP_RETURN_ERROR; + } + + if(pico_socket_bind(server.sck , &anything, &server.port)!=0) + { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return HTTP_RETURN_ERROR; + } + + if (pico_socket_listen(server.sck, BACKLOG) != 0) + { + pico_err = PICO_ERR_EADDRINUSE; + return HTTP_RETURN_ERROR; + } + server.wakeup = wakeup; + server.state = HTTP_SERVER_LISTEN; + return HTTP_RETURN_OK; +} + +/* + * API for accepting new connections. This function should be + * called when the event EV_HTTP_CON is triggered, if not called + * when noticed the connection will be considered rejected and the + * socket will be dropped. + * + * Returns the ID of the new connection or a negative value if error. + */ +int pico_http_server_accept(void) +{ + struct pico_ip4 orig; + struct httpClient * client; + uint16_t port; + + client = pico_zalloc(sizeof(struct httpClient)); + if(!client) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + + client->sck = pico_socket_accept(server.sck,&orig,&port); + + if(!client->sck) + { + pico_err = PICO_ERR_ENOMEM; + pico_free(client); + return HTTP_RETURN_ERROR; + } + + server.accepted = TRUE; + // buffer used for async sending + client->state = HTTP_WAIT_HDR; + client->buffer = NULL; + client->bufferSize = 0; + client->connectionID = pico_rand() & 0x7FFF; + + //add element to the tree, if duplicate because the rand + //regenerate + while(pico_tree_insert(&pico_http_clients,client)!=NULL) + client->connectionID = pico_rand() & 0x7FFF; + + return client->connectionID; +} + +/* + * Function used for getting the resource asked by the + * client. It is useful after the request header (EV_HTTP_REQ) + * from client was received, otherwise NULL is returned. + */ +char * pico_http_getResource(uint16_t conn) +{ + struct httpClient * client = findClient(conn); + + if(!client) + return NULL; + else + return client->resource; +} + +/* + * After the resource was asked by the client (EV_HTTP_REQ) + * before doing anything else, the server has to let know + * the client if the resource can be provided or not. + * + * This is controlled via the code parameter which can + * have two values : + * + * HTTP_RESOURCE_FOUND or HTTP_RESOURCE_NOT_FOUND + * + * If a resource is reported not found the 404 header will be sent and the connection + * will be closed , otherwise the 200 header is sent and the user should + * immediately submit data. + * + */ +int pico_http_respond(uint16_t conn, uint16_t code) +{ + struct httpClient * client = findClient(conn); + + if(!client) + { + dbg("Client not found !\n"); + return HTTP_RETURN_ERROR; + } + + if(client->state == HTTP_WAIT_RESPONSE) + { + if(code == HTTP_RESOURCE_FOUND) + { + client->state = HTTP_WAIT_DATA; + return pico_socket_write(client->sck,returnOkHeader,sizeof(returnOkHeader)-1);//remove \0 + } + else + { + int length; + + length = pico_socket_write(client->sck,returnFailHeader,sizeof(returnFailHeader)-1);//remove \0 + pico_socket_close(client->sck); + client->state = HTTP_CLOSED; + return length; + + } + } + else + { + dbg("Bad state for the client \n"); + return HTTP_RETURN_ERROR; + } + +} + +/* + * API used to submit data to the client. + * Server sends data only using Transfer-Encoding: chunked. + * + * With this function the user will submit a data chunk to + * be sent. + * The function will send the chunk size in hex and the rest will + * be sent using WR event from sockets. + * After each transmision EV_HTTP_PROGRESS is called and at the + * end of the chunk EV_HTTP_SENT is called. + * + * To let the client know this is the last chunk, the user + * should pass a NULL buffer. + */ +int pico_http_submitData(uint16_t conn, void * buffer, int len) +{ + + struct httpClient * client = findClient(conn); + char chunkStr[10]; + int chunkCount; + + if(client->state != HTTP_WAIT_DATA) + { + dbg("Client is in a different state than accepted\n"); + return HTTP_RETURN_ERROR; + } + + if(client->buffer) + { + dbg("Already a buffer submited\n"); + return HTTP_RETURN_ERROR; + } + + if(!client) + { + dbg("Wrong connection ID\n"); + return HTTP_RETURN_ERROR; + } + + if(!buffer) + { + len = 0; + } + + if(len > 0) + { + client->buffer = pico_zalloc(len); + if(!client->buffer) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + // taking over the buffer + memcpy(client->buffer,buffer,len); + } + else + client->buffer = NULL; + + + client->bufferSize = len; + client->bufferSent = 0; + + // create the chunk size and send it + if(len > 0) + { + client->state = HTTP_SENDING_DATA; + chunkCount = pico_itoaHex(client->bufferSize,chunkStr); + chunkStr[chunkCount++] = '\r'; + chunkStr[chunkCount++] = '\n'; + pico_socket_write(client->sck,chunkStr,chunkCount); + } + else if(len == 0) + { + dbg("->\n"); + // end of transmision + pico_socket_write(client->sck,"0\r\n\r\n",5u); + // nothing left, close the client + pico_socket_close(client->sck); + client->state = HTTP_CLOSED; + } + + return HTTP_RETURN_OK; +} + +/* + * When EV_HTTP_PROGRESS is triggered you can use this + * function to check the state of the chunk. + */ + +int pico_http_getProgress(uint16_t conn, uint16_t * sent, uint16_t *total) +{ + struct httpClient * client = findClient(conn); + + if(!client) + { + dbg("Wrong connection id !\n"); + return HTTP_RETURN_ERROR; + } + + *sent = client->bufferSent; + *total = client->bufferSize; + + return HTTP_RETURN_OK; +} + +/* + * This API can be used to close either a client + * or the server ( if you pass HTTP_SERVER_ID as a connection ID). + */ +int pico_http_close(uint16_t conn) +{ + // close the server + if(conn == HTTP_SERVER_ID) + { + if(server.state == HTTP_SERVER_LISTEN) + { + struct pico_tree_node * index, * tmp; + // close the server + pico_socket_close(server.sck); + server.sck = NULL; + + // destroy the tree + pico_tree_foreach_safe(index,&pico_http_clients,tmp) + { + struct httpClient * client = index->keyValue; + + if(client->resource) + pico_free(client->resource); + + pico_socket_close(client->sck); + pico_tree_delete(&pico_http_clients,client); + } + + server.state = HTTP_SERVER_CLOSED; + return HTTP_RETURN_OK; + } + else // nothing to close + return HTTP_RETURN_ERROR; + } // close a connection in this case + else + { + + struct httpClient * client = findClient(conn); + + if(!client) + { + dbg("Client not found..\n"); + return HTTP_RETURN_ERROR; + } + + pico_tree_delete(&pico_http_clients,client); + + if(client->resource) + pico_free(client->resource); + + if(client->buffer) + pico_free(client->buffer); + + if(client->state != HTTP_CLOSED || !client->sck) + pico_socket_close(client->sck); + + pico_free(client); + return HTTP_RETURN_OK; + } +} + +// check the integrity of the request +int parseRequest(struct httpClient * client) +{ + char c; + //read first line + consumeChar(c); + if(c == 'G') + { // possible GET + + char line[HTTP_HEADER_MAX_LINE]; + int index = 0; + + line[index] = c; + + // consume the full line + while(consumeChar(c)>0) // read char by char only the first line + { + line[++index] = c; + if(c == '\n') + break; + + if(index >= HTTP_HEADER_MAX_LINE) + { + dbg("Size exceeded \n"); + return HTTP_RETURN_ERROR; + } + } + + // extract the function and the resource + if(memcmp(line,"GET",3u) || line[3u]!=' ' || index < 10u || line[index] !='\n') + { + dbg("Wrong command or wrong ending\n"); + return HTTP_RETURN_ERROR; + } + + // start reading the resource + index = 4u; // go after ' ' + while(line[index]!=' ') + { + if(line[index]=='\n') // no terminator ' ' + { + dbg("No terminator...\n"); + return HTTP_RETURN_ERROR; + } + + index++; + } + + client->resource = pico_zalloc(index - 3u);// allocate without the GET in front + 1 which is \0 + + if(!client) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_RETURN_ERROR; + } + + // copy the resource + memcpy(client->resource,line+4u,index-4u);// copy without the \0 which was already set by pico_zalloc + + client->state = HTTP_WAIT_EOF_HDR; + return HTTP_RETURN_OK; + + } + + return HTTP_RETURN_ERROR; +} + + + +int readRemainingHeader(struct httpClient * client) +{ + char line[100]; + int count = 0; + int len; + + while( (len = pico_socket_read(client->sck,line,100u)) > 0) + { + char c; + int index = 0; + // parse the response + while(index < len) + { + c = line[index++]; + if(c!='\r' && c!='\n') + count++; + if(c=='\n') + { + if(!count) + { + client->state = HTTP_EOF_HDR; + dbg("End of header !\n"); + break; + } + count = 0; + + } + } + } + + return HTTP_RETURN_OK; +} + +void sendData(struct httpClient * client) +{ + int length; + while( client->bufferSent < client->bufferSize && + (length = pico_socket_write(client->sck,client->buffer+client->bufferSent,client->bufferSize-client->bufferSent)) > 0 ) + { + client->bufferSent += length; + server.wakeup(EV_HTTP_PROGRESS,client->connectionID); + } + + if(client->bufferSent == client->bufferSize && client->bufferSize) + { + //send chunk trail + if(pico_socket_write(client->sck,"\r\n",2) > 0) + { + client->state = HTTP_WAIT_DATA; + //free the buffer + pico_free(client->buffer); + client->buffer = NULL; + server.wakeup(EV_HTTP_SENT,client->connectionID); + } + } + +} + +int readData(struct httpClient * client) +{ + if(client->state == HTTP_WAIT_HDR) + { + if(parseRequest(client)<0 || readRemainingHeader(client)<0) + { + return HTTP_RETURN_ERROR; + } + } // continue with this in case the header comes line by line not a big chunk + else if(client->state == HTTP_WAIT_EOF_HDR) + { + if(readRemainingHeader(client)<0 ) + return HTTP_RETURN_ERROR; + } + + if(client->state == HTTP_EOF_HDR) + { + client->state = HTTP_WAIT_RESPONSE; + pico_socket_shutdown(client->sck,PICO_SHUT_RD); + server.wakeup(EV_HTTP_REQ,client->connectionID); + } + + return HTTP_RETURN_OK; +} + +struct httpClient * findClient(uint16_t conn) +{ + struct httpClient dummy = {.connectionID = conn}; + + return pico_tree_findKey(&pico_http_clients,&dummy); +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_server.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,40 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#ifndef PICO_HTTP_SERVER_H_ +#define PICO_HTTP_SERVER_H_ + +#include <stdint.h> +#include "pico_http_util.h" + +// Response codes +#define HTTP_RESOURCE_FOUND 0 +#define HTTP_RESOURCE_NOT_FOUND 1 + +// Generic id for the server +#define HTTP_SERVER_ID 0 + +/* + * Server functions + */ +int pico_http_server_start(uint16_t port, void (*wakeup)(uint16_t ev, uint16_t conn)); +int pico_http_server_accept(void); + +/* + * Client functions + */ +char * pico_http_getResource(uint16_t conn); +int pico_http_getProgress(uint16_t conn, uint16_t * sent, uint16_t *total); + +/* + * Handshake and data functions + */ +int pico_http_respond(uint16_t conn, uint16_t code); +int pico_http_submitData(uint16_t conn, void * buffer, int len); +int pico_http_close(uint16_t conn); + +#endif /* PICO_HTTP_SERVER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_util.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,186 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#include <stdint.h> +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_protocol.h" +#include "pico_http_util.h" + +#define TRUE 1 +#define FALSE 0 + +#define HTTP_PROTO_TOK "http://" +#define HTTP_PROTO_LEN 7u + +#if defined PICO_SUPPORT_HTTP_CLIENT || defined PICO_SUPPORT_HTTP_SERVER + +int pico_itoaHex(uint16_t port, char * ptr) +{ + int size = 0; + int index; + + // transform to from number to string [ in backwards ] + while(port) + { + ptr[size] = ((port & 0xF) < 10) ? ((port & 0xF) + '0') : ((port & 0xF) - 10 + 'a'); + port = port>>4u; //divide by 16 + size++; + } + + // invert positions + for(index=0 ;index < size>>1u ;index++) + { + char c = ptr[index]; + ptr[index] = ptr[size-index-1]; + ptr[size-index-1] = c; + } + ptr[size] = '\0'; + return size; +} + +int pico_itoa(uint16_t port, char * ptr) +{ + int size = 0; + int index; + + // transform to from number to string [ in backwards ] + while(port) + { + ptr[size] = port%10 + '0'; + port = port/10; + size++; + } + + // invert positions + for(index=0 ;index < size>>1u ;index++) + { + char c = ptr[index]; + ptr[index] = ptr[size-index-1]; + ptr[size-index-1] = c; + } + ptr[size] = '\0'; + return size; +} + + +int pico_processURI(const char * uri, struct pico_http_uri * urikey) +{ + + uint16_t lastIndex = 0, index; + + if(!uri || !urikey || uri[0] == '/') + { + pico_err = PICO_ERR_EINVAL; + goto error; + } + + // detect protocol => search for "://" + if(memcmp(uri,HTTP_PROTO_TOK,HTTP_PROTO_LEN) == 0) // could be optimized + { // protocol identified, it is http + urikey->protoHttp = TRUE; + lastIndex = HTTP_PROTO_LEN; + } + else + { + if(strstr(uri,"://")) // different protocol specified + { + urikey->protoHttp = FALSE; + goto error; + } + // no protocol specified, assuming by default it's http + urikey->protoHttp = TRUE; + } + + // detect hostname + index = lastIndex; + while(uri[index] && uri[index]!='/' && uri[index]!=':') index++; + + if(index == lastIndex) + { + // wrong format + urikey->host = urikey->resource = NULL; + urikey->port = urikey->protoHttp = 0u; + + goto error; + } + else + { + // extract host + urikey->host = (char *)pico_zalloc(index-lastIndex+1); + + if(!urikey->host) + { + // no memory + goto error; + } + memcpy(urikey->host,uri+lastIndex,index-lastIndex); + } + + if(!uri[index]) + { + // nothing specified + urikey->port = 80u; + urikey->resource = pico_zalloc(2u); + urikey->resource[0] = '/'; + return HTTP_RETURN_OK; + } + else if(uri[index] == '/') + { + urikey->port = 80u; + } + else if(uri[index] == ':') + { + urikey->port = 0u; + index++; + while(uri[index] && uri[index]!='/') + { + // should check if every component is a digit + urikey->port = urikey->port*10 + (uri[index] - '0'); + index++; + } + } + + // extract resource + if(!uri[index]) + { + urikey->resource = pico_zalloc(2u); + urikey->resource[0] = '/'; + } + else + { + lastIndex = index; + while(uri[index] && uri[index]!='?' && uri[index]!='&' && uri[index]!='#') index++; + urikey->resource = (char *)pico_zalloc(index-lastIndex+1); + + if(!urikey->resource) + { + // no memory + pico_err = PICO_ERR_ENOMEM; + goto error; + } + + memcpy(urikey->resource,uri+lastIndex,index-lastIndex); + } + + return HTTP_RETURN_OK; + + error : + if(urikey->resource) + { + pico_free(urikey->resource); + urikey->resource = NULL; + } + if(urikey->host) + { + pico_free(urikey->host); + urikey->host = NULL; + } + + return HTTP_RETURN_ERROR; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_http_util.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,117 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#ifndef PICO_HTTP_UTIL_H_ +#define PICO_HTTP_UTIL_H_ + +/* Informational reponses */ +#define HTTP_CONTINUE 100u +#define HTTP_SWITCHING_PROTOCOLS 101u +#define HTTP_PROCESSING 102u + +/* Success */ +#define HTTP_OK 200u +#define HTTP_CREATED 201u +#define HTTP_ACCEPTED 202u +#define HTTP_NON_AUTH_INFO 203u +#define HTTP_NO_CONTENT 204u +#define HTTP_RESET_CONTENT 205u +#define HTTP_PARTIAL_CONTENT 206u +#define HTTP_MULTI_STATUS 207u +#define HTTP_ALREADY_REPORTED 208u +#define HTTP_LOW_SPACE 250u +#define HTTP_IM_SPACE 226u + +/* Redirection */ +#define HTTP_MULTI_CHOICE 300u +#define HTTP_MOVED_PERMANENT 301u +#define HTTP_FOUND 302u +#define HTTP_SEE_OTHER 303u +#define HTTP_NOT_MODIFIED 304u +#define HTTP_USE_PROXY 305u +#define HTTP_SWITCH_PROXY 306u +#define HTTP_TEMP_REDIRECT 307u +#define HTTP_PERM_REDIRECT 308u + +/* Client error */ +#define HTTP_BAD_REQUEST 400u +#define HTTP_UNAUTH 401u +#define HTTP_PAYMENT_REQ 402u +#define HTTP_FORBIDDEN 403u +#define HTTP_NOT_FOUND 404u +#define HTTP_METH_NOT_ALLOWED 405u +#define HTTP_NOT_ACCEPTABLE 406u +#define HTTP_PROXY_AUTH_REQ 407u +#define HTTP_REQ_TIMEOUT 408u +#define HTTP_CONFLICT 409u +#define HTTP_GONE 410u +#define HTTP_LEN_REQ 411u +#define HTTP_PRECONDITION_FAIL 412u +#define HTTP_REQ_ENT_LARGE 413u +#define HTTP_URI_TOO_LONG 414u +#define HTTP_UNSUPORTED_MEDIA 415u +#define HTTP_REQ_RANGE_NOK 416u +#define HTTP_EXPECT_FAILED 417u +#define HTTP_TEAPOT 418u +#define HTTP_UNPROC_ENTITY 422u +#define HTTP_LOCKED 423u +#define HTTP_METHOD_FAIL 424u +#define HTTP_UNORDERED 425u +#define HTTP_UPGRADE_REQ 426u +#define HTTP_PRECOND_REQ 428u +#define HTTP_TOO_MANY_REQ 429u +#define HTTP_HEDER_FIELD_LARGE 431u + +/* Server error */ +#define HTTP_INTERNAL_SERVER_ERR 500u +#define HTTP_NOT_IMPLEMENTED 501u +#define HTTP_BAD_GATEWAY 502u +#define HTTP_SERVICE_UNAVAILABLE 503u +#define HTTP_GATEWAY_TIMEOUT 504u +#define HTTP_NOT_SUPPORTED 505u +#define HTTP_SERV_LOW_STORAGE 507u +#define HTTP_LOOP_DETECTED 508u +#define HTTP_NOT_EXTENDED 510u +#define HTTP_NETWORK_AUTH 511u +#define HTTP_PERMISSION_DENIED 550u + +/* Returns used */ +#define HTTP_RETURN_ERROR -1 +#define HTTP_RETURN_OK 0 + +/* List of events - shared between client and server */ +#define EV_HTTP_CON 1u +#define EV_HTTP_REQ 2u +#define EV_HTTP_PROGRESS 4u +#define EV_HTTP_SENT 8u +#define EV_HTTP_CLOSE 16u +#define EV_HTTP_ERROR 32u +#define EV_HTTP_BODY 64u +#define EV_HTTP_DNS 128u + +#ifndef TRUE + #define TRUE 1 +#endif + +#ifndef FALSE + #define FALSE 0 +#endif + +struct pico_http_uri +{ + uint8_t protoHttp; // is the protocol Http ? + char * host; // hostname + uint16_t port; // port if specified + char * resource; // resource , ignoring the other possible parameters +}; + +// used for chunks +int pico_itoaHex(uint16_t port, char * ptr); +int pico_itoa(uint16_t port, char * ptr); +int pico_processURI(const char * uri, struct pico_http_uri * urikey); + +#endif /* PICO_HTTP_UTIL_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_icmp4.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,307 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Daniele Lacamera +*********************************************************************/ + + +#include "pico_icmp4.h" +#include "pico_config.h" +#include "pico_ipv4.h" +#include "pico_eth.h" +#include "pico_device.h" +#include "pico_stack.h" +#include "pico_tree.h" + +/* Queues */ +static struct pico_queue icmp_in = {}; +static struct pico_queue icmp_out = {}; + + +/* Functions */ + +static int pico_icmp4_checksum(struct pico_frame *f) +{ + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + if (!hdr) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + hdr->crc = 0; + hdr->crc = short_be(pico_checksum(hdr, f->transport_len)); + return 0; +} + +#ifdef PICO_SUPPORT_PING +static void ping_recv_reply(struct pico_frame *f); +#endif + +static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + if (hdr->type == PICO_ICMP_ECHO) { + hdr->type = PICO_ICMP_ECHOREPLY; + /* outgoing frames require a f->len without the ethernet header len */ + if (f->dev->eth) + f->len -= PICO_SIZE_ETHHDR; + pico_icmp4_checksum(f); + pico_ipv4_rebound(f); + } else if (hdr->type == PICO_ICMP_UNREACH) { + f->net_hdr = f->transport_hdr + PICO_ICMPHDR_UN_SIZE; + pico_ipv4_unreachable(f, hdr->code); + } else if (hdr->type == PICO_ICMP_ECHOREPLY) { +#ifdef PICO_SUPPORT_PING + ping_recv_reply(f); +#endif + pico_frame_discard(f); + } else { + pico_frame_discard(f); + } + return 0; +} + +static int pico_icmp4_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + dbg("Called %s\n", __FUNCTION__); + return 0; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_icmp4 = { + .name = "icmp4", + .proto_number = PICO_PROTO_ICMP4, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_icmp4_process_in, + .process_out = pico_icmp4_process_out, + .q_in = &icmp_in, + .q_out = &icmp_out, +}; + +static int pico_icmp4_notify(struct pico_frame *f, uint8_t type, uint8_t code) +{ + struct pico_frame *reply; + struct pico_icmp4_hdr *hdr; + struct pico_ipv4_hdr *info; + if (f == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + reply = pico_proto_ipv4.alloc(&pico_proto_ipv4, 8 + sizeof(struct pico_ipv4_hdr) + PICO_ICMPHDR_UN_SIZE); + info = (struct pico_ipv4_hdr*)(f->net_hdr); + hdr = (struct pico_icmp4_hdr *) reply->transport_hdr; + hdr->type = type; + hdr->code = code; + hdr->hun.ih_pmtu.ipm_nmtu = short_be(1500); + hdr->hun.ih_pmtu.ipm_void = 0; + reply->transport_len = 8 + sizeof(struct pico_ipv4_hdr) + PICO_ICMPHDR_UN_SIZE; + reply->payload = reply->transport_hdr + PICO_ICMPHDR_UN_SIZE; + memcpy(reply->payload, f->net_hdr, 8 + sizeof(struct pico_ipv4_hdr)); + pico_icmp4_checksum(reply); + pico_ipv4_frame_push(reply, &info->src, PICO_PROTO_ICMP4); + return 0; +} + +int pico_icmp4_port_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_PORT); +} + +int pico_icmp4_proto_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_PROTOCOL); +} + +int pico_icmp4_dest_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_HOST); +} + +int pico_icmp4_ttl_expired(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_TIME_EXCEEDED, PICO_ICMP_TIMXCEED_INTRANS); +} + +int pico_icmp4_packet_filtered(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + /*Packet Filtered: type 3, code 13 (Communication Administratively Prohibited)*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_FILTER_PROHIB); +} + +/***********************/ +/* Ping implementation */ +/***********************/ +/***********************/ +/***********************/ +/***********************/ + + +#ifdef PICO_SUPPORT_PING + + +struct pico_icmp4_ping_cookie +{ + struct pico_ip4 dst; + uint16_t err; + uint16_t id; + uint16_t seq; + uint16_t size; + int count; + unsigned long timestamp; + int interval; + int timeout; + void (*cb)(struct pico_icmp4_stats*); + +}; + +static int cookie_compare(void *ka, void *kb) +{ + struct pico_icmp4_ping_cookie *a = ka, *b = kb; + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return (a->seq - b->seq); +} + +PICO_TREE_DECLARE(Pings,cookie_compare); + +static int pico_icmp4_send_echo(struct pico_icmp4_ping_cookie *cookie) +{ + struct pico_frame *echo = pico_proto_ipv4.alloc(&pico_proto_ipv4, PICO_ICMPHDR_UN_SIZE + cookie->size); + struct pico_icmp4_hdr *hdr; + + hdr = (struct pico_icmp4_hdr *) echo->transport_hdr; + + hdr->type = PICO_ICMP_ECHO; + hdr->code = 0; + hdr->hun.ih_idseq.idseq_id = short_be(cookie->id); + hdr->hun.ih_idseq.idseq_seq = short_be(cookie->seq); + echo->transport_len = PICO_ICMPHDR_UN_SIZE + cookie->size; + echo->payload = echo->transport_hdr + PICO_ICMPHDR_UN_SIZE; + echo->payload_len = cookie->size; + /* XXX: Fill payload */ + pico_icmp4_checksum(echo); + pico_ipv4_frame_push(echo, &cookie->dst, PICO_PROTO_ICMP4); + return 0; +} + + +static void ping_timeout(unsigned long now, void *arg) +{ + struct pico_icmp4_ping_cookie *cookie = (struct pico_icmp4_ping_cookie *)arg; + if(pico_tree_findKey(&Pings,cookie)){ + if (cookie->err == PICO_PING_ERR_PENDING) { + struct pico_icmp4_stats stats; + stats.dst = cookie->dst; + stats.seq = cookie->seq; + stats.time = 0; + stats.size = cookie->size; + stats.err = PICO_PING_ERR_TIMEOUT; + dbg(" ---- Ping timeout!!!\n"); + cookie->cb(&stats); + } + + pico_tree_delete(&Pings,cookie); + pico_free(cookie); + } +} + +static void next_ping(unsigned long now, void *arg); +static inline void send_ping(struct pico_icmp4_ping_cookie *cookie) +{ + pico_icmp4_send_echo(cookie); + cookie->timestamp = pico_tick; + pico_timer_add(cookie->timeout, ping_timeout, cookie); + if (cookie->seq < cookie->count) + pico_timer_add(cookie->interval, next_ping, cookie); +} + +static void next_ping(unsigned long now, void *arg) +{ + struct pico_icmp4_ping_cookie *newcookie, *cookie = (struct pico_icmp4_ping_cookie *)arg; + + if(pico_tree_findKey(&Pings,cookie)){ + if (cookie->seq < cookie->count) { + newcookie = pico_zalloc(sizeof(struct pico_icmp4_ping_cookie)); + if (!newcookie) + return; + memcpy(newcookie, cookie, sizeof(struct pico_icmp4_ping_cookie)); + newcookie->seq++; + + pico_tree_insert(&Pings,newcookie); + send_ping(newcookie); + } + } +} + + +static void ping_recv_reply(struct pico_frame *f) +{ + struct pico_icmp4_ping_cookie test, *cookie; + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + test.id = short_be(hdr->hun.ih_idseq.idseq_id ); + test.seq = short_be(hdr->hun.ih_idseq.idseq_seq); + + cookie = pico_tree_findKey(&Pings, &test); + if (cookie) { + struct pico_icmp4_stats stats; + cookie->err = PICO_PING_ERR_REPLIED; + stats.dst = cookie->dst; + stats.seq = cookie->seq; + stats.size = cookie->size; + stats.time = pico_tick - cookie->timestamp; + stats.err = cookie->err; + stats.ttl = ((struct pico_ipv4_hdr *)f->net_hdr)->ttl; + if(cookie->cb != NULL) + cookie->cb(&stats); + } else { + dbg("Reply for seq=%d, not found.\n", test.seq); + } +} + +int pico_icmp4_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp4_stats *)) +{ + static uint16_t next_id = 0x91c0; + struct pico_icmp4_ping_cookie *cookie; + + if((dst == NULL) || (interval == 0) || (timeout == 0) || (count == 0)){ + pico_err = PICO_ERR_EINVAL; + return -1; + } + + cookie = pico_zalloc(sizeof(struct pico_icmp4_ping_cookie)); + if (!cookie) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (pico_string_to_ipv4(dst, &cookie->dst.addr) < 0) { + pico_err = PICO_ERR_EINVAL; + pico_free(cookie); + return -1; + } + cookie->seq = 1; + cookie->id = next_id++; + cookie->err = PICO_PING_ERR_PENDING; + cookie->size = size; + cookie->interval = interval; + cookie->timeout = timeout; + cookie->cb = cb; + cookie->count = count; + + pico_tree_insert(&Pings,cookie); + send_ping(cookie); + + return 0; + +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_icmp4.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,149 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_ICMP4 +#define _INCLUDE_PICO_ICMP4 +#include "pico_addressing.h" +#include "pico_protocol.h" + +extern struct pico_protocol pico_proto_icmp4; + +struct __attribute__((packed)) pico_icmp4_hdr { + uint8_t type; + uint8_t code; + uint16_t crc; + + /* hun */ + union { + uint8_t ih_pptr; + struct pico_ip4 ih_gwaddr; + struct { + uint16_t idseq_id; + uint16_t idseq_seq; + } ih_idseq; + uint32_t ih_void; + struct { + uint16_t ipm_void; + uint16_t ipm_nmtu; + } ih_pmtu; + struct { + uint8_t rta_numgw; + uint8_t rta_wpa; + uint16_t rta_lifetime; + } ih_rta; + } hun; + + /* dun */ + union { + struct { + uint32_t ts_otime; + uint32_t ts_rtime; + uint32_t ts_ttime; + } id_ts; + struct { + uint32_t ip_options; + uint32_t ip_data_hi; + uint32_t ip_data_lo; + } id_ip; + struct { + uint32_t ira_addr; + uint32_t ira_pref; + } id_ra; + uint32_t id_mask; + uint8_t id_data[1]; + } dun; +}; + +#define PICO_ICMPHDR_DRY_SIZE 4 +#define PICO_ICMPHDR_UN_SIZE 8 + +#define PICO_ICMP_ECHOREPLY 0 +#define PICO_ICMP_DEST_UNREACH 3 +#define PICO_ICMP_SOURCE_QUENCH 4 +#define PICO_ICMP_REDIRECT 5 +#define PICO_ICMP_ECHO 8 +#define PICO_ICMP_TIME_EXCEEDED 11 +#define PICO_ICMP_PARAMETERPROB 12 +#define PICO_ICMP_TIMESTAMP 13 +#define PICO_ICMP_TIMESTAMPREPLY 14 +#define PICO_ICMP_INFO_REQUEST 15 +#define PICO_ICMP_INFO_REPLY 16 +#define PICO_ICMP_ADDRESS 17 +#define PICO_ICMP_ADDRESSREPLY 18 + + +#define PICO_ICMP_UNREACH 3 +#define PICO_ICMP_SOURCEQUENCH 4 +#define PICO_ICMP_ROUTERADVERT 9 +#define PICO_ICMP_ROUTERSOLICIT 10 +#define PICO_ICMP_TIMXCEED 11 +#define PICO_ICMP_PARAMPROB 12 +#define PICO_ICMP_TSTAMP 13 +#define PICO_ICMP_TSTAMPREPLY 14 +#define PICO_ICMP_IREQ 15 +#define PICO_ICMP_IREQREPLY 16 +#define PICO_ICMP_MASKREQ 17 +#define PICO_ICMP_MASKREPLY 18 + +#define PICO_ICMP_MAXTYPE 18 + + +#define PICO_ICMP_UNREACH_NET 0 +#define PICO_ICMP_UNREACH_HOST 1 +#define PICO_ICMP_UNREACH_PROTOCOL 2 +#define PICO_ICMP_UNREACH_PORT 3 +#define PICO_ICMP_UNREACH_NEEDFRAG 4 +#define PICO_ICMP_UNREACH_SRCFAIL 5 +#define PICO_ICMP_UNREACH_NET_UNKNOWN 6 +#define PICO_ICMP_UNREACH_HOST_UNKNOWN 7 +#define PICO_ICMP_UNREACH_ISOLATED 8 +#define PICO_ICMP_UNREACH_NET_PROHIB 9 +#define PICO_ICMP_UNREACH_HOST_PROHIB 10 +#define PICO_ICMP_UNREACH_TOSNET 11 +#define PICO_ICMP_UNREACH_TOSHOST 12 +#define PICO_ICMP_UNREACH_FILTER_PROHIB 13 +#define PICO_ICMP_UNREACH_HOST_PRECEDENCE 14 +#define PICO_ICMP_UNREACH_PRECEDENCE_CUTOFF 15 + + +#define PICO_ICMP_REDIRECT_NET 0 +#define PICO_ICMP_REDIRECT_HOST 1 +#define PICO_ICMP_REDIRECT_TOSNET 2 +#define PICO_ICMP_REDIRECT_TOSHOST 3 + + +#define PICO_ICMP_TIMXCEED_INTRANS 0 +#define PICO_ICMP_TIMXCEED_REASS 1 + + +#define PICO_ICMP_PARAMPROB_OPTABSENT 1 + +#define PICO_SIZE_ICMP4HDR ((sizeof(struct pico_icmp4_hdr))) + +struct pico_icmp4_stats +{ + struct pico_ip4 dst; + unsigned long size; + unsigned long seq; + unsigned long time; + unsigned long ttl; + int err; +}; + +int pico_icmp4_port_unreachable(struct pico_frame *f); +int pico_icmp4_proto_unreachable(struct pico_frame *f); +int pico_icmp4_dest_unreachable(struct pico_frame *f); +int pico_icmp4_ttl_expired(struct pico_frame *f); +int pico_icmp4_packet_filtered(struct pico_frame *f); + +int pico_icmp4_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp4_stats *)); +#define PICO_PING_ERR_REPLIED 0 +#define PICO_PING_ERR_TIMEOUT 1 +#define PICO_PING_ERR_UNREACH 2 +#define PICO_PING_ERR_PENDING 0xFFFF + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_igmp.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,1120 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +RFC 1112, 2236, 3376, 3569, 3678, 4607 + +Authors: Kristof Roelants (IGMPv3), Simon Maes, Brecht Van Cauwenberghe +*********************************************************************/ + +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_igmp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_addressing.h" +#include "pico_frame.h" +#include "pico_tree.h" +#include "pico_device.h" +#include "pico_socket.h" + +#define igmp_dbg(...) do{}while(0) +//#define igmp_dbg dbg + +/* membership states */ +#define IGMP_STATE_NON_MEMBER (0x0) +#define IGMP_STATE_DELAYING_MEMBER (0x1) +#define IGMP_STATE_IDLE_MEMBER (0x2) + +/* events */ +#define IGMP_EVENT_DELETE_GROUP (0x0) +#define IGMP_EVENT_CREATE_GROUP (0x1) +#define IGMP_EVENT_UPDATE_GROUP (0x2) +#define IGMP_EVENT_QUERY_RECV (0x3) +#define IGMP_EVENT_REPORT_RECV (0x4) +#define IGMP_EVENT_TIMER_EXPIRED (0x5) + +/* message types */ +#define IGMP_TYPE_MEM_QUERY (0x11) +#define IGMP_TYPE_MEM_REPORT_V1 (0x12) +#define IGMP_TYPE_MEM_REPORT_V2 (0x16) +#define IGMP_TYPE_LEAVE_GROUP (0x17) +#define IGMP_TYPE_MEM_REPORT_V3 (0x22) + +/* group record types */ +#define IGMP_MODE_IS_INCLUDE (1) +#define IGMP_MODE_IS_EXCLUDE (2) +#define IGMP_CHANGE_TO_INCLUDE_MODE (3) +#define IGMP_CHANGE_TO_EXCLUDE_MODE (4) +#define IGMP_ALLOW_NEW_SOURCES (5) +#define IGMP_BLOCK_OLD_SOURCES (6) + +/* host flag */ +#define IGMP_HOST_LAST (0x1) +#define IGMP_HOST_NOT_LAST (0x0) + +/* list of timers, counters and their default values */ +#define IGMP_ROBUSTNESS (2) +#define IGMP_QUERY_INTERVAL (125) /* secs */ +#define IGMP_QUERY_RESPONSE_INTERVAL (10) /* secs */ +#define IGMP_STARTUP_QUERY_INTERVAL (IGMPV3_QUERY_INTERVAL / 4) +#define IGMP_STARTUP_QUERY_COUNT (IGMPV3_ROBUSTNESS) +#define IGMP_LAST_MEMBER_QUERY_INTERVAL (1) /* secs */ +#define IGMP_LAST_MEMBER_QUERY_COUNT (IGMPV3_ROBUSTNESS) +#define IGMP_UNSOLICITED_REPORT_INTERVAL (1) /* secs */ +#define IGMP_DEFAULT_MAX_RESPONSE_TIME (100) + +/* custom timers types */ +#define IGMP_TIMER_GROUP_REPORT (1) +#define IGMP_TIMER_V1_QUERIER (2) +#define IGMP_TIMER_V2_QUERIER (3) + +/* IGMP groups */ +#define IGMP_ALL_HOST_GROUP long_be(0xE0000001) /* 224.0.0.1 */ +#define IGMP_ALL_ROUTER_GROUP long_be(0xE0000002) /* 224.0.0.2 */ +#define IGMPV3_ALL_ROUTER_GROUP long_be(0xE0000016) /* 224.0.0.22 */ + +/* misc */ +#define IGMP_TIMER_STOPPED (1) +#define IP_OPTION_ROUTER_ALERT_LEN (4) +#define IGMP_MAX_GROUPS (32) /* max 255 */ + +struct __attribute__((packed)) igmp_message { + uint8_t type; + uint8_t max_resp_time; + uint16_t crc; + uint32_t mcast_group; +}; + +struct __attribute__((packed)) igmpv3_query { + uint8_t type; + uint8_t max_resp_time; + uint16_t crc; + uint32_t mcast_group; + uint8_t rsq; + uint8_t qqic; + uint16_t sources; + uint32_t source_addr[0]; +}; + +struct __attribute__((packed)) igmpv3_group_record { + uint8_t type; + uint8_t aux; + uint16_t sources; + uint32_t mcast_group; + uint32_t source_addr[0]; +}; + +struct __attribute__((packed)) igmpv3_report { + uint8_t type; + uint8_t res0; + uint16_t crc; + uint16_t res1; + uint16_t groups; + struct igmpv3_group_record record[0]; +}; + +struct igmp_parameters { + uint8_t event; + uint8_t state; + uint8_t last_host; + uint8_t filter_mode; + uint8_t max_resp_time; + struct pico_ip4 mcast_link; + struct pico_ip4 mcast_group; + struct pico_tree *MCASTFilter; + struct pico_frame *f; +}; + +struct igmp_timer { + uint8_t type; + uint8_t stopped; + unsigned long start; + unsigned long delay; + struct pico_ip4 mcast_link; + struct pico_ip4 mcast_group; + struct pico_frame *f; + void (*callback)(struct igmp_timer *t); +}; + +/* queues */ +static struct pico_queue igmp_in = {}; +static struct pico_queue igmp_out = {}; + +/* finite state machine caller */ +static int pico_igmp_process_event(struct igmp_parameters *p); + +/* state callback prototype */ +typedef int (*callback)(struct igmp_parameters *); + +/* redblack trees */ +static int igmp_timer_cmp(void *ka, void *kb) +{ + struct igmp_timer *a = ka, *b =kb; + if (a->type < b->type) + return -1; + if (a->type > b->type) + return 1; + 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; +} +PICO_TREE_DECLARE(IGMPTimers, igmp_timer_cmp); + +static int igmp_parameters_cmp(void *ka, void *kb) +{ + struct igmp_parameters *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; +} +PICO_TREE_DECLARE(IGMPParameters, igmp_parameters_cmp); + +static int igmp_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; +} +PICO_TREE_DECLARE(IGMPAllow, igmp_sources_cmp); +PICO_TREE_DECLARE(IGMPBlock, igmp_sources_cmp); + +static struct igmp_parameters *pico_igmp_find_parameter(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group) +{ + struct igmp_parameters test = {0}; + test.mcast_link.addr = mcast_link->addr; + test.mcast_group.addr = mcast_group->addr; + return pico_tree_findKey(&IGMPParameters, &test); +} + +static int pico_igmp_delete_parameter(struct igmp_parameters *p) +{ + if (pico_tree_delete(&IGMPParameters, p)) + pico_free(p); + else + return -1; + + return 0; +} + +static void pico_igmp_timer_expired(unsigned long now, void *arg) +{ + struct igmp_timer *t = NULL, *timer = NULL, test = {0}; + + t = (struct igmp_timer *)arg; + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + igmp_dbg("IGMP: timer expired for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay); + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) { + return; + } + if (timer->stopped == IGMP_TIMER_STOPPED) { + pico_free(t); + return; + } + if (timer->start + timer->delay < PICO_TIME_MS()) { + pico_tree_delete(&IGMPTimers, timer); + if (timer->callback) + timer->callback(timer); + pico_free(timer); + } else { + igmp_dbg("IGMP: restart timer for %08X, delay %lu, new delay %lu\n", t->mcast_group.addr, t->delay, (timer->start + timer->delay) - PICO_TIME_MS()); + pico_timer_add((timer->start + timer->delay) - PICO_TIME_MS(), &pico_igmp_timer_expired, timer); + } + return; +} + +static int pico_igmp_timer_reset(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = {0}; + + igmp_dbg("IGMP: reset timer for %08X, delay %lu\n", t->mcast_group.addr, t->delay); + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) + return -1; + + *timer = *t; + timer->start = PICO_TIME_MS(); + return 0; +} + +static int pico_igmp_timer_start(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = {0}; + + igmp_dbg("IGMP: start timer for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay); + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (timer) + return pico_igmp_timer_reset(t); + + timer = pico_zalloc(sizeof(struct igmp_timer)); + if (!timer) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + *timer = *t; + timer->start = PICO_TIME_MS(); + + pico_tree_insert(&IGMPTimers, timer); + pico_timer_add(timer->delay, &pico_igmp_timer_expired, timer); + return 0; +} + +static int pico_igmp_timer_stop(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = {0}; + + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) + return 0; + + igmp_dbg("IGMP: stop timer for %08X, delay %lu\n", timer->mcast_group.addr, timer->delay); + timer->stopped = IGMP_TIMER_STOPPED; + return 0; +} + +static int pico_igmp_timer_is_running(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = {0}; + + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (timer) + return 1; + return 0; +} + +static struct igmp_timer *pico_igmp_find_timer(uint8_t type, struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group) +{ + struct igmp_timer test = {0}; + + test.type = type; + test.mcast_link = *mcast_link; + test.mcast_group = *mcast_group; + return pico_tree_findKey(&IGMPTimers, &test); +} + +static void pico_igmp_report_expired(struct igmp_timer *t) +{ + struct igmp_parameters *p = NULL; + + p = pico_igmp_find_parameter(&t->mcast_link, &t->mcast_group); + if (!p) + return; + + p->event = IGMP_EVENT_TIMER_EXPIRED; + pico_igmp_process_event(p); +} + +static void pico_igmp_v2querier_expired(struct igmp_timer *t) +{ + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + + link = pico_ipv4_link_by_dev(t->f->dev); + if (!link) + return; + + /* When changing compatibility mode, cancel all pending response + * and retransmission timers. + */ + pico_tree_foreach_safe(index, &IGMPTimers, _tmp) + { + ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED; + pico_tree_delete(&IGMPTimers, index->keyValue); + } + igmp_dbg("IGMP: switch to compatibility mode IGMPv3\n"); + link->mcast_compatibility = PICO_IGMPV3; + return; +} + +static int pico_igmp_is_checksum_valid(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = NULL; + uint8_t ihl = 24, datalen = 0; + + hdr = (struct pico_ipv4_hdr *)f->net_hdr; + ihl = (hdr->vhl & 0x0F) * 4; /* IHL is in 32bit words */ + datalen = short_be(hdr->len) - ihl; + + if (short_be(pico_checksum(f->transport_hdr, datalen)) == 0) + return 1; + igmp_dbg("IGMP: invalid checksum\n"); + return 0; +} + +/* RFC 3376 $7.1 */ +static int pico_igmp_compatibility_mode(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = NULL; + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct igmp_timer t = {0}; + uint8_t ihl = 24, datalen = 0; + + link = pico_ipv4_link_by_dev(f->dev); + if (!link) + return -1; + + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + ihl = (hdr->vhl & 0x0F) * 4; /* IHL is in 32bit words */ + datalen = short_be(hdr->len) - ihl; + igmp_dbg("IGMP: IHL = %u, LEN = %u, OCTETS = %u\n", ihl, short_be(hdr->len), datalen); + + if (datalen > 12) { + /* IGMPv3 query */ + t.type = IGMP_TIMER_V2_QUERIER; + if (pico_igmp_timer_is_running(&t)) { /* IGMPv2 querier present timer still running */ + return -1; + } else { + link->mcast_compatibility = PICO_IGMPV3; + return 0; + } + } else if (datalen == 8) { + struct igmp_message *query = (struct igmp_message *)f->transport_hdr; + if (query->max_resp_time != 0) { + /* IGMPv2 query */ + /* When changing compatibility mode, cancel all pending response + * and retransmission timers. + */ + pico_tree_foreach_safe(index, &IGMPTimers, _tmp) + { + ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED; + pico_tree_delete(&IGMPTimers, index->keyValue); + } + igmp_dbg("IGMP: switch to compatibility mode IGMPv2\n"); + link->mcast_compatibility = PICO_IGMPV2; + t.type = IGMP_TIMER_V2_QUERIER; + t.delay = ((IGMP_ROBUSTNESS * link->mcast_last_query_interval) + IGMP_QUERY_RESPONSE_INTERVAL) * 1000; + t.f = f; + t.callback = pico_igmp_v2querier_expired; + /* only one of this type of timer may exist! */ + pico_igmp_timer_start(&t); + } else { + /* IGMPv1 query, not supported */ + return -1; + } + } else { + /* invalid query, silently ignored */ + return -1; + } + return 0; +} + +static struct igmp_parameters *pico_igmp_analyse_packet(struct pico_frame *f) +{ + struct igmp_message *message = NULL; + struct igmp_parameters *p = NULL; + struct pico_ipv4_link *link = NULL; + struct pico_ip4 mcast_group = {0}; + + link = pico_ipv4_link_by_dev(f->dev); + if (!link) + return NULL; + + /* IGMPv2 and IGMPv3 have a similar structure for the first 8 bytes */ + message = (struct igmp_message *)f->transport_hdr; + mcast_group.addr = message->mcast_group; + p = pico_igmp_find_parameter(&link->address, &mcast_group); + if (!p && mcast_group.addr == 0) { /* general query */ + p = pico_zalloc(sizeof(struct igmp_parameters)); + if (!p) + return NULL; + p->state = IGMP_STATE_NON_MEMBER; + p->mcast_link.addr = link->address.addr; + p->mcast_group.addr = mcast_group.addr; + pico_tree_insert(&IGMPParameters, p); + } else if (!p) { + return NULL; + } + + switch (message->type) { + case IGMP_TYPE_MEM_QUERY: + p->event = IGMP_EVENT_QUERY_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V1: + p->event = IGMP_EVENT_REPORT_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V2: + p->event = IGMP_EVENT_REPORT_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V3: + p->event = IGMP_EVENT_REPORT_RECV; + break; + default: + return NULL; + } + p->max_resp_time = message->max_resp_time; /* if IGMPv3 report this will be 0 (res0 field) */ + p->f = f; + + return p; +} + +static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct igmp_parameters *p = NULL; + + if (!pico_igmp_is_checksum_valid(f)) + goto out; + if (pico_igmp_compatibility_mode(f) < 0) + goto out; + p = pico_igmp_analyse_packet(f); + if (!p) + goto out; + + return pico_igmp_process_event(p); + + out: + pico_frame_discard(f); + return 0; +} + +static int pico_igmp_process_out(struct pico_protocol *self, struct pico_frame *f) { + /* packets are directly transferred to the IP layer by calling pico_ipv4_frame_push */ + return 0; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_igmp = { + .name = "igmp", + .proto_number = PICO_PROTO_IGMP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_igmp_process_in, + .process_out = pico_igmp_process_out, + .q_in = &igmp_in, + .q_out = &igmp_out, +}; + +int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *MCASTFilter, uint8_t state) +{ + struct igmp_parameters *p = NULL; + + if (mcast_group->addr == IGMP_ALL_HOST_GROUP) + return 0; + + p = pico_igmp_find_parameter(mcast_link, mcast_group); + if (!p && state == PICO_IGMP_STATE_CREATE) { + p = pico_zalloc(sizeof(struct igmp_parameters)); + if (!p) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + p->state = IGMP_STATE_NON_MEMBER; + p->mcast_link = *mcast_link; + p->mcast_group = *mcast_group; + pico_tree_insert(&IGMPParameters, p); + } else if (!p) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (state) { + case PICO_IGMP_STATE_CREATE: + p->event = IGMP_EVENT_CREATE_GROUP; + break; + + case PICO_IGMP_STATE_UPDATE: + p->event = IGMP_EVENT_UPDATE_GROUP; + break; + + case PICO_IGMP_STATE_DELETE: + p->event = IGMP_EVENT_DELETE_GROUP; + break; + + default: + return -1; + } + p->filter_mode = filter_mode; + p->MCASTFilter = MCASTFilter; + + return pico_igmp_process_event(p); +} + +static int pico_igmp_send_report(struct igmp_parameters *p, struct pico_frame *f) +{ + struct pico_ip4 dst = {0}; + struct pico_ip4 mcast_group = {0}; + struct pico_ipv4_link *link = NULL; + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + mcast_group.addr = p->mcast_group.addr; + switch (link->mcast_compatibility) { + case PICO_IGMPV2: + if (p->event == IGMP_EVENT_DELETE_GROUP) + dst.addr = IGMP_ALL_ROUTER_GROUP; + else + dst.addr = mcast_group.addr; + break; + + case PICO_IGMPV3: + dst.addr = IGMPV3_ALL_ROUTER_GROUP; + break; + + default: + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + igmp_dbg("IGMP: send membership report on group %08X to %08X\n", mcast_group.addr, dst.addr); + pico_ipv4_frame_push(f, &dst, PICO_PROTO_IGMP); + return 0; +} + +static int pico_igmp_generate_report(struct igmp_parameters *p) +{ + struct pico_ipv4_link *link = NULL; + int i = 0; + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (link->mcast_compatibility) { + case PICO_IGMPV1: + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + + case PICO_IGMPV2: + { + struct igmp_message *report = NULL; + uint8_t report_type = IGMP_TYPE_MEM_REPORT_V2; + if (p->event == IGMP_EVENT_DELETE_GROUP) + report_type = IGMP_TYPE_LEAVE_GROUP; + + p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + sizeof(struct igmp_message)); + p->f->net_len += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_len -= IP_OPTION_ROUTER_ALERT_LEN; + p->f->dev = pico_ipv4_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + report = (struct igmp_message *)p->f->transport_hdr; + report->type = report_type; + report->max_resp_time = IGMP_DEFAULT_MAX_RESPONSE_TIME; + report->mcast_group = p->mcast_group.addr; + + report->crc = 0; + report->crc = short_be(pico_checksum(report, sizeof(struct igmp_message))); + break; + } + case PICO_IGMPV3: + { + struct igmpv3_report *report = NULL; + struct igmpv3_group_record *record = NULL; + struct pico_mcast_group *g = NULL, test = {0}; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_tree *IGMPFilter = NULL; + struct pico_ip4 *source = NULL; + uint8_t record_type = 0; + uint8_t sources = 0; + int len = 0; + + test.mcast_addr = p->mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (p->event == IGMP_EVENT_DELETE_GROUP) { /* "non-existent" state of filter mode INCLUDE and empty source list */ + p->filter_mode = PICO_IP_MULTICAST_INCLUDE; + p->MCASTFilter = NULL; + } + + /* cleanup filters */ + pico_tree_foreach_safe(index, &IGMPAllow, _tmp) + { + pico_tree_delete(&IGMPAllow, index->keyValue); + } + pico_tree_foreach_safe(index, &IGMPBlock, _tmp) + { + pico_tree_delete(&IGMPBlock, index->keyValue); + } + + switch (g->filter_mode) { + + case PICO_IP_MULTICAST_INCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + if (p->event == IGMP_EVENT_DELETE_GROUP) { /* all ADD_SOURCE_MEMBERSHIP had an equivalent DROP_SOURCE_MEMBERSHIP */ + /* TO_IN (B) */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + IGMPFilter = &IGMPAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + } /* else { IGMPAllow stays empty } */ + break; + } + + /* ALLOW (B-A) */ + /* if event is CREATE A will be empty, thus only ALLOW (B-A) has sense */ + if (p->event == IGMP_EVENT_CREATE_GROUP) /* first ADD_SOURCE_MEMBERSHIP */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + else + record_type = IGMP_ALLOW_NEW_SOURCES; + IGMPFilter = &IGMPAllow; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&IGMPAllow, index->keyValue); + if (source) { + pico_tree_delete(&IGMPAllow, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */ + break; + + /* BLOCK (A-B) */ + record_type = IGMP_BLOCK_OLD_SOURCES; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&IGMPBlock, index->keyValue); + if (source) { + pico_tree_delete(&IGMPBlock, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */ + break; + + /* ALLOW (B-A) and BLOCK (A-B) are empty: do not send report (RFC 3376 $5.1) */ + p->f = NULL; + return 0; + + case PICO_IP_MULTICAST_EXCLUDE: + /* TO_EX (B) */ + record_type = IGMP_CHANGE_TO_EXCLUDE_MODE; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + + case PICO_IP_MULTICAST_EXCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + /* TO_IN (B) */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + IGMPFilter = &IGMPAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + } /* else { IGMPAllow stays empty } */ + break; + + case PICO_IP_MULTICAST_EXCLUDE: + /* BLOCK (B-A) */ + record_type = IGMP_BLOCK_OLD_SOURCES; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, p->MCASTFilter) + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&IGMPBlock, index->keyValue); /* B */ + if (source) { + pico_tree_delete(&IGMPBlock, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */ + break; + + /* ALLOW (A-B) */ + record_type = IGMP_ALLOW_NEW_SOURCES; + IGMPFilter = &IGMPAllow; + pico_tree_foreach(index, &g->MCASTSources) + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&IGMPAllow, index->keyValue); /* A */ + if (source) { + pico_tree_delete(&IGMPAllow, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */ + break; + + /* BLOCK (B-A) and ALLOW (A-B) are empty: do not send report (RFC 3376 $5.1) */ + p->f = NULL; + return 0; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + len = sizeof(struct igmpv3_report) + sizeof(struct igmpv3_group_record) + (sources * sizeof(struct pico_ip4)); + p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + len); + p->f->net_len += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_len -= IP_OPTION_ROUTER_ALERT_LEN; + p->f->dev = pico_ipv4_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + report = (struct igmpv3_report *)p->f->transport_hdr; + report->type = IGMP_TYPE_MEM_REPORT_V3; + report->res0 = 0; + report->crc = 0; + report->res1 = 0; + report->groups = short_be(1); + + record = &report->record[0]; + record->type = record_type; + record->aux = 0; + record->sources = short_be(sources); + record->mcast_group = p->mcast_group.addr; + if (!pico_tree_empty(IGMPFilter)) { + i = 0; + pico_tree_foreach(index, IGMPFilter) + { + record->source_addr[i] = ((struct pico_ip4 *)index->keyValue)->addr; + i++; + } + } + report->crc = short_be(pico_checksum(report, len)); + break; + } + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + return 0; +} + +/* stop timer, send leave if flag set */ +static int stslifs(struct igmp_parameters *p) +{ + struct igmp_timer t = {0}; + + igmp_dbg("IGMP: event = leave group | action = stop timer, send leave if flag set\n"); + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_igmp_timer_stop(&t) < 0) + return -1; + + /* always send leave, even if not last host */ + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + pico_igmp_delete_parameter(p); + igmp_dbg("IGMP: new state = non-member\n"); + return 0; +} + +/* send report, set flag, start timer */ +static int srsfst(struct igmp_parameters *p) +{ + struct igmp_timer t = {0}; + struct pico_frame *copy_frame = NULL; + + igmp_dbg("IGMP: event = join group | action = send report, set flag, start timer\n"); + + p->last_host = IGMP_HOST_LAST; + + if (pico_igmp_generate_report(p) < 0) + return -1; + if (!p->f) + return 0; + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* merge report, send report, reset timer (IGMPv3 only) */ +static int mrsrrt(struct igmp_parameters *p) +{ + struct igmp_timer *t = NULL; + struct pico_frame *copy_frame = NULL; + struct pico_ipv4_link *link = NULL; + + igmp_dbg("IGMP: event = update group | action = merge report, send report, reset timer (IGMPv3 only)\n"); + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_IGMPV3) { + igmp_dbg("IGMP: no IGMPv3 compatible router on network\n"); + return -1; + } + + /* XXX: merge with pending report rfc 3376 $5.1 */ + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + t->delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + pico_igmp_timer_reset(t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* send report, start timer (IGMPv3 only) */ +static int srst(struct igmp_parameters *p) +{ + struct igmp_timer t = {0}; + struct pico_frame *copy_frame = NULL; + struct pico_ipv4_link *link = NULL; + + igmp_dbg("IGMP: event = update group | action = send report, start timer (IGMPv3 only)\n"); + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_IGMPV3) { + igmp_dbg("IGMP: no IGMPv3 compatible router on network\n"); + return -1; + } + + if (pico_igmp_generate_report(p) < 0) + return -1; + if (!p->f) + return 0; + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* send leave if flag set */ +static int slifs(struct igmp_parameters *p) +{ + igmp_dbg("IGMP: event = leave group | action = send leave if flag set\n"); + + /* always send leave, even if not last host */ + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + pico_igmp_delete_parameter(p); + igmp_dbg("IGMP: new state = non-member\n"); + return 0; +} + +/* start timer */ +static int st(struct igmp_parameters *p) +{ + struct igmp_timer t = {0}; + + igmp_dbg("IGMP: event = query received | action = start timer\n"); + + if (pico_igmp_generate_report(p) < 0) + return -1; + if (!p->f) + return -1; + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (p->max_resp_time * 100)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* stop timer, clear flag */ +static int stcl(struct igmp_parameters *p) +{ + struct igmp_timer t = {0}; + + igmp_dbg("IGMP: event = report received | action = stop timer, clear flag\n"); + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_igmp_timer_stop(&t) < 0) + return -1; + + p->last_host = IGMP_HOST_NOT_LAST; + p->state = IGMP_STATE_IDLE_MEMBER; + igmp_dbg("IGMP: new state = idle member\n"); + return 0; +} + +/* send report, set flag */ +static int srsf(struct igmp_parameters *p) +{ + igmp_dbg("IGMP: event = timer expired | action = send report, set flag\n"); + + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + p->state = IGMP_STATE_IDLE_MEMBER; + igmp_dbg("IGMP: new state = idle member\n"); + return 0; +} + +/* reset timer if max response time < current timer */ +static int rtimrtct(struct igmp_parameters *p) +{ + struct igmp_timer *t = NULL; + unsigned long time_to_run = 0; + + igmp_dbg("IGMP: event = query received | action = reset timer if max response time < current timer\n"); + + t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + + time_to_run = t->start + t->delay - PICO_TIME_MS(); + if ((p->max_resp_time * 100) < time_to_run) { /* max_resp_time in units of 1/10 seconds */ + t->delay = pico_rand() % (p->max_resp_time * 100); + pico_igmp_timer_reset(t); + } + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +static int discard(struct igmp_parameters *p){ + igmp_dbg("IGMP: ignore and discard frame\n"); + pico_frame_discard(p->f); + return 0; +} + +/* finite state machine table */ +const callback host_membership_diagram_table[3][6] = +{ /* event |Delete Group |Create Group |Update Group |Query Received |Report Received |Timer Expired */ +/* state Non-Member */ { discard, srsfst, srsfst, discard, discard, discard }, +/* state Delaying Member */ { stslifs, mrsrrt, mrsrrt, rtimrtct, stcl, srsf }, +/* state Idle Member */ { slifs, srst, srst, st, discard, discard } +}; + +static int pico_igmp_process_event(struct igmp_parameters *p) +{ + struct pico_tree_node *index = NULL; + struct igmp_parameters *_p = NULL; + + igmp_dbg("IGMP: process event on group address %08X\n", p->mcast_group.addr); + if (p->event == IGMP_EVENT_QUERY_RECV && p->mcast_group.addr == 0) { /* general query */ + pico_tree_foreach(index, &IGMPParameters) { + _p = index->keyValue; + _p->max_resp_time = p->max_resp_time; + _p->event = IGMP_EVENT_QUERY_RECV; + igmp_dbg("IGMP: for each mcast_group = %08X | state = %u\n", _p->mcast_group.addr, _p->state); + host_membership_diagram_table[_p->state][_p->event](_p); + } + } else { + igmp_dbg("IGMP: state = %u (0: non-member - 1: delaying member - 2: idle member)\n", p->state); + host_membership_diagram_table[p->state][p->event](p); + } + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_igmp.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,26 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Kristof Roelants, Simon Maes, Brecht Van Cauwenberghe +*********************************************************************/ + +#ifndef _INCLUDE_PICO_IGMP +#define _INCLUDE_PICO_IGMP + +#define PICO_IGMPV1 1 +#define PICO_IGMPV2 2 +#define PICO_IGMPV3 3 + +#define PICO_IGMP_STATE_CREATE 1 +#define PICO_IGMP_STATE_UPDATE 2 +#define PICO_IGMP_STATE_DELETE 3 + +#define PICO_IGMP_QUERY_INTERVAL 125 + +extern struct pico_protocol pico_proto_igmp; + +int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *MCASTFilter, uint8_t state); +#endif /* _INCLUDE_PICO_IGMP */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_ipfilter.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,267 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Simon Maes +*********************************************************************/ + +#include "pico_ipv4.h" +#include "pico_config.h" +#include "pico_icmp4.h" +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_ipfilter.h" +#include "pico_tcp.h" +#include "pico_udp.h" + + +//#define ipf_dbg dbg +#define ipf_dbg(...) do{}while(0) + +struct filter_node; +typedef int (*func_pntr)(struct filter_node *filter, struct pico_frame *f); + +struct filter_node { + struct pico_device *fdev; + struct filter_node *next_filter; + uint32_t out_addr; + uint32_t out_addr_netmask; + uint32_t in_addr; + uint32_t in_addr_netmask; + uint16_t out_port; + uint16_t in_port; + uint8_t proto; + int8_t priority; + uint8_t tos; + uint8_t filter_id; + func_pntr function_ptr; +}; + +static struct filter_node *head = NULL; +static struct filter_node *tail = NULL; + +/*======================== FUNCTION PNTRS ==========================*/ + +static int fp_accept(struct filter_node *filter, struct pico_frame *f) {return 0;} + +static int fp_priority(struct filter_node *filter, struct pico_frame *f) { + + //TODO do priority-stuff + return 0; +} + +static int fp_reject(struct filter_node *filter, struct pico_frame *f) { +// TODO check first if sender is pico itself or not + ipf_dbg("ipfilter> #reject\n"); + pico_icmp4_packet_filtered(f); + pico_frame_discard(f); + return 1; +} + +static int fp_drop(struct filter_node *filter, struct pico_frame *f) { + + ipf_dbg("ipfilter> # drop\n"); + pico_frame_discard(f); + return 1; +} + +/*============================ API CALLS ============================*/ +int pico_ipv4_filter_add(struct pico_device *dev, uint8_t proto, struct pico_ip4 *out_addr, struct pico_ip4 *out_addr_netmask, struct pico_ip4 *in_addr, struct pico_ip4 *in_addr_netmask, uint16_t out_port, uint16_t in_port, int8_t priority, uint8_t tos, enum filter_action action) +{ + static uint8_t filter_id = 0; + struct filter_node *new_filter; + + if ( !(dev != NULL || proto != 0 || (out_addr != NULL && out_addr->addr != 0U) || (out_addr_netmask != NULL && out_addr_netmask->addr != 0U)|| (in_addr != NULL && in_addr->addr != 0U) || (in_addr_netmask != NULL && in_addr_netmask->addr != 0U)|| out_port != 0 || in_port !=0 || tos != 0 )) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + if ( priority > 10 || priority < -10) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + if (action > 3 || action < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + ipf_dbg("ipfilter> # adding filter\n"); + + new_filter = pico_zalloc(sizeof(struct filter_node)); + if (!head) { + head = tail = new_filter; + } else { + tail->next_filter = new_filter; + tail = new_filter; + } + + new_filter->fdev = dev; + new_filter->proto = proto; + if (out_addr != NULL) + new_filter->out_addr = out_addr->addr; + else + new_filter->out_addr = 0U; + + if (out_addr_netmask != NULL) + new_filter->out_addr_netmask = out_addr_netmask->addr; + else + new_filter->out_addr_netmask = 0U; + + if (in_addr != NULL) + new_filter->in_addr = in_addr->addr; + else + new_filter->in_addr = 0U; + + if (in_addr_netmask != NULL) + new_filter->in_addr_netmask = in_addr_netmask->addr; + else + new_filter->in_addr_netmask = 0U; + + new_filter->out_port = out_port; + new_filter->in_port = in_port; + new_filter->priority = priority; + new_filter->tos = tos; + new_filter->filter_id = filter_id++; + + /*Define filterType_functionPointer here instead of in ipfilter-function, to prevent running multiple times through switch*/ + switch (action) { + case FILTER_ACCEPT: + new_filter->function_ptr = fp_accept; + break; + case FILTER_PRIORITY: + new_filter->function_ptr = fp_priority; + break; + case FILTER_REJECT: + new_filter->function_ptr = fp_reject; + break; + case FILTER_DROP: + new_filter->function_ptr = fp_drop; + break; + default: + ipf_dbg("ipfilter> #unknown filter action\n"); + break; + } + return new_filter->filter_id; +} + +int pico_ipv4_filter_del(uint8_t filter_id) +{ + struct filter_node *work; + struct filter_node *prev; + + if (!tail || !head) { + pico_err = PICO_ERR_EPERM; + return -1; + } + + work = head; + if (work->filter_id == filter_id) { + /*delete filter_node from linked list*/ + head = work->next_filter; + pico_free(work); + return 0; + } + prev = work; + work = work->next_filter; + + while (1) { + if (work->filter_id == filter_id) { + if (work != tail) { + /*delete filter_node from linked list*/ + prev->next_filter = work->next_filter; + pico_free(work); + return 0; + } else { + prev->next_filter = NULL; + pico_free(work); + return 0; + } + } else { + /*check next filter_node*/ + prev = work; + work = work->next_filter; + if (work == tail) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + } +} + +/*================================== CORE FILTER FUNCTIONS ==================================*/ +int match_filter(struct filter_node *filter, struct pico_frame *f) +{ + struct filter_node temp; + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_tcp_hdr *tcp_hdr; + struct pico_udp_hdr *udp_hdr; + + if (!filter|| !f) { + ipf_dbg("ipfilter> ## nullpointer in match filter \n"); + return -1; + } + + temp.fdev = f->dev; + temp.out_addr = ipv4_hdr->dst.addr; + temp.in_addr = ipv4_hdr->src.addr; + if (ipv4_hdr->proto == PICO_PROTO_TCP ) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + temp.out_port = short_be(tcp_hdr->trans.dport); + temp.in_port = short_be(tcp_hdr->trans.sport); + }else if (ipv4_hdr->proto == PICO_PROTO_UDP ) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + temp.out_port = short_be(udp_hdr->trans.dport); + temp.in_port = short_be(udp_hdr->trans.sport); + } else { + temp.out_port = temp.in_port = 0; + } + temp.proto = ipv4_hdr->proto; + temp.priority = f->priority; + temp.tos = ipv4_hdr->tos; + + + + if ( ((filter->fdev == NULL || filter->fdev == temp.fdev) && \ + (filter->in_addr == 0 || ((filter->in_addr_netmask == 0) ? (filter->in_addr == temp.in_addr) : 1)) &&\ + (filter->in_port == 0 || filter->in_port == temp.in_port) &&\ + (filter->out_addr == 0 || ((filter->out_addr_netmask == 0) ? (filter->out_addr == temp.out_addr) : 1)) && \ + (filter->out_port == 0 || filter->out_port == temp.out_port) && \ + (filter->proto == 0 || filter->proto == temp.proto ) &&\ + (filter->priority == 0 || filter->priority == temp.priority ) &&\ + (filter->tos == 0 || filter->tos == temp.tos ) &&\ + (filter->out_addr_netmask == 0 || ((filter->out_addr & filter->out_addr_netmask) == (temp.out_addr & filter->out_addr_netmask)) ) &&\ + (filter->in_addr_netmask == 0 || ((filter->in_addr & filter->in_addr_netmask) == (temp.in_addr & filter->in_addr_netmask)) )\ + ) ) + return 0; + + //No filter match! + ipf_dbg("ipfilter> #no match\n"); + return 1; +} + +int ipfilter(struct pico_frame *f) +{ + struct filter_node *work = head; + + /*return 1 if pico_frame is discarded as result of the filtering, 0 for an incomming packet, -1 for faults*/ + if (!tail || !head) { + return 0; + } + + if ( match_filter(work, f) == 0 ) { + ipf_dbg("ipfilter> # ipfilter match\n"); + /*filter match, execute filter!*/ + return work->function_ptr(work, f); + } + while (tail != work) { + ipf_dbg("ipfilter> next filter..\n"); + work = work->next_filter; + if ( match_filter(work, f) == 0 ) { + ipf_dbg("ipfilter> # ipfilter match\n"); + /*filter match, execute filter!*/ + return work->function_ptr(work, f); + } + } + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_ipfilter.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,31 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Simon Maes +*********************************************************************/ +#ifndef _INCLUDE_PICO_IPFILTER +#define _INCLUDE_PICO_IPFILTER + +#include "pico_device.h" + +enum filter_action { + FILTER_ACCEPT = 0, + FILTER_PRIORITY, + FILTER_REJECT, + FILTER_DROP, +}; + + + +int pico_ipv4_filter_add(struct pico_device *dev, uint8_t proto, + struct pico_ip4 *out_addr, struct pico_ip4 *out_addr_netmask, struct pico_ip4 *in_addr, + struct pico_ip4 *in_addr_netmask, uint16_t out_port, uint16_t in_port, + int8_t priority, uint8_t tos, enum filter_action action); + +int pico_ipv4_filter_del(uint8_t filter_id); + +int ipfilter(struct pico_frame *f); + +#endif /* _INCLUDE_PICO_IPFILTER */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_ipv4.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,1418 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Daniele Lacamera, Markian Yskout +*********************************************************************/ + + +#include "pico_config.h" +#include "pico_ipfilter.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_nat.h" +#include "pico_igmp.h" +#include "pico_tree.h" + +#ifdef PICO_SUPPORT_IPV4 + +#ifdef PICO_SUPPORT_MCAST +# define ip_mcast_dbg(...) do{}while(0) /* so_mcast_dbg in pico_socket.c */ +# define PICO_MCAST_ALL_HOSTS long_be(0xE0000001) /* 224.0.0.1 */ +/* Default network interface for multicast transmission */ +static struct pico_ipv4_link *mcast_default_link = NULL; +#endif +#ifdef PICO_SUPPORT_IPFRAG +# define reassembly_dbg(...) do{}while(0) +#endif + +/* Queues */ +static struct pico_queue in = {}; +static struct pico_queue out = {}; + +/* Functions */ +static int ipv4_route_compare(void *ka, void * kb); + +int pico_ipv4_to_string(char *ipbuf, const uint32_t ip) +{ + const unsigned char *addr = (unsigned char *) &ip; + int i; + + if (!ipbuf) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + for(i = 0; i < 4; i++) + { + if(addr[i] > 99){ + *ipbuf++ = '0' + (addr[i] / 100); + *ipbuf++ = '0' + ((addr[i] % 100) / 10); + *ipbuf++ = '0' + ((addr[i] % 100) % 10); + }else if(addr[i] > 9){ + *ipbuf++ = '0' + (addr[i] / 10); + *ipbuf++ = '0' + (addr[i] % 10); + }else{ + *ipbuf++ = '0' + addr[i]; + } + if(i < 3) + *ipbuf++ = '.'; + } + *ipbuf = '\0'; + + return 0; +} + +int pico_string_to_ipv4(const char *ipstr, uint32_t *ip) +{ + unsigned char buf[4] = {0}; + int cnt = 0; + int p; + + if(!ipstr || !ip) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + while((p = *ipstr++) != 0) + { + if(pico_is_digit(p)){ + buf[cnt] = (10 * buf[cnt]) + (p - '0'); + }else if(p == '.'){ + cnt++; + }else{ + return -1; + } + } + + /* Handle short notation */ + if(cnt == 1){ + buf[3] = buf[1]; + buf[1] = 0; + buf[2] = 0; + }else if (cnt == 2){ + buf[3] = buf[2]; + buf[2] = 0; + }else if(cnt != 3){ + /* String could not be parsed, return error */ + return -1; + } + + *ip = long_from(buf); + + return 0; + +} + +int pico_ipv4_valid_netmask(uint32_t mask) +{ + int cnt = 0; + int end = 0; + int i; + uint32_t mask_swap = long_be(mask); + + /* + * Swap bytes for convenient parsing + * e.g. 0x..f8ff will become 0xfff8.. + * Then, we count the consecutive bits + * + * */ + + for(i = 0; i < 32; i++){ + if((mask_swap << i) & (1 << 31)){ + if(end) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + cnt++; + }else{ + end = 1; + } + } + return cnt; +} + +int pico_ipv4_is_unicast(uint32_t address) +{ + const unsigned char *addr = (unsigned char *) &address; + if((addr[0] & 0xe0) == 0xe0) + return 0; /* multicast */ + + return 1; +} + +int pico_ipv4_is_multicast(uint32_t address) +{ + const unsigned char *addr = (unsigned char *) &address; + if((addr[0] != 0xff) && ((addr[0] & 0xe0) == 0xe0)) + return 1; /* multicast */ + + return 0; +} + +static int pico_ipv4_checksum(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) + return -1; + hdr->crc = 0; + hdr->crc = short_be(pico_checksum(hdr, f->net_len)); + return 0; +} + +#ifdef PICO_SUPPORT_IPFRAG +struct pico_ipv4_fragmented_packet { + uint16_t id; + uint8_t proto; + struct pico_ip4 src; + struct pico_ip4 dst; + uint16_t total_len; + struct pico_tree *t; +}; + +static int pico_ipv4_fragmented_packet_cmp(void *ka, void *kb) +{ + struct pico_ipv4_fragmented_packet *a = ka, *b = kb; + + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return 1; + else { + if (a->proto < b->proto) + return -1; + else if (a->proto > b->proto) + return 1; + else { + if (a->src.addr < b->src.addr) + return -1; + else if (a->src.addr > b->src.addr) + return 1; + else { + if (a->dst.addr < b->dst.addr) + return -1; + else if (a->dst.addr > b->dst.addr) + return 1; + else + return 0; + } + } + } +} + +static int pico_ipv4_fragmented_element_cmp(void *ka, void *kb) +{ + struct pico_frame *frame_a = ka, *frame_b = kb; + struct pico_ipv4_hdr *a, *b; + a = (struct pico_ipv4_hdr *) frame_a->net_hdr; + b = (struct pico_ipv4_hdr *) frame_b->net_hdr; + + if (short_be((a->frag & PICO_IPV4_FRAG_MASK)) < short_be((b->frag & PICO_IPV4_FRAG_MASK))) + return -1; + else if (short_be((a->frag & PICO_IPV4_FRAG_MASK)) > short_be((b->frag & PICO_IPV4_FRAG_MASK))) + return 1; + else + return 0; +} + +PICO_TREE_DECLARE(pico_ipv4_fragmented_tree, pico_ipv4_fragmented_packet_cmp); + +static inline void pico_ipv4_fragmented_cleanup(struct pico_ipv4_fragmented_packet *pfrag) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_frame *f_frag = NULL; + + pico_tree_foreach_safe(index, pfrag->t, _tmp) { + f_frag = index->keyValue; + reassembly_dbg("REASSEMBLY: remove packet with offset %u\n", short_be(((struct pico_ipv4_hdr *)f_frag->net_hdr)->frag) & PICO_IPV4_FRAG_MASK); + pico_tree_delete(pfrag->t, f_frag); + pico_frame_discard(f_frag); + } + pico_tree_delete(&pico_ipv4_fragmented_tree, pfrag); + pico_free(pfrag->t); + pico_free(pfrag); +} +#endif /* PICO_SUPPORT_IPFRAG */ + +#ifdef PICO_SUPPORT_IPFRAG +static inline int pico_ipv4_fragmented_check(struct pico_protocol *self, struct pico_frame **f) +{ + uint8_t *running_pointer = NULL; + uint16_t running_offset = 0; + uint16_t offset = 0; + uint16_t data_len = 0; + struct pico_ipv4_hdr *f_frag_hdr = NULL, *hdr = (struct pico_ipv4_hdr *) (*f)->net_hdr; + struct pico_udp_hdr *udp_hdr = NULL; + struct pico_tcp_hdr *tcp_hdr = NULL; + struct pico_ipv4_fragmented_packet *pfrag = NULL, frag; + struct pico_frame *f_new = NULL, *f_frag = NULL; + struct pico_tree_node *index, *_tmp; + + data_len = short_be(hdr->len) - (*f)->net_len; + offset = short_be(hdr->frag) & PICO_IPV4_FRAG_MASK; + if (short_be(hdr->frag) & PICO_IPV4_MOREFRAG) { + if (!offset) { + reassembly_dbg("REASSEMBLY: first element of a fragmented packet with id %X and offset %u\n", short_be(hdr->id), offset); + if (!pico_tree_empty(&pico_ipv4_fragmented_tree)) { + reassembly_dbg("REASSEMBLY: cleanup tree\n"); + // only one entry allowed in this tree + pfrag = pico_tree_first(&pico_ipv4_fragmented_tree); + pico_ipv4_fragmented_cleanup(pfrag); + } + // add entry in tree for this ID and create secondary tree to contain fragmented elements + pfrag = pico_zalloc(sizeof(struct pico_ipv4_fragmented_packet)); + if (!pfrag) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + pfrag->id = short_be(hdr->id); + pfrag->proto = hdr->proto; + pfrag->src.addr = long_be(hdr->src.addr); + pfrag->dst.addr = long_be(hdr->dst.addr); + pfrag->total_len = short_be(hdr->len) - (*f)->net_len; + pfrag->t = pico_zalloc(sizeof(struct pico_tree)); + if (!pfrag->t) { + pico_free(pfrag); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + pfrag->t->root = &LEAF; + pfrag->t->compare = pico_ipv4_fragmented_element_cmp; + + pico_tree_insert(pfrag->t, *f); + pico_tree_insert(&pico_ipv4_fragmented_tree, pfrag); + return 0; + } + else { + reassembly_dbg("REASSEMBLY: intermediate element of a fragmented packet with id %X and offset %u\n", short_be(hdr->id), offset); + frag.id = short_be(hdr->id); + frag.proto = hdr->proto; + frag.src.addr = long_be(hdr->src.addr); + frag.dst.addr = long_be(hdr->dst.addr); + pfrag = pico_tree_findKey(&pico_ipv4_fragmented_tree, &frag); + if (pfrag) { + pfrag->total_len += (short_be(hdr->len) - (*f)->net_len); + pico_tree_insert(pfrag->t, *f); + return 0; + } else { + reassembly_dbg("REASSEMBLY: silently discard intermediate frame, first packet was lost or disallowed (one fragmented packet at a time)\n"); + pico_frame_discard(*f); + return 0; + } + } + } else if (offset) { + reassembly_dbg("REASSEMBLY: last element of a fragmented packet with id %X and offset %u\n", short_be(hdr->id), offset); + frag.id = short_be(hdr->id); + frag.proto = hdr->proto; + frag.src.addr = long_be(hdr->src.addr); + frag.dst.addr = long_be(hdr->dst.addr); + pfrag = pico_tree_findKey(&pico_ipv4_fragmented_tree, &frag); + if (pfrag) { + pfrag->total_len += (short_be(hdr->len) - (*f)->net_len); + reassembly_dbg("REASSEMBLY: fragmented packet in tree, reassemble packet of %u data bytes\n", pfrag->total_len); + f_new = self->alloc(self, pfrag->total_len); + + f_frag = pico_tree_first(pfrag->t); + reassembly_dbg("REASSEMBLY: copy IP header information len = %lu\n", f_frag->net_len); + f_frag_hdr = (struct pico_ipv4_hdr *)f_frag->net_hdr; + data_len = short_be(f_frag_hdr->len) - f_frag->net_len; + memcpy(f_new->net_hdr, f_frag->net_hdr, f_frag->net_len); + memcpy(f_new->transport_hdr, f_frag->transport_hdr, data_len); + running_pointer = f_new->transport_hdr + data_len; + offset = short_be(f_frag_hdr->frag) & PICO_IPV4_FRAG_MASK; + running_offset = data_len / 8; + pico_tree_delete(pfrag->t, f_frag); + pico_frame_discard(f_frag); + reassembly_dbg("REASSEMBLY: reassembled first packet of %u data bytes, offset = %u next expected offset = %u\n", data_len, offset, running_offset); + + pico_tree_foreach_safe(index, pfrag->t, _tmp) + { + f_frag = index->keyValue; + f_frag_hdr = (struct pico_ipv4_hdr *)f_frag->net_hdr; + data_len = short_be(f_frag_hdr->len) - f_frag->net_len; + memcpy(running_pointer, f_frag->transport_hdr, data_len); + running_pointer += data_len; + offset = short_be(f_frag_hdr->frag) & PICO_IPV4_FRAG_MASK; + if (offset != running_offset) { + reassembly_dbg("REASSEMBLY: error reassembling intermediate packet: offset %u != expected offset %u (missing fragment)\n", offset, running_offset); + pico_ipv4_fragmented_cleanup(pfrag); + return -1; + } + running_offset += (data_len / 8); + pico_tree_delete(pfrag->t, f_frag); + pico_frame_discard(f_frag); + reassembly_dbg("REASSEMBLY: reassembled intermediate packet of %u data bytes, offset = %u next expected offset = %u\n", data_len, offset, running_offset); + } + pico_tree_delete(&pico_ipv4_fragmented_tree, pfrag); + pico_free(pfrag); + + data_len = short_be(hdr->len) - (*f)->net_len; + memcpy(running_pointer, (*f)->transport_hdr, data_len); + offset = short_be(hdr->frag) & PICO_IPV4_FRAG_MASK; + pico_frame_discard(*f); + reassembly_dbg("REASSEMBLY: reassembled last packet of %u data bytes, offset = %u\n", data_len, offset); + + hdr = (struct pico_ipv4_hdr *)f_new->net_hdr; + hdr->len = pfrag->total_len; + hdr->frag = 0; /* flags cleared and no offset */ + hdr->crc = 0; + hdr->crc = short_be(pico_checksum(hdr, f_new->net_len)); + /* Optional, the UDP/TCP CRC should already be correct */ + if (0) { + #ifdef PICO_SUPPORT_TCP + } else if (hdr->proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f_new->transport_hdr; + tcp_hdr->crc = 0; + tcp_hdr->crc = short_be(pico_tcp_checksum_ipv4(f_new)); + #endif + #ifdef PICO_SUPPORT_UDP + } else if (hdr->proto == PICO_PROTO_UDP){ + udp_hdr = (struct pico_udp_hdr *) f_new->transport_hdr; + udp_hdr->crc = 0; + udp_hdr->crc = short_be(pico_udp_checksum_ipv4(f_new)); + #endif + } + reassembly_dbg("REASSEMBLY: packet with id %X reassembled correctly\n", short_be(hdr->id)); + *f = f_new; + return 1; + } else { + reassembly_dbg("REASSEMBLY: silently discard last frame, first packet was lost or disallowed (one fragmented packet at a time)\n"); + pico_frame_discard(*f); + return 0; + } + } else { + return 1; + } +} +#else +static inline int pico_ipv4_fragmented_check(struct pico_protocol *self, struct pico_frame **f) +{ + return 1; +} +#endif /* PICO_SUPPORT_IPFRAG */ + +#ifdef PICO_SUPPORT_CRC +static inline int pico_ipv4_crc_check(struct pico_frame *f) +{ + uint16_t checksum_invalid = 1; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + checksum_invalid = short_be(pico_checksum(hdr, f->net_len)); + if (checksum_invalid) { + dbg("IP: checksum failed!\n"); + pico_frame_discard(f); + return 0; + } + return 1; +} +#else +static inline int pico_ipv4_crc_check(struct pico_frame *f) +{ + return 1; +} +#endif /* PICO_SUPPORT_CRC */ + +static int pico_ipv4_forward(struct pico_frame *f); +#ifdef PICO_SUPPORT_MCAST +static int pico_ipv4_mcast_filter(struct pico_frame *f); +#endif + +static int ipv4_link_compare(void *ka, void *kb) +{ + struct pico_ipv4_link *a = ka, *b =kb; + if (a->address.addr < b->address.addr) + return -1; + if (a->address.addr > b->address.addr) + return 1; + + //zero can be assigned multiple times (e.g. for DHCP) + if (a->dev != NULL && b->dev != NULL && a->address.addr == PICO_IP4_ANY && b->address.addr == PICO_IP4_ANY){ + if (a->dev < b->dev) + return -1; + if (a->dev > b->dev) + return 1; + } + return 0; +} + +PICO_TREE_DECLARE(Tree_dev_link, ipv4_link_compare); + +static int pico_ipv4_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + uint8_t option_len = 0; + int ret = 0; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_ipv4_link test = {.address = {.addr = PICO_IP4_ANY}, .dev = NULL}; + + /* NAT needs transport header information */ + if(((hdr->vhl) & 0x0F )> 5){ + option_len = 4*(((hdr->vhl) & 0x0F)-5); + } + f->transport_hdr = ((uint8_t *)f->net_hdr) + PICO_SIZE_IP4HDR + option_len; + f->transport_len = short_be(hdr->len) - PICO_SIZE_IP4HDR - option_len; + f->net_len = PICO_SIZE_IP4HDR + option_len; + +#ifdef PICO_SUPPORT_IPFILTER + if (ipfilter(f)) { + /*pico_frame is discarded as result of the filtering*/ + return 0; + } +#endif + + /* ret == 1 indicates to continue the function */ + ret = pico_ipv4_crc_check(f); + if (ret < 1) + return ret; + ret = pico_ipv4_fragmented_check(self, &f); + if (ret < 1) + return ret; + +#ifdef PICO_SUPPORT_MCAST + /* Multicast address in source, discard quietly */ + if (pico_ipv4_is_multicast(hdr->src.addr)) { + ip_mcast_dbg("MCAST: ERROR multicast address %08X in source address\n", hdr->src.addr); + pico_frame_discard(f); + return 0; + } +#endif + if (hdr->frag & 0x80) { + pico_frame_discard(f); //RFC 3514 + return 0; + } + if (0) { +#ifdef PICO_SUPPORT_UDP + } else if (pico_ipv4_is_broadcast(hdr->dst.addr) && (hdr->proto == PICO_PROTO_UDP)) { + /* Receiving UDP broadcast datagram */ + f->flags |= PICO_FRAME_FLAG_BCAST; + pico_enqueue(pico_proto_udp.q_in, f); +#endif + } else if (pico_ipv4_is_multicast(hdr->dst.addr)) { +#ifdef PICO_SUPPORT_MCAST + /* Receiving UDP multicast datagram TODO set f->flags? */ + if (hdr->proto == PICO_PROTO_IGMP) { + ip_mcast_dbg("MCAST: received IGMP message\n"); + pico_transport_receive(f, PICO_PROTO_IGMP); + } else if ((pico_ipv4_mcast_filter(f) == 0) && (hdr->proto == PICO_PROTO_UDP)) { + pico_enqueue(pico_proto_udp.q_in, f); + } else { + pico_frame_discard(f); + } +#endif + } else if (pico_ipv4_link_find(&hdr->dst)) { + if (pico_ipv4_nat_isenabled_in(f) == 0) { /* if NAT enabled (dst port registerd), do NAT */ + if(pico_ipv4_nat(f, hdr->dst) != 0) { + return -1; + } + pico_ipv4_forward(f); /* Local packet became forward packet after NAT */ + } else { /* no NAT so enqueue to next layer */ + pico_transport_receive(f, hdr->proto); + } + } else if (pico_tree_findKey(&Tree_dev_link, &test)){ +#ifdef PICO_SUPPORT_UDP + //address of this device is apparently 0.0.0.0; might be a DHCP packet + pico_enqueue(pico_proto_udp.q_in, f); +#endif + } else { + /* Packet is not local. Try to forward. */ + if (pico_ipv4_forward(f) != 0) { + pico_frame_discard(f); + } + } + return 0; +} + +PICO_TREE_DECLARE(Routes, ipv4_route_compare); + + +static int pico_ipv4_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + f->start = (uint8_t*) f->net_hdr; + #ifdef PICO_SUPPORT_IPFILTER + if (ipfilter(f)) { + /*pico_frame is discarded as result of the filtering*/ + return 0; + } + #endif + return pico_sendto_dev(f); +} + + +static struct pico_frame *pico_ipv4_alloc(struct pico_protocol *self, int size) +{ + struct pico_frame *f = pico_frame_alloc(size + PICO_SIZE_IP4HDR + PICO_SIZE_ETHHDR); + if (!f) + return NULL; + f->datalink_hdr = f->buffer; + f->net_hdr = f->buffer + PICO_SIZE_ETHHDR; + f->net_len = PICO_SIZE_IP4HDR; + f->transport_hdr = f->net_hdr + PICO_SIZE_IP4HDR; + f->transport_len = size; + f->len = size + PICO_SIZE_IP4HDR; + return f; +} + +static int pico_ipv4_frame_sock_push(struct pico_protocol *self, struct pico_frame *f); + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_ipv4 = { + .name = "ipv4", + .proto_number = PICO_PROTO_IPV4, + .layer = PICO_LAYER_NETWORK, + .alloc = pico_ipv4_alloc, + .process_in = pico_ipv4_process_in, + .process_out = pico_ipv4_process_out, + .push = pico_ipv4_frame_sock_push, + .q_in = &in, + .q_out = &out, +}; + +struct pico_ipv4_route +{ + struct pico_ip4 dest; + struct pico_ip4 netmask; + struct pico_ip4 gateway; + struct pico_ipv4_link *link; + uint32_t metric; +}; + + +static int ipv4_route_compare(void *ka, void * kb) +{ + struct pico_ipv4_route *a = ka, *b = kb; + + /* Routes are sorted by (host side) netmask len, then by addr, then by metric. */ + if (long_be(a->netmask.addr) < long_be(b->netmask.addr)) + return -1; + + if (long_be(a->netmask.addr) > long_be(b->netmask.addr)) + return 1; + + if (a->dest.addr < b->dest.addr) + return -1; + + if (a->dest.addr > b->dest.addr) + return 1; + + if (a->metric < b->metric) + return -1; + + if (a->metric > b->metric) + return 1; + + return 0; +} + +static struct pico_ipv4_route *route_find(struct pico_ip4 *addr) +{ + struct pico_ipv4_route *r; + struct pico_tree_node * index; + + if(addr->addr != PICO_IP4_BCAST) + { + pico_tree_foreach_reverse(index, &Routes) { + r = index->keyValue; + if ((addr->addr & (r->netmask.addr)) == (r->dest.addr)) { + return r; + } + } + } + else + { + r = pico_tree_first(&Routes); + if(!r->netmask.addr) + { + return r; + } + else + { + dbg("WARNING: no default route for a global broadcast found\n"); + } + } + + return NULL; +} + +struct pico_ip4 pico_ipv4_route_get_gateway(struct pico_ip4 *addr) +{ + struct pico_ip4 nullip; + struct pico_ipv4_route *route; + nullip.addr = 0U; + + if(!addr) { + pico_err = PICO_ERR_EINVAL; + return nullip; + } + + route = route_find(addr); + if (!route) { + pico_err = PICO_ERR_EHOSTUNREACH; + return nullip; + } + else + return route->gateway; +} + +struct pico_ip4 *pico_ipv4_source_find(struct pico_ip4 *dst) +{ + struct pico_ip4 *myself = NULL; + struct pico_ipv4_route *rt; + + if(!dst) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + rt = route_find(dst); + if (rt) { + myself = &rt->link->address; + } else + pico_err = PICO_ERR_EHOSTUNREACH; + return myself; +} + + +#ifdef PICO_SUPPORT_MCAST +/* link + * | + * MCASTGroups + * | | | + * ------------ | ------------ + * | | | + * MCASTSources MCASTSources MCASTSources + * | | | | | | | | | | | | + * S S S S S S S S S S S S + * + * MCASTGroups: RBTree(mcast_group) + * MCASTSources: RBTree(source) + */ +static int ipv4_mcast_groups_cmp(void * ka, void * kb) +{ + struct pico_mcast_group *a = ka, *b = kb; + if (a->mcast_addr.addr < b->mcast_addr.addr) { + return -1; + } else if (a->mcast_addr.addr > b->mcast_addr.addr) { + return 1; + } else { + return 0; + } +} + +static int ipv4_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 void pico_ipv4_mcast_print_groups(struct pico_ipv4_link *mcast_link) +{ + uint16_t i = 0; + struct pico_mcast_group __attribute__ ((unused)) *g = NULL; + struct pico_ip4 __attribute__ ((unused)) *source = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + + ip_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); + ip_mcast_dbg("+ MULTICAST list interface %-16s +\n", mcast_link->dev->name); + ip_mcast_dbg("+---------------------------------------------------------------------------------+\n"); + ip_mcast_dbg("+ nr | interface | host group | reference count | filter mode | source +\n"); + ip_mcast_dbg("+---------------------------------------------------------------------------------+\n"); + + pico_tree_foreach(index, mcast_link->MCASTGroups) + { + g = index->keyValue; + ip_mcast_dbg("+ %04d | %16s | %08X | %05u | %u | %8s +\n", i, mcast_link->dev->name, g->mcast_addr.addr, g->reference_count, g->filter_mode, ""); + pico_tree_foreach(index2, &g->MCASTSources) + { + source = index2->keyValue; + ip_mcast_dbg("+ %4s | %16s | %8s | %5s | %s | %08X +\n", "", "", "", "", "", source->addr); + } + i++; + } + ip_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); +} + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + struct pico_mcast_group *g = NULL, test = {0}; + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip4 *source = NULL; + + if (mcast_link) + link = pico_ipv4_link_get(mcast_link); + else + link = mcast_default_link; + + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (reference_count) + g->reference_count++; + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_UPDATE); + } else { + g = pico_zalloc(sizeof(struct pico_mcast_group)); + if (!g) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + /* "non-existent" state of filter mode INCLUDE and empty source list */ + g->filter_mode = PICO_IP_MULTICAST_INCLUDE; + g->reference_count = 1; + g->mcast_addr = *mcast_group; + g->MCASTSources.root = &LEAF; + g->MCASTSources.compare = ipv4_mcast_sources_cmp; + pico_tree_insert(link->MCASTGroups, g); + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_CREATE); + } + + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) + { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + pico_free(source); + } + /* insert new filter */ + if (MCASTFilter) { + pico_tree_foreach(index, MCASTFilter) + { + source = pico_zalloc(sizeof(struct pico_ip4)); + if (!source) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + source->addr = ((struct pico_ip4 *)index->keyValue)->addr; + pico_tree_insert(&g->MCASTSources, source); + } + } + g->filter_mode = filter_mode; + + pico_ipv4_mcast_print_groups(link); + return 0; +} + +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + + struct pico_mcast_group *g = NULL, test = {0}; + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip4 *source = NULL; + + if (mcast_link) + link = pico_ipv4_link_get(mcast_link); + else + link = mcast_default_link; + + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + if (reference_count && (--(g->reference_count) < 1)) { + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_DELETE); + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) + { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + pico_free(source); + } + pico_tree_delete(link->MCASTGroups, g); + pico_free(g); + } else { + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_UPDATE); + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) + { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + pico_free(source); + } + /* insert new filter */ + if (MCASTFilter) { + pico_tree_foreach(index, MCASTFilter) + { + source = pico_zalloc(sizeof(struct pico_ip4)); + if (!source) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + source->addr = ((struct pico_ip4 *)index->keyValue)->addr; + pico_tree_insert(&g->MCASTSources, source); + } + } + g->filter_mode = filter_mode; + } + } + + pico_ipv4_mcast_print_groups(link); + return 0; +} + +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void) +{ + return mcast_default_link; +} + +static int pico_ipv4_mcast_filter(struct pico_frame *f) +{ + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + struct pico_mcast_group *g = NULL, test = {0}; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + test.mcast_addr = hdr->dst; + + pico_tree_foreach(index, &Tree_dev_link) + { + link = index->keyValue; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (f->dev == link->dev) { + ip_mcast_dbg("MCAST: IP %08X is group member of current link %s\n", hdr->dst.addr, f->dev->name); + /* perform source filtering */ + switch (g->filter_mode) + { + case PICO_IP_MULTICAST_INCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) + { + if (hdr->src.addr == ((struct pico_ip4 *)index2->keyValue)->addr) { + ip_mcast_dbg("MCAST: IP %08X in included interface source list\n", hdr->src.addr); + return 0; + } + } + ip_mcast_dbg("MCAST: IP %08X NOT in included interface source list\n", hdr->src.addr); + return -1; + break; + + case PICO_IP_MULTICAST_EXCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) + { + if (hdr->src.addr == ((struct pico_ip4 *)index2->keyValue)->addr) { + ip_mcast_dbg("MCAST: IP %08X in excluded interface source list\n", hdr->src.addr); + return -1; + } + } + ip_mcast_dbg("MCAST: IP %08X NOT in excluded interface source list\n", hdr->src.addr); + return 0; + break; + + default: + return -1; + break; + } + } else { + ip_mcast_dbg("MCAST: IP %08X is group member of different link %s\n", hdr->dst.addr, link->dev->name); + } + } else { + ip_mcast_dbg("MCAST: IP %08X is not a group member of link %s\n", hdr->dst.addr, f->dev->name); + } + } + return -1; +} + +#else + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return NULL; +} +#endif /* PICO_SUPPORT_MCAST */ + +int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t proto) +{ + + struct pico_ipv4_route *route; + struct pico_ipv4_link *link; + struct pico_ipv4_hdr *hdr; + uint8_t ttl = PICO_IPV4_DEFAULT_TTL; + uint8_t vhl = 0x45; /* version 4, header length 20 */ + static uint16_t ipv4_progressive_id = 0x91c0; +#ifdef PICO_SUPPORT_MCAST + struct pico_tree_node *index; +#endif + + if(!f || !dst) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) { + dbg("IP header error\n"); + pico_err = PICO_ERR_EINVAL; + goto drop; + } + + if (dst->addr == 0) { + dbg("IP destination addr error\n"); + pico_err = PICO_ERR_EINVAL; + goto drop; + } + + route = route_find(dst); + if (!route) { + dbg("Route to %08x not found.\n", long_be(dst->addr)); + pico_err = PICO_ERR_EHOSTUNREACH; + goto drop; + } else { + link = route->link; +#ifdef PICO_SUPPORT_MCAST + if (pico_ipv4_is_multicast(dst->addr)) { /* if multicast */ + switch (proto) { + case PICO_PROTO_UDP: + if(pico_udp_get_mc_ttl(f->sock, &ttl) < 0) + ttl = PICO_IP_DEFAULT_MULTICAST_TTL; + break; + case PICO_PROTO_IGMP: + vhl = 0x46; /* header length 24 */ + ttl = 1; + /* router alert (RFC 2113) */ + hdr->options[0] = 0x94; + hdr->options[1] = 0x04; + hdr->options[2] = 0x00; + hdr->options[3] = 0x00; + if (f->dev && link->dev != f->dev) { /* default link is not requested link */ + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + if (link->dev == f->dev) + break; + } + } + break; + default: + ttl = PICO_IPV4_DEFAULT_TTL; + } + } +#endif + } + + hdr->vhl = vhl; + hdr->len = short_be(f->transport_len + f->net_len); + if (f->transport_hdr != f->payload) + ipv4_progressive_id++; + hdr->id = short_be(ipv4_progressive_id); + hdr->dst.addr = dst->addr; + hdr->src.addr = link->address.addr; + hdr->ttl = ttl; + hdr->proto = proto; + hdr->frag = short_be(PICO_IPV4_DONTFRAG); +#ifdef PICO_SUPPORT_IPFRAG +# ifdef PICO_SUPPORT_UDP + if (proto == PICO_PROTO_UDP) { + /* first fragment, can not use transport_len to calculate IP length */ + if (f->transport_hdr != f->payload) + hdr->len = short_be(f->payload_len + sizeof(struct pico_udp_hdr) + f->net_len); + /* set fragmentation flags and offset calculated in socket layer */ + hdr->frag = f->frag; + } +# endif /* PICO_SUPPORT_UDP */ +#endif /* PICO_SUPPORT_IPFRAG */ + pico_ipv4_checksum(f); + + if (f->sock && f->sock->dev){ + //if the socket has its device set, use that (currently used for DHCP) + f->dev = f->sock->dev; + } else { + f->dev = link->dev; + } + +#ifdef PICO_SUPPORT_MCAST + if (pico_ipv4_is_multicast(hdr->dst.addr)) { + struct pico_frame *cpy; + /* Sending UDP multicast datagram, am I member? If so, loopback copy */ + if ((proto != PICO_PROTO_IGMP) && (pico_ipv4_mcast_filter(f) == 0)) { + ip_mcast_dbg("MCAST: sender is member of group, loopback copy\n"); + cpy = pico_frame_copy(f); + pico_enqueue(&in, cpy); + } + } +#endif + + if(pico_ipv4_link_get(&hdr->dst)){ + //it's our own IP + return pico_enqueue(&in, f); + }else{ + /* TODO: Check if there are members subscribed here */ + return pico_enqueue(&out, f); + } + +drop: + pico_frame_discard(f); + return -1; +} + + +static int pico_ipv4_frame_sock_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_ip4 *dst; + struct pico_remote_duple *remote_duple = (struct pico_remote_duple *) f->info; + if (!f->sock) { + pico_frame_discard(f); + return -1; + } + + if (remote_duple) { + dst = &remote_duple->remote_addr.ip4; + } else { + dst = &f->sock->remote_addr.ip4; + } + + return pico_ipv4_frame_push(f, dst, f->sock->proto->proto_number); +} + + +#ifdef DEBUG_ROUTE +static void dbg_route(void) +{ + struct pico_ipv4_route *r; + struct pico_tree_node * index; + pico_tree_foreach(index,&Routes){ + r = index->keyValue; + dbg("Route to %08x/%08x, gw %08x, dev: %s, metric: %d\n", r->dest.addr, r->netmask.addr, r->gateway.addr, r->link->dev->name, r->metric); + } +} +#else +#define dbg_route() do{ }while(0) +#endif + +int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) +{ + struct pico_ipv4_route test, *new; + test.dest.addr = address.addr; + test.netmask.addr = netmask.addr; + test.metric = metric; + + if(pico_tree_findKey(&Routes,&test)){ + pico_err = PICO_ERR_EINVAL; + return -1; + } + + new = pico_zalloc(sizeof(struct pico_ipv4_route)); + if (!new) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + new->dest.addr = address.addr; + new->netmask.addr = netmask.addr; + new->gateway.addr = gateway.addr; + new->metric = metric; + if (gateway.addr == 0) { + /* No gateway provided, use the link */ + new->link = link; + } else { + struct pico_ipv4_route *r = route_find(&gateway); + if (!r ) { /* Specified Gateway is unreachable */ + pico_err = PICO_ERR_EHOSTUNREACH; + pico_free(new); + return -1; + } + if (r->gateway.addr) { /* Specified Gateway is not a neighbor */ + pico_err = PICO_ERR_ENETUNREACH; + pico_free(new); + return -1; + } + new->link = r->link; + } + if (!new->link) { + pico_err = PICO_ERR_EINVAL; + pico_free(new); + return -1; + } + + pico_tree_insert(&Routes,new); + dbg_route(); + return 0; +} + +int pico_ipv4_route_del(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) +{ + struct pico_ipv4_route test, *found; + if (!link) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + test.dest.addr = address.addr; + test.netmask.addr = netmask.addr; + test.metric = metric; + + found = pico_tree_findKey(&Routes,&test); + if (found) { + + pico_tree_delete(&Routes,found); + pico_free(found); + + dbg_route(); + return 0; + } + pico_err = PICO_ERR_EINVAL; + return -1; +} + + +int pico_ipv4_link_add(struct pico_device *dev, struct pico_ip4 address, struct pico_ip4 netmask) +{ + struct pico_ipv4_link test, *new; + struct pico_ip4 network, gateway; + char ipstr[30]; + + if(!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + test.address.addr = address.addr; + test.netmask.addr = netmask.addr; + test.dev = dev; + /** XXX: Valid netmask / unicast address test **/ + + if(pico_tree_findKey(&Tree_dev_link, &test)) { + dbg("IPv4: Trying to assign an invalid address (in use)\n"); + pico_err = PICO_ERR_EADDRINUSE; + return -1; + } + + /** XXX: Check for network already in use (e.g. trying to assign 10.0.0.1/24 where 10.1.0.1/8 is in use) **/ + new = pico_zalloc(sizeof(struct pico_ipv4_link)); + if (!new) { + dbg("IPv4: Out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + new->address.addr = address.addr; + new->netmask.addr = netmask.addr; + new->dev = dev; +#ifdef PICO_SUPPORT_MCAST + new->MCASTGroups = pico_zalloc(sizeof(struct pico_tree)); + if (!new->MCASTGroups) { + pico_free(new); + dbg("IPv4: Out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + new->MCASTGroups->root = &LEAF; + new->MCASTGroups->compare = ipv4_mcast_groups_cmp; + new->mcast_compatibility = PICO_IGMPV3; /* default RFC 3376 $7.2.1 */ + new->mcast_last_query_interval = PICO_IGMP_QUERY_INTERVAL; +#endif + + pico_tree_insert(&Tree_dev_link, new); +#ifdef PICO_SUPPORT_MCAST + do { + struct pico_ip4 mcast_all_hosts, mcast_addr, mcast_nm, mcast_gw; + if (!mcast_default_link) { + mcast_addr.addr = long_be(0xE0000000); /* 224.0.0.0 */ + mcast_nm.addr = long_be(0xF0000000); /* 15.0.0.0 */ + mcast_gw.addr = long_be(0x00000000); + mcast_default_link = new; + pico_ipv4_route_add(mcast_addr, mcast_nm, mcast_gw, 1, new); + } + mcast_all_hosts.addr = PICO_MCAST_ALL_HOSTS; + pico_ipv4_mcast_join(&address, &mcast_all_hosts, 1, PICO_IP_MULTICAST_EXCLUDE, NULL); + } while(0); +#endif + + network.addr = address.addr & netmask.addr; + gateway.addr = 0U; + pico_ipv4_route_add(network, netmask, gateway, 1, new); + pico_ipv4_to_string(ipstr, new->address.addr); + dbg("Assigned ipv4 %s to device %s\n", ipstr, new->dev->name); + return 0; +} + + +int pico_ipv4_link_del(struct pico_device *dev, struct pico_ip4 address) +{ + struct pico_ipv4_link test, *found; + struct pico_ip4 network; + + if(!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + test.address.addr = address.addr; + test.dev = dev; + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return -1; + } + + network.addr = found->address.addr & found->netmask.addr; + pico_ipv4_route_del(network, found->netmask,pico_ipv4_route_get_gateway(&found->address), 1, found); +#ifdef PICO_SUPPORT_MCAST + do { + struct pico_ip4 mcast_all_hosts, mcast_addr, mcast_nm, mcast_gw; + struct pico_mcast_group *g = NULL; + struct pico_tree_node * index, * _tmp; + if (found == mcast_default_link) { + mcast_addr.addr = long_be(0xE0000000); /* 224.0.0.0 */ + mcast_nm.addr = long_be(0xF0000000); /* 15.0.0.0 */ + mcast_gw.addr = long_be(0x00000000); + mcast_default_link = NULL; + pico_ipv4_route_del(mcast_addr, mcast_nm, mcast_gw, 1, found); + } + mcast_all_hosts.addr = PICO_MCAST_ALL_HOSTS; + pico_ipv4_mcast_leave(&address, &mcast_all_hosts, 1, PICO_IP_MULTICAST_EXCLUDE, NULL); + pico_tree_foreach_safe(index,found->MCASTGroups, _tmp) + { + g = index->keyValue; + pico_tree_delete(found->MCASTGroups, g); + pico_free(g); + } + } while(0); +#endif + + pico_tree_delete(&Tree_dev_link, found); + /* XXX: pico_free(found); */ + /* XXX: cleanup all routes containing the removed link */ + return 0; +} + + +struct pico_ipv4_link *pico_ipv4_link_get(struct pico_ip4 *address) +{ + struct pico_ipv4_link test = {0}, *found = NULL; + test.address.addr = address->addr; + + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) + return NULL; + else + return found; +} + +struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev) +{ + struct pico_tree_node *index = NULL; + struct pico_ipv4_link *link = NULL; + + pico_tree_foreach(index, &Tree_dev_link) + { + link = index->keyValue; + if (link->dev == dev) + return link; + } + return NULL; +} + + +struct pico_device *pico_ipv4_link_find(struct pico_ip4 *address) +{ + struct pico_ipv4_link test, *found; + if(!address) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + test.dev = NULL; + test.address.addr = address->addr; + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return NULL; + } + return found->dev; +} + +int pico_ipv4_rebound(struct pico_frame *f) +{ + struct pico_ip4 dst; + struct pico_ipv4_hdr *hdr; + if(!f) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + dst.addr = hdr->src.addr; + return pico_ipv4_frame_push(f, &dst, hdr->proto); +} + +static int pico_ipv4_forward(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)f->net_hdr; + struct pico_ipv4_route *rt; + if (!hdr) { + return -1; + } + + //dbg("IP> FORWARDING.\n"); + rt = route_find(&hdr->dst); + if (!rt) { + pico_notify_dest_unreachable(f); + return -1; + } + //dbg("ROUTE: valid..\n"); + f->dev = rt->link->dev; + hdr->ttl-=1; + if (hdr->ttl < 1) { + pico_notify_ttl_expired(f); + return -1; + } + hdr->crc++; + + /* check if NAT enbled on link and do NAT if so */ + if (pico_ipv4_nat_isenabled_out(rt->link) == 0) + pico_ipv4_nat(f, rt->link->address); + + //dbg("Routing towards %s\n", f->dev->name); + f->start = f->net_hdr; + if(f->dev->eth != NULL) + f->len -= PICO_SIZE_ETHHDR; + pico_sendto_dev(f); + return 0; + +} + +int pico_ipv4_is_broadcast(uint32_t addr) +{ + struct pico_ipv4_link *link; + struct pico_tree_node * index; + if (addr == PICO_IP4_ANY) + return 1; + if (addr == PICO_IP4_BCAST) + return 1; + + pico_tree_foreach(index,&Tree_dev_link) { + link = index->keyValue; + if ((link->address.addr | (~link->netmask.addr)) == addr) + return 1; + } + return 0; +} + +void pico_ipv4_unreachable(struct pico_frame *f, int err) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; +#if defined PICO_SUPPORT_TCP || defined PICO_SUPPORT_UDP + f->transport_hdr = ((uint8_t *)f->net_hdr) + PICO_SIZE_IP4HDR; + pico_transport_error(f, hdr->proto, err); +#endif +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_ipv4.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,96 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_IPV4 +#define _INCLUDE_PICO_IPV4 +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_tree.h" + +#define PICO_IPV4_INADDR_ANY 0x00000000U + +#define PICO_SIZE_IP4HDR ((sizeof(struct pico_ipv4_hdr))) +#define PICO_IPV4_DONTFRAG 0x4000 +#define PICO_IPV4_MOREFRAG 0x2000 +#define PICO_IPV4_FRAG_MASK 0x1FFF +#define PICO_IPV4_DEFAULT_TTL 64 + +extern struct pico_protocol pico_proto_ipv4; + +struct __attribute__((packed)) pico_ipv4_hdr { + uint8_t vhl; + uint8_t tos; + uint16_t len; + uint16_t id; + uint16_t frag; + uint8_t ttl; + uint8_t proto; + uint16_t crc; + struct pico_ip4 src; + struct pico_ip4 dst; + uint8_t options[0]; +}; + +struct __attribute__((packed)) pico_ipv4_pseudo_hdr +{ + struct pico_ip4 src; + struct pico_ip4 dst; + uint8_t zeros; + uint8_t proto; + uint16_t len; +}; + +/* Interface: link to device */ +struct pico_mcast_list; + +struct pico_ipv4_link +{ + struct pico_device *dev; + struct pico_ip4 address; + struct pico_ip4 netmask; +#ifdef PICO_SUPPORT_MCAST + struct pico_tree *MCASTGroups; + uint8_t mcast_compatibility; + uint8_t mcast_last_query_interval; +#endif +}; + +#ifdef PICO_SUPPORT_MCAST +struct pico_mcast_group { + uint8_t filter_mode; + uint16_t reference_count; + struct pico_ip4 mcast_addr; + struct pico_tree MCASTSources; +}; +#endif + +int pico_ipv4_to_string(char *ipbuf, const uint32_t ip); +int pico_string_to_ipv4(const char *ipstr, uint32_t *ip); +int pico_ipv4_valid_netmask(uint32_t mask); +int pico_ipv4_is_unicast(uint32_t address); +int pico_ipv4_is_multicast(uint32_t address); +int pico_ipv4_is_broadcast(uint32_t addr); + +int pico_ipv4_link_add(struct pico_device *dev, struct pico_ip4 address, struct pico_ip4 netmask); +int pico_ipv4_link_del(struct pico_device *dev, struct pico_ip4 address); +int pico_ipv4_rebound(struct pico_frame *f); + +int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t proto); +struct pico_ipv4_link *pico_ipv4_link_get(struct pico_ip4 *address); +struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev); +struct pico_device *pico_ipv4_link_find(struct pico_ip4 *address); +struct pico_ip4 *pico_ipv4_source_find(struct pico_ip4 *dst); +int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link); +int pico_ipv4_route_del(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link); +struct pico_ip4 pico_ipv4_route_get_gateway(struct pico_ip4 *addr); +void pico_ipv4_unreachable(struct pico_frame *f, int err); + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter); +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter); +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void); + +#endif /* _INCLUDE_PICO_IPV4 */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_ipv6.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,30 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_IPV6 +#define _INCLUDE_PICO_IPV6 +#include "pico_addressing.h" +#include "pico_protocol.h" + +extern struct pico_protocol pico_proto_ipv6; +extern const uint8_t PICO_IPV6_ANY[PICO_SIZE_IP6]; + + +/* This module is responsible for routing outgoing packets and + * delivering incoming packets to other layers + */ + +/* Interface for processing incoming ipv6 packets (decap/deliver) */ +int pico_ipv6_process_in(struct pico_frame *f); + +/* Interface for processing outgoing ipv6 frames (encap/push) */ +int pico_ipv6_process_out(struct pico_frame *f); + +/* Return estimated overhead for ipv6 frames to define allocation */ +int pico_ipv6_overhead(struct pico_frame *f); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_nat.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,683 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Kristof Roelants, Brecht Van Cauwenberghe, + Simon Maes, Philippe Mariman +*********************************************************************/ + +#include "pico_stack.h" +#include "pico_frame.h" +#include "pico_tcp.h" +#include "pico_udp.h" +#include "pico_ipv4.h" +#include "pico_addressing.h" +#include "pico_nat.h" + + +#ifdef PICO_SUPPORT_IPV4 +#ifdef PICO_SUPPORT_NAT + +#define nat_dbg(...) do{}while(0) +//#define nat_dbg dbg +#define NAT_TCP_TIMEWAIT 240000 /* 4mins (in msec) */ +//#define NAT_TCP_TIMEWAIT 10000 /* 10 sec (in msec) - for testing purposes only*/ + + +struct pico_nat_key { + struct pico_ip4 pub_addr; + uint16_t pub_port; + struct pico_ip4 priv_addr; + uint16_t priv_port; + uint8_t proto; + /* + del_flags: + 1 0 + 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|B|S|R|P|~| CONNECTION ACTIVE | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + F: FIN from Forwarding packet + B: FIN from Backwarding packet + S: SYN + R: RST + P: Persistant + + */ + uint16_t del_flags; + /* Connector for trees */ +}; + +static struct pico_ipv4_link pub_link; +static uint8_t enable_nat_flag = 0; + +static int nat_cmp_backward(void * ka, void * kb) +{ + struct pico_nat_key *a = ka, *b = kb; + if (a->pub_port < b->pub_port) { + return -1; + } + else if (a->pub_port > b->pub_port) { + return 1; + } + else { + if (a->proto < b->proto) { + return -1; + } + else if (a->proto > b->proto) { + return 1; + } + else { + /* a and b are identical */ + return 0; + } + } +} + +static int nat_cmp_forward(void * ka, void * kb) +{ + struct pico_nat_key *a =ka, *b = kb; + if (a->priv_addr.addr < b->priv_addr.addr) { + return -1; + } + else if (a->priv_addr.addr > b->priv_addr.addr) { + return 1; + } + else { + if (a->priv_port < b->priv_port) { + return -1; + } + else if (a->priv_port > b->priv_port) { + return 1; + } + else { + if (a->proto < b->proto) { + return -1; + } + else if (a->proto > b->proto) { + return 1; + } + else { + /* a and b are identical */ + return 0; + } + } + } +} + +PICO_TREE_DECLARE(KEYTable_forward,nat_cmp_forward); +PICO_TREE_DECLARE(KEYTable_backward,nat_cmp_backward); + +/* + 2 options: + find on proto and pub_port + find on priv_addr, priv_port and proto + zero the unused parameters +*/ +static struct pico_nat_key *pico_ipv4_nat_find_key(uint16_t pub_port, struct pico_ip4 *priv_addr, uint16_t priv_port, uint8_t proto) +{ + struct pico_nat_key test; + test.pub_port = pub_port; + test.priv_port = priv_port; + test.proto = proto; + if (priv_addr) + test.priv_addr = *priv_addr; + else + test.priv_addr.addr = 0; + + /* returns NULL if test can not be found */ + if (!pub_port) + return pico_tree_findKey(&KEYTable_forward,&test); + else + return pico_tree_findKey(&KEYTable_backward, &test); +} + +int pico_ipv4_nat_find(uint16_t pub_port, struct pico_ip4 *priv_addr, uint16_t priv_port, uint8_t proto) +{ + struct pico_nat_key *k = NULL; + + k = pico_ipv4_nat_find_key(pub_port, priv_addr, priv_port, proto); + if (k) + return 0; + else + return -1; +} + +int pico_ipv4_nat_snif_forward(struct pico_nat_key *nk, struct pico_frame *f) +{ + uint8_t proto; + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *)f->net_hdr; + struct pico_tcp_hdr *tcp_hdr; + + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + if (tcp_hdr->flags & PICO_TCP_FIN) { + nk->del_flags |= PICO_DEL_FLAGS_FIN_FORWARD; //FIN from forwarding packet + } + if (tcp_hdr->flags & PICO_TCP_SYN) { + nk->del_flags |= PICO_DEL_FLAGS_SYN; + } + if (tcp_hdr->flags & PICO_TCP_RST) { + nk->del_flags |= PICO_DEL_FLAGS_RST; + } + } else if (proto == PICO_PROTO_UDP) { + /* set conn active to 1 */ + nk->del_flags &= 0xFE00; + nk->del_flags++; + } + return 0; +} + + +int pico_ipv4_nat_snif_backward(struct pico_nat_key *nk, struct pico_frame *f) +{ + uint8_t proto; + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *)f->net_hdr; + struct pico_tcp_hdr *tcp_hdr; + + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + if (tcp_hdr->flags & PICO_TCP_FIN) { + nk->del_flags |= PICO_DEL_FLAGS_FIN_BACKWARD; //FIN from backwarding packet + } + if (tcp_hdr->flags & PICO_TCP_SYN) { + nk->del_flags |= PICO_DEL_FLAGS_SYN; + } + if (tcp_hdr->flags & PICO_TCP_RST) { + nk->del_flags |= PICO_DEL_FLAGS_RST; + } + } else if (proto == PICO_PROTO_UDP) { + /* set conn active to 1 */ + nk->del_flags &= 0xFE00; + nk->del_flags++; + } + return 0; +} + +void pico_ipv4_nat_table_cleanup(unsigned long now, void *_unused) +{ + struct pico_tree_node * idx, * safe; + struct pico_nat_key *k = NULL; + nat_dbg("NAT: before table cleanup:\n"); + pico_ipv4_nat_print_table(); + + //struct pico_nat_key *tmp; + pico_tree_foreach_reverse_safe(idx,&KEYTable_forward,safe){ + k = idx->keyValue; + switch (k->proto) + { + case PICO_PROTO_TCP: + if ((k->del_flags & 0x0800) >> 11) { + /* entry is persistant */ + break; + } + else if ((k->del_flags & 0x01FF) == 0) { + /* conn active is zero, delete entry */ + pico_ipv4_nat_del(k->pub_port, k->proto); + } + else if ((k->del_flags & 0x1000) >> 12) { + /* RST flag set, set conn active to zero */ + k->del_flags &= 0xFE00; + } + else if (((k->del_flags & 0x8000) >> 15) && ((k->del_flags & 0x4000) >> 14)) { + /* FIN1 and FIN2 set, set conn active to zero */ + k->del_flags &= 0xFE00; + } + else if ((k->del_flags & 0x01FF) > 360) { + /* conn is active for 24 hours, delete entry */ + pico_ipv4_nat_del(k->pub_port, k->proto); + } + else { + k->del_flags++; + } + break; + + case PICO_PROTO_UDP: + if ((k->del_flags & 0x0800) >> 11) { + /* entry is persistant */ + break; + } + else if ((k->del_flags & 0x01FF) > 1) { + /* Delete entry when it has existed NAT_TCP_TIMEWAIT */ + pico_ipv4_nat_del(k->pub_port, k->proto); + } + else { + k->del_flags++; + } + break; + + default: + /* Unknown protocol in NAT table, delete when it has existed NAT_TCP_TIMEWAIT */ + if ((k->del_flags & 0x01FF) > 1) { + pico_ipv4_nat_del(k->pub_port, k->proto); + } + else { + k->del_flags++; + } + } + } + + nat_dbg("NAT: after table cleanup:\n"); + pico_ipv4_nat_print_table(); + pico_timer_add(NAT_TCP_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL); +} + +int pico_ipv4_nat_add(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto) +{ + struct pico_nat_key *key = pico_zalloc(sizeof(struct pico_nat_key)); + if (!key) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + key->pub_addr = pub_addr; + key->pub_port = pub_port; + key->priv_addr = priv_addr; + key->priv_port = priv_port; + key->proto = proto; + key->del_flags = 0x0001; /* set conn active to 1, other flags to 0 */ + + /* RB_INSERT returns NULL when element added, pointer to the element if already in tree */ + if(!pico_tree_insert(&KEYTable_forward, key) && !pico_tree_insert(&KEYTable_backward, key)){ + return 0; /* New element added */ + } + else { + pico_free(key); + pico_err = PICO_ERR_EINVAL; + return -1; /* Element key already exists */ + } +} + + +int pico_ipv4_nat_del(uint16_t pub_port, uint8_t proto) +{ + struct pico_nat_key *key = NULL; + key = pico_ipv4_nat_find_key(pub_port, NULL, 0, proto); + if (!key) { + nat_dbg("NAT: key to delete not found: proto %u | pub_port %u\n", proto, pub_port); + return -1; + } + else { + nat_dbg("NAT: key to delete found: proto %u | pub_port %u\n", proto, pub_port); + /* RB_REMOVE returns pointer to removed element, NULL to indicate error */ + if(pico_tree_delete(&KEYTable_forward, key) && pico_tree_delete(&KEYTable_backward, key)) + pico_free(key); + else + return -1; /* Error on removing element, do not free! */ + } + return 0; +} + +int pico_ipv4_port_forward(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto, uint8_t persistant) +{ + struct pico_nat_key *key = NULL; + + switch (persistant) + { + case PICO_IPV4_FORWARD_ADD: + if (pico_ipv4_nat_add(pub_addr, pub_port, priv_addr, priv_port, proto) != 0) + return -1; /* pico_err set in nat_add */ + key = pico_ipv4_nat_find_key(pub_port, &priv_addr, priv_port, proto); + if (!key) { + pico_err = PICO_ERR_EAGAIN; + return -1; + } + key->del_flags = (key->del_flags & ~(0x1 << 11)) | (persistant << 11); + break; + + case PICO_IPV4_FORWARD_DEL: + return pico_ipv4_nat_del(pub_port, proto); + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + pico_ipv4_nat_print_table(); + return 0; +} + + +void pico_ipv4_nat_print_table(void) +{ + struct pico_nat_key __attribute__((unused)) *k = NULL ; + struct pico_tree_node * index; + uint16_t i = 0; + + nat_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); + nat_dbg("+ NAT table +\n"); + nat_dbg("+-----------------------------------------------------------------------------------------------------------------------+\n"); + nat_dbg("+ pointer | private_addr | private_port | proto | pub_addr | pub_port | conn active | FIN1 | FIN2 | SYN | RST | PERS +\n"); + nat_dbg("+-----------------------------------------------------------------------------------------------------------------------+\n"); + + pico_tree_foreach(index,&KEYTable_forward){ + k = index->keyValue; + nat_dbg("+ %10p | %08X | %05u | %04u | %08X | %05u | %03u | %u | %u | %u | %u | %u +\n", + k, k->priv_addr.addr, k->priv_port, k->proto, k->pub_addr.addr, k->pub_port, (k->del_flags)&0x01FF, ((k->del_flags)&0x8000)>>15, + ((k->del_flags)&0x4000)>>14, ((k->del_flags)&0x2000)>>13, ((k->del_flags)&0x1000)>>12, ((k->del_flags)&0x0800)>>11); + i++; + } + nat_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); +} + +int pico_ipv4_nat_generate_key(struct pico_nat_key* nk, struct pico_frame* f, struct pico_ip4 pub_addr) +{ + uint16_t pub_port = 0; + uint8_t proto; + struct pico_tcp_hdr *tcp_hdr = NULL; /* forced to use pico_trans */ + struct pico_udp_hdr *udp_hdr = NULL; /* forced to use pico_trans */ + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *)f->net_hdr; + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + do { + /* 1. generate valid new NAT port entry */ + uint32_t rand = pico_rand(); + pub_port = (uint16_t) (rand & 0xFFFFU); + pub_port = (uint16_t)(pub_port % (65535 - 1024)) + 1024U; + pub_port = short_be(pub_port); + + /* 2. check if already in table, if no exit */ + nat_dbg("NAT: check if generated port %u is free\n", short_be(pub_port)); + if (pico_is_port_free(proto, pub_port, NULL, &pico_proto_ipv4)) + break; + + } while (1); + nat_dbg("NAT: port %u is free\n", short_be(pub_port)); + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + nk->priv_port = tcp_hdr->trans.sport; + } else if (proto == PICO_PROTO_UDP) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + if (!udp_hdr) + return -1; + nk->priv_port = udp_hdr->trans.sport; + } else if (proto == PICO_PROTO_ICMP4) { + nk->priv_port = (uint16_t)(ipv4_hdr->src.addr & 0x00FF); + pub_port = (uint16_t)(ipv4_hdr->dst.addr & 0x00FF); + if (!pico_is_port_free(proto, pub_port, NULL, &pico_proto_ipv4)) + return -1; + } + + nk->pub_addr = pub_addr; /* get public ip address from device */ + nk->pub_port = pub_port; + nk->priv_addr = ipv4_hdr->src; + nk->proto = ipv4_hdr->proto; + nk->del_flags = 0x0001; /* set conn active to 1 */ + if (pico_ipv4_nat_add(nk->pub_addr, nk->pub_port, nk->priv_addr, nk->priv_port, nk->proto) < 0) { + return -1; + } else { + return 0; + } +} + + +static int pico_nat_tcp_checksum(struct pico_frame *f) +{ + struct pico_tcp_hdr *trans_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + struct pico_ipv4_hdr *net_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + struct tcp_pseudo_hdr_ipv4 pseudo; + if (!trans_hdr || !net_hdr) + return -1; + + pseudo.src.addr = net_hdr->src.addr; + pseudo.dst.addr = net_hdr->dst.addr; + pseudo.res = 0; + pseudo.proto = PICO_PROTO_TCP; + pseudo.tcp_len = short_be(f->transport_len); + + trans_hdr->crc = 0; + trans_hdr->crc = pico_dualbuffer_checksum(&pseudo, sizeof(struct tcp_pseudo_hdr_ipv4), trans_hdr, f->transport_len); + trans_hdr->crc = short_be(trans_hdr->crc); + return 0; +} + + +int pico_ipv4_nat_translate(struct pico_nat_key* nk, struct pico_frame* f) +{ + uint8_t proto; + struct pico_tcp_hdr *tcp_hdr = NULL; /* forced to use pico_trans */ + struct pico_udp_hdr *udp_hdr = NULL; /* forced to use pico_trans */ + + struct pico_ipv4_hdr* ipv4_hdr = (struct pico_ipv4_hdr *)f->net_hdr; + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + tcp_hdr->trans.sport = nk->pub_port; + } else if (proto == PICO_PROTO_UDP) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + if (!udp_hdr) + return -1; + udp_hdr->trans.sport = nk->pub_port; + } + + //if(f->proto == PICO_PROTO_ICMP){ + //} XXX no action + + ipv4_hdr->src = nk->pub_addr; + + if (proto == PICO_PROTO_TCP) { + pico_nat_tcp_checksum(f); + } else if (proto == PICO_PROTO_UDP){ + udp_hdr->crc = 0; + udp_hdr->crc = short_be(pico_udp_checksum_ipv4(f)); + } + + // pico_ipv4_checksum(f); + ipv4_hdr->crc = 0; + ipv4_hdr->crc = short_be(pico_checksum(ipv4_hdr, f->net_len)); + + return 0; +} + + +int pico_ipv4_nat_port_forward(struct pico_frame* f) +{ + struct pico_nat_key *nk = NULL; + struct pico_tcp_hdr *tcp_hdr = NULL; + struct pico_udp_hdr *udp_hdr = NULL; + struct pico_icmp4_hdr *icmp_hdr = NULL; + struct pico_ipv4_hdr* ipv4_hdr; + uint16_t pub_port = 0; + uint8_t proto; + + ipv4_hdr = (struct pico_ipv4_hdr *)f->net_hdr; + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + pub_port = tcp_hdr->trans.dport; + } else if (proto == PICO_PROTO_UDP) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + if (!udp_hdr) + return -1; + pub_port = udp_hdr->trans.dport; + } else if (proto == PICO_PROTO_ICMP4) { + icmp_hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + if (!icmp_hdr) + return -1; + /* XXX PRELIMINARY ONLY LAST 16 BITS OF IP */ + pub_port = (uint16_t)(ipv4_hdr->src.addr & 0x00FF); + } + + nk = pico_ipv4_nat_find_key(pub_port, 0, 0, proto); + + if (!nk) { + nat_dbg("\nNAT: ERROR key not found in table\n"); + return -1; + } else { + pico_ipv4_nat_snif_forward(nk,f); + ipv4_hdr->dst.addr = nk->priv_addr.addr; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr->trans.dport = nk->priv_port; + pico_nat_tcp_checksum(f); + } else if (proto == PICO_PROTO_UDP) { + udp_hdr->trans.dport = nk->priv_port; + udp_hdr->crc = 0; + udp_hdr->crc = short_be(pico_udp_checksum_ipv4(f)); + } + } + + ipv4_hdr->crc = 0; + ipv4_hdr->crc = short_be(pico_checksum(ipv4_hdr, f->net_len)); + + return 0; +} + + + +int pico_ipv4_nat(struct pico_frame *f, struct pico_ip4 pub_addr) +{ + /*do nat---------*/ + struct pico_nat_key *nk = NULL; + struct pico_nat_key key; + struct pico_ipv4_hdr *net_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_tcp_hdr *tcp_hdr = NULL; + struct pico_udp_hdr *udp_hdr = NULL; + int ret; + uint8_t proto = net_hdr->proto; + uint16_t priv_port = 0; + struct pico_ip4 priv_addr= net_hdr->src; + + nk= &key; + + /* TODO DELME check if IN */ + if (pub_addr.addr == net_hdr->dst.addr) { + nat_dbg("NAT: backward translation {dst.addr, dport}: {%08X,%u} ->", net_hdr->dst.addr, ((struct pico_trans *)f->transport_hdr)->dport); + ret = pico_ipv4_nat_port_forward(f); /* our IN definition */ + nat_dbg(" {%08X,%u}\n", net_hdr->dst.addr, short_be(((struct pico_trans *)f->transport_hdr)->dport)); + } else { + if (net_hdr->proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + priv_port = tcp_hdr->trans.sport; + } else if (net_hdr->proto == PICO_PROTO_UDP) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + priv_port = udp_hdr->trans.sport; + } else if (net_hdr->proto == PICO_PROTO_ICMP4) { + //udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + priv_port = (uint16_t)(net_hdr->src.addr & 0x00FF); + } + + ret = pico_ipv4_nat_find(0, &priv_addr, priv_port, proto); + if (ret >= 0) { + // Key is available in table + nk = pico_ipv4_nat_find_key(0, &priv_addr, priv_port, proto); + } else { + nat_dbg("NAT: key not found in NAT table -> generate key\n"); + pico_ipv4_nat_generate_key(nk, f, pub_addr); + } + pico_ipv4_nat_snif_backward(nk,f); + nat_dbg("NAT: forward translation {src.addr, sport}: {%08X,%u} ->", net_hdr->src.addr, short_be(((struct pico_trans *)f->transport_hdr)->sport)); + pico_ipv4_nat_translate(nk, f); /* our OUT definition */ + nat_dbg(" {%08X,%u}\n", net_hdr->src.addr, short_be(((struct pico_trans *)f->transport_hdr)->sport)); + } + return 0; +} + + +int pico_ipv4_nat_enable(struct pico_ipv4_link *link) +{ + if (link == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pub_link = *link; + pico_timer_add(NAT_TCP_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL); + enable_nat_flag = 1; + return 0; +} + +int pico_ipv4_nat_disable(void) +{ + pub_link.address.addr = 0; + enable_nat_flag = 0; + return 0; +} + + +int pico_ipv4_nat_isenabled_out(struct pico_ipv4_link *link) +{ + if (enable_nat_flag) { + // is pub_link = *link + if (pub_link.address.addr == link->address.addr) + return 0; + else + return -1; + } else { + return -1; + } +} + + +int pico_ipv4_nat_isenabled_in(struct pico_frame *f) +{ + if (enable_nat_flag) { + struct pico_tcp_hdr *tcp_hdr = NULL; + struct pico_udp_hdr *udp_hdr = NULL; + uint16_t pub_port = 0; + int ret; + uint8_t proto; + + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!ipv4_hdr) + return -1; + proto = ipv4_hdr->proto; + + if (proto == PICO_PROTO_TCP) { + tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (!tcp_hdr) + return -1; + pub_port= tcp_hdr->trans.dport; + } else if (proto == PICO_PROTO_UDP) { + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + if (!udp_hdr) + return -1; + pub_port= udp_hdr->trans.dport; + } else if (proto == PICO_PROTO_ICMP4) { + //icmp_hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + //if (!icmp_hdr) + // return -1; + /* XXX PRELIMINARY ONLY LAST 16 BITS OF IP */ + pub_port = (uint16_t)(ipv4_hdr->src.addr & 0x00FF); + } + ret = pico_ipv4_nat_find(pub_port, NULL, 0, proto); + if (ret == 0) + return 0; + else + return -1; + } else { + return -1; + } +} +#endif +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_nat.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,88 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Kristof Roelants, Simon Maes, Brecht Van Cauwenberghe +*********************************************************************/ + +#ifndef _INCLUDE_PICO_NAT +#define _INCLUDE_PICO_NAT +#include "pico_frame.h" + +#define PICO_DEL_FLAGS_FIN_FORWARD (0x8000) +#define PICO_DEL_FLAGS_FIN_BACKWARD (0x4000) +#define PICO_DEL_FLAGS_SYN (0x2000) +#define PICO_DEL_FLAGS_RST (0x1000) + +#define PICO_IPV4_FORWARD_DEL 0 +#define PICO_IPV4_FORWARD_ADD 1 + +#ifdef PICO_SUPPORT_NAT +void pico_ipv4_nat_print_table(void); +int pico_ipv4_nat_add(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto); +int pico_ipv4_nat_del(uint16_t pub_port, uint8_t proto); +int pico_ipv4_nat_find(uint16_t pub_port, struct pico_ip4 *priv_addr, uint16_t priv_port, uint8_t proto); +int pico_ipv4_port_forward(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto, uint8_t persistant); + +int pico_ipv4_nat(struct pico_frame* f, struct pico_ip4 pub_addr); +int pico_ipv4_nat_enable(struct pico_ipv4_link *link); +int pico_ipv4_nat_isenabled_out(struct pico_ipv4_link *link); +int pico_ipv4_nat_isenabled_in(struct pico_frame *f); + +#else + +static inline int pico_ipv4_nat_isenabled_out(struct pico_ipv4_link *link) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +static inline int pico_ipv4_nat_isenabled_in(struct pico_frame *f) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat(struct pico_frame* f, struct pico_ip4 pub_addr) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_enable(struct pico_ipv4_link *link) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +#define pico_ipv4_nat_print_table() do{}while(0) + +static inline int pico_ipv4_nat_add(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_del(uint16_t pub_port, uint8_t proto) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + + +static inline int pico_ipv4_nat_find(uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_port_forward(struct pico_ip4 pub_addr, uint16_t pub_port, struct pico_ip4 priv_addr, uint16_t priv_port, uint8_t proto, uint8_t persistant) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +#endif + +#endif /* _INCLUDE_PICO_NAT */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_simple_http.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,128 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#include "pico_config.h" +#include "pico_socket.h" +#include "pico_tcp.h" +#include "pico_ipv4.h" +#include "pico_simple_http.h" + +/* The HTTP Server cannot be available without TCP support */ +#if (defined PICO_SUPPORT_HTTP) && (defined PICO_SUPPORT_IPV4) && (defined PICO_SUPPORT_TCP) + +#define HTTP_LISTEN_PORT 80u +#define HTTP_BACKLOG 5u +#define HTTP_HEADER_SIZE 256u + +#define HTTP_SUCCESS 0 +#define HTTP_ERROR -1 + +static struct pico_socket * httpServer = NULL; +static char httpResponse[] = +"HTTP/1.0 200 OK\r\n\ +Content-Type: text/html\r\n\ +\r\n\ +<html><head>\r\n\ +<title>picoTCP Simple Http server</title>\r\n\ +</head>\r\n\ +<body>\r\n\ +<h1>Hello world from picoTCP !!</h1>\r\n\ +</body>\r\n"; + +static void httpEventCbk(uint16_t ev, struct pico_socket *self) +{ + static struct pico_socket * client = NULL; + uint32_t peer; + uint16_t port; + int r; + char buffer[HTTP_HEADER_SIZE]; + + switch(ev) + { + case PICO_SOCK_EV_CONN : + if(!client) + client = pico_socket_accept(self, &peer, &port); + break; + + case PICO_SOCK_EV_RD: + // do not check http integrity, just mark that the http header has arrived + // prepare to send the response + r = pico_socket_recvfrom(self, buffer, HTTP_HEADER_SIZE, &peer, &port); + if(r>0 && memcmp(buffer,"GET",3u) == 0u) + { // it is an http header asking for data, return data and close + pico_socket_write(self,httpResponse,sizeof(httpResponse)); + pico_socket_close(self); + } + else + { + // kill the connection, invalid header + pico_socket_close(self); + } + break; + + case PICO_SOCK_EV_ERR: + case PICO_SOCK_EV_CLOSE: + // free the used socket + client = NULL; + break; + + default : + break; + } +} + +int pico_startHttpServer(struct pico_ip4 * address) +{ + + uint16_t localHttpPort = short_be(HTTP_LISTEN_PORT); + + if(!pico_is_port_free(localHttpPort,PICO_PROTO_TCP, address, &pico_proto_ipv4)) + { + pico_err = PICO_ERR_EADDRINUSE; + return HTTP_ERROR; + } + + httpServer = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, httpEventCbk); + + if(!httpServer) + { + pico_err = PICO_ERR_ENOMEM; + return HTTP_ERROR; + } + + // both functions set the pico_err themselves. + if(pico_socket_bind(httpServer,address,&localHttpPort)) + return HTTP_ERROR; + + if(pico_socket_listen(httpServer,HTTP_BACKLOG)) + return HTTP_ERROR; + + return HTTP_SUCCESS; +} + +int pico_stopHttpServer(void) +{ + if(!httpServer) + { + pico_err = PICO_ERR_EINVAL; + return HTTP_ERROR; + } + + if(pico_socket_close(httpServer)) + { + // no need to set the error here, function already set it + httpServer = NULL; + return HTTP_ERROR; + } + + httpServer = NULL; + return HTTP_SUCCESS; +} + +#endif + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_simple_http.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,14 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Author: Andrei Carp <andrei.carp@tass.be> +*********************************************************************/ + +#ifndef PICO_SIMPLE_HTTP +#define PICO_SIMPLE_HTTP + +extern int pico_startHttpServer(struct pico_ip4 * address); +extern int pico_stopHttpServer(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_tcp.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,2217 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Daniele Lacamera, Philippe Mariman +*********************************************************************/ + +#include "pico_tcp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_stack.h" +#include "pico_socket.h" +#include "pico_queue.h" +#include "pico_tree.h" + +#define TCP_SOCK(s) ((struct pico_socket_tcp *)s) +#define SEQN(f) (f?(long_be(((struct pico_tcp_hdr *)(f->transport_hdr))->seq)):0) +#define ACKN(f) (f?(long_be(((struct pico_tcp_hdr *)(f->transport_hdr))->ack)):0) + +#define PICO_TCP_RTO_MIN 10 +#define PICO_TCP_RTO_MAX 120000 +#define PICO_TCP_IW 2 +#define PICO_TCP_SYN_TO 1000 + +#define PICO_TCP_MAX_CONNECT_RETRIES 7 + +#define PICO_TCP_LOOKAHEAD 0x00 +#define PICO_TCP_FIRST_DUPACK 0x01 +#define PICO_TCP_SECOND_DUPACK 0x02 +#define PICO_TCP_RECOVER 0x03 +#define PICO_TCP_BLACKOUT 0x04 +#define PICO_TCP_UNREACHABLE 0x05 +#define PICO_TCP_WINDOW_FULL 0x06 + +/* check if the Nagle algorithm is enabled on the socket */ +#define IS_NAGLE_ENABLED(s) (!(!(!(s->opt_flags & (1 << PICO_SOCKET_OPT_TCPNODELAY))))) +/* check if tcp connection is "idle" according to Nagle (RFC 896) */ +#define IS_TCP_IDLE(t) ((t->in_flight == 0) && (t->tcpq_out.size == 0)) +/* check if the hold queue contains data (again Nagle) */ +#define IS_TCP_HOLDQ_EMPTY(t) (t->tcpq_hold.size == 0) + +#ifdef PICO_SUPPORT_TCP +#define tcp_dbg_nagle(...) do{}while(0) +#define tcp_dbg_options(...) do{}while(0) + + +#define tcp_dbg(...) do{}while(0) +//#define tcp_dbg dbg + + +#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 + + +static inline int seq_compare(uint32_t a, uint32_t b) +{ + uint32_t thresh = ((uint32_t)(-1))>>1; + if (((a > thresh) && (b > thresh)) || ((a <= thresh) && (b <= thresh))) { + if (a > b) + return 1; + if (b > a) + return -1; + } else { + if (a > b) + return -2; + if (b > a) + return 2; + } + return 0; +} + +static int segment_compare(void * ka, void * kb) +{ + struct pico_frame *a = ka, *b = kb; + return seq_compare(SEQN(a), SEQN(b)); +} + +struct pico_tcp_queue +{ + struct pico_tree pool; + uint32_t max_size; + uint32_t size; + uint32_t frames; +}; + +static struct pico_frame *peek_segment(struct pico_tcp_queue *tq, uint32_t seq) +{ + struct pico_tcp_hdr H; + struct pico_frame f = {}; + f.transport_hdr = (uint8_t *) (&H); + H.seq = long_be(seq); + + return pico_tree_findKey(&tq->pool,&f); +} + +static struct pico_frame *first_segment(struct pico_tcp_queue *tq) +{ + return pico_tree_first(&tq->pool); +} + +static struct pico_frame *next_segment(struct pico_tcp_queue *tq, struct pico_frame *cur) +{ + if (!cur) + return NULL; + return peek_segment(tq, SEQN(cur) + cur->payload_len); +} + +static int pico_enqueue_segment(struct pico_tcp_queue *tq, struct pico_frame *f) +{ + int ret = -1; + if (f->payload_len <= 0) { + tcp_dbg("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TRIED TO ENQUEUE INVALID SEGMENT!\n"); + //abort(); + return -1; + } + LOCK(Mutex); + if ((tq->size + f->payload_len) > tq->max_size) + { + ret = 0; + goto out; + } + if (pico_tree_insert(&tq->pool,f) != 0) + { + ret = 0; + goto out; + } + tq->size += f->payload_len; + if (f->payload_len > 0) + tq->frames++; + ret = f->payload_len; + +out : + UNLOCK(Mutex); + return ret; +} + +static void pico_discard_segment(struct pico_tcp_queue *tq, struct pico_frame *f) +{ + struct pico_frame *f1; + LOCK(Mutex); + f1 = pico_tree_delete(&tq->pool,f); + if (f1) { + tq->size -= f->payload_len; + if (f->payload_len > 0) + tq->frames--; + } + pico_frame_discard(f); + UNLOCK(Mutex); +} + +/* Structure for TCP socket */ +struct tcp_sack_block { + uint32_t left; + uint32_t right; + struct tcp_sack_block *next; +}; + +struct pico_socket_tcp { + struct pico_socket sock; + + /* Tree/queues */ + struct pico_tcp_queue tcpq_in; + struct pico_tcp_queue tcpq_out; + struct pico_tcp_queue tcpq_hold; /* buffer to hold delayed frames according to Nagle */ + + /* tcp_output */ + uint32_t snd_nxt; + uint32_t snd_last; + uint32_t snd_old_ack; + uint32_t snd_retry; + uint32_t snd_last_out; + + /* congestion control */ + uint32_t avg_rtt; + uint32_t rttvar; + uint32_t rto; + uint32_t in_flight; + uint8_t timer_running; + uint8_t keepalive_timer_running; + uint16_t cwnd_counter; + uint16_t cwnd; + uint16_t ssthresh; + uint16_t recv_wnd; + uint16_t recv_wnd_scale; + + /* tcp_input */ + uint32_t rcv_nxt; + uint32_t rcv_ackd; + uint32_t rcv_processed; + uint16_t wnd; + uint16_t wnd_scale; + + /* options */ + uint32_t ts_nxt; + uint16_t mss; + uint8_t sack_ok; + uint8_t ts_ok; + uint8_t mss_ok; + uint8_t scale_ok; + struct tcp_sack_block *sacks; + uint8_t jumbo; + + /* Transmission */ + uint8_t x_mode; + uint8_t dupacks; + uint8_t backoff; + +}; + +/* Queues */ +static struct pico_queue tcp_in = {}; +static struct pico_queue tcp_out = {}; + +/* If Nagle enabled, this function can make 1 new segment from smaller segments in hold queue */ +static struct pico_frame * pico_hold_segment_make(struct pico_socket_tcp *t); + +/* checks if tcpq_in is empty */ +int pico_tcp_queue_in_is_empty(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + + if (t->tcpq_in.frames == 0) + return 1; + else + return 0; +} + +/* Useful for getting rid of the beginning of the buffer (read() op) */ +static int release_until(struct pico_tcp_queue *q, uint32_t seq) +{ + struct pico_frame *head = first_segment(q); + int ret = 0; + while (head && (seq_compare(SEQN(head) + head->payload_len, seq) <= 0)) { + struct pico_frame *cur = head; + head = next_segment(q, cur); + tcp_dbg("Releasing %p\n", q); + pico_discard_segment(q, cur); + ret++; + } + return ret; +} + +static int release_all_until(struct pico_tcp_queue *q, uint32_t seq) +{ + struct pico_frame *f = NULL, *tmp __attribute__((unused)); + struct pico_tree_node * idx, * temp; + int ret = 0; + + pico_tree_foreach_safe(idx,&q->pool,temp){ + f = idx->keyValue; + if (seq_compare(SEQN(f) + f->payload_len, seq) <= 0) { + tcp_dbg("Releasing %p\n", f); + pico_discard_segment(q, f); + ret++; + } else + return ret; + } + return ret; +} + +/* API calls */ + +uint16_t pico_tcp_checksum_ipv4(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_tcp_hdr *tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + struct pico_socket *s = f->sock; + struct pico_ipv4_pseudo_hdr pseudo; + + if (s) { + /* Case of outgoing frame */ + //dbg("TCP CRC: on outgoing frame\n"); + pseudo.src.addr = s->local_addr.ip4.addr; + pseudo.dst.addr = s->remote_addr.ip4.addr; + } else { + /* Case of incomming frame */ + //dbg("TCP CRC: on incomming frame\n"); + pseudo.src.addr = hdr->src.addr; + pseudo.dst.addr = hdr->dst.addr; + } + pseudo.zeros = 0; + pseudo.proto = PICO_PROTO_TCP; + pseudo.len = short_be(f->transport_len); + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv4_pseudo_hdr), tcp_hdr, f->transport_len); +} + +static void tcp_send_fin(struct pico_socket_tcp *t); +static int pico_tcp_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr; + struct pico_socket_tcp *t = (struct pico_socket_tcp *)f->sock; + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + + if (f->payload_len > 0) { + tcp_dbg("Process out: sending %p (%d bytes)\n",f, f->payload_len); + } else { + tcp_dbg("Sending empty packet\n"); + } + + if (f->payload_len > 0) { + if (seq_compare(SEQN(f) + f->payload_len, t->snd_nxt) > 0) { + t->snd_nxt = SEQN(f) + f->payload_len; + tcp_dbg("%s: snd_nxt is now %08x\n", __FUNCTION__, t->snd_nxt); + } + } else if (hdr->flags == PICO_TCP_ACK) { /* pure ack */ + //hdr->seq = long_be(t->snd_nxt); /* XXX disabled this to not to mess with seq nrs of ACKs anymore */ + } else { + tcp_dbg("%s: non-pure ACK with len=0, fl:%04x\n", __FUNCTION__, hdr->flags); + } + + pico_network_send(f); + return 0; +} + +int pico_tcp_push(struct pico_protocol *self, struct pico_frame *data); + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_tcp = { + .name = "tcp", + .proto_number = PICO_PROTO_TCP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_transport_process_in, + .process_out = pico_tcp_process_out, + .push = pico_tcp_push, + .q_in = &tcp_in, + .q_out = &tcp_out, +}; + +static uint32_t pico_paws(void) +{ + static unsigned long _paws = 0; + _paws = pico_rand(); + return long_be(_paws); /*XXX: implement paws */ +} + +static void tcp_add_options(struct pico_socket_tcp *ts, struct pico_frame *f, uint16_t flags, int optsiz) +{ + uint32_t tsval = long_be(pico_tick); + uint32_t tsecr = long_be(ts->ts_nxt); + int i = 0; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + + memset(f->start, PICO_TCP_OPTION_NOOP, optsiz); /* fill blanks with noop */ + + if (flags & PICO_TCP_SYN) { + f->start[i++] = PICO_TCP_OPTION_MSS; + f->start[i++] = PICO_TCPOPTLEN_MSS; + f->start[i++] = (ts->mss >> 8) & 0xFF; + f->start[i++] = ts->mss & 0xFF; + f->start[i++] = PICO_TCP_OPTION_SACK_OK; + f->start[i++] = PICO_TCPOPTLEN_SACK_OK; + } + + f->start[i++] = PICO_TCP_OPTION_WS; + f->start[i++] = PICO_TCPOPTLEN_WS; + f->start[i++] = ts->wnd_scale; + + if (optsiz >= 12) { + f->start[i++] = PICO_TCP_OPTION_TIMESTAMP; + f->start[i++] = PICO_TCPOPTLEN_TIMESTAMP; + memcpy(f->start + i, &tsval, 4); + i += 4; + memcpy(f->start + i, &tsecr, 4); + i += 4; + } + + if (flags & PICO_TCP_ACK) { + struct tcp_sack_block *sb; + int len_off; + + if (ts->sack_ok && ts->sacks) { + f->start[i++] = PICO_TCP_OPTION_SACK; + len_off = i; + f->start[i++] = PICO_TCPOPTLEN_SACK; + while(ts->sacks) { + sb = ts->sacks; + ts->sacks = sb->next; + memcpy(f->start + i, sb, 2 * sizeof(uint32_t)); + i += (2 * sizeof(uint32_t)); + f->start[len_off] += (2 * sizeof(uint32_t)); + pico_free(sb); + } + } + } + if (i < optsiz) + f->start[ optsiz - 1 ] = PICO_TCP_OPTION_END; +} + +static void tcp_send_ack(struct pico_socket_tcp *t); + +static void tcp_set_space(struct pico_socket_tcp *t) +{ + int mtu, space; + int shift = 0; + + mtu = t->mss + PICO_SIZE_TCPHDR + PICO_SIZE_TCPOPT_SYN ; + if (t->tcpq_in.max_size == 0) { + space = 1024 * 1024 * 1024; /* One Gigabyte, for unlimited sockets. */ + } else { + space = ((t->tcpq_in.max_size - t->tcpq_in.size) / mtu) * t->mss; + } + if (space < 0) + space = 0; + while(space > 0xFFFF) { + space >>= 1; + shift++; + } + if ((space != t->wnd) || (shift != t->wnd_scale) || ((space - t->wnd) > (space>>2))) { + t->wnd = space; + t->wnd_scale = shift; + } +} + +/* Return 32-bit aligned option size */ +static int tcp_options_size(struct pico_socket_tcp *t, uint16_t flags) +{ + int size = 0; + struct tcp_sack_block *sb = t->sacks; + + if (flags & PICO_TCP_SYN) { /* Full options */ + size = PICO_TCPOPTLEN_MSS + PICO_TCP_OPTION_SACK_OK + PICO_TCPOPTLEN_WS + PICO_TCPOPTLEN_TIMESTAMP; + } else { + + /* Always update window scale. */ + size += PICO_TCPOPTLEN_WS; + + if (t->ts_ok) + size += PICO_TCPOPTLEN_TIMESTAMP; + + size+= PICO_TCPOPTLEN_END; + } + if ((flags & PICO_TCP_ACK) && (t->sack_ok && sb)) { + size += 2; + while(sb) { + size += (2 * sizeof(uint32_t)); + sb = sb->next; + } + } + size = (((size + 3) >> 2) << 2); + return size; +} + +int pico_tcp_overhead(struct pico_socket *s) +{ + if (!s) + return 0; + + return PICO_SIZE_TCPHDR + tcp_options_size((struct pico_socket_tcp *)s, 0); /* hdr + Options size for data pkt */ + +} + +static void tcp_process_sack(struct pico_socket_tcp *t, uint32_t start, uint32_t end) +{ + struct pico_frame *f; + struct pico_tree_node * index, * temp; + int cmp; + int count = 0; + + pico_tree_foreach_safe(index,&t->tcpq_out.pool,temp){ + f = index->keyValue; + cmp = seq_compare(SEQN(f), start); + if (cmp > 0) + goto done; + + if (cmp == 0) { + cmp = seq_compare(SEQN(f) + f->payload_len, end); + if (cmp > 0) { + tcp_dbg("Invalid SACK: ignoring.\n"); + } + + tcp_dbg("Marking (by SACK) segment %08x BLK:[%08x::%08x]\n", SEQN(f), start, end); + f->flags |= PICO_FRAME_FLAG_SACKED; + count++; + + if (cmp == 0) { + /* that was last segment sacked. Job done */ + goto done; + } + } + } + +done: + if (t->x_mode > PICO_TCP_LOOKAHEAD) { + if (t->in_flight > (count)) + t->in_flight -= (count); + else + t->in_flight = 0; + } +} + +static void tcp_rcv_sack(struct pico_socket_tcp *t, uint8_t *opt, int len) +{ + uint32_t start, end; + int i = 0; + if (len % 8) { + tcp_dbg("SACK: Invalid len.\n"); + return; + } + while (i < len) { + start = long_from(opt + i); + i += 4; + end = long_from(opt + i); + i += 4; + tcp_process_sack(t, long_be(start), long_be(end)); + } +} + +static void tcp_parse_options(struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)f->sock; + uint8_t *opt = f->transport_hdr + PICO_SIZE_TCPHDR; + int i = 0; + f->timestamp = 0; + while (i < (f->transport_len - PICO_SIZE_TCPHDR)) { + uint8_t type = opt[i++]; + uint8_t len; + if(i < (f->transport_len - PICO_SIZE_TCPHDR) && (type > 1)) + len = opt[i++]; + else + len = 1; + if (f->payload && ((opt + i) > f->payload)) + break; + tcp_dbg_options("Received option '%d', len = %d \n", type, len); + switch (type) { + case PICO_TCP_OPTION_NOOP: + case PICO_TCP_OPTION_END: + break; + case PICO_TCP_OPTION_WS: + if (len != PICO_TCPOPTLEN_WS) { + tcp_dbg_options("TCP Window scale: bad len received (%d).\n", len); + i += len - 2; + break; + } + t->recv_wnd_scale = opt[i++]; + tcp_dbg_options("TCP Window scale: received %d\n", t->recv_wnd_scale); + break; + case PICO_TCP_OPTION_SACK_OK: + if (len != PICO_TCPOPTLEN_SACK_OK) { + tcp_dbg_options("TCP option sack: bad len received.\n"); + i += len - 2; + break; + } + t->sack_ok = 1; + break; + case PICO_TCP_OPTION_MSS: { + uint16_t mss; + if (len != PICO_TCPOPTLEN_MSS) { + tcp_dbg_options("TCP option mss: bad len received.\n"); + i += len - 2; + break; + } + t->mss_ok = 1; + mss = short_from(opt + i); + i += sizeof(uint16_t); + if (t->mss > short_be(mss)) + t->mss = short_be(mss); + break; + } + case PICO_TCP_OPTION_TIMESTAMP: { + uint32_t tsval, tsecr; + if (len != PICO_TCPOPTLEN_TIMESTAMP) { + tcp_dbg_options("TCP option timestamp: bad len received.\n"); + i += len - 2; + break; + } + t->ts_ok = 1; + tsval = long_from(opt + i); + i += sizeof(uint32_t); + tsecr = long_from(opt + i); + f->timestamp = long_be(tsecr); + i += sizeof(uint32_t); + t->ts_nxt = long_be(tsval); + break; + } + case PICO_TCP_OPTION_SACK: + { + tcp_rcv_sack(t, opt + i, len - 2); + i += len - 2; + break; + } + default: + tcp_dbg_options("TCP: received unsupported option %u\n", type); + i += len - 2; + } + } +} + +static int tcp_send(struct pico_socket_tcp *ts, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr= (struct pico_tcp_hdr *) f->transport_hdr; + struct pico_frame *cpy; + hdr->trans.sport = ts->sock.local_port; + hdr->trans.dport = ts->sock.remote_port; + if (!hdr->seq) + hdr->seq = long_be(ts->snd_nxt); + + if (ts->rcv_nxt != 0) { + if ( (ts->rcv_ackd == 0) || (seq_compare(ts->rcv_ackd, ts->rcv_nxt) != 0) || (hdr->flags & PICO_TCP_ACK)) { + hdr->flags |= PICO_TCP_ACK; + hdr->ack = long_be(ts->rcv_nxt); + ts->rcv_ackd = ts->rcv_nxt; + } + } + + if (hdr->flags & PICO_TCP_SYN) { + ts->snd_nxt++; + } + if (f->payload_len > 0) { + hdr->flags |= PICO_TCP_PSH | PICO_TCP_ACK; + hdr->ack = long_be(ts->rcv_nxt); + ts->rcv_ackd = ts->rcv_nxt; + ts->keepalive_timer_running = 2; /* XXX TODO check fix: added 1 to counter to postpone sending keepalive, ACK is in data segments */ + } + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(ts->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + + /* TCP: ENQUEUE to PROTO ( Transmit ) */ + cpy = pico_frame_copy(f); + if ((pico_enqueue(&tcp_out, cpy) > 0)) { + if (f->payload_len > 0) { + ts->in_flight++; + ts->snd_nxt += f->payload_len; /* update next pointer here to prevent sending same segment twice when called twice in same tick */ + } + tcp_dbg("DBG> [tcp output] state: %02x --> local port:%d remote port: %d seq: %08x ack: %08x flags: %02x = t_len: %d, hdr: %u payload: %d\n", + TCPSTATE(&ts->sock) >> 8, short_be(hdr->trans.sport), short_be(hdr->trans.dport), SEQN(f), ACKN(f), hdr->flags, f->transport_len, (hdr->len & 0xf0) >> 2 , f->payload_len ); + } else { + pico_frame_discard(cpy); + } + return 0; +} + +//#define PICO_TCP_SUPPORT_SOCKET_STATS + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS +static void sock_stats(unsigned long when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + tcp_dbg("STATISTIC> [%lu] socket state: %02x --> local port:%d remote port: %d queue size: %d snd_una: %08x snd_nxt: %08x timer: %d cwnd: %d\n", + when, t->sock.state, short_be(t->sock.local_port), short_be(t->sock.remote_port), t->tcpq_out.size, SEQN(first_segment(&t->tcpq_out)), t->snd_nxt, t->timer_running, t->cwnd); + pico_timer_add(2000, sock_stats, t); +} +#endif + +struct pico_socket *pico_tcp_open(void) +{ + struct pico_socket_tcp *t = pico_zalloc(sizeof(struct pico_socket_tcp)); + if (!t) + return NULL; + t->mss = PICO_TCP_DEFAULT_MSS; + + t->tcpq_in.pool.root = t->tcpq_hold.pool.root = t->tcpq_out.pool.root = &LEAF; + t->tcpq_hold.pool.compare = t->tcpq_in.pool.compare = t->tcpq_out.pool.compare = segment_compare; + + t->tcpq_in.max_size = PICO_DEFAULT_SOCKETQ; + t->tcpq_out.max_size = PICO_DEFAULT_SOCKETQ; + t->tcpq_hold.max_size = 2*PICO_TCP_DEFAULT_MSS; + + /* disable Nagle by default */ + t->sock.opt_flags |= (1 << PICO_SOCKET_OPT_TCPNODELAY); + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS + pico_timer_add(2000, sock_stats, t); +#endif + tcp_set_space(t); + + return &t->sock; +} + +int pico_tcp_read(struct pico_socket *s, void *buf, int len) +{ + struct pico_socket_tcp *t = TCP_SOCK(s); + struct pico_frame *f; + uint32_t in_frame_off, in_frame_len; + int tot_rd_len = 0; + + while (tot_rd_len < len) { + /* To be sure we don't have garbage at the beginning */ + release_until(&t->tcpq_in, t->rcv_processed); + f = first_segment(&t->tcpq_in); + if (!f) { + tcp_set_space(t); + goto out; + } + + /* Hole at the beginning of data, awaiting retransmissions. */ + if (seq_compare(t->rcv_processed, SEQN(f)) < 0) { + tcp_dbg("TCP> read hole beginning of data, %08x - %08x. rcv_nxt is %08x\n",t->rcv_processed, SEQN(f), t->rcv_nxt); + goto out; + } + + if(seq_compare(t->rcv_processed, SEQN(f)) > 0) { + in_frame_off = t->rcv_processed - SEQN(f); + in_frame_len = f->payload_len - in_frame_off; + } else { + in_frame_off = 0; + in_frame_len = f->payload_len; + } + if ((in_frame_len + tot_rd_len) > len) { + in_frame_len = len - tot_rd_len; + } + + if (in_frame_len > f->payload_len - in_frame_off) + in_frame_len = f->payload_len - in_frame_off; + + memcpy((uint8_t *)buf + tot_rd_len, f->payload + in_frame_off, in_frame_len); + tot_rd_len += in_frame_len; + t->rcv_processed += in_frame_len; + + if ((in_frame_len == 0) || (in_frame_len == f->payload_len)) { + pico_discard_segment(&t->tcpq_in, f); + } + } + +out: + tcp_set_space(t); + if (t->tcpq_in.size == 0) { + s->ev_pending &= (~PICO_SOCK_EV_RD); + } + return tot_rd_len; +} + +int pico_tcp_initconn(struct pico_socket *s); +static void initconn_retry(unsigned long when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + if (TCPSTATE(&t->sock) == PICO_SOCKET_STATE_TCP_SYN_SENT) { + if (t->backoff > PICO_TCP_MAX_CONNECT_RETRIES) { + tcp_dbg("TCP> Connection timeout. \n"); + if (t->sock.wakeup) + t->sock.wakeup(PICO_SOCK_EV_ERR, &t->sock); + return; + } + tcp_dbg("TCP> SYN retry %d...\n", t->backoff); + t->backoff++; + pico_tcp_initconn(&t->sock); + } else { + tcp_dbg("TCP> Connection is already established: no retry needed. good.\n"); + } +} + +int pico_tcp_initconn(struct pico_socket *s) +{ + struct pico_socket_tcp *ts = TCP_SOCK(s); + struct pico_frame *syn; + struct pico_tcp_hdr *hdr; + int opt_len = tcp_options_size(ts, PICO_TCP_SYN); + + syn = s->net->alloc(s->net, PICO_SIZE_TCPHDR + opt_len); + if (!syn) + return -1; + hdr = (struct pico_tcp_hdr *) syn->transport_hdr; + + if (!ts->snd_nxt) + ts->snd_nxt = long_be(pico_paws()); + ts->snd_last = ts->snd_nxt; + ts->cwnd = PICO_TCP_IW; + ts->ssthresh = 40; + syn->sock = s; + hdr->seq = long_be(ts->snd_nxt); + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | ts->jumbo; + hdr->flags = PICO_TCP_SYN; + tcp_set_space(ts); + hdr->rwnd = short_be(ts->wnd); + tcp_add_options(ts,syn, PICO_TCP_SYN, opt_len); + hdr->trans.sport = ts->sock.local_port; + hdr->trans.dport = ts->sock.remote_port; + + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(syn)); + + /* TCP: ENQUEUE to PROTO ( SYN ) */ + tcp_dbg("Sending SYN... (ports: %d - %d) size: %d\n", short_be(ts->sock.local_port), short_be(ts->sock.remote_port), syn->buffer_len); + pico_enqueue(&tcp_out, syn); + pico_timer_add(PICO_TCP_SYN_TO << ts->backoff, initconn_retry, ts); + return 0; +} + +static int tcp_send_synack(struct pico_socket *s) +{ + struct pico_socket_tcp *ts = TCP_SOCK(s); + struct pico_frame *synack; + struct pico_tcp_hdr *hdr; + int opt_len = tcp_options_size(ts, PICO_TCP_SYN | PICO_TCP_ACK); + + synack = s->net->alloc(s->net, PICO_SIZE_TCPHDR + opt_len); + if (!synack) + return -1; + hdr = (struct pico_tcp_hdr *) synack->transport_hdr; + + synack->sock = s; + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | ts->jumbo; + hdr->flags = PICO_TCP_SYN | PICO_TCP_ACK; + hdr->rwnd = short_be(ts->wnd); + hdr->seq = long_be(ts->snd_nxt); + ts->rcv_processed = long_be(hdr->seq); + ts->snd_last = ts->snd_nxt; + tcp_set_space(ts); + tcp_add_options(ts,synack, hdr->flags, opt_len); + synack->payload_len = 0; + synack->timestamp = pico_tick; + tcp_send(ts, synack); + pico_frame_discard(synack); + return 0; +} + +static void tcp_send_empty(struct pico_socket_tcp *t, uint16_t flags) +{ + struct pico_frame *f; + struct pico_tcp_hdr *hdr; + int opt_len = tcp_options_size(t, flags); + f = t->sock.net->alloc(t->sock.net, PICO_SIZE_TCPHDR + opt_len); + if (!f) { + return; + } + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo; + hdr->flags = flags; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t,f, flags, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_nxt); + if ((flags & PICO_TCP_ACK) != 0) + hdr->ack = long_be(t->rcv_nxt); + t->rcv_ackd = t->rcv_nxt; + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); +} + +static void tcp_send_ack(struct pico_socket_tcp *t) +{ + return tcp_send_empty(t, PICO_TCP_ACK); +} + +static int tcp_send_rst(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_frame *f; + struct pico_tcp_hdr *hdr, *hdr_rcv; + int opt_len = tcp_options_size(t, PICO_TCP_RST); + int close; + + tcp_dbg("TCP SEND_RST >>>>>>>>>>>>>>> START\n"); + + f = t->sock.net->alloc(t->sock.net, PICO_SIZE_TCPHDR + opt_len); + + if (!f) { + return -1; + } + + hdr_rcv = (struct pico_tcp_hdr *) fr->transport_hdr; + + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo; + hdr->flags = PICO_TCP_RST; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t,f, PICO_TCP_RST, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_nxt); + + /* check if state is synchronized */ + if (((s->state & PICO_SOCKET_STATE_TCP) > PICO_SOCKET_STATE_TCP_SYN_RECV)) { + /* in synchronized state: send RST with seq = ack from previous segment */ + hdr->seq = hdr_rcv->ack; + close = 0; + } else { + /* non-synchronized state */ + /* go to CLOSED here to prevent timer callback to go on after timeout */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + close = 1; + } + + hdr->ack = long_be(t->rcv_nxt); + t->rcv_ackd = t->rcv_nxt; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); + + /* goto CLOSED */ + if (close) { + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + + /* call EV_FIN wakeup before deleting */ + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + + /* delete socket */ + pico_socket_del(&t->sock); + + tcp_dbg("TCP SEND_RST >>>>>>>>>>>>>>> DONE, deleted socket\n"); + } + + return 0; +} + +int pico_tcp_reply_rst(struct pico_frame *fr) +{ + struct pico_tcp_hdr *hdr; + struct pico_frame *f; + int size = PICO_SIZE_TCPHDR; + + tcp_dbg("TCP>>>>>>>>>>>>>>>> sending RST ... <<<<<<<<<<<<<<<<<<\n"); + + f = fr->sock->net->alloc(fr->sock->net, size); + + /* fill in IP data from original frame */ + // TODO if IPv4 + ((struct pico_ipv4_hdr *)(f->net_hdr))->dst.addr = ((struct pico_ipv4_hdr *)(fr->net_hdr))->src.addr; + ((struct pico_ipv4_hdr *)(f->net_hdr))->src.addr = ((struct pico_ipv4_hdr *)(fr->net_hdr))->dst.addr; + + /* fill in TCP data from original frame */ + ((struct pico_tcp_hdr *)(f->transport_hdr))->trans.dport = ((struct pico_tcp_hdr *)(fr->transport_hdr))->trans.sport; + ((struct pico_tcp_hdr *)(f->transport_hdr))->trans.sport = ((struct pico_tcp_hdr *)(fr->transport_hdr))->trans.dport; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = size << 2; + hdr->flags = PICO_TCP_RST | PICO_TCP_ACK; + hdr->rwnd = 0; + if (((struct pico_tcp_hdr *)(fr->transport_hdr))->flags & PICO_TCP_ACK) { + hdr->seq = ((struct pico_tcp_hdr *)(fr->transport_hdr))->ack; + } else { + hdr->seq = 0U; + } + + hdr->ack = ((struct pico_tcp_hdr *)(fr->transport_hdr))->seq + short_be(fr->payload_len); + + /* enqueue for transmission */ + pico_ipv4_frame_push(f,&(((struct pico_ipv4_hdr *)(f->net_hdr))->dst),PICO_PROTO_TCP); + + return 0; +} + +static int tcp_nosync_rst(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_frame *f; + struct pico_tcp_hdr *hdr, *hdr_rcv; + int opt_len = tcp_options_size(t, PICO_TCP_RST | PICO_TCP_ACK); + + tcp_dbg("TCP SEND RST (NON-SYNC) >>>>>>>>>>>>>>>>>> state %x\n",(s->state & PICO_SOCKET_STATE_TCP)); + if (((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_LISTEN)) { + /* XXX TODO NOTE: to prevent the parent socket from trying to send, because this socket has no knowledge of dst IP !!! */ + return pico_tcp_reply_rst(fr); + } + + /***************************************************************************/ + /* sending RST */ + f = t->sock.net->alloc(t->sock.net, PICO_SIZE_TCPHDR + opt_len); + + if (!f) { + return -1; + } + + hdr_rcv = (struct pico_tcp_hdr *) fr->transport_hdr; + + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo; + hdr->flags = PICO_TCP_RST | PICO_TCP_ACK; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t,f, PICO_TCP_RST | PICO_TCP_ACK, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + + /* non-synchronized state */ + if (hdr_rcv->flags & PICO_TCP_ACK) { + hdr->seq = hdr_rcv->ack; + } else { + hdr->seq = 0U; + } + + hdr->ack = hdr_rcv->seq + short_be(fr->payload_len); + + t->rcv_ackd = t->rcv_nxt; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); + + /***************************************************************************/ + + tcp_dbg("TCP SEND_RST (NON_SYNC) >>>>>>>>>>>>>>> DONE, ...\n"); + + return 0; +} + +static void tcp_send_fin(struct pico_socket_tcp *t) +{ + struct pico_frame *f; + struct pico_tcp_hdr *hdr; + int opt_len = tcp_options_size(t, PICO_TCP_FIN); + f = t->sock.net->alloc(t->sock.net, PICO_SIZE_TCPHDR + opt_len); + if (!f) { + return; + } + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo; + hdr->flags = PICO_TCP_FIN | PICO_TCP_ACK; + hdr->ack = long_be(t->rcv_nxt); + t->rcv_ackd = t->rcv_nxt; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t,f, PICO_TCP_FIN, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_nxt); /* XXX TODO check correct ?? --> snd_last? otherwise maybe data after FIN */ + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + //tcp_dbg("SENDING FIN...\n"); + /* TCP: ENQUEUE to PROTO ( Pure ACK ) */ + pico_enqueue(&tcp_out, f); + t->snd_nxt++; +} + +static void tcp_sack_prepare(struct pico_socket_tcp *t) +{ + struct pico_frame *pkt; + uint32_t left=0, right=0; + struct tcp_sack_block *sb; + int n = 0; + if (t->sacks) /* previous sacks are pending */ + return; + + pkt = first_segment(&t->tcpq_in); + while(n < 3) { + if (!pkt) { + if(left) { + sb = pico_zalloc(sizeof(struct tcp_sack_block)); + if (!sb) + break; + sb->left = long_be(left); + sb->right = long_be(right); + n++; + sb->next = t->sacks; + t->sacks = sb; + left = 0; + right = 0; + } + break; + } + if ((SEQN(pkt) < t->rcv_nxt)) { + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } + if (!left) { + left = SEQN(pkt); + right = SEQN(pkt) + pkt->payload_len; + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } + if(SEQN(pkt) == (right + 1)) { + right += pkt->payload_len; + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } else { + sb = pico_zalloc(sizeof(struct tcp_sack_block)); + if (!sb) + break; + sb->left = long_be(left); + sb->right = long_be(right); + n++; + sb->next = t->sacks; + t->sacks = sb; + left = 0; + right = 0; + pkt = next_segment(&t->tcpq_in, pkt); + } + } +} + +static int tcp_data_in(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + + if (((hdr->len & 0xf0) >> 2) <= f->transport_len) { + tcp_parse_options(f); + f->payload = f->transport_hdr + ((hdr->len & 0xf0) >>2); + f->payload_len = f->transport_len - ((hdr->len & 0xf0) >>2); + tcp_dbg("TCP> Received segment. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + + if (seq_compare(SEQN(f), t->rcv_nxt) <= 0) { + struct pico_frame *nxt; + if (seq_compare(SEQN(f), t->rcv_nxt) == 0) { /* Exactly what we expected */ + struct pico_frame *cpy = pico_frame_copy(f); + /* Enqueue: try to put into RCV buffer */ + if(pico_enqueue_segment(&t->tcpq_in, cpy) <= 0) { + pico_frame_discard(cpy); + } + t->rcv_nxt = SEQN(f) + f->payload_len; + nxt = peek_segment(&t->tcpq_in, t->rcv_nxt); + while(nxt) { + tcp_dbg("scrolling rcv_nxt...%08x\n", t->rcv_nxt); + t->rcv_nxt += f->payload_len; + nxt = peek_segment(&t->tcpq_in, t->rcv_nxt); + } + t->sock.ev_pending |= PICO_SOCK_EV_RD; + t->rcv_nxt = SEQN(f) + f->payload_len; + } else { + tcp_dbg("TCP> lo segment. Uninteresting retransmission. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + } + } else { + tcp_dbg("TCP> hi segment. Possible packet loss. I'll dupack this. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + if (t->sack_ok) { + tcp_sack_prepare(t); + } + } + /* In either case, ack til recv_nxt. */ + if ( ((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_CLOSE_WAIT) && ((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_SYN_SENT) && ((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_SYN_RECV)) { + //tcp_dbg("SENDACK CALLED FROM OUTSIDE tcp_synack, state %x\n",t->sock.state); + tcp_send_ack(t); + } else { + //tcp_dbg("SENDACK PREVENTED IN SYNSENT STATE\n"); + } + return 0; + } else { + tcp_dbg("TCP: invalid data in pkt len, exp: %d, got %d\n", (hdr->len & 0xf0) >> 2, f->transport_len); + return -1; + } +} + +static int tcp_ack_advance_una(struct pico_socket_tcp *t, struct pico_frame *f) +{ + int ret = release_all_until(&t->tcpq_out, ACKN(f)); + if (ret > 0) + t->sock.ev_pending |= PICO_SOCK_EV_WR; + return ret; +} + +static uint16_t time_diff(unsigned long a, unsigned long b) +{ + if (a >= b) + return (a - b); + else + return (b - a); +} + +static void tcp_rtt(struct pico_socket_tcp *t, uint32_t rtt) +{ + + uint32_t avg = t->avg_rtt; + uint32_t rvar = t->rttvar; + if (!avg) { + /* This follows RFC2988 + * (2.2) When the first RTT measurement R is made, the host MUST set + * + * SRTT <- R + * RTTVAR <- R/2 + * RTO <- SRTT + max (G, K*RTTVAR) + */ + t->avg_rtt = rtt; + t->rttvar = rtt >> 1; + t->rto = t->avg_rtt + (t->rttvar << 4); + } else { + int var = (t->avg_rtt - rtt); + if (var < 0) + var = 0-var; + /* RFC2988, section (2.3). Alpha and beta are the ones suggested. */ + + /* First, evaluate a new value for the rttvar */ + t->rttvar <<= 2; + t->rttvar -= rvar; + t->rttvar += var; + t->rttvar >>= 2; + + /* Then, calculate the new avg_rtt */ + t->avg_rtt <<= 3; + t->avg_rtt -= avg; + t->avg_rtt += rtt; + t->avg_rtt >>= 3; + + /* Finally, assign a new value for the RTO, as specified in the RFC, with K=4 */ + t->rto = t->avg_rtt + (t->rttvar << 2); + } + tcp_dbg(" -----=============== RTT CUR: %u AVG: %u RTTVAR: %u RTO: %u ======================----\n", rtt, t->avg_rtt, t->rttvar, t->rto); +} + +static void tcp_congestion_control(struct pico_socket_tcp *t) +{ + if (t->x_mode > PICO_TCP_LOOKAHEAD) + return; + if (t->cwnd > t->tcpq_out.frames) { + tcp_dbg("Limited by app: %d\n", t->cwnd); + if (t->sock.wakeup) + t->sock.wakeup(PICO_SOCK_EV_WR, &t->sock); + return; + } + tcp_dbg("Doing congestion control\n"); + if (t->cwnd < t->ssthresh) { + t->cwnd++; + } else { + t->cwnd_counter++; + if (t->cwnd_counter >= t->cwnd) { + t->cwnd++; + t->cwnd_counter = 0; + } + } + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", pico_tick, t->cwnd, t->ssthresh, t->in_flight); +} + +static void add_retransmission_timer(struct pico_socket_tcp *t, unsigned long next_ts); +static void tcp_retrans_timeout(unsigned long val, void *sock) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) sock; + struct pico_frame *f = NULL; + unsigned long limit = val - t->rto; + struct pico_tcp_hdr *hdr; + if( t->sock.net && ((t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_ESTABLISHED + || (t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_CLOSE_WAIT) ) + { + tcp_dbg("\n\nTIMEOUT! backoff = %d\n", t->backoff); + /* was timer cancelled? */ + if (t->timer_running == 0) { + add_retransmission_timer(t, 0); + return; + } + t->timer_running--; + + f = first_segment(&t->tcpq_out); + while (f) { + if ((t->x_mode == PICO_TCP_WINDOW_FULL) || + ((f->timestamp != 0) && (f->timestamp <= limit))) { + struct pico_frame *cpy; + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + tcp_dbg("TCP BLACKOUT> TIMED OUT (output) frame %08x, len= %d rto=%d Win full: %d frame flags: %04x\n", SEQN(f), f->payload_len, t->rto, t->x_mode == PICO_TCP_WINDOW_FULL, f->flags); + if ((t->x_mode != PICO_TCP_WINDOW_FULL) ) { + t->x_mode = PICO_TCP_BLACKOUT; + tcp_dbg("Mode: Blackout.\n"); + t->cwnd = PICO_TCP_IW; + t->in_flight = 0; + } + f->timestamp = pico_tick; + tcp_add_options(t, f, 0, f->transport_len - f->payload_len - PICO_SIZE_TCPHDR); + hdr->rwnd = short_be(t->wnd); + hdr->flags |= PICO_TCP_PSH; + hdr->ack = long_be(t->rcv_nxt); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + /* TCP: ENQUEUE to PROTO ( retransmit )*/ + cpy = pico_frame_copy(f); + if (pico_enqueue(&tcp_out, cpy) > 0) { + t->backoff++; + add_retransmission_timer(t, (t->rto << t->backoff) + pico_tick); + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", pico_tick, t->cwnd, t->ssthresh, t->in_flight); + return; + } else { + add_retransmission_timer(t, (t->rto << t->backoff) + pico_tick); + pico_frame_discard(cpy); + } + } + f = next_segment(&t->tcpq_out, f); + } + t->backoff = 0; + add_retransmission_timer(t, 0); + if (t->tcpq_out.size < t->tcpq_out.max_size) + t->sock.ev_pending |= PICO_SOCK_EV_WR; + return; + } +} + +static void add_retransmission_timer(struct pico_socket_tcp *t, unsigned long next_ts) +{ + struct pico_tree_node * index; + + if (t->timer_running > 0) + return; + + if (next_ts == 0) { + struct pico_frame *f; + + pico_tree_foreach(index,&t->tcpq_out.pool){ + f = index->keyValue; + if (((next_ts == 0) || (f->timestamp < next_ts)) && (f->timestamp > 0)) { + next_ts = f->timestamp; + } + } + } + if (next_ts > 0) { + if ((next_ts + t->rto) > pico_tick) { + pico_timer_add(next_ts + t->rto - pico_tick, tcp_retrans_timeout, t); + } else { + pico_timer_add(1, tcp_retrans_timeout, t); + } + t->timer_running++; + } +} + +static int tcp_retrans(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_frame *cpy; + struct pico_tcp_hdr *hdr; + if (f) { + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + tcp_dbg("TCP> RETRANS (by dupack) frame %08x, len= %d\n", SEQN(f), f->payload_len); + f->timestamp = pico_tick; + tcp_add_options(t, f, 0, f->transport_len - f->payload_len - PICO_SIZE_TCPHDR); + hdr->rwnd = short_be(t->wnd); + hdr->flags |= PICO_TCP_PSH; + hdr->ack = long_be(t->rcv_nxt); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum_ipv4(f)); + /* TCP: ENQUEUE to PROTO ( retransmit )*/ + cpy = pico_frame_copy(f); + if (pico_enqueue(&tcp_out, cpy) > 0) { + t->in_flight++; + t->snd_last_out = SEQN(cpy); + add_retransmission_timer(t, pico_tick + t->rto); + } else { + pico_frame_discard(cpy); + } + return(f->payload_len); + } + return 0; +} + +#ifdef TCP_ACK_DBG +static void tcp_ack_dbg(struct pico_socket *s, struct pico_frame *f) +{ + uint32_t una, nxt, ack, cur; + struct pico_frame *una_f = NULL, *cur_f; + struct pico_tree_node *idx; + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + char info[64]; + char tmp[64]; + ack = ACKN(f); + nxt = t->snd_nxt; + tcp_dbg("===================================\n"); + tcp_dbg("Queue out (%d/%d). ACKED=%08x\n", t->tcpq_out.size, t->tcpq_out.max_size, ack); + + pico_tree_foreach(idx, &t->tcpq_out.pool) { + info[0] = 0; + cur_f = idx->keyValue; + cur = SEQN(cur_f); + if (!una_f) { + una_f = cur_f; + una = SEQN(una_f); + } + + if (cur == nxt) { + strncpy(tmp, info, strlen(info)); + snprintf(info,64, "%s SND_NXT", tmp); + } + if (cur == ack) { + strncpy(tmp, info, strlen(info)); + snprintf(info,64, "%s ACK", tmp); + } + if (cur == una) { + strncpy(tmp, info, strlen(info)); + snprintf(info,64, "%s SND_UNA", tmp); + } + if (cur == t->snd_last) { + strncpy(tmp, info, strlen(info)); + snprintf(info,64, "%s SND_LAST", tmp); + } + tcp_dbg("%08x %d%s\n", cur, cur_f->payload_len, info); + + } + tcp_dbg("SND_NXT is %08x, snd_LAST is %08x", nxt, t->snd_last); + tcp_dbg("===================================\n"); + tcp_dbg("\n\n"); +} +#endif + +static int tcp_ack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_frame *f_new; /* use with Nagle to push to out queue */ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + uint32_t rtt = 0; + int acked = 0; + struct pico_frame *una = NULL; + if ((hdr->flags & PICO_TCP_ACK) == 0) + return -1; + +#ifdef TCP_ACK_DBG + tcp_ack_dbg(s,f); +#endif + + tcp_parse_options(f); + t->recv_wnd = short_be(hdr->rwnd); + + acked = tcp_ack_advance_una(t, f); + una = first_segment(&t->tcpq_out); + + if ((t->x_mode == PICO_TCP_BLACKOUT) || + ((t->x_mode == PICO_TCP_WINDOW_FULL) && ((t->recv_wnd << t->recv_wnd_scale) > t->mss))) { + tcp_dbg("Re-entering look-ahead...\n\n\n"); + t->x_mode = PICO_TCP_LOOKAHEAD; + t->backoff = 0; + } + + /* One should be acked. */ +// if ((acked == 0) && (t->in_flight > 0)) + if ((acked == 0) && (f->payload_len == 0) && (t->in_flight > 0)) + t->in_flight--; + if (!una || acked > 0) { + t->x_mode = PICO_TCP_LOOKAHEAD; + tcp_dbg("Mode: Look-ahead. In flight: %d/%d buf: %d\n", t->in_flight, t->cwnd, t->tcpq_out.frames); + t->backoff = 0; + + /* Do rtt/rttvar/rto calculations */ + /* First, try with timestamps, using the value from options */ + if(f && (f->timestamp != 0)) { + rtt = time_diff(pico_tick, f->timestamp); + if (rtt) + tcp_rtt(t, rtt); + } else if(una && (una->timestamp != 0)) { + /* If no timestamps are there, use conservatve estimation on the una */ + rtt = time_diff(pico_tick, una->timestamp); + if (rtt) + tcp_rtt(t, rtt); + } + + tcp_dbg("TCP ACK> FRESH ACK %08x (acked %d) Queue size: %u/%u frames: %u cwnd: %u in_flight: %u snd_una: %u\n", ACKN(f), acked, t->tcpq_out.size, t->tcpq_out.max_size, t->tcpq_out.frames, t->cwnd, t->in_flight, SEQN(una)); + if (acked > t->in_flight) { + tcp_dbg("WARNING: in flight < 0\n"); + t->in_flight = 0; + } else + t->in_flight -= (acked); + + } else if ((t->snd_old_ack == ACKN(f)) && /* We've just seen this ack, and... */ + ((0 == (hdr->flags & (PICO_TCP_PSH | PICO_TCP_SYN))) && + (f->payload_len == 0)) && /* This is a pure ack, and... */ + (ACKN(f) != t->snd_nxt)) /* There is something in flight awaiting to be acked... */ + { + /* Process incoming duplicate ack. */ + if (t->x_mode < PICO_TCP_RECOVER) { + t->x_mode++; + tcp_dbg("Mode: DUPACK %d, due to PURE ACK %0x, len = %d\n", t->x_mode, SEQN(f), f->payload_len); + tcp_dbg("ACK: %x - QUEUE: %x\n",ACKN(f), SEQN(first_segment(&t->tcpq_out))); + if (t->x_mode == PICO_TCP_RECOVER) { /* Switching mode */ + t->snd_retry = SEQN(first_segment(&t->tcpq_out)); + if (t->ssthresh > t->cwnd) + t->ssthresh >>=2; + else + t->ssthresh = (t->cwnd >> 1); + if (t->ssthresh < 2) + t->ssthresh = 2; + } + } else if (t->x_mode == PICO_TCP_RECOVER) { + tcp_dbg("TCP RECOVER> DUPACK! snd_una: %08x, snd_nxt: %08x, acked now: %08x\n", SEQN(first_segment(&t->tcpq_out)), t->snd_nxt, ACKN(f)); + if (t->in_flight <= t->cwnd) { + struct pico_frame *nxt = peek_segment(&t->tcpq_out, t->snd_retry); + if (!nxt) + nxt = first_segment(&t->tcpq_out); + + while (nxt && (nxt->flags & PICO_FRAME_FLAG_SACKED) && (nxt != first_segment(&t->tcpq_out))) { + tcp_dbg("Skipping %08x because it is sacked.\n", SEQN(nxt)); + nxt = next_segment(&t->tcpq_out, nxt); + } + + if (nxt && (seq_compare(SEQN(nxt), t->snd_nxt)) > 0) + nxt = NULL; + if (nxt && (seq_compare(SEQN(nxt), SEQN(first_segment(&t->tcpq_out))) > (t->recv_wnd << t->recv_wnd_scale))) + nxt = NULL; + + if(!nxt) + nxt = first_segment(&t->tcpq_out); + if (nxt) { + tcp_retrans(t, peek_segment(&t->tcpq_out, t->snd_retry)); + t->snd_retry = SEQN(nxt); + } + } + + if (++t->cwnd_counter > 1) { + t->cwnd--; + if (t->cwnd < 2) + t->cwnd = 2; + t->cwnd_counter = 0; + } + } else { + tcp_dbg("DUPACK in mode %d \n", t->x_mode); + + } + } /* End case duplicate ack detection */ + + /* Do congestion control */ + tcp_congestion_control(t); + if ((acked > 0) && t->sock.wakeup) { + if (t->tcpq_out.size < t->tcpq_out.max_size) + t->sock.wakeup(PICO_SOCK_EV_WR, &(t->sock)); + //t->sock.ev_pending |= PICO_SOCK_EV_WR; + } + + /* if Nagle enabled, check if no unack'ed data and fill out queue (till window) */ + if (IS_NAGLE_ENABLED((&(t->sock)))) { + while (!IS_TCP_HOLDQ_EMPTY(t) && ((t->tcpq_out.max_size - t->tcpq_out.size) >= PICO_TCP_DEFAULT_MSS)) { + tcp_dbg_nagle("TCP_ACK - NAGLE add new segment\n"); + f_new = pico_hold_segment_make(t); + if (f_new == NULL) + break; /* XXX corrupt !!! (or no memory) */ + if (pico_enqueue_segment(&t->tcpq_out,f_new) <= 0) + // handle error + tcp_dbg_nagle("TCP_ACK - NAGLE FAILED to enqueue in out\n"); + } + } + + /* If some space was created, put a few segments out. */ + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", pico_tick, t->cwnd, t->ssthresh, t->in_flight); + if (t->x_mode == PICO_TCP_LOOKAHEAD) { + if ((t->cwnd >= t->in_flight) && (t->snd_nxt > t->snd_last_out)) { + pico_tcp_output(&t->sock, t->cwnd - t->in_flight); + } + } + + t->snd_old_ack = ACKN(f); + return 0; +} + +static int tcp_finwaitack(struct pico_socket *s, struct pico_frame *f) +{ + tcp_dbg("RECEIVED ACK IN FIN_WAIT1\nTCP> IN STATE FIN_WAIT2\n"); + + /* acking part */ + tcp_ack(s,f); + /* update TCP state */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_FIN_WAIT2; + + return 0; +} + +static void tcp_deltcb(unsigned long when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + if (TCPSTATE(&t->sock) == PICO_SOCKET_STATE_TCP_TIME_WAIT) { + tcp_dbg("TCP> state: time_wait, final timer expired, going to closed state\n"); + /* update state */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + /* call EV_FIN wakeup before deleting */ + if (t->sock.wakeup) { + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + } + /* delete socket */ + pico_socket_del(&t->sock); + } else { + tcp_dbg("TCP> trying to go to closed, wrong state\n"); + } +} + +static int tcp_finwaitfin(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + tcp_dbg("TCP> received fin in FIN_WAIT2\n"); + /* received FIN, increase ACK nr */ + t->rcv_nxt = long_be(hdr->seq) + 1; + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_CLOSE, s); + if (f->payload_len > 0) /* needed?? */ + tcp_data_in(s,f); + /* send ACK */ + tcp_send_ack(t); + /* set timer */ + pico_timer_add(200, tcp_deltcb, t); + return 0; +} + +static int tcp_closewaitack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("TCP> received ack in CLOSING\n"); + /* acking part */ + tcp_ack(s,f); + /* update TCP state */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set timer */ + pico_timer_add(200, tcp_deltcb, t); + return 0; +} + +static int tcp_lastackwait(struct pico_socket *s, struct pico_frame *f) +{ + tcp_dbg("TCP> state: last_ack, received ack, to closed\n"); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSED; + s->state &= 0xFF00U; + s->state |= PICO_SOCKET_STATE_CLOSED; + /* call socket wakeup with EV_FIN */ + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_FIN, s); + /* delete socket */ + pico_socket_del(s); + return 0; +} + +static int tcp_syn(struct pico_socket *s, struct pico_frame *f) +{ + /* TODO: Check against backlog length */ + struct pico_socket_tcp *new = (struct pico_socket_tcp *)pico_socket_clone(s); + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + if (!new) + return -1; + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS + pico_timer_add(2000, sock_stats, s); +#endif + + new->sock.remote_port = ((struct pico_trans *)f->transport_hdr)->sport; +#ifdef PICO_SUPPORT_IPV4 + if (IS_IPV4(f)) { + new->sock.remote_addr.ip4.addr = ((struct pico_ipv4_hdr *)(f->net_hdr))->src.addr; + new->sock.local_addr.ip4.addr = ((struct pico_ipv4_hdr *)(f->net_hdr))->dst.addr; + } +#endif +#ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) { + new->sock.remote_addr.ip6 = ((struct pico_ipv6_hdr *)(f->net_hdr))->src; + new->sock.local_addr.ip6 = ((struct pico_ipv6_hdr *)(f->net_hdr))->dst; + } +#endif + + /* Set socket limits */ + new->tcpq_in.max_size = PICO_DEFAULT_SOCKETQ; + new->tcpq_out.max_size = PICO_DEFAULT_SOCKETQ; + new->tcpq_hold.max_size = 2*PICO_TCP_DEFAULT_MSS; + + f->sock = &new->sock; + tcp_parse_options(f); + new->mss = PICO_TCP_DEFAULT_MSS; + new->rcv_nxt = long_be(hdr->seq) + 1; + new->snd_nxt = long_be(pico_paws()); + new->snd_last = new->snd_nxt; + new->cwnd = PICO_TCP_IW; + new->ssthresh = 40; + new->recv_wnd = short_be(hdr->rwnd); + new->jumbo = hdr->len & 0x07; + new->sock.parent = s; + new->sock.wakeup = s->wakeup; + /* Initialize timestamp values */ + new->sock.state = PICO_SOCKET_STATE_BOUND | PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_RECV; + pico_socket_add(&new->sock); + tcp_send_synack(&new->sock); + tcp_dbg("SYNACK sent, socket added. snd_nxt is %08x\n", new->snd_nxt); + return 0; +} + +static void tcp_set_init_point(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->rcv_processed = t->rcv_nxt; +} + +static int tcp_synack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + + if (ACKN(f) == (1 + t->snd_nxt)) { + t->rcv_nxt = long_be(hdr->seq); + t->rcv_processed = t->rcv_nxt + 1; + tcp_ack(s, f); + + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_ESTABLISHED; + tcp_dbg("TCP> Established. State: %x\n", s->state); + + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_CONN, s); + s->ev_pending |= PICO_SOCK_EV_WR; + + t->rcv_nxt++; + t->snd_nxt++; + tcp_send_ack(t); /* return ACK */ + + return 0; + + } else { + tcp_dbg("TCP> Not established, RST sent.\n"); + tcp_nosync_rst(s,f); + return 0; + } +} + +static int tcp_first_ack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("ACK in SYN_RECV: expecting %08x got %08x\n", t->snd_nxt, ACKN(f)); + if (t->snd_nxt == ACKN(f)) { + tcp_set_init_point(s); + tcp_ack(s, f); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_ESTABLISHED; + tcp_dbg("TCP: Established. State now: %04x\n", s->state); + if( !s->parent && s->wakeup) { /* If the socket has no parent, -> sending socket that has a sim_open */ + tcp_dbg("FIRST ACK - No parent found -> sending socket\n"); + s->wakeup(PICO_SOCK_EV_CONN, s); + } + if (s->parent && s->parent->wakeup) { + tcp_dbg("FIRST ACK - Parent found -> listening socket\n"); + s->wakeup = s->parent->wakeup; + s->parent->wakeup(PICO_SOCK_EV_CONN, s->parent); + } + s->ev_pending |= PICO_SOCK_EV_WR; + tcp_dbg("%s: snd_nxt is now %08x\n", __FUNCTION__, t->snd_nxt); + return 0; + } else { + tcp_nosync_rst(s,f); + return 0; + } +} + +static int tcp_closewait(struct pico_socket *s, struct pico_frame *f) +{ + + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + + + if (f->payload_len > 0) + tcp_data_in(s,f); + if (f->flags & PICO_TCP_ACK) + tcp_ack(s,f); + if (seq_compare(SEQN(f), t->rcv_nxt) == 0) { + /* received FIN, increase ACK nr */ + t->rcv_nxt = long_be(hdr->seq) + 1; + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSE_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + tcp_dbg("TCP> Close-wait\n"); + + if (s->wakeup){ + if(f->payload_len>0){ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->sock.ev_pending |=PICO_SOCK_EV_CLOSE; + }else + s->wakeup(PICO_SOCK_EV_CLOSE, s); + } + } else { + tcp_send_ack(t); /* return ACK */ + } + return 0; +} + +/*static int tcp_fin(struct pico_socket *s, struct pico_frame *f) +{ + return 0; +}*/ + +static int tcp_rcvfin(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("TCP> Received FIN in FIN_WAIT1\n"); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSING; + t->rcv_processed = t->rcv_nxt + 1; + t->rcv_nxt++; + /* send ACK */ + tcp_send_ack(t); + return 0; +} + +static int tcp_finack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("TCP> ENTERED finack\n"); + t->rcv_nxt++; + /* send ACK */ + tcp_send_ack(t); + + /* call socket wakeup with EV_FIN */ + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_FIN, s); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + pico_timer_add(2000, tcp_deltcb, t); + + return 0; +} + +static int tcp_rst(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + + tcp_dbg("TCP >>>>>>>>>>>>>> received RST <<<<<<<<<<<<<<<<<<<<\n"); + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_SYN_SENT) { + /* the RST is acceptable if the ACK field acknowledges the SYN */ + if ((t->snd_nxt + 1) == ACKN(f)) { /* valid, got to closed state */ + /* update state */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + + /* call EV_FIN wakeup before deleting */ + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + + /* call EV_ERR wakeup before deleting */ + pico_err = PICO_ERR_ECONNRESET; + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_ERR, &(t->sock)); + + /* delete socket */ + pico_socket_del(&t->sock); + } else { /* not valid, ignore */ + tcp_dbg("TCP RST> IGNORE\n"); + return 0; + } + } else { /* all other states */ + /* all reset (RST) segments are validated by checking their SEQ-fields, + a reset is valid if its sequence number is in the window */ + if ((long_be(hdr->seq) >= t->rcv_ackd) && (long_be(hdr->seq) <= ((short_be(hdr->rwnd)<<(t->wnd_scale)) + t->rcv_ackd))) { + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_SYN_RECV) { + /* go to closed */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + /* call EV_ERR wakeup */ + pico_err = PICO_ERR_ECONNRESET; + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_ERR, &(t->sock)); + tcp_dbg("TCP RST> SOCKET BACK TO LISTEN\n"); + pico_socket_del(s); + } else { + /* go to closed */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + + /* call EV_FIN wakeup before deleting */ + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + /* call EV_ERR wakeup before deleting */ + pico_err = PICO_ERR_ECONNRESET; + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_ERR, &(t->sock)); + + /* delete socket */ + pico_socket_del(&t->sock); + } + } else { /* not valid, ignore */ + tcp_dbg("TCP RST> IGNORE\n"); + return 0; + } + } + + return 0; +} + +struct tcp_action_entry { + uint16_t tcpstate; + int (*syn)(struct pico_socket *s, struct pico_frame *f); + int (*synack)(struct pico_socket *s, struct pico_frame *f); + int (*ack)(struct pico_socket *s, struct pico_frame *f); + int (*data)(struct pico_socket *s, struct pico_frame *f); + int (*fin)(struct pico_socket *s, struct pico_frame *f); + int (*finack)(struct pico_socket *s, struct pico_frame *f); + int (*rst)(struct pico_socket *s, struct pico_frame *f); +}; + +static struct tcp_action_entry tcp_fsm[] = { + /* State syn synack ack data fin finack rst*/ + { PICO_SOCKET_STATE_TCP_UNDEF, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, + { PICO_SOCKET_STATE_TCP_CLOSED, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, + { PICO_SOCKET_STATE_TCP_LISTEN, &tcp_syn, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, NULL }, + { PICO_SOCKET_STATE_TCP_SYN_SENT, &tcp_nosync_rst, &tcp_synack, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_SYN_RECV, NULL, &tcp_nosync_rst, &tcp_first_ack, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_nosync_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_ESTABLISHED, NULL, &tcp_ack, &tcp_ack, &tcp_data_in, &tcp_closewait, &tcp_closewait, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_CLOSE_WAIT, NULL, &tcp_ack, &tcp_ack, &tcp_send_rst, &tcp_closewait, &tcp_closewait, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_LAST_ACK, NULL, &tcp_ack, &tcp_lastackwait, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_FIN_WAIT1, NULL, &tcp_ack, &tcp_finwaitack, &tcp_data_in, &tcp_rcvfin, &tcp_finack, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_FIN_WAIT2, NULL, &tcp_ack, &tcp_ack, &tcp_data_in, &tcp_finwaitfin, &tcp_finack, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_CLOSING, NULL, &tcp_ack, &tcp_closewaitack, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_TIME_WAIT, NULL, &tcp_ack, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_rst } +}; + +/* + NOTE: in SYN-RECV receiving syn when cloned by default (see yellow pos-it), should send reset. +*/ + +int pico_tcp_input(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + int ret = 0; + uint8_t flags = hdr->flags; + struct tcp_action_entry *action = &tcp_fsm[s->state >> 8]; + + f->payload = (f->transport_hdr + ((hdr->len & 0xf0) >> 2)); + f->payload_len = f->transport_len - ((hdr->len & 0xf0) >> 2); + + tcp_dbg("[%lu] TCP> [tcp input] socket: %p state: %d <-- local port:%d remote port: %d seq: %08x ack: %08x flags: %02x = t_len: %d, hdr: %u payload: %d\n", pico_tick, + s, s->state >> 8, short_be(hdr->trans.dport), short_be(hdr->trans.sport), SEQN(f), ACKN(f), hdr->flags, f->transport_len, (hdr->len & 0xf0) >> 2, f->payload_len ); + + /* This copy of the frame has the current socket as owner */ + f->sock = s; + + /* Those are not supported at this time. */ + flags &= ~(PICO_TCP_CWR | PICO_TCP_URG | PICO_TCP_ECN); + if (flags == PICO_TCP_SYN) { + if (action->syn) + action->syn(s,f); + } else if (flags == (PICO_TCP_SYN | PICO_TCP_ACK)) { + if (action->synack) + action->synack(s,f); + } else { + if ((flags == PICO_TCP_ACK) || (flags == (PICO_TCP_ACK | PICO_TCP_PSH))) { + if (action->ack) { + action->ack(s,f); + } + } + if (f->payload_len > 0) { + ret = f->payload_len; + if (action->data) + action->data(s,f); + } + if (flags == PICO_TCP_FIN) { + if (action->fin) + action->fin(s,f); + } + if ((flags == (PICO_TCP_FIN | PICO_TCP_ACK)) || (flags == (PICO_TCP_FIN | PICO_TCP_ACK | PICO_TCP_PSH))) { + if (action->finack) + action->finack(s,f); + } + if (flags & PICO_TCP_RST) { + if (action->rst) + action->rst(s,f); + } + } + +//discard: + pico_frame_discard(f); + return ret; +} + +static void tcp_send_keepalive(unsigned long when, void *_t) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)_t; + tcp_dbg("\n\nSending keepalive (%d), [State = %d]...\n", t->backoff,t->sock.state ); + if( t->sock.net && ((t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_ESTABLISHED) ) + { + tcp_send_ack(t); + + if (t->keepalive_timer_running > 0) { + t->keepalive_timer_running--; + } + + if (t->keepalive_timer_running == 0) { + t->keepalive_timer_running++; + tcp_dbg("[Self] Adding timer(retransmit keepalive)\n"); + pico_timer_add(t->rto << (++t->backoff), tcp_send_keepalive, t); + } + } +} + +int pico_tcp_output(struct pico_socket *s, int loop_score) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_frame *f, *una; + struct pico_tcp_hdr *hdr; + int sent = 0; + + una = first_segment(&t->tcpq_out); + + f = peek_segment(&t->tcpq_out, t->snd_nxt); + while((f) && (t->cwnd >= t->in_flight)) { + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + f->timestamp = pico_tick; + tcp_add_options(t, f, hdr->flags, tcp_options_size(t, hdr->flags)); + if (seq_compare(SEQN(f) + f->payload_len, SEQN(una) + (t->recv_wnd << t->recv_wnd_scale)) > 0) { + t->cwnd = t->in_flight; + if (t->cwnd < 1) + t->cwnd = 1; + if (t->x_mode != PICO_TCP_WINDOW_FULL) { + tcp_dbg("TCP> RIGHT SIZING (rwnd: %d, frame len: %d\n",t->recv_wnd << t->recv_wnd_scale, f->payload_len); + tcp_dbg("In window full...\n"); + t->snd_nxt = SEQN(una); /* XXX prevent out-of-order-packets ! */ /*DLA re-enabled.*/ + t->snd_retry = SEQN(una); /* XXX replace by retry pointer? */ + + /* Alternative to the line above: (better performance, but seems to lock anyway with larger buffers) + if (seq_compare(t->snd_nxt, SEQN(una)) > 0) + t->snd_nxt -= f->payload_len; + */ + + t->x_mode = PICO_TCP_WINDOW_FULL; + if (t->keepalive_timer_running == 0) { + tcp_dbg("[Window full] Adding timer(send keepalive)\n"); + tcp_send_keepalive(0, t); + } + } + break; + } + tcp_dbg("TCP> DEQUEUED (for output) frame %08x, acks %08x len= %d, remaining frames %d\n", SEQN(f), ACKN(f), f->payload_len,t->tcpq_out.frames); + tcp_send(t, f); + sent++; + loop_score--; + t->snd_last_out = SEQN(f); + if (loop_score < 1) + break; + if (f->payload_len > 0) { + f = next_segment(&t->tcpq_out, f); + } else { + f = NULL; + } + } + if (sent > 0) { + if (t->rto < PICO_TCP_RTO_MIN) + t->rto = PICO_TCP_RTO_MIN; + //if (s->wakeup) + // t->sock.wakeup(PICO_SOCK_EV_WR, &t->sock); + add_retransmission_timer(t, pico_tick + t->rto); + } else { + /* Nothing to transmit. */ + } + + if ((t->tcpq_out.frames == 0) && (s->state & PICO_SOCKET_STATE_SHUT_LOCAL)) { /* if no more packets in queue, XXX replacled !f by tcpq check */ + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED) { + tcp_dbg("TCP> buffer empty, shutdown established ...\n"); + /* send fin if queue empty and in state shut local (write) */ + tcp_send_fin(t); + /* change tcp state to FIN_WAIT1 */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_FIN_WAIT1; + } else if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_CLOSE_WAIT) { + /* send fin if queue empty and in state shut local (write) */ + tcp_send_fin(t); + /* change tcp state to LAST_ACK */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_LAST_ACK; + tcp_dbg("TCP> STATE: LAST_ACK.\n"); + } + } + return loop_score; +} + +/* function to make new segment from hold queue with specific size (mss) */ +static struct pico_frame * pico_hold_segment_make(struct pico_socket_tcp *t) +{ + struct pico_frame *f_temp,*f_new; + struct pico_socket *s = (struct pico_socket *) &t->sock; + struct pico_tcp_hdr *hdr; + int total_len = 0, total_payload_len = 0; + int off = 0, test = 0; + + off = pico_tcp_overhead(s); + + /* init with first frame in hold queue */ + f_temp = first_segment(&t->tcpq_hold); + total_len = f_temp->payload_len; + f_temp = next_segment(&t->tcpq_hold, f_temp); + + /* check till total_len <= MSS */ + while ((f_temp != NULL) && ((total_len+f_temp->payload_len) <= PICO_TCP_DEFAULT_MSS)) { + total_len += f_temp->payload_len; + f_temp = next_segment(&t->tcpq_hold, f_temp); + if (f_temp == NULL) + break; + } + /* alloc new frame with payload size = off + total_len */ + f_new = pico_socket_frame_alloc(s, off + total_len); + if (!f_new) { + pico_err = PICO_ERR_ENOMEM; + return f_new; + } + + hdr = (struct pico_tcp_hdr *) f_new->transport_hdr; + /* init new frame */ + f_new->payload += off; + f_new->payload_len -= off; + f_new->sock = s; + + f_temp = first_segment(&t->tcpq_hold); + hdr->seq = ((struct pico_tcp_hdr *)(f_temp->transport_hdr))->seq; /* get sequence number of first frame */ + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + + /* check till total_payload_len <= MSS */ + while ((f_temp != NULL) && ((total_payload_len + f_temp->payload_len) <= PICO_TCP_DEFAULT_MSS)) { + /* cpy data and discard frame */ + test++; + memcpy(f_new->payload + total_payload_len, f_temp->payload, f_temp->payload_len); + total_payload_len += f_temp->payload_len; + pico_discard_segment(&t->tcpq_hold, f_temp); + f_temp = first_segment(&t->tcpq_hold); + } + + hdr->len = (f_new->payload - f_new->transport_hdr) << 2 | t->jumbo; + + tcp_dbg_nagle("NAGLE make - joined %d segments, len %d bytes\n",test,total_payload_len); + + return f_new; +} + +/* original behavior kept when Nagle disabled; + Nagle algorithm added here, keeping hold frame queue instead of eg linked list of data */ +int pico_tcp_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + struct pico_socket_tcp *t = (struct pico_socket_tcp *) f->sock; + struct pico_frame *f_new; + int total_len = 0; + + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_last + 1); + hdr->len = (f->payload - f->transport_hdr) << 2 | t->jumbo; + + if (f->payload_len > (t->tcpq_out.max_size - t->tcpq_out.size)) + t->sock.ev_pending &= (~PICO_SOCK_EV_WR); + + /***************************************************************************/ + + if (!IS_NAGLE_ENABLED((&(t->sock)))) { + /* TCP_NODELAY enabled, original behavior */ + if (pico_enqueue_segment(&t->tcpq_out,f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NO NAGLE - Pushing segment %08x, len %08x to socket %p\n", t->snd_last + 1, f->payload_len, t); + t->snd_last += f->payload_len; + return f->payload_len; + } else { + tcp_dbg("Enqueue failed.\n"); + return 0; + } + } + /***************************************************************************/ + else { + /* Nagle's algorithm enabled, check if ready to send, or put frame in hold queue */ + if (IS_TCP_IDLE(t) && IS_TCP_HOLDQ_EMPTY(t)) { /* opt 1. send frame */ + if (pico_enqueue_segment(&t->tcpq_out,f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushing segment %08x, len %08x to socket %p\n", t->snd_last + 1, f->payload_len, t); + t->snd_last += f->payload_len; + return f->payload_len; + } else { + tcp_dbg("Enqueue failed.\n"); + return 0; + } + } else { /* opt 2. hold data back */ + total_len = f->payload_len + t->tcpq_hold.size; + if ((total_len >= PICO_TCP_DEFAULT_MSS) && ((t->tcpq_out.max_size - t->tcpq_out.size) >= PICO_TCP_DEFAULT_MSS)) {/* TODO check mss socket */ + /* IF enough data in hold (>mss) AND space in out queue (>mss) */ + /* add current frame in hold and make new segment */ + if (pico_enqueue_segment(&t->tcpq_hold,f) > 0 ) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushed into hold, make new (enqueued frames out %d)\n",t->tcpq_out.frames); + t->snd_last += f->payload_len; /* XXX WATCH OUT */ + f_new = pico_hold_segment_make(t); + } else { + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue hold failed 1\n"); + return 0; + } + /* and put new frame in out queue */ + if ((f_new != NULL) && (pico_enqueue_segment(&t->tcpq_out,f_new) > 0)) { + return f_new->payload_len; + } else { + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue out failed, f_new = %p\n",f_new); + return -1; /* XXX something seriously wrong */ + } + } else { + /* ELSE put frame in hold queue */ + if (pico_enqueue_segment(&t->tcpq_hold,f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushed into hold (enqueued frames out %d)\n",t->tcpq_out.frames); + t->snd_last += f->payload_len; /* XXX WATCH OUT */ + return f->payload_len; + } else { + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue hold failed 2\n"); + return 0; + } + } + } + } + /***************************************************************************/ +} +#endif //PICO_SUPPORT_TCP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_tcp.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,98 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_TCP +#define _INCLUDE_PICO_TCP +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_socket.h" + +extern struct pico_protocol pico_proto_tcp; + +struct __attribute__((packed)) pico_tcp_hdr { + struct pico_trans trans; + uint32_t seq; + uint32_t ack; + uint8_t len; + uint8_t flags; + uint16_t rwnd; + uint16_t crc; + uint16_t urgent; +}; + +struct __attribute__((packed)) tcp_pseudo_hdr_ipv4 +{ + struct pico_ip4 src; + struct pico_ip4 dst; + uint16_t tcp_len; + uint8_t res; + uint8_t proto; +}; + +#define PICO_TCPHDR_SIZE 20 +#define PICO_SIZE_TCPOPT_SYN 20 +#define PICO_SIZE_TCPHDR (sizeof(struct pico_tcp_hdr)) + +#define PICO_TCP_DEFAULT_MSS 1444 + + + +/* TCP options */ +#define PICO_TCP_OPTION_END 0x00 +#define PICO_TCPOPTLEN_END 1 +#define PICO_TCP_OPTION_NOOP 0x01 +#define PICO_TCPOPTLEN_NOOP 1 +#define PICO_TCP_OPTION_MSS 0x02 +#define PICO_TCPOPTLEN_MSS 4 +#define PICO_TCP_OPTION_WS 0x03 +#define PICO_TCPOPTLEN_WS 3 +#define PICO_TCP_OPTION_SACK_OK 0x04 +#define PICO_TCPOPTLEN_SACK_OK 2 +#define PICO_TCP_OPTION_SACK 0x05 +#define PICO_TCPOPTLEN_SACK 2 /* Plus the block */ +#define PICO_TCP_OPTION_TIMESTAMP 0x08 +#define PICO_TCPOPTLEN_TIMESTAMP 10 + +/* TCP flags */ +#define PICO_TCP_FIN 0x01 +#define PICO_TCP_SYN 0x02 +#define PICO_TCP_RST 0x04 +#define PICO_TCP_PSH 0x08 +#define PICO_TCP_ACK 0x10 +#define PICO_TCP_URG 0x20 +#define PICO_TCP_ECN 0x40 +#define PICO_TCP_CWR 0x80 + + + +struct __attribute__((packed)) pico_tcp_option +{ + uint8_t kind; + uint8_t len; +#if 0 + union { + uint16_t mss; + uint8_t wshift; + struct { + uint32_t tsval; + uint32_t tsecr; + } timestamp; + } data; +#endif +}; + +struct pico_socket *pico_tcp_open(void); +int pico_tcp_read(struct pico_socket *s, void *buf, int len); +int pico_tcp_initconn(struct pico_socket *s); +int pico_tcp_input(struct pico_socket *s, struct pico_frame *f); +uint16_t pico_tcp_checksum_ipv4(struct pico_frame *f); +int pico_tcp_overhead(struct pico_socket *s); +int pico_tcp_output(struct pico_socket *s, int loop_score); +int pico_tcp_queue_in_is_empty(struct pico_socket *s); +int pico_tcp_reply_rst(struct pico_frame *f); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_udp.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,176 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +Authors: Daniele Lacamera +*********************************************************************/ + + +#include "pico_udp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_stack.h" + + +/* Queues */ +static struct pico_queue udp_in = {}; +static struct pico_queue udp_out = {}; + + +/* Functions */ + +uint16_t pico_udp_checksum_ipv4(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + struct pico_socket *s = f->sock; + struct pico_ipv4_pseudo_hdr pseudo; + + if (s) { + /* Case of outgoing frame */ + //dbg("UDP CRC: on outgoing frame\n"); + pseudo.src.addr = s->local_addr.ip4.addr; + pseudo.dst.addr = s->remote_addr.ip4.addr; + } else { + /* Case of incomming frame */ + //dbg("UDP CRC: on incomming frame\n"); + pseudo.src.addr = hdr->src.addr; + pseudo.dst.addr = hdr->dst.addr; + } + pseudo.zeros = 0; + pseudo.proto = PICO_PROTO_UDP; + pseudo.len = short_be(f->transport_len); + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv4_pseudo_hdr), udp_hdr, f->transport_len); +} + + +static int pico_udp_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + return pico_network_send(f); +} + +static int pico_udp_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_udp_hdr *hdr = (struct pico_udp_hdr *) f->transport_hdr; + struct pico_remote_duple *remote_duple = (struct pico_remote_duple *) f->info; + + /* this (fragmented) frame should contain a transport header */ + if (f->transport_hdr != f->payload) { + hdr->trans.sport = f->sock->local_port; + if (remote_duple) { + hdr->trans.dport = remote_duple->remote_port; + } else { + hdr->trans.dport = f->sock->remote_port; + } + hdr->len = short_be(f->transport_len); + /* do not perform CRC validation. If you want to, a system needs to be + implemented to calculate the CRC over the total payload of a + fragmented payload */ + hdr->crc = 0; + } + + if (pico_enqueue(self->q_out, f) > 0) { + return f->payload_len; + } else { + return 0; + } +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_udp = { + .name = "udp", + .proto_number = PICO_PROTO_UDP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_transport_process_in, + .process_out = pico_udp_process_out, + .push = pico_udp_push, + .q_in = &udp_in, + .q_out = &udp_out, +}; + + +#define PICO_UDP_MODE_UNICAST 0x01 +#define PICO_UDP_MODE_MULTICAST 0x02 +#define PICO_UDP_MODE_BROADCAST 0xFF + +struct pico_socket_udp +{ + struct pico_socket sock; + int mode; +#ifdef PICO_SUPPORT_MCAST + uint8_t mc_ttl; /* Multicasting TTL */ +#endif +}; + +#ifdef PICO_SUPPORT_MCAST +int pico_udp_set_mc_ttl(struct pico_socket *s, uint8_t ttl) +{ + struct pico_socket_udp *u; + if(!s) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + u = (struct pico_socket_udp *) s; + u->mc_ttl = ttl; + return 0; +} + +int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl) +{ + struct pico_socket_udp *u; + if(!s) + return -1; + u = (struct pico_socket_udp *) s; + *ttl = u->mc_ttl; + return 0; +} +#endif /* PICO_SUPPORT_MCAST */ + +struct pico_socket *pico_udp_open(void) +{ + struct pico_socket_udp *u = pico_zalloc(sizeof(struct pico_socket_udp)); + if (!u) + return NULL; + u->mode = PICO_UDP_MODE_UNICAST; + +#ifdef PICO_SUPPORT_MCAST + u->mc_ttl = PICO_IP_DEFAULT_MULTICAST_TTL; + /* enable multicast loopback by default */ + u->sock.opt_flags |= (1 << PICO_SOCKET_OPT_MULTICAST_LOOP); +#endif + + return &u->sock; +} + +int pico_udp_recv(struct pico_socket *s, void *buf, int len, void *src, uint16_t *port) +{ + struct pico_frame *f = pico_queue_peek(&s->q_in); + if (f) { + f->payload = f->transport_hdr + sizeof(struct pico_udp_hdr); + f->payload_len = f->transport_len - sizeof(struct pico_udp_hdr); +// dbg("expected: %d, got: %d\n", len, f->payload_len); + if (src) + pico_store_network_origin(src, f); + if (port) { + struct pico_trans *hdr = (struct pico_trans *)f->transport_hdr; + *port = hdr->sport; + } + if (f->payload_len > len) { + memcpy(buf, f->payload, len); + f->payload += len; + f->payload_len -= len; + return len; + } else { + int ret = f->payload_len; + memcpy(buf, f->payload, f->payload_len); + f = pico_dequeue(&s->q_in); + pico_frame_discard(f); + return ret; + } + } else return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_udp.h Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,42 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +. + +*********************************************************************/ +#ifndef _INCLUDE_PICO_UDP +#define _INCLUDE_PICO_UDP +#include "pico_addressing.h" +#include "pico_protocol.h" + +extern struct pico_protocol pico_proto_udp; + +struct __attribute__((packed)) pico_udp_hdr { + struct pico_trans trans; + uint16_t len; + uint16_t crc; +}; +#define PICO_UDPHDR_SIZE 8 + +struct pico_socket *pico_udp_open(void); +int pico_udp_recv(struct pico_socket *s, void *buf, int len, void *src, uint16_t *port); +uint16_t pico_udp_checksum_ipv4(struct pico_frame *f); + +#ifdef PICO_SUPPORT_MCAST +int pico_udp_set_mc_ttl(struct pico_socket *s, uint8_t ttl); +int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl); +#else +static inline int pico_udp_set_mc_ttl(struct pico_socket *s, uint8_t ttl) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +static inline int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +#endif /* PICO_SUPPORT_MCAST */ + +#endif