sortix-mirror/kernel/net/ether.cpp

140 lines
4.2 KiB
C++

/*
* Copyright (c) 2016, 2017 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* net/ether.cpp
* Ethernet.
*/
#include <assert.h>
#include <errno.h>
#include <endian.h>
#include <netinet/if_ether.h>
#include <stdint.h>
#include <string.h>
#include <sortix/kernel/crc32.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/if.h>
#include <sortix/kernel/packet.h>
#include "arp.h"
#include "ether.h"
#include "ip.h"
namespace Sortix {
namespace Ether {
size_t GetMTU(NetworkInterface* netif)
{
(void) netif;
return ETHERMTU;
}
void Handle(Ref<Packet> pkt, bool checksum_offloaded)
{
assert(pkt->netif);
assert(pkt->offset <= pkt->length);
const unsigned char* in = pkt->from + pkt->offset;
size_t inlen = pkt->length - pkt->offset;
struct ether_header hdr;
if ( !checksum_offloaded )
{
struct ether_footer ftr;
if ( inlen < sizeof(hdr) + sizeof(ftr) /* ETHER_LEN */ )
return;
size_t ftr_offset = inlen - sizeof(ftr);
memcpy(&ftr, in + ftr_offset, sizeof(ftr));
pkt->length -= sizeof(ftr);
inlen -= sizeof(ftr);
ftr.ether_crc = le32toh(ftr.ether_crc);
if ( ftr.ether_crc != crc32(0, in, inlen) )
return;
}
else if ( inlen < sizeof(hdr) /* ETHER_HDR_LEN */ )
return;
memcpy(&hdr, in, sizeof(hdr));
hdr.ether_type = be16toh(hdr.ether_type);
pkt->offset += sizeof(hdr);
const struct ether_addr* src = (const struct ether_addr*) &hdr.ether_shost;
const struct ether_addr* dst = (const struct ether_addr*) &hdr.ether_dhost;
// Drop invalid frames with broadcast source.
if ( !memcmp(src, &etheraddr_broadcast, sizeof(struct ether_addr)) )
return;
// Accept only frames with destination being broadcast or our address.
bool dst_broadcast =
!memcmp(dst, &etheraddr_broadcast, sizeof(struct ether_addr));
if ( !dst_broadcast )
{
ScopedLock(&pkt->netif->cfg_lock);
const struct ether_addr* local = &pkt->netif->cfg.ether.address;
if ( pkt->netif->ifinfo.type != IF_TYPE_LOOPBACK &&
memcmp(dst, local, sizeof(struct ether_addr)) != 0 )
return;
}
switch ( hdr.ether_type )
{
case ETHERTYPE_IP: // IPv4
IP::Handle(pkt, src, dst, dst_broadcast);
break;
case ETHERTYPE_ARP: // Address Resolution Protocol
ARP::Handle(pkt, src, dst, dst_broadcast);
break;
case ETHERTYPE_IPV6: // IPv6
break;
default:
break;
}
}
bool Send(Ref<Packet> pktin,
const struct ether_addr* src,
const struct ether_addr* dst,
uint16_t ether_type,
NetworkInterface* netif)
{
if ( ETHERMTU < pktin->length )
return errno = EMSGSIZE, false;
Ref<Packet> pkt = GetPacket();
if ( !pkt )
return false;
const unsigned char* in = pktin->from;
size_t inlen = pktin->length;
size_t padding = inlen < ETHERMIN ? ETHERMIN - inlen : 0;
unsigned char* out = pkt->from;
struct ether_header hdr;
struct ether_footer ftr;
size_t outlen = sizeof(hdr) /* ETHER_HDR_LEN */ + inlen + padding;
if ( !(netif->ifinfo.features & IF_FEATURE_ETHERNET_CRC_OFFLOAD) )
outlen += sizeof(ftr) /* ETHER_CRC_LEN */;
if ( pkt->pmap.size < outlen )
return errno = EMSGSIZE, false;
pkt->length = outlen;
memcpy(&hdr.ether_dhost, dst, sizeof(struct ether_addr));
memcpy(&hdr.ether_shost, src, sizeof(struct ether_addr));
hdr.ether_type = htobe16(ether_type);
memcpy(out, &hdr, sizeof(hdr));
memcpy(out + sizeof(hdr), in, inlen);
memset(out + sizeof(hdr) + inlen, 0, padding);
if ( !(netif->ifinfo.features & IF_FEATURE_ETHERNET_CRC_OFFLOAD) )
{
ftr.ether_crc = htole32(crc32(0, out, pkt->length));
memcpy(out + sizeof(hdr) + inlen + padding, &ftr, sizeof(ftr));
}
return netif->Send(pkt);
}
} // namespace Ether
} // namespace Sortix