699 lines
22 KiB
C
699 lines
22 KiB
C
/*********************************************************************
|
|
PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved.
|
|
See LICENSE and COPYING for usage.
|
|
|
|
.
|
|
|
|
Authors: Kristof Roelants, Daniele Lacamera
|
|
*********************************************************************/
|
|
|
|
#include "pico_config.h"
|
|
#include "pico_icmp6.h"
|
|
#include "pico_ipv6_nd.h"
|
|
#include "pico_eth.h"
|
|
#include "pico_device.h"
|
|
#include "pico_stack.h"
|
|
#include "pico_tree.h"
|
|
#include "pico_socket.h"
|
|
#include "pico_mld.h"
|
|
#define icmp6_dbg(...) do { }while(0);
|
|
|
|
static struct pico_queue icmp6_in;
|
|
static struct pico_queue icmp6_out;
|
|
|
|
uint16_t pico_icmp6_checksum(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
|
|
struct pico_icmp6_hdr *icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
|
struct pico_ipv6_pseudo_hdr pseudo;
|
|
|
|
pseudo.src = ipv6_hdr->src;
|
|
pseudo.dst = ipv6_hdr->dst;
|
|
pseudo.len = long_be(f->transport_len);
|
|
pseudo.nxthdr = PICO_PROTO_ICMP6;
|
|
|
|
pseudo.zero[0] = 0;
|
|
pseudo.zero[1] = 0;
|
|
pseudo.zero[2] = 0;
|
|
|
|
return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv6_pseudo_hdr), icmp6_hdr, f->transport_len);
|
|
}
|
|
|
|
#ifdef PICO_SUPPORT_PING
|
|
static void pico_icmp6_ping_recv_reply(struct pico_frame *f);
|
|
#endif
|
|
|
|
static int pico_icmp6_send_echoreply(struct pico_frame *echo)
|
|
{
|
|
struct pico_frame *reply = NULL;
|
|
struct pico_icmp6_hdr *ehdr = NULL, *rhdr = NULL;
|
|
struct pico_ip6 src;
|
|
struct pico_ip6 dst;
|
|
|
|
reply = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(echo->transport_len));
|
|
if (!reply) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE;
|
|
reply->payload = reply->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE;
|
|
reply->payload_len = echo->transport_len;
|
|
reply->dev = echo->dev;
|
|
|
|
ehdr = (struct pico_icmp6_hdr *)echo->transport_hdr;
|
|
rhdr = (struct pico_icmp6_hdr *)reply->transport_hdr;
|
|
rhdr->type = PICO_ICMP6_ECHO_REPLY;
|
|
rhdr->code = 0;
|
|
rhdr->msg.info.echo_reply.id = ehdr->msg.info.echo_reply.id;
|
|
rhdr->msg.info.echo_reply.seq = ehdr->msg.info.echo_request.seq;
|
|
memcpy(reply->payload, echo->payload, (uint32_t)(echo->transport_len - PICO_ICMP6HDR_ECHO_REQUEST_SIZE));
|
|
rhdr->crc = 0;
|
|
rhdr->crc = short_be(pico_icmp6_checksum(reply));
|
|
/* Get destination and source swapped */
|
|
memcpy(dst.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->src.addr, PICO_SIZE_IP6);
|
|
memcpy(src.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->dst.addr, PICO_SIZE_IP6);
|
|
pico_ipv6_frame_push(reply, &src, &dst, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame *f)
|
|
{
|
|
struct pico_icmp6_hdr *hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
|
|
|
IGNORE_PARAMETER(self);
|
|
|
|
icmp6_dbg("Process IN, type = %d\n", hdr->type);
|
|
|
|
switch (hdr->type)
|
|
{
|
|
case PICO_ICMP6_DEST_UNREACH:
|
|
pico_ipv6_unreachable(f, hdr->code);
|
|
break;
|
|
|
|
case PICO_ICMP6_ECHO_REQUEST:
|
|
icmp6_dbg("ICMP6: Received ECHO REQ\n");
|
|
f->transport_len = (uint16_t)(f->len - f->net_len - (uint16_t)(f->net_hdr - f->buffer));
|
|
pico_icmp6_send_echoreply(f);
|
|
pico_frame_discard(f);
|
|
break;
|
|
|
|
case PICO_ICMP6_ECHO_REPLY:
|
|
#ifdef PICO_SUPPORT_PING
|
|
pico_icmp6_ping_recv_reply(f);
|
|
#endif
|
|
pico_frame_discard(f);
|
|
break;
|
|
#ifdef PICO_SUPPORT_MCAST
|
|
case PICO_MLD_QUERY:
|
|
case PICO_MLD_REPORT:
|
|
case PICO_MLD_DONE:
|
|
case PICO_MLD_REPORTV2:
|
|
pico_mld_process_in(f);
|
|
break;
|
|
#endif
|
|
default:
|
|
return pico_ipv6_nd_recv(f); /* CAUTION -- Implies: pico_frame_discard in any case, keep in the default! */
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int pico_icmp6_process_out(struct pico_protocol *self, struct pico_frame *f)
|
|
{
|
|
IGNORE_PARAMETER(self);
|
|
IGNORE_PARAMETER(f);
|
|
return 0;
|
|
}
|
|
|
|
/* Interface: protocol definition */
|
|
struct pico_protocol pico_proto_icmp6 = {
|
|
.name = "icmp6",
|
|
.proto_number = PICO_PROTO_ICMP6,
|
|
.layer = PICO_LAYER_TRANSPORT,
|
|
.process_in = pico_icmp6_process_in,
|
|
.process_out = pico_icmp6_process_out,
|
|
.q_in = &icmp6_in,
|
|
.q_out = &icmp6_out,
|
|
};
|
|
|
|
static int pico_icmp6_notify(struct pico_frame *f, uint8_t type, uint8_t code, uint32_t ptr)
|
|
{
|
|
struct pico_frame *notice = NULL;
|
|
struct pico_ipv6_hdr *ipv6_hdr = NULL;
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
|
uint16_t len = 0;
|
|
|
|
if (!f)
|
|
return -1;
|
|
|
|
ipv6_hdr = (struct pico_ipv6_hdr *)(f->net_hdr);
|
|
len = (uint16_t)(short_be(ipv6_hdr->len) + PICO_SIZE_IP6HDR);
|
|
switch (type)
|
|
{
|
|
case PICO_ICMP6_DEST_UNREACH:
|
|
/* as much of invoking packet as possible without exceeding the minimum IPv6 MTU */
|
|
if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_DEST_UNREACH_SIZE + len > PICO_IPV6_MIN_MTU)
|
|
len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_DEST_UNREACH_SIZE);
|
|
|
|
notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_DEST_UNREACH_SIZE + len));
|
|
if (!notice) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
notice->payload = notice->transport_hdr + PICO_ICMP6HDR_DEST_UNREACH_SIZE;
|
|
notice->payload_len = len;
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr;
|
|
icmp6_hdr->msg.err.dest_unreach.unused = 0;
|
|
break;
|
|
|
|
case PICO_ICMP6_TIME_EXCEEDED:
|
|
/* as much of invoking packet as possible without exceeding the minimum IPv6 MTU */
|
|
if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_TIME_XCEEDED_SIZE + len > PICO_IPV6_MIN_MTU)
|
|
len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_TIME_XCEEDED_SIZE);
|
|
|
|
notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_TIME_XCEEDED_SIZE + len));
|
|
if (!notice) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
notice->payload = notice->transport_hdr + PICO_ICMP6HDR_TIME_XCEEDED_SIZE;
|
|
notice->payload_len = len;
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr;
|
|
icmp6_hdr->msg.err.time_exceeded.unused = 0;
|
|
break;
|
|
|
|
case PICO_ICMP6_PARAM_PROBLEM:
|
|
if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE + len > PICO_IPV6_MIN_MTU)
|
|
len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE);
|
|
|
|
notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_PARAM_PROBLEM_SIZE + len));
|
|
if (!notice) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
notice->payload = notice->transport_hdr + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE;
|
|
notice->payload_len = len;
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr;
|
|
icmp6_hdr->msg.err.param_problem.ptr = long_be(ptr);
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
icmp6_hdr->type = type;
|
|
icmp6_hdr->code = code;
|
|
memcpy(notice->payload, f->net_hdr, notice->payload_len);
|
|
notice->dev = f->dev;
|
|
/* f->src is set in frame_push, checksum calculated there */
|
|
pico_ipv6_frame_push(notice, NULL, &ipv6_hdr->src, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
int pico_icmp6_port_unreachable(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_PORT, 0);
|
|
}
|
|
|
|
int pico_icmp6_proto_unreachable(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADDR, 0);
|
|
}
|
|
|
|
int pico_icmp6_dest_unreachable(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADDR, 0);
|
|
}
|
|
|
|
int pico_icmp6_ttl_expired(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_TIME_EXCEEDED, PICO_ICMP6_TIMXCEED_INTRANS, 0);
|
|
}
|
|
|
|
int pico_icmp6_pkt_too_big(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_PKT_TOO_BIG, 0, 0);
|
|
}
|
|
|
|
#ifdef PICO_SUPPORT_IPFILTER
|
|
int pico_icmp6_packet_filtered(struct pico_frame *f)
|
|
{
|
|
return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADMIN, 0);
|
|
}
|
|
#endif
|
|
|
|
int pico_icmp6_parameter_problem(struct pico_frame *f, uint8_t problem, uint32_t ptr)
|
|
{
|
|
return pico_icmp6_notify(f, PICO_ICMP6_PARAM_PROBLEM, problem, ptr);
|
|
}
|
|
|
|
MOCKABLE int pico_icmp6_frag_expired(struct pico_frame *f)
|
|
{
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
if (pico_ipv6_is_multicast(hdr->dst.addr))
|
|
return 0;
|
|
|
|
return pico_icmp6_notify(f, PICO_ICMP6_TIME_EXCEEDED, PICO_ICMP6_TIMXCEED_REASS, 0);
|
|
}
|
|
|
|
/* RFC 4861 $7.2.2: sending neighbor solicitations */
|
|
int pico_icmp6_neighbor_solicitation(struct pico_device *dev, struct pico_ip6 *dst, uint8_t type)
|
|
{
|
|
struct pico_frame *sol = NULL;
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
|
struct pico_icmp6_opt_lladdr *opt = NULL;
|
|
struct pico_ip6 daddr = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00 }};
|
|
uint8_t i = 0;
|
|
uint16_t len = 0;
|
|
|
|
if (pico_ipv6_is_multicast(dst->addr))
|
|
return -1;
|
|
|
|
len = PICO_ICMP6HDR_NEIGH_SOL_SIZE;
|
|
if (type != PICO_ICMP6_ND_DAD)
|
|
len = (uint16_t)(len + 8);
|
|
|
|
sol = pico_proto_ipv6.alloc(&pico_proto_ipv6, len);
|
|
if (!sol) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
sol->payload = sol->transport_hdr + len;
|
|
sol->payload_len = 0;
|
|
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)sol->transport_hdr;
|
|
icmp6_hdr->type = PICO_ICMP6_NEIGH_SOL;
|
|
icmp6_hdr->code = 0;
|
|
icmp6_hdr->msg.info.neigh_sol.unused = 0;
|
|
icmp6_hdr->msg.info.neigh_sol.target = *dst;
|
|
|
|
if (type != PICO_ICMP6_ND_DAD) {
|
|
opt = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.neigh_sol) + sizeof(struct neigh_sol_s));
|
|
opt->type = PICO_ND_OPT_LLADDR_SRC;
|
|
opt->len = 1;
|
|
memcpy(opt->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH);
|
|
}
|
|
|
|
if (type == PICO_ICMP6_ND_SOLICITED || type == PICO_ICMP6_ND_DAD) {
|
|
for (i = 1; i <= 3; ++i) {
|
|
daddr.addr[PICO_SIZE_IP6 - i] = dst->addr[PICO_SIZE_IP6 - i];
|
|
}
|
|
} else {
|
|
daddr = *dst;
|
|
}
|
|
|
|
sol->dev = dev;
|
|
|
|
/* f->src is set in frame_push, checksum calculated there */
|
|
pico_ipv6_frame_push(sol, NULL, &daddr, PICO_PROTO_ICMP6, (type == PICO_ICMP6_ND_DAD));
|
|
return 0;
|
|
}
|
|
|
|
/* RFC 4861 $7.2.4: sending solicited neighbor advertisements */
|
|
int pico_icmp6_neighbor_advertisement(struct pico_frame *f, struct pico_ip6 *target)
|
|
{
|
|
struct pico_frame *adv = NULL;
|
|
struct pico_ipv6_hdr *ipv6_hdr = NULL;
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
|
struct pico_icmp6_opt_lladdr *opt = NULL;
|
|
struct pico_ip6 dst = {{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}};
|
|
|
|
ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
|
adv = pico_proto_ipv6.alloc(&pico_proto_ipv6, PICO_ICMP6HDR_NEIGH_ADV_SIZE + 8);
|
|
if (!adv) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
adv->payload = adv->transport_hdr + PICO_ICMP6HDR_NEIGH_ADV_SIZE + 8;
|
|
adv->payload_len = 0;
|
|
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)adv->transport_hdr;
|
|
icmp6_hdr->type = PICO_ICMP6_NEIGH_ADV;
|
|
icmp6_hdr->code = 0;
|
|
icmp6_hdr->msg.info.neigh_adv.target = *target;
|
|
icmp6_hdr->msg.info.neigh_adv.rsor = long_be(0x60000000); /* !router && solicited && override */
|
|
if (pico_ipv6_is_unspecified(ipv6_hdr->src.addr)) {
|
|
/* solicited = clear && dst = all-nodes address (scope link-local) */
|
|
icmp6_hdr->msg.info.neigh_adv.rsor ^= long_be(0x40000000);
|
|
} else {
|
|
/* solicited = set && dst = source of solicitation */
|
|
dst = ipv6_hdr->src;
|
|
}
|
|
|
|
/* XXX if the target address is either an anycast address or a unicast
|
|
* address for which the node is providing proxy service, or the target
|
|
* link-layer Address option is not included, the Override flag SHOULD
|
|
* be set to zero.
|
|
*/
|
|
|
|
/* XXX if the target address is an anycast address, the sender SHOULD delay
|
|
* sending a response for a random time between 0 and MAX_ANYCAST_DELAY_TIME seconds.
|
|
*/
|
|
|
|
opt = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s));
|
|
opt->type = PICO_ND_OPT_LLADDR_TGT;
|
|
opt->len = 1;
|
|
memcpy(opt->addr.mac.addr, f->dev->eth->mac.addr, PICO_SIZE_ETH);
|
|
adv->dev = f->dev;
|
|
|
|
/* f->src is set in frame_push, checksum calculated there */
|
|
pico_ipv6_frame_push(adv, NULL, &dst, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
/* RFC 4861 $6.3.7: sending router solicitations */
|
|
int pico_icmp6_router_solicitation(struct pico_device *dev, struct pico_ip6 *src)
|
|
{
|
|
struct pico_frame *sol = NULL;
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
|
struct pico_icmp6_opt_lladdr *lladdr = NULL;
|
|
uint16_t len = 0;
|
|
struct pico_ip6 daddr = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }};
|
|
|
|
len = PICO_ICMP6HDR_ROUTER_SOL_SIZE;
|
|
if (!pico_ipv6_is_unspecified(src->addr))
|
|
len = (uint16_t)(len + 8);
|
|
|
|
sol = pico_proto_ipv6.alloc(&pico_proto_ipv6, len);
|
|
if (!sol) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
sol->payload = sol->transport_hdr + len;
|
|
sol->payload_len = 0;
|
|
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)sol->transport_hdr;
|
|
icmp6_hdr->type = PICO_ICMP6_ROUTER_SOL;
|
|
icmp6_hdr->code = 0;
|
|
|
|
if (!pico_ipv6_is_unspecified(src->addr)) {
|
|
lladdr = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.router_sol) + sizeof(struct router_sol_s));
|
|
lladdr->type = PICO_ND_OPT_LLADDR_SRC;
|
|
lladdr->len = 1;
|
|
memcpy(lladdr->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH);
|
|
}
|
|
|
|
sol->dev = dev;
|
|
|
|
/* f->src is set in frame_push, checksum calculated there */
|
|
pico_ipv6_frame_push(sol, NULL, &daddr, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
#define PICO_RADV_VAL_LIFETIME (long_be(86400))
|
|
#define PICO_RADV_PREF_LIFETIME (long_be(14400))
|
|
|
|
/* RFC 4861: sending router advertisements */
|
|
int pico_icmp6_router_advertisement(struct pico_device *dev, struct pico_ip6 *dst)
|
|
{
|
|
struct pico_frame *adv = NULL;
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
|
struct pico_icmp6_opt_lladdr *lladdr;
|
|
struct pico_icmp6_opt_prefix *prefix;
|
|
uint16_t len = 0;
|
|
struct pico_ip6 dst_mcast = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }};
|
|
uint8_t *nxt_opt;
|
|
|
|
len = PICO_ICMP6HDR_ROUTER_ADV_SIZE + PICO_ICMP6_OPT_LLADDR_SIZE + sizeof(struct pico_icmp6_opt_prefix);
|
|
|
|
adv = pico_proto_ipv6.alloc(&pico_proto_ipv6, len);
|
|
if (!adv) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
adv->payload = adv->transport_hdr + len;
|
|
adv->payload_len = 0;
|
|
adv->dev = dev;
|
|
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)adv->transport_hdr;
|
|
icmp6_hdr->type = PICO_ICMP6_ROUTER_ADV;
|
|
icmp6_hdr->code = 0;
|
|
icmp6_hdr->msg.info.router_adv.life_time = short_be(45);
|
|
icmp6_hdr->msg.info.router_adv.hop = 64;
|
|
nxt_opt = (uint8_t *)&icmp6_hdr->msg.info.router_adv + sizeof(struct router_adv_s);
|
|
|
|
prefix = (struct pico_icmp6_opt_prefix *)nxt_opt;
|
|
prefix->type = PICO_ND_OPT_PREFIX;
|
|
prefix->len = sizeof(struct pico_icmp6_opt_prefix) >> 3;
|
|
prefix->prefix_len = 64; /* Only /64 are forwarded */
|
|
prefix->aac = 1;
|
|
prefix->onlink = 1;
|
|
prefix->val_lifetime = PICO_RADV_VAL_LIFETIME;
|
|
prefix->pref_lifetime = PICO_RADV_PREF_LIFETIME;
|
|
memcpy(&prefix->prefix, dst, sizeof(struct pico_ip6));
|
|
|
|
nxt_opt += (sizeof (struct pico_icmp6_opt_prefix));
|
|
lladdr = (struct pico_icmp6_opt_lladdr *)nxt_opt;
|
|
lladdr->type = PICO_ND_OPT_LLADDR_SRC;
|
|
lladdr->len = 1;
|
|
memcpy(lladdr->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH);
|
|
icmp6_hdr->crc = 0;
|
|
icmp6_hdr->crc = short_be(pico_icmp6_checksum(adv));
|
|
/* f->src is set in frame_push, checksum calculated there */
|
|
pico_ipv6_frame_push(adv, NULL, &dst_mcast, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
/***********************/
|
|
/* Ping implementation */
|
|
/***********************/
|
|
|
|
#ifdef PICO_SUPPORT_PING
|
|
struct pico_icmp6_ping_cookie
|
|
{
|
|
uint16_t id;
|
|
uint16_t seq;
|
|
uint16_t size;
|
|
uint16_t err;
|
|
int count;
|
|
int interval;
|
|
int timeout;
|
|
pico_time timestamp;
|
|
struct pico_ip6 dst;
|
|
struct pico_device *dev;
|
|
void (*cb)(struct pico_icmp6_stats*);
|
|
};
|
|
|
|
static int icmp6_cookie_compare(void *ka, void *kb)
|
|
{
|
|
struct pico_icmp6_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(IPV6Pings, icmp6_cookie_compare);
|
|
|
|
static int pico_icmp6_send_echo(struct pico_icmp6_ping_cookie *cookie)
|
|
{
|
|
struct pico_frame *echo = NULL;
|
|
struct pico_icmp6_hdr *hdr = NULL;
|
|
|
|
echo = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_ECHO_REQUEST_SIZE + cookie->size));
|
|
if (!echo) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE;
|
|
echo->payload_len = cookie->size;
|
|
|
|
hdr = (struct pico_icmp6_hdr *)echo->transport_hdr;
|
|
hdr->type = PICO_ICMP6_ECHO_REQUEST;
|
|
hdr->code = 0;
|
|
hdr->msg.info.echo_request.id = short_be(cookie->id);
|
|
hdr->msg.info.echo_request.seq = short_be(cookie->seq);
|
|
/* XXX: Fill payload */
|
|
hdr->crc = 0;
|
|
hdr->crc = short_be(pico_icmp6_checksum(echo));
|
|
echo->dev = cookie->dev;
|
|
pico_ipv6_frame_push(echo, NULL, &cookie->dst, PICO_PROTO_ICMP6, 0);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void pico_icmp6_ping_timeout(pico_time now, void *arg)
|
|
{
|
|
struct pico_icmp6_ping_cookie *cookie = NULL;
|
|
|
|
IGNORE_PARAMETER(now);
|
|
|
|
cookie = (struct pico_icmp6_ping_cookie *)arg;
|
|
if (pico_tree_findKey(&IPV6Pings, cookie)) {
|
|
if (cookie->err == PICO_PING6_ERR_PENDING) {
|
|
struct pico_icmp6_stats stats = {
|
|
0
|
|
};
|
|
stats.dst = cookie->dst;
|
|
stats.seq = cookie->seq;
|
|
stats.time = 0;
|
|
stats.size = cookie->size;
|
|
stats.err = PICO_PING6_ERR_TIMEOUT;
|
|
dbg(" ---- Ping6 timeout!!!\n");
|
|
if (cookie->cb)
|
|
cookie->cb(&stats);
|
|
}
|
|
|
|
pico_tree_delete(&IPV6Pings, cookie);
|
|
PICO_FREE(cookie);
|
|
}
|
|
}
|
|
|
|
static void pico_icmp6_next_ping(pico_time now, void *arg);
|
|
static inline void pico_icmp6_send_ping(struct pico_icmp6_ping_cookie *cookie)
|
|
{
|
|
pico_icmp6_send_echo(cookie);
|
|
cookie->timestamp = pico_tick;
|
|
pico_timer_add((pico_time)(cookie->interval), pico_icmp6_next_ping, cookie);
|
|
pico_timer_add((pico_time)(cookie->timeout), pico_icmp6_ping_timeout, cookie);
|
|
}
|
|
|
|
static void pico_icmp6_next_ping(pico_time now, void *arg)
|
|
{
|
|
struct pico_icmp6_ping_cookie *cookie = NULL, *new = NULL;
|
|
|
|
IGNORE_PARAMETER(now);
|
|
|
|
cookie = (struct pico_icmp6_ping_cookie *)arg;
|
|
if (pico_tree_findKey(&IPV6Pings, cookie)) {
|
|
if (cookie->err == PICO_PING6_ERR_ABORTED)
|
|
return;
|
|
|
|
if (cookie->seq < (uint16_t)cookie->count) {
|
|
new = PICO_ZALLOC(sizeof(struct pico_icmp6_ping_cookie));
|
|
if (!new) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return;
|
|
}
|
|
|
|
memcpy(new, cookie, sizeof(struct pico_icmp6_ping_cookie));
|
|
new->seq++;
|
|
|
|
pico_tree_insert(&IPV6Pings, new);
|
|
pico_icmp6_send_ping(new);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pico_icmp6_ping_recv_reply(struct pico_frame *f)
|
|
{
|
|
struct pico_icmp6_ping_cookie *cookie = NULL, test = {
|
|
0
|
|
};
|
|
struct pico_icmp6_hdr *hdr = NULL;
|
|
|
|
hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
|
test.id = short_be(hdr->msg.info.echo_reply.id);
|
|
test.seq = short_be(hdr->msg.info.echo_reply.seq);
|
|
cookie = pico_tree_findKey(&IPV6Pings, &test);
|
|
if (cookie) {
|
|
struct pico_icmp6_stats stats = {
|
|
0
|
|
};
|
|
if (cookie->err == PICO_PING6_ERR_ABORTED)
|
|
return;
|
|
|
|
cookie->err = PICO_PING6_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_ipv6_hdr *)f->net_hdr)->hop;
|
|
if(cookie->cb)
|
|
cookie->cb(&stats);
|
|
} else {
|
|
dbg("Reply for seq=%d, not found.\n", test.seq);
|
|
}
|
|
}
|
|
|
|
int pico_icmp6_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp6_stats *), struct pico_device *dev)
|
|
{
|
|
static uint16_t next_id = 0x91c0;
|
|
struct pico_icmp6_ping_cookie *cookie = NULL;
|
|
|
|
if(!dst || !count || !interval || !timeout) {
|
|
pico_err = PICO_ERR_EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
cookie = PICO_ZALLOC(sizeof(struct pico_icmp6_ping_cookie));
|
|
if (!cookie) {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
if (pico_string_to_ipv6(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_PING6_ERR_PENDING;
|
|
cookie->size = (uint16_t)size;
|
|
cookie->interval = interval;
|
|
cookie->timeout = timeout;
|
|
cookie->cb = cb;
|
|
cookie->count = count;
|
|
cookie->dev = dev;
|
|
|
|
pico_tree_insert(&IPV6Pings, cookie);
|
|
pico_icmp6_send_ping(cookie);
|
|
return (int)cookie->id;
|
|
}
|
|
|
|
int pico_icmp6_ping_abort(int id)
|
|
{
|
|
struct pico_tree_node *node;
|
|
int found = 0;
|
|
pico_tree_foreach(node, &IPV6Pings)
|
|
{
|
|
struct pico_icmp6_ping_cookie *ck =
|
|
(struct pico_icmp6_ping_cookie *) node->keyValue;
|
|
if (ck->id == (uint16_t)id) {
|
|
ck->err = PICO_PING6_ERR_ABORTED;
|
|
found++;
|
|
}
|
|
}
|
|
if (found > 0)
|
|
return 0; /* OK if at least one pending ping has been canceled */
|
|
|
|
pico_err = PICO_ERR_ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
#endif
|