blob: 6f1e7ade4aeef513007bf22d7144d9b5995dfe36 [file] [log] [blame]
/**
* Contains shamelessly stolen code from BSD & lwip, both have
* BSD-style licenses
*
*/
#include <ros/common.h>
#include <string.h>
#include <kmalloc.h>
#include <socket.h>
#include <net.h>
#include <sys/queue.h>
#include <atomic.h>
#include <bits/netinet.h>
#include <net/ip.h>
#include <net/udp.h>
#include <slab.h>
#include <socket.h>
#include <debug.h>
struct udp_pcb *udp_pcbs;
uint16_t udp_port_num = SOCKET_PORT_START;
struct udp_pcb* udp_new(void){
struct udp_pcb *pcb = kmem_cache_alloc(udp_pcb_kcache, 0);
// if pcb is only tracking ttl, then no need!
if (pcb!= NULL){
pcb->ttl = UDP_TTL;
memset(pcb, 0, sizeof(struct udp_pcb));
}
return pcb;
}
int udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
/* send to the packet using remote ip and port stored in the pcb */
// rip and rport should be in socket not pcb?
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}
typedef unsigned char u16;
typedef unsigned long u32;
u16 udp_sum_calc(u16 len_udp, u16 src_addr[],u16 dest_addr[], int padding, u16 buff[])
{
u16 prot_udp=17;
u16 padd=0;
u16 word16;
u32 sum;
int i;
// Find out if the length of data is even or odd number. If odd,
// add a padding byte = 0 at the end of packet
if ((padding&1)==1){
padd=1;
buff[len_udp]=0;
}
//initialize sum to zero
sum=0;
// make 16 bit words out of every two adjacent 8 bit words and
// calculate the sum of all 16 vit words
for (i=0;i<len_udp+padd;i=i+2){
word16 =((buff[i]<<8)&0xFF00)+(buff[i+1]&0xFF);
sum = sum + (unsigned long)word16;
}
// add the UDP pseudo header which contains the IP source and destinationn addresses
for (i=0;i<4;i=i+2){
word16 =((src_addr[i]<<8)&0xFF00)+(src_addr[i+1]&0xFF);
sum=sum+word16;
}
for (i=0;i<4;i=i+2){
word16 =((dest_addr[i]<<8)&0xFF00)+(dest_addr[i+1]&0xFF);
sum=sum+word16;
}
// the protocol number and the length of the UDP packet
sum = sum + prot_udp + len_udp;
// keep only the last 16 bits of the 32 bit calculated sum and add the carries
while (sum>>16)
sum = (sum & 0xFFFF)+(sum >> 16);
// Take the one's complement of sum
sum = ~sum;
return ((u16) sum);
}
int udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
struct in_addr *dst_ip, uint16_t dst_port){
// we now have one netif to send to, otherwise we need to route
// ip_route();
struct udp_hdr *udphdr;
struct pbuf *q;
printd("udp_sendto ip %x, port %d\n", dst_ip->s_addr, dst_port);
// broadcast?
if (pcb->local_port == 0) {
/* if the PCB not bound to a port, bind it and give local ip */
if (udp_bind(pcb, &pcb->local_ip, pcb->local_port)!=0)
warn("udp binding failed \n");
}
if (pbuf_header(p, UDP_HLEN)){ // we could probably save this check for block.
// CHECK: should allocate enough for the other headers too
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
if (q == NULL)
panic("out of memory");
// if the original packet is not empty
if (p->tot_len !=0) {
pbuf_chain(q,p);
// check if it is chained properly ..
}
} else {
/* Successfully padded the header*/
q = p;
}
udphdr = (struct udp_hdr *) q->payload;
printd("src port %d, dst port %d \n, length %d ", pcb->local_port, ntohs(dst_port), q->tot_len);
udphdr->src_port = htons(pcb->local_port);
udphdr->dst_port = (dst_port);
udphdr->length = htons(q->tot_len);
udphdr->checksum = 0; // just to be sure.
// printd("checksum inet_chksum %x \n", udphdr->checksum);
printd("params src addr %x, dst addr %x, length %x \n", LOCAL_IP_ADDR.s_addr, (dst_ip->s_addr),
q->tot_len);
udphdr->checksum = inet_chksum_pseudo(q, htonl(LOCAL_IP_ADDR.s_addr), dst_ip->s_addr,
IPPROTO_UDP, q->tot_len);
printd ("method ours %x\n", udphdr->checksum);
// 0x0000; //either use brho's checksum or use cards' capabilities
ip_output(q, &LOCAL_IP_ADDR, dst_ip, pcb->ttl, pcb->tos, IPPROTO_UDP);
// ip_output(q, &global_ip, dst_ip, IPPROTO_UDP);
return 0;
}
/* TODO: use the real queues we have implemented... */
int udp_bind(struct udp_pcb *pcb, const struct in_addr *ip, uint16_t port){
int rebind = pcb->local_port;
struct udp_pcb *ipcb;
assert(pcb);
/* trying to assign port */
if (port != 0)
pcb->local_port = port;
/* no lock needed since we are just traversing/reading */
/* Check for double bind and rebind of the same pcb */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
/* is this UDP PCB already on active list? */
if (pcb == ipcb) {
rebind = 1; //already on the list
} else if (ipcb->local_port == port){
warn("someone else is using the port %d\n" , port);
return -1;
}
}
/* accept data for all interfaces */
if (ip == NULL || (ip->s_addr == INADDR_ANY.s_addr))
/* true right now */
pcb->local_ip = INADDR_ANY;
/* assign a port */
if (port == 0) {
port = SOCKET_PORT_START;
ipcb = udp_pcbs;
while ((ipcb != NULL) && (port != SOCKET_PORT_END)) {
if (ipcb->local_port == port) {
/* port is already used by another udp_pcb */
port++;
/* restart scanning all udp pcbs */
ipcb = udp_pcbs;
} else {
/* go on with next udp pcb */
ipcb = ipcb->next;
}
}
if (ipcb != NULL){
warn("No more udp ports available!");
}
}
if (rebind == 0) {
/* place the PCB on the active list if not already there */
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
printk("local port bound to 0x%x \n", port);
pcb->local_port = port;
return 0;
}
/* port are in host order, ips are in network order */
/* Think: a pcb is here, if someone is waiting for a connection or the udp conn
* has been established */
static struct udp_pcb* find_pcb(struct udp_pcb* list, uint16_t src_port, uint16_t dst_port,
uint16_t srcip, uint16_t dstip) {
struct udp_pcb* uncon_pcb = NULL;
struct udp_pcb* pcb = NULL;
uint8_t local_match = 0;
for (pcb = list; pcb != NULL; pcb = pcb->next) {
local_match = 0;
if ((pcb->local_port == dst_port)
&& (pcb->local_ip.s_addr == dstip
|| ip_addr_isany(&pcb->local_ip))){
local_match = 1;
if ((uncon_pcb == NULL) &&
((pcb->flags & UDP_FLAGS_CONNECTED) == 0)) {
/* the first unconnected matching PCB */
uncon_pcb = pcb;
}
}
if (local_match && (pcb->remote_port == src_port) &&
(ip_addr_isany(&pcb->remote_ip) ||
pcb->remote_ip.s_addr == srcip))
/* perfect match */
return pcb;
}
return uncon_pcb;
}
#if 0 // not working yet
// need to have pbuf queue support
int udp_attach(struct pbuf *p, struct sock *socket) {
// pretend the attaching of packet is succesful
/*
recv_q->last->next = p;
recv_q->last=p->last
*/
}
#endif
/** Process an incoming UDP datagram.
* Given an incoming UDP datagram, this function finds the right PCB
* which links to the right socket buffer, and attaches the datagram
* to the right socket.
* If no appropriate PCB is found, the pbuf is freed.
*/
/** TODO: think about combining udp_input and ip_input together */
// TODO: figure out if we even need a PCB? or just socket buff.
// TODO: test out looking up pcbs.. since matching function may fail
int udp_input(struct pbuf *p){
struct udp_hdr *udphdr;
struct udp_pcb *pcb, uncon_pcb;
struct ip_hdr *iphdr;
uint16_t src, dst;
bool local_match = 0;
iphdr = (struct ip_hdr *)p->payload;
/* Move the header to where the udp header is */
if (pbuf_header(p, -((int16_t)((iphdr->hdr_len) * 4)))) {
warn("udp_input: Did not find a matching PCB for a udp packet\n");
pbuf_free(p);
return -1;
}
printk("start of udp %p\n", p->payload);
udphdr = (struct udp_hdr *)p->payload;
/* convert the src port and dst port to host order */
src = ntohs(udphdr->src_port);
dst = ntohs(udphdr->dst_port);
pcb = find_pcb(udp_pcbs, src, dst, iphdr->src_addr, iphdr->dst_addr);
/* TODO: Possibly adjust the pcb to the head of the queue? */
/* TODO: Linux uses a set of hashtables to lookup PCBs
* Look at __udp4_lib_lookup function in Linux kernel 2.6.21.1
*/
/* Anything that is not directed at this pcb should have been dropped */
if (pcb == NULL){
warn("udp_input: Did not find a matching PCB for a udp packet\n");
pbuf_free(p);
return -1;
}
/* checksum check */
if (udphdr->checksum != 0) {
if (inet_chksum_pseudo(p, (iphdr->src_addr), (iphdr->dst_addr),
IPPROTO_UDP, p->tot_len) != 0){
warn("udp_input: UPD datagram discarded due to failed chksum!");
pbuf_free(p);
return -1;
}
}
/* ignore SO_REUSE */
if (pcb != NULL && pcb->pcbsock != NULL){
/* For each in the pbuf chain, disconnect from the chain and add it to the
* recv_buff of the correct socket
*/
struct socket *sock = pcb->pcbsock;
attach_pbuf(p, &sock->recv_buff);
struct kthread *kthread;
int8_t irq_state = 0;
/* First notify any blocking recv calls,
* then notify anyone who might be waiting in a select
*/
// multiple people might be waiting on the socket here..
/* TODO: consider a helper with this and tcp.c */
if (!sem_up_irqsave(&sock->sem, &irq_state)) {
// wake up all waiters
struct semaphore_entry *sentry, *sentry_tmp;
spin_lock(&sock->waiter_lock);
LIST_FOREACH_SAFE(sentry, &sock->waiters, link, sentry_tmp) {
//should only wake up one waiter
sem_up_irqsave(&sentry->sem, &irq_state);
LIST_REMOVE(sentry, link);
/* do not need to free since all the sentry are stack-based vars
* */
}
spin_unlock(&sock->waiter_lock);
}
// the attaching of pbuf should have increfed pbuf ref, so free is simply a decref
pbuf_free(p);
}
return 0;
}