blob: 93c997bb60297721d85e15568a2fd10e8b34c5d8 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <assert.h>
#include <endian.h>
#include <string.h>
#include "base/container_of.h"
#include "base/die.h"
#include "base/init_funcs.h"
#include "base/list.h"
#include "base/xalloc.h"
#include "net/ethernet.h"
#include "net/ipv4/ipv4.h"
#include "net/ipv4/uip/arp.h"
#include "net/ipv4/uip/udp/packet.h"
#include "net/ipv4/uip/uip.h"
#include "net/net.h"
ListNode ipv4_udp_connections;
static int ipv4_udp_con_init(Ipv4UdpCon *con)
{
con->uip_con = uip_udp_new(&con->remote_ip, htonw(con->port));
if (!con->uip_con) {
printf("Failed to set up UDP connection.\n");
return 1;
}
list_insert_after(&con->list_node, &ipv4_udp_connections);
return 0;
}
static int ipv4_udp_bind(NetConOps *me, uint16_t port)
{
Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
if (!con->uip_con && ipv4_udp_con_init(con))
return 1;
uip_udp_bind(con->uip_con, htonw(port));
return 0;
}
static int ipv4_udp_incoming(NetConOps *me, size_t *size)
{
Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
if (!con->uip_con && ipv4_udp_con_init(con))
return 1;
*size = con->incoming_count;
// Poke the device to receive any data it has.
if (!*size) {
net_service_dev(con->dev);
*size = con->incoming_count;
}
return 0;
}
static int ipv4_udp_send(NetConOps *me, const void *data, size_t len)
{
Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
if (!con->uip_con && ipv4_udp_con_init(con))
return 1;
uip_udp_packet_send(con->dev, con->uip_con, data, len);
return 0;
}
static int ipv4_udp_receive(NetConOps *me, void *data, size_t *len,
size_t max_len)
{
Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
if (!con->uip_con && ipv4_udp_con_init(con))
return 1;
size_t size;
if (me->incoming(me, &size))
return 1;
if (!size) {
printf("No data to receive.\n");
return 1;
} else if (size > max_len) {
printf("Insufficient space: putting %zd bytes in %zd bytes.\n",
size, max_len);
return 1;
}
*len = size;
if (data)
memcpy(data, con->incoming, size);
con->incoming_count = 0;
return 0;
}
static int ipv4_udp_close(NetConOps *me)
{
Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
if (!con->uip_con && ipv4_udp_con_init(con))
return 1;
list_remove(&con->list_node);
uip_udp_remove(con->uip_con);
con->uip_con = NULL;
return 0;
}
Ipv4UdpCon *new_ipv4_udp_con(NetDevice *dev, uip_ipaddr_t remote_ip,
uint16_t port)
{
Ipv4UdpCon *con = xzalloc(sizeof(*con));
con->ops.bind = &ipv4_udp_bind;
con->ops.incoming = &ipv4_udp_incoming;
con->ops.send = &ipv4_udp_send;
con->ops.receive = &ipv4_udp_receive;
con->ops.close = &ipv4_udp_close;
con->remote_ip = remote_ip;
con->port = port;
con->dev = dev;
return con;
}
void ipv4_uip_udp_callback(void)
{
// Extract the destination port.
size_t header_size = sizeof(EtherHdr) + sizeof(struct uip_udpip_hdr);
if (uip_len < header_size)
return;
struct uip_udpip_hdr *hdr = (void *)&uip_buf[sizeof(EtherHdr)];
// Figure out who's data this is.
Ipv4UdpCon *con;
list_for_each(con, ipv4_udp_connections, list_node) {
if (con->uip_con->lport == hdr->destport) {
con->uip_con->rport = hdr->srcport;
if (con->incoming_count) {
printf("Already have data. Dropping.\n");
return;
}
if (uip_datalen() > sizeof(con->incoming)) {
printf("Too much data. Dropping.\n");
return;
}
con->incoming_count = uip_datalen();
memcpy(con->incoming, &uip_buf[header_size],
con->incoming_count);
return;
}
}
}
void ipv4_uip_tcp_callback(void)
{
die("TCP connections aren't supported.\n");
}
int ipv4_ipv4_process(EthTypeOps *me, NetDevice *dev, void *data, size_t len)
{
assert(len <= CONFIG_UIP_BUFSIZE);
memcpy(uip_buf, data, len);
uip_len = len;
uip_arp_ipin();
uip_input();
if (uip_len > 0) {
uip_arp_out();
dev->send(dev, uip_buf, uip_len);
}
return 0;
}
static EthTypeOps ipv4_ipv4_type = {
.type = EthType_Ipv4,
.process = &ipv4_ipv4_process,
};
int ipv4_arp_process(EthTypeOps *me, NetDevice *dev, void *data, size_t len)
{
assert(len <= CONFIG_UIP_BUFSIZE);
memcpy(uip_buf, data, len);
uip_len = len;
uip_arp_arpin();
if (uip_len > 0)
dev->send(dev, uip_buf, uip_len);
return 0;
}
static EthTypeOps ipv4_arp_type = {
.type = EthType_Arp,
.process = &ipv4_arp_process,
};
static int ipv4_install_eth_types(void)
{
list_insert_after(&ipv4_ipv4_type.list_node, &net_eth_types);
list_insert_after(&ipv4_arp_type.list_node, &net_eth_types);
return 0;
}
INIT_FUNC(ipv4_install_eth_types);