375 lines
8.1 KiB
C
375 lines
8.1 KiB
C
//
|
|
// networking protocol support (IP, UDP, ARP, etc.).
|
|
//
|
|
|
|
#include "types.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "riscv.h"
|
|
#include "spinlock.h"
|
|
#include "proc.h"
|
|
#include "net.h"
|
|
#include "defs.h"
|
|
|
|
static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15); // qemu's idea of the guest IP
|
|
static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
|
|
static uint8 broadcast_mac[ETHADDR_LEN] = { 0xFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF };
|
|
|
|
// Strips data from the start of the buffer and returns a pointer to it.
|
|
// Returns 0 if less than the full requested length is available.
|
|
char *
|
|
mbufpull(struct mbuf *m, unsigned int len)
|
|
{
|
|
char *tmp = m->head;
|
|
if (m->len < len)
|
|
return 0;
|
|
m->len -= len;
|
|
m->head += len;
|
|
return tmp;
|
|
}
|
|
|
|
// Prepends data to the beginning of the buffer and returns a pointer to it.
|
|
char *
|
|
mbufpush(struct mbuf *m, unsigned int len)
|
|
{
|
|
m->head -= len;
|
|
if (m->head < m->buf)
|
|
panic("mbufpush");
|
|
m->len += len;
|
|
return m->head;
|
|
}
|
|
|
|
// Appends data to the end of the buffer and returns a pointer to it.
|
|
char *
|
|
mbufput(struct mbuf *m, unsigned int len)
|
|
{
|
|
char *tmp = m->head + m->len;
|
|
m->len += len;
|
|
if (m->len > MBUF_SIZE)
|
|
panic("mbufput");
|
|
return tmp;
|
|
}
|
|
|
|
// Strips data from the end of the buffer and returns a pointer to it.
|
|
// Returns 0 if less than the full requested length is available.
|
|
char *
|
|
mbuftrim(struct mbuf *m, unsigned int len)
|
|
{
|
|
if (len > m->len)
|
|
return 0;
|
|
m->len -= len;
|
|
return m->head + m->len;
|
|
}
|
|
|
|
// Allocates a packet buffer.
|
|
struct mbuf *
|
|
mbufalloc(unsigned int headroom)
|
|
{
|
|
struct mbuf *m;
|
|
|
|
if (headroom > MBUF_SIZE)
|
|
return 0;
|
|
m = kalloc();
|
|
if (m == 0)
|
|
return 0;
|
|
m->next = 0;
|
|
m->head = (char *)m->buf + headroom;
|
|
m->len = 0;
|
|
memset(m->buf, 0, sizeof(m->buf));
|
|
return m;
|
|
}
|
|
|
|
// Frees a packet buffer.
|
|
void
|
|
mbuffree(struct mbuf *m)
|
|
{
|
|
kfree(m);
|
|
}
|
|
|
|
// Pushes an mbuf to the end of the queue.
|
|
void
|
|
mbufq_pushtail(struct mbufq *q, struct mbuf *m)
|
|
{
|
|
m->next = 0;
|
|
if (!q->head){
|
|
q->head = q->tail = m;
|
|
return;
|
|
}
|
|
q->tail->next = m;
|
|
q->tail = m;
|
|
}
|
|
|
|
// Pops an mbuf from the start of the queue.
|
|
struct mbuf *
|
|
mbufq_pophead(struct mbufq *q)
|
|
{
|
|
struct mbuf *head = q->head;
|
|
if (!head)
|
|
return 0;
|
|
q->head = head->next;
|
|
return head;
|
|
}
|
|
|
|
// Returns one (nonzero) if the queue is empty.
|
|
int
|
|
mbufq_empty(struct mbufq *q)
|
|
{
|
|
return q->head == 0;
|
|
}
|
|
|
|
// Intializes a queue of mbufs.
|
|
void
|
|
mbufq_init(struct mbufq *q)
|
|
{
|
|
q->head = 0;
|
|
}
|
|
|
|
// This code is lifted from FreeBSD's ping.c, and is copyright by the Regents
|
|
// of the University of California.
|
|
static unsigned short
|
|
in_cksum(const unsigned char *addr, int len)
|
|
{
|
|
int nleft = len;
|
|
const unsigned short *w = (const unsigned short *)addr;
|
|
unsigned int sum = 0;
|
|
unsigned short answer = 0;
|
|
|
|
/*
|
|
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
|
|
* sequential 16 bit words to it, and at the end, fold back all the
|
|
* carry bits from the top 16 bits into the lower 16 bits.
|
|
*/
|
|
while (nleft > 1) {
|
|
sum += *w++;
|
|
nleft -= 2;
|
|
}
|
|
|
|
/* mop up an odd byte, if necessary */
|
|
if (nleft == 1) {
|
|
*(unsigned char *)(&answer) = *(const unsigned char *)w;
|
|
sum += answer;
|
|
}
|
|
|
|
/* add back carry outs from top 16 bits to low 16 bits */
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
sum += (sum >> 16);
|
|
/* guaranteed now that the lower 16 bits of sum are correct */
|
|
|
|
answer = ~sum; /* truncate to 16 bits */
|
|
return answer;
|
|
}
|
|
|
|
// sends an ethernet packet
|
|
static void
|
|
net_tx_eth(struct mbuf *m, uint16 ethtype)
|
|
{
|
|
struct eth *ethhdr;
|
|
|
|
ethhdr = mbufpushhdr(m, *ethhdr);
|
|
memmove(ethhdr->shost, local_mac, ETHADDR_LEN);
|
|
// In a real networking stack, dhost would be set to the address discovered
|
|
// through ARP. Because we don't support enough of the ARP protocol, set it
|
|
// to broadcast instead.
|
|
memmove(ethhdr->dhost, broadcast_mac, ETHADDR_LEN);
|
|
ethhdr->type = htons(ethtype);
|
|
if (e1000_transmit(m)) {
|
|
mbuffree(m);
|
|
}
|
|
}
|
|
|
|
// sends an IP packet
|
|
static void
|
|
net_tx_ip(struct mbuf *m, uint8 proto, uint32 dip)
|
|
{
|
|
struct ip *iphdr;
|
|
|
|
// push the IP header
|
|
iphdr = mbufpushhdr(m, *iphdr);
|
|
memset(iphdr, 0, sizeof(*iphdr));
|
|
iphdr->ip_vhl = (4 << 4) | (20 >> 2);
|
|
iphdr->ip_p = proto;
|
|
iphdr->ip_src = htonl(local_ip);
|
|
iphdr->ip_dst = htonl(dip);
|
|
iphdr->ip_len = htons(m->len);
|
|
iphdr->ip_ttl = 100;
|
|
iphdr->ip_sum = in_cksum((unsigned char *)iphdr, sizeof(*iphdr));
|
|
|
|
// now on to the ethernet layer
|
|
net_tx_eth(m, ETHTYPE_IP);
|
|
}
|
|
|
|
// sends a UDP packet
|
|
void
|
|
net_tx_udp(struct mbuf *m, uint32 dip,
|
|
uint16 sport, uint16 dport)
|
|
{
|
|
struct udp *udphdr;
|
|
|
|
// put the UDP header
|
|
udphdr = mbufpushhdr(m, *udphdr);
|
|
udphdr->sport = htons(sport);
|
|
udphdr->dport = htons(dport);
|
|
udphdr->ulen = htons(m->len);
|
|
udphdr->sum = 0; // zero means no checksum is provided
|
|
|
|
// now on to the IP layer
|
|
net_tx_ip(m, IPPROTO_UDP, dip);
|
|
}
|
|
|
|
// sends an ARP packet
|
|
static int
|
|
net_tx_arp(uint16 op, uint8 dmac[ETHADDR_LEN], uint32 dip)
|
|
{
|
|
struct mbuf *m;
|
|
struct arp *arphdr;
|
|
|
|
m = mbufalloc(MBUF_DEFAULT_HEADROOM);
|
|
if (!m)
|
|
return -1;
|
|
|
|
// generic part of ARP header
|
|
arphdr = mbufputhdr(m, *arphdr);
|
|
arphdr->hrd = htons(ARP_HRD_ETHER);
|
|
arphdr->pro = htons(ETHTYPE_IP);
|
|
arphdr->hln = ETHADDR_LEN;
|
|
arphdr->pln = sizeof(uint32);
|
|
arphdr->op = htons(op);
|
|
|
|
// ethernet + IP part of ARP header
|
|
memmove(arphdr->sha, local_mac, ETHADDR_LEN);
|
|
arphdr->sip = htonl(local_ip);
|
|
memmove(arphdr->tha, dmac, ETHADDR_LEN);
|
|
arphdr->tip = htonl(dip);
|
|
|
|
// header is ready, send the packet
|
|
net_tx_eth(m, ETHTYPE_ARP);
|
|
return 0;
|
|
}
|
|
|
|
// receives an ARP packet
|
|
static void
|
|
net_rx_arp(struct mbuf *m)
|
|
{
|
|
struct arp *arphdr;
|
|
uint8 smac[ETHADDR_LEN];
|
|
uint32 sip, tip;
|
|
|
|
arphdr = mbufpullhdr(m, *arphdr);
|
|
if (!arphdr)
|
|
goto done;
|
|
|
|
// validate the ARP header
|
|
if (ntohs(arphdr->hrd) != ARP_HRD_ETHER ||
|
|
ntohs(arphdr->pro) != ETHTYPE_IP ||
|
|
arphdr->hln != ETHADDR_LEN ||
|
|
arphdr->pln != sizeof(uint32)) {
|
|
goto done;
|
|
}
|
|
|
|
// only requests are supported so far
|
|
// check if our IP was solicited
|
|
tip = ntohl(arphdr->tip); // target IP address
|
|
if (ntohs(arphdr->op) != ARP_OP_REQUEST || tip != local_ip)
|
|
goto done;
|
|
|
|
// handle the ARP request
|
|
memmove(smac, arphdr->sha, ETHADDR_LEN); // sender's ethernet address
|
|
sip = ntohl(arphdr->sip); // sender's IP address (qemu's slirp)
|
|
net_tx_arp(ARP_OP_REPLY, smac, sip);
|
|
|
|
done:
|
|
mbuffree(m);
|
|
}
|
|
|
|
// receives a UDP packet
|
|
static void
|
|
net_rx_udp(struct mbuf *m, uint16 len, struct ip *iphdr)
|
|
{
|
|
struct udp *udphdr;
|
|
uint32 sip;
|
|
uint16 sport, dport;
|
|
|
|
|
|
udphdr = mbufpullhdr(m, *udphdr);
|
|
if (!udphdr)
|
|
goto fail;
|
|
|
|
// TODO: validate UDP checksum
|
|
|
|
// validate lengths reported in headers
|
|
if (ntohs(udphdr->ulen) != len)
|
|
goto fail;
|
|
len -= sizeof(*udphdr);
|
|
if (len > m->len)
|
|
goto fail;
|
|
// minimum packet size could be larger than the payload
|
|
mbuftrim(m, m->len - len);
|
|
|
|
// parse the necessary fields
|
|
sip = ntohl(iphdr->ip_src);
|
|
sport = ntohs(udphdr->sport);
|
|
dport = ntohs(udphdr->dport);
|
|
sockrecvudp(m, sip, dport, sport);
|
|
return;
|
|
|
|
fail:
|
|
mbuffree(m);
|
|
}
|
|
|
|
// receives an IP packet
|
|
static void
|
|
net_rx_ip(struct mbuf *m)
|
|
{
|
|
struct ip *iphdr;
|
|
uint16 len;
|
|
|
|
iphdr = mbufpullhdr(m, *iphdr);
|
|
if (!iphdr)
|
|
goto fail;
|
|
|
|
// check IP version and header len
|
|
if (iphdr->ip_vhl != ((4 << 4) | (20 >> 2)))
|
|
goto fail;
|
|
// validate IP checksum
|
|
if (in_cksum((unsigned char *)iphdr, sizeof(*iphdr)))
|
|
goto fail;
|
|
// can't support fragmented IP packets
|
|
if (htons(iphdr->ip_off) != 0)
|
|
goto fail;
|
|
// is the packet addressed to us?
|
|
if (htonl(iphdr->ip_dst) != local_ip)
|
|
goto fail;
|
|
// can only support UDP
|
|
if (iphdr->ip_p != IPPROTO_UDP)
|
|
goto fail;
|
|
|
|
len = ntohs(iphdr->ip_len) - sizeof(*iphdr);
|
|
net_rx_udp(m, len, iphdr);
|
|
return;
|
|
|
|
fail:
|
|
mbuffree(m);
|
|
}
|
|
|
|
// called by e1000 driver's interrupt handler to deliver a packet to the
|
|
// networking stack
|
|
void net_rx(struct mbuf *m)
|
|
{
|
|
struct eth *ethhdr;
|
|
uint16 type;
|
|
|
|
ethhdr = mbufpullhdr(m, *ethhdr);
|
|
if (!ethhdr) {
|
|
mbuffree(m);
|
|
return;
|
|
}
|
|
|
|
type = ntohs(ethhdr->type);
|
|
if (type == ETHTYPE_IP)
|
|
net_rx_ip(m);
|
|
else if (type == ETHTYPE_ARP)
|
|
net_rx_arp(m);
|
|
else
|
|
mbuffree(m);
|
|
}
|