/* * Copyright (c) 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/lo/lo.cpp * Loopback device. */ #include #include #include #include #include #include #include #include #include "../ether.h" #include "lo.h" // The loopback device currently communicates through the Ethernet layer and // pretends to do offload Ethernet checksumming as an optimization. // The shared worker thread is used for processing. Whenever a packet needs to // be sent, if the worker thread isn't scheduled, it is scheduled. The worker // thread transmits all the packets that were in the queue when it begins, but // not any more than that. If any work remains at the end, it schedules itself // again to run later (to avoid starving other tasks using the shared worker // thread). The packet queue is a singly linked list of packets. namespace Sortix { namespace Loopback { class Loopback : public NetworkInterface { friend void Loopback__Recv(void* ctx); public: Loopback(); ~Loopback(); public: bool Send(Ref pkt); private: void Recv(); private: kthread_mutex_t socket_lock; Ref first_packet; Ref last_packet; bool worker_scheduled; }; Loopback::Loopback() { ifinfo.type = IF_TYPE_LOOPBACK; ifinfo.features = IF_FEATURE_ETHERNET_CRC_OFFLOAD; ifinfo.addrlen = 0; ifstatus.flags = IF_STATUS_FLAGS_UP; cfg.inet.address.s_addr = htobe32(INADDR_LOOPBACK); cfg.inet.router.s_addr = htobe32(INADDR_ANY); cfg.inet.subnet.s_addr = htobe32(INADDR_LOOPMASK); socket_lock = KTHREAD_MUTEX_INITIALIZER; // first_packet initialized by constructor // last_packet initialized by constructor worker_scheduled = false; } Loopback::~Loopback() { // Avoid stack overflow in first_packet recursive destructor. while ( first_packet ) { Ref next = first_packet->next; first_packet->next.Reset(); first_packet = next; } last_packet.Reset(); } void Loopback__Recv(void* ctx) { ((Loopback*) ctx)->Recv(); } void Loopback::Recv() { kthread_mutex_lock(&socket_lock); Ref next_packet = first_packet; first_packet.Reset(); last_packet.Reset(); kthread_mutex_unlock(&socket_lock); while ( next_packet ) { Ref packet = next_packet; next_packet = next_packet->next; packet->next.Reset(); packet->netif = this; Ether::Handle(packet, true); } kthread_mutex_lock(&socket_lock); bool should_schedule = first_packet; if ( !should_schedule ) worker_scheduled = false; kthread_mutex_unlock(&socket_lock); if ( should_schedule ) Worker::Schedule(Loopback__Recv, this); } bool Loopback::Send(Ref pkt) { kthread_mutex_lock(&socket_lock); if ( last_packet ) last_packet->next = pkt; else first_packet = pkt; last_packet = pkt; bool should_schedule = !worker_scheduled; worker_scheduled = true; kthread_mutex_unlock(&socket_lock); if ( should_schedule ) Worker::Schedule(Loopback__Recv, this); return true; } void Init(const char* /*devpath*/, Ref dev) { Loopback* lo = new Loopback(); if ( !lo ) PanicF("Failed to allocate loopback device"); size_t index = 0; snprintf(lo->ifinfo.name, sizeof(lo->ifinfo.name), "lo%zu", index); if ( !RegisterNetworkInterface(lo, dev) ) PanicF("Failed to register %s as network interface", lo->ifinfo.name); } } // namespace Loopback } // namespace Sortix