HelenOS sources
This source file includes following definitions.
- inet_ocadd16
- inet_checksum_calc
- inet_pdu_encode
- inet_pdu_encode6
- inet_pdu_decode
- inet_pdu_decode6
- ndp_pdu_encode
- ndp_pdu_decode
#include <align.h>
#include <bitops.h>
#include <byteorder.h>
#include <errno.h>
#include <fibril_synch.h>
#include <inet/eth_addr.h>
#include <io/log.h>
#include <macros.h>
#include <mem.h>
#include <stdlib.h>
#include "inetsrv.h"
#include "inet_std.h"
#include "pdu.h"
static uint16_t inet_ocadd16(uint16_t a, uint16_t b)
{
uint32_t s;
s = (uint32_t)a + (uint32_t)b;
return (s & 0xffff) + (s >> 16);
}
uint16_t inet_checksum_calc(uint16_t ivalue, void *data, size_t size)
{
uint16_t sum;
uint16_t w;
size_t words, i;
uint8_t *bdata;
sum = ~ivalue;
words = size / 2;
bdata = (uint8_t *)data;
for (i = 0; i < words; i++) {
w = ((uint16_t)bdata[2 * i] << 8) | bdata[2 * i + 1];
sum = inet_ocadd16(sum, w);
}
if (size % 2 != 0) {
w = ((uint16_t)bdata[2 * words] << 8);
sum = inet_ocadd16(sum, w);
}
return ~sum;
}
errno_t inet_pdu_encode(inet_packet_t *packet, addr32_t src, addr32_t dest,
size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
{
size_t fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l + 1);
if (packet->size > FRAG_OFFS_UNIT * fragoff_limit)
return ELIMIT;
size_t hdr_size = sizeof(ip_header_t);
if (hdr_size >= mtu)
return EINVAL;
assert(hdr_size % 4 == 0);
assert(offs % FRAG_OFFS_UNIT == 0);
assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
uint16_t foff = offs / FRAG_OFFS_UNIT;
size_t spc_avail = mtu - hdr_size;
spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
size_t xfer_size = min(packet->size - offs, spc_avail);
size_t size = hdr_size + xfer_size;
size_t rem_offs = offs + xfer_size;
uint16_t flags_foff =
(packet->df ? BIT_V(uint16_t, FF_FLAG_DF) : 0) +
(rem_offs < packet->size ? BIT_V(uint16_t, FF_FLAG_MF) : 0) +
(foff << FF_FRAGOFF_l);
void *data = calloc(size, 1);
if (data == NULL)
return ENOMEM;
ip_header_t *hdr = (ip_header_t *) data;
hdr->ver_ihl =
(4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
hdr->tos = packet->tos;
hdr->tot_len = host2uint16_t_be(size);
hdr->id = host2uint16_t_be(packet->ident);
hdr->flags_foff = host2uint16_t_be(flags_foff);
hdr->ttl = packet->ttl;
hdr->proto = packet->proto;
hdr->chksum = 0;
hdr->src_addr = host2uint32_t_be(src);
hdr->dest_addr = host2uint32_t_be(dest);
uint16_t chksum = inet_checksum_calc(INET_CHECKSUM_INIT,
(void *) hdr, hdr_size);
hdr->chksum = host2uint16_t_be(chksum);
memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
*rdata = data;
*rsize = size;
*roffs = rem_offs;
return EOK;
}
errno_t inet_pdu_encode6(inet_packet_t *packet, addr128_t src, addr128_t dest,
size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
{
if (mtu < 1280)
return ELIMIT;
size_t fragoff_limit = 1 << (OF_FRAGOFF_h - OF_FRAGOFF_l);
if (offs + packet->size > FRAG_OFFS_UNIT * fragoff_limit)
return ELIMIT;
bool fragment;
if (offs == 0)
fragment = (packet->size + sizeof(ip6_header_t) > mtu);
else
fragment = true;
size_t hdr_size;
if (fragment)
hdr_size = sizeof(ip6_header_t) + sizeof(ip6_header_fragment_t);
else
hdr_size = sizeof(ip6_header_t);
if (hdr_size >= mtu)
return EINVAL;
static_assert(sizeof(ip6_header_t) % 8 == 0, "");
assert(hdr_size % 8 == 0);
assert(offs % FRAG_OFFS_UNIT == 0);
assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
uint16_t foff = offs / FRAG_OFFS_UNIT;
size_t spc_avail = mtu - hdr_size;
spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
size_t xfer_size = min(packet->size - offs, spc_avail);
size_t size = hdr_size + xfer_size;
size_t rem_offs = offs + xfer_size;
uint16_t offsmf =
(rem_offs < packet->size ? BIT_V(uint16_t, OF_FLAG_M) : 0) +
(foff << OF_FRAGOFF_l);
void *data = calloc(size, 1);
if (data == NULL)
return ENOMEM;
ip6_header_t *hdr6 = (ip6_header_t *) data;
hdr6->ver_tc = (6 << (VI_VERSION_l));
memset(hdr6->tc_fl, 0, 3);
hdr6->hop_limit = packet->ttl;
host2addr128_t_be(src, hdr6->src_addr);
host2addr128_t_be(dest, hdr6->dest_addr);
if (fragment) {
assert(offsmf != 0);
hdr6->payload_len = host2uint16_t_be(packet->size +
sizeof(ip6_header_fragment_t));
hdr6->next = IP6_NEXT_FRAGMENT;
ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
(hdr6 + 1);
hdr6f->next = packet->proto;
hdr6f->reserved = 0;
hdr6f->offsmf = host2uint16_t_be(offsmf);
hdr6f->id = host2uint32_t_be(packet->ident);
} else {
assert(offsmf == 0);
hdr6->payload_len = host2uint16_t_be(packet->size);
hdr6->next = packet->proto;
}
memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
*rdata = data;
*rsize = size;
*roffs = rem_offs;
return EOK;
}
errno_t inet_pdu_decode(void *data, size_t size, service_id_t link_id,
inet_packet_t *packet)
{
log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode()");
if (size < sizeof(ip_header_t)) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
return EINVAL;
}
ip_header_t *hdr = (ip_header_t *) data;
uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
VI_VERSION_l, hdr->ver_ihl);
if (version != 4) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 4", version);
return EINVAL;
}
size_t tot_len = uint16_t_be2host(hdr->tot_len);
if (tot_len < sizeof(ip_header_t)) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length too small (%zu)", tot_len);
return EINVAL;
}
if (tot_len > size) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length = %zu > PDU size = %zu",
tot_len, size);
return EINVAL;
}
uint16_t ident = uint16_t_be2host(hdr->id);
uint16_t flags_foff = uint16_t_be2host(hdr->flags_foff);
uint16_t foff = BIT_RANGE_EXTRACT(uint16_t, FF_FRAGOFF_h, FF_FRAGOFF_l,
flags_foff);
inet_addr_set(uint32_t_be2host(hdr->src_addr), &packet->src);
inet_addr_set(uint32_t_be2host(hdr->dest_addr), &packet->dest);
packet->tos = hdr->tos;
packet->proto = hdr->proto;
packet->ttl = hdr->ttl;
packet->ident = ident;
packet->df = (flags_foff & BIT_V(uint16_t, FF_FLAG_DF)) != 0;
packet->mf = (flags_foff & BIT_V(uint16_t, FF_FLAG_MF)) != 0;
packet->offs = foff * FRAG_OFFS_UNIT;
size_t data_offs = sizeof(uint32_t) *
BIT_RANGE_EXTRACT(uint8_t, VI_IHL_h, VI_IHL_l, hdr->ver_ihl);
packet->size = tot_len - data_offs;
packet->data = calloc(packet->size, 1);
if (packet->data == NULL) {
log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
return ENOMEM;
}
memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
packet->link_id = link_id;
return EOK;
}
errno_t inet_pdu_decode6(void *data, size_t size, service_id_t link_id,
inet_packet_t *packet)
{
log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode6()");
if (size < sizeof(ip6_header_t)) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
return EINVAL;
}
ip6_header_t *hdr6 = (ip6_header_t *) data;
uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
VI_VERSION_l, hdr6->ver_tc);
if (version != 6) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 6", version);
return EINVAL;
}
size_t payload_len = uint16_t_be2host(hdr6->payload_len);
if (payload_len + sizeof(ip6_header_t) > size) {
log_msg(LOG_DEFAULT, LVL_DEBUG, "Payload Length = %zu > PDU size = %zu",
payload_len + sizeof(ip6_header_t), size);
return EINVAL;
}
uint32_t ident;
uint16_t offsmf;
uint16_t foff;
uint16_t next;
size_t data_offs = sizeof(ip6_header_t);
if (hdr6->next == IP6_NEXT_FRAGMENT) {
ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
(hdr6 + 1);
ident = uint32_t_be2host(hdr6f->id);
offsmf = uint16_t_be2host(hdr6f->offsmf);
foff = BIT_RANGE_EXTRACT(uint16_t, OF_FRAGOFF_h, OF_FRAGOFF_l,
offsmf);
next = hdr6f->next;
data_offs += sizeof(ip6_header_fragment_t);
payload_len -= sizeof(ip6_header_fragment_t);
} else {
ident = 0;
offsmf = 0;
foff = 0;
next = hdr6->next;
}
addr128_t src;
addr128_t dest;
addr128_t_be2host(hdr6->src_addr, src);
inet_addr_set6(src, &packet->src);
addr128_t_be2host(hdr6->dest_addr, dest);
inet_addr_set6(dest, &packet->dest);
packet->tos = 0;
packet->proto = next;
packet->ttl = hdr6->hop_limit;
packet->ident = ident;
packet->df = 1;
packet->mf = (offsmf & BIT_V(uint16_t, OF_FLAG_M)) != 0;
packet->offs = foff * FRAG_OFFS_UNIT;
packet->size = payload_len;
packet->data = calloc(packet->size, 1);
if (packet->data == NULL) {
log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
return ENOMEM;
}
memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
packet->link_id = link_id;
return EOK;
}
errno_t ndp_pdu_encode(ndp_packet_t *ndp, inet_dgram_t *dgram)
{
inet_addr_set6(ndp->sender_proto_addr, &dgram->src);
inet_addr_set6(ndp->target_proto_addr, &dgram->dest);
dgram->tos = 0;
dgram->size = sizeof(icmpv6_message_t) + sizeof(ndp_message_t);
dgram->data = calloc(1, dgram->size);
if (dgram->data == NULL)
return ENOMEM;
icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
icmpv6->type = ndp->opcode;
icmpv6->code = 0;
memset(icmpv6->un.ndp.reserved, 0, 3);
ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
if (ndp->opcode == ICMPV6_NEIGHBOUR_SOLICITATION) {
host2addr128_t_be(ndp->solicited_ip, message->target_address);
message->option = 1;
icmpv6->un.ndp.flags = 0;
} else {
host2addr128_t_be(ndp->sender_proto_addr, message->target_address);
message->option = 2;
icmpv6->un.ndp.flags = NDP_FLAG_OVERRIDE | NDP_FLAG_SOLICITED;
}
message->length = 1;
eth_addr_encode(&ndp->sender_hw_addr, message->mac);
icmpv6_phdr_t phdr;
host2addr128_t_be(ndp->sender_proto_addr, phdr.src_addr);
host2addr128_t_be(ndp->target_proto_addr, phdr.dest_addr);
phdr.length = host2uint32_t_be(dgram->size);
memset(phdr.zeroes, 0, 3);
phdr.next = IP_PROTO_ICMPV6;
uint16_t cs_phdr =
inet_checksum_calc(INET_CHECKSUM_INIT, &phdr,
sizeof(icmpv6_phdr_t));
uint16_t cs_all = inet_checksum_calc(cs_phdr, dgram->data,
dgram->size);
icmpv6->checksum = host2uint16_t_be(cs_all);
return EOK;
}
errno_t ndp_pdu_decode(inet_dgram_t *dgram, ndp_packet_t *ndp)
{
ip_ver_t src_ver = inet_addr_get(&dgram->src, NULL,
&ndp->sender_proto_addr);
if (src_ver != ip_v6)
return EINVAL;
if (dgram->size < sizeof(icmpv6_message_t) + sizeof(ndp_message_t))
return EINVAL;
icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
ndp->opcode = icmpv6->type;
ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
addr128_t_be2host(message->target_address, ndp->target_proto_addr);
eth_addr_decode(message->mac, &ndp->sender_hw_addr);
return EOK;
}
HelenOS homepage, sources at GitHub