mirror of https://github.com/apache/cloudstack.git
1189 lines
32 KiB
C
1189 lines
32 KiB
C
/*
|
|
* Copyright (C) 2005, 2006 Mike Wray <mike.wray@hp.com>.
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of the
|
|
* License, or (at your option) any later version. This library 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <string.h>
|
|
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/select.h>
|
|
|
|
#include <asm/types.h> // For __u32 etc.
|
|
|
|
#include <linux/ip.h> // For struct iphdr.
|
|
#include <linux/udp.h> // For struct udphdr.
|
|
|
|
#include <linux/if.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_tun.h>
|
|
|
|
#include "sys_kernel.h"
|
|
#include "skbuff.h"
|
|
#include "spinlock.h"
|
|
|
|
#include "allocate.h"
|
|
|
|
#include "file_stream.h"
|
|
#include "string_stream.h"
|
|
#include "socket_stream.h"
|
|
#include "sys_net.h"
|
|
|
|
#include "enum.h"
|
|
#include "sxpr.h"
|
|
#include "sxpr_parser.h"
|
|
|
|
#include "connection.h"
|
|
#include "select.h"
|
|
#include "timer.h"
|
|
|
|
#include "if_etherip.h"
|
|
#include "if_varp.h"
|
|
#include "varp.h"
|
|
#include "vnet.h"
|
|
#include "vnet_dev.h"
|
|
#include "vnet_eval.h"
|
|
#include "vnet_forward.h"
|
|
#include "tunnel.h"
|
|
#include "etherip.h"
|
|
#include "sxpr_util.h"
|
|
|
|
#define MODULE_NAME "VNETD"
|
|
#define DEBUG 1
|
|
#undef DEBUG
|
|
#include "debug.h"
|
|
|
|
#define PROGRAM "vnetd"
|
|
#define VERSION "1.0"
|
|
|
|
typedef struct Vnetd {
|
|
unsigned long port;
|
|
int ttl;
|
|
int verbose;
|
|
int etherip;
|
|
|
|
int udp_sock;
|
|
struct sockaddr_in udp_sock_addr;
|
|
int mcast_sock;
|
|
struct sockaddr_in mcast_sock_addr;
|
|
int etherip_sock;
|
|
struct sockaddr_in etherip_sock_addr;
|
|
int unix_sock;
|
|
char *unix_path;
|
|
|
|
int raw_sock;
|
|
|
|
struct sockaddr_in ucast_addr;
|
|
struct sockaddr_in mcast_addr;
|
|
|
|
HashTable *vnet_table;
|
|
|
|
ConnList *conns;
|
|
|
|
} Vnetd;
|
|
|
|
Vnetd _vnetd = {}, *vnetd = &_vnetd;
|
|
|
|
uint32_t vnetd_intf_addr(Vnetd *vnetd){
|
|
return vnetd->ucast_addr.sin_addr.s_addr;
|
|
}
|
|
|
|
uint32_t vnetd_mcast_addr(Vnetd *vnetd){
|
|
return vnetd->mcast_addr.sin_addr.s_addr;
|
|
}
|
|
|
|
void vnetd_set_mcast_addr(Vnetd *vnetd, uint32_t addr){
|
|
varp_mcast_addr = addr;
|
|
vnetd->mcast_addr.sin_addr.s_addr = addr;
|
|
}
|
|
|
|
uint16_t vnetd_mcast_port(Vnetd *vnetd){
|
|
return vnetd->mcast_addr.sin_port;
|
|
}
|
|
|
|
uint32_t vnetd_addr(void){
|
|
return vnetd_intf_addr(vnetd);
|
|
}
|
|
|
|
/** Open tap device.
|
|
*/
|
|
int tap_open(struct net_device *dev){
|
|
int err;
|
|
/* IFF_TAP : Ethernet tap device.
|
|
* IFF_NO_PI : Don't add packet info struct when reading.
|
|
* IFF_ONE_QUEUE: Drop packets when the dev queue is full. The driver uses
|
|
* the queue size from the device, which defaults to 1000 for etherdev.
|
|
* If not set the driver stops the device queue when it goes over
|
|
* TUN_READQ_SIZE, which is 10. Broken - makes the device stall
|
|
* under load.
|
|
*/
|
|
struct ifreq ifr = { };
|
|
ifr.ifr_flags = (IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE);
|
|
|
|
dprintf(">\n");
|
|
dev->tapfd = open("/dev/net/tun", O_RDWR);
|
|
if(dev->tapfd < 0){
|
|
err = -errno;
|
|
perror("open");
|
|
goto exit;
|
|
}
|
|
strcpy(ifr.ifr_name, dev->name);
|
|
err = ioctl(dev->tapfd, TUNSETIFF, (void *)&ifr);
|
|
if(err < 0){
|
|
err = -errno;
|
|
perror("ioctl");
|
|
goto exit;
|
|
}
|
|
strcpy(dev->name, ifr.ifr_name);
|
|
dprintf("> dev=%s\n", dev->name);
|
|
// Make it non-blocking.
|
|
fcntl(dev->tapfd, F_SETFL, O_NONBLOCK);
|
|
|
|
exit:
|
|
if(err && (dev->tapfd >= 0)){
|
|
close(dev->tapfd);
|
|
dev->tapfd = -1;
|
|
}
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Close tap device.
|
|
*/
|
|
int tap_close(struct net_device *dev){
|
|
int err = 0;
|
|
|
|
if(dev->tapfd >= 0){
|
|
err = close(dev->tapfd);
|
|
dev->tapfd = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/** Open vnif tap device for a vnet.
|
|
*/
|
|
int vnet_dev_add(struct Vnet *vnet){
|
|
int err = 0;
|
|
struct net_device *dev = ALLOCATE(struct net_device);
|
|
strcpy(dev->name, vnet->device);
|
|
err = tap_open(dev);
|
|
if(err){
|
|
wprintf("> Unable to open tap device.\n"
|
|
"The tun module must be loaded and\n"
|
|
"the vnet kernel module must not be loaded.\n");
|
|
deallocate(dev);
|
|
goto exit;
|
|
}
|
|
vnet->dev = dev;
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/** Close vnif tap device for a vnet.
|
|
*/
|
|
void vnet_dev_remove(struct Vnet *vnet){
|
|
if(vnet->dev){
|
|
tap_close(vnet->dev);
|
|
deallocate(vnet->dev);
|
|
vnet->dev = NULL;
|
|
}
|
|
}
|
|
|
|
/** Receive decapsulated ethernet packet on skb->dev.
|
|
* Always succeeds. The skb must not be referred to after
|
|
* this is called.
|
|
*/
|
|
int netif_rx(struct sk_buff *skb){
|
|
int err = 0, n, k;
|
|
struct net_device *dev = skb->dev;
|
|
if(!dev){
|
|
err = -ENODEV;
|
|
goto exit;
|
|
}
|
|
n = skb->tail - skb->mac.raw;
|
|
k = write(dev->tapfd, skb->mac.raw, n);
|
|
if(k < 0){
|
|
err = -errno;
|
|
perror("write");
|
|
} else if(k < n){
|
|
//todo: What?
|
|
}
|
|
exit:
|
|
kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
static const int SKB_SIZE = 1700;
|
|
|
|
struct sk_buff *skb_new(void){
|
|
return alloc_skb(SKB_SIZE, GFP_ATOMIC);
|
|
}
|
|
|
|
/** Receive a packet and fill-in source and destination addresses.
|
|
* Just like recvfrom() but adds the destination address.
|
|
* The socket must have the IP_PKTINFO option set so that the
|
|
* destination address information is available.
|
|
*
|
|
* @param sock socket
|
|
* @param buf receive buffer
|
|
* @param len size of buffer
|
|
* @param flags receive flags
|
|
* @param from source address
|
|
* @param fromlen size of source address
|
|
* @param dest destination address
|
|
* @param destlen size of destination address
|
|
* @return number of bytes read on success, negative otherwise
|
|
*/
|
|
int recvfromdest(int sock, void *buf, size_t len, int flags,
|
|
struct sockaddr *from, socklen_t *fromlen,
|
|
struct sockaddr *dest, socklen_t *destlen){
|
|
int ret = 0;
|
|
struct iovec iov;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
char cbuf[1024];
|
|
struct in_pktinfo *info;
|
|
struct sockaddr_in *dest_in = (struct sockaddr_in *)dest;
|
|
|
|
//dest_in->sin_family = AF_INET;
|
|
//dest_in->sin_port = 0;
|
|
getsockname(sock, dest, destlen);
|
|
|
|
iov.iov_base = buf;
|
|
iov.iov_len = len;
|
|
msg.msg_name = from;
|
|
msg.msg_namelen = *fromlen;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cbuf;
|
|
msg.msg_controllen = sizeof(cbuf);
|
|
|
|
ret = recvmsg(sock, &msg, flags);
|
|
if(ret < 0) goto exit;
|
|
*fromlen = msg.msg_namelen;
|
|
|
|
for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)){
|
|
if((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)){
|
|
info = (void*)CMSG_DATA(cmsg);
|
|
dest_in->sin_addr = info->ipi_addr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
/** Read an skb from a udp socket and fill in its headers.
|
|
*/
|
|
int skb_recv_udp(int sock, int flags,
|
|
struct sockaddr_in *peer, socklen_t *peer_n,
|
|
struct sockaddr_in *dest, socklen_t *dest_n,
|
|
struct sk_buff **pskb){
|
|
int err = 0, n;
|
|
struct sk_buff *skb = skb_new();
|
|
|
|
skb->mac.raw = skb->data;
|
|
skb_reserve(skb, ETH_HLEN);
|
|
skb->nh.raw = skb->data;
|
|
skb_reserve(skb, sizeof(struct iphdr));
|
|
// Rcvr wants skb->data pointing at the udphdr.
|
|
skb->h.raw = skb_put(skb, sizeof(struct udphdr));
|
|
n = recvfromdest(sock, skb->tail, skb_tailroom(skb), flags,
|
|
(struct sockaddr *)peer, peer_n,
|
|
(struct sockaddr *)dest, dest_n);
|
|
if(n < 0){
|
|
err = -errno;
|
|
//perror("recvfrom");
|
|
goto exit;
|
|
}
|
|
dprintf("> peer=%s:%d\n", inet_ntoa(peer->sin_addr), ntohs(peer->sin_port));
|
|
dprintf("> dest=%s:%d\n", inet_ntoa(dest->sin_addr), ntohs(dest->sin_port));
|
|
skb_put(skb, n);
|
|
skb->protocol = skb->nh.iph->protocol = IPPROTO_UDP;
|
|
skb->nh.iph->saddr = peer->sin_addr.s_addr;
|
|
skb->h.uh->source = peer->sin_port;
|
|
skb->nh.iph->daddr = dest->sin_addr.s_addr;
|
|
skb->h.uh->dest = dest->sin_port;
|
|
exit:
|
|
if(err < 0){
|
|
kfree_skb(skb);
|
|
*pskb = NULL;
|
|
} else {
|
|
*pskb = skb;
|
|
}
|
|
return (err < 0 ? err : n);
|
|
}
|
|
|
|
/** Read an skb fom a raw socket and fill in its headers.
|
|
*/
|
|
int skb_recv_raw(int sock, int flags,
|
|
struct sockaddr_in *peer, socklen_t *peer_n,
|
|
struct sockaddr_in *dest, socklen_t *dest_n,
|
|
struct sk_buff **pskb){
|
|
int err = 0, n;
|
|
struct sk_buff *skb = skb_new();
|
|
|
|
skb->mac.raw = skb->data;
|
|
skb_reserve(skb, ETH_HLEN);
|
|
skb->nh.raw = skb->data;
|
|
skb_reserve(skb, sizeof(struct iphdr));
|
|
// Rcvr wants skb->data pointing after ip hdr, at raw protocol hdr.
|
|
n = recvfromdest(sock, skb->tail, skb_tailroom(skb), flags,
|
|
(struct sockaddr *)peer, peer_n,
|
|
(struct sockaddr *)dest, dest_n);
|
|
if(n < 0){
|
|
err = -errno;
|
|
//perror("recvfrom");
|
|
goto exit;
|
|
}
|
|
skb_put(skb, n);
|
|
// On a raw socket the port in the address is the protocol.
|
|
skb->protocol = skb->nh.iph->protocol = peer->sin_port;
|
|
skb->nh.iph->saddr = peer->sin_addr.s_addr;
|
|
skb->nh.iph->daddr = dest->sin_addr.s_addr;
|
|
exit:
|
|
if(err < 0){
|
|
kfree_skb(skb);
|
|
*pskb = NULL;
|
|
} else {
|
|
*pskb = skb;
|
|
}
|
|
return (err < 0 ? err : n);
|
|
}
|
|
|
|
/** Read an skb from a file descriptor.
|
|
* Used for skbs coming to us from the tap device.
|
|
* The skb content is an ethernet frame.
|
|
*/
|
|
int skb_read(int fd, struct sk_buff **pskb){
|
|
int err = 0, n;
|
|
struct sk_buff *skb = skb_new();
|
|
|
|
// Reserve space for the headers we will add.
|
|
skb_reserve(skb, 100);
|
|
// Rcvr will want ethhdr on the skb.
|
|
skb->mac.raw = skb->tail;
|
|
n = read(fd, skb->tail, skb_tailroom(skb));
|
|
if(n < 0){
|
|
err = -errno;
|
|
//perror("read");
|
|
goto exit;
|
|
}
|
|
skb_put(skb, n);
|
|
exit:
|
|
if(err < 0){
|
|
kfree_skb(skb);
|
|
*pskb = NULL;
|
|
} else {
|
|
*pskb = skb;
|
|
}
|
|
return (err < 0 ? err : n);
|
|
}
|
|
|
|
/** Read an skb from the tap device for a vnet and send it.
|
|
*/
|
|
int vnet_read(Vnet *vnet){
|
|
int err;
|
|
struct sk_buff *skb = NULL;
|
|
|
|
err = skb_read(vnet->dev->tapfd, &skb);
|
|
if(err < 0) goto exit;
|
|
err = vnet_skb_send(skb, &vnet->vnet);
|
|
exit:
|
|
if(skb) kfree_skb(skb);
|
|
return (err < 0 ? err : 0);
|
|
}
|
|
|
|
/** Transmit an skb to the network.
|
|
*/
|
|
int _skb_xmit(struct sk_buff *skb, uint32_t saddr){
|
|
int err = 0;
|
|
int sock;
|
|
unsigned char *data;
|
|
struct sockaddr_in addr = { .sin_family = AF_INET };
|
|
int flags = 0;
|
|
|
|
if(saddr){
|
|
dprintf("> Raw IP send\n");
|
|
sock = vnetd->raw_sock;
|
|
skb->nh.iph->saddr = saddr;
|
|
addr.sin_addr.s_addr = skb->nh.iph->daddr;
|
|
// Should be the protocol, but is ignored. See raw(7) man page.
|
|
addr.sin_port = 0;
|
|
// Data includes the ip header.
|
|
data = (void*)(skb->nh.iph);
|
|
} else {
|
|
switch(skb->nh.iph->protocol){
|
|
case IPPROTO_UDP:
|
|
dprintf("> protocol=UDP\n");
|
|
sock = vnetd->udp_sock;
|
|
// Data comes after the udp header.
|
|
data = (void*)(skb->h.uh + 1);
|
|
addr.sin_addr.s_addr = skb->nh.iph->daddr;
|
|
addr.sin_port = skb->h.uh->dest;
|
|
break;
|
|
case IPPROTO_ETHERIP:
|
|
dprintf("> protocol=ETHERIP\n");
|
|
if(vnetd->etherip_sock < 0){
|
|
err = -ENOSYS;
|
|
goto exit;
|
|
}
|
|
sock = vnetd->etherip_sock;
|
|
// Data comes after the ip header.
|
|
data = (void*)(skb->nh.iph + 1);
|
|
addr.sin_addr.s_addr = skb->nh.iph->daddr;
|
|
// Should be the protocol, but is ignored. See raw(7) man page.
|
|
addr.sin_port = 0;
|
|
break;
|
|
default:
|
|
err = -ENOSYS;
|
|
wprintf("> protocol=%d, %d\n", skb->nh.iph->protocol, skb->protocol);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
dprintf("> sending %d bytes to %s:%d protocol=%d\n",
|
|
skb->tail - data,
|
|
inet_ntoa(addr.sin_addr),
|
|
ntohs(addr.sin_port),
|
|
skb->nh.iph->protocol);
|
|
|
|
err = sendto(sock, data, skb->tail - data, flags,
|
|
(struct sockaddr *)&addr, sizeof(addr));
|
|
if(err < 0){
|
|
err = -errno;
|
|
perror("sendto");
|
|
}
|
|
exit:
|
|
if(err >= 0){
|
|
// Caller will assume skb freed if no error.
|
|
kfree_skb(skb);
|
|
err = 0;
|
|
}
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
int varp_open(uint32_t mcaddr, uint16_t port){
|
|
return 0;
|
|
}
|
|
|
|
void varp_close(void){
|
|
}
|
|
|
|
/** Create a raw socket.
|
|
*
|
|
* @param protocol protocol
|
|
* @param flags flags (VSOCK_*)
|
|
* @param mcaddr multicast addr used with flag VSOCK_MULTICAST
|
|
* @param sock return value for the socket
|
|
*/
|
|
int vnetd_raw_socket(Vnetd *vnetd, int protocol, int flags,
|
|
uint32_t mcaddr, int *sock){
|
|
int err;
|
|
int bcast = (flags & VSOCK_BROADCAST);
|
|
|
|
err = *sock = socket(AF_INET, SOCK_RAW, protocol);
|
|
if(err < 0){
|
|
err = -errno;
|
|
perror("socket");
|
|
goto exit;
|
|
}
|
|
if(bcast){
|
|
err = setsock_broadcast(*sock, bcast);
|
|
if(err < 0) goto exit;
|
|
}
|
|
if(flags & VSOCK_MULTICAST){
|
|
err = setsock_multicast(*sock, INADDR_ANY, mcaddr);
|
|
if(err < 0) goto exit;
|
|
}
|
|
//todo ?? fcntl(*sock, F_SETFL, O_NONBLOCK);
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
int get_dev_address(char *dev, unsigned long *addr){
|
|
int err = 0;
|
|
int sock = -1;
|
|
struct ifreq ifreq = {};
|
|
struct sockaddr_in *in_addr;
|
|
|
|
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if(sock < 0){
|
|
err = -errno;
|
|
goto exit;
|
|
}
|
|
strncpy(ifreq.ifr_name, dev, IFNAMSIZ);
|
|
err = ioctl(sock, SIOCGIFADDR, &ifreq);
|
|
if(err){
|
|
err = -errno;
|
|
goto exit;
|
|
}
|
|
in_addr = (struct sockaddr_in *) &ifreq.ifr_addr;
|
|
*addr = in_addr->sin_addr.s_addr;
|
|
//iprintf("> dev=%s addr=%s\n", dev, inet_ntoa(in_addr->sin_addr));
|
|
exit:
|
|
if(sock >= 0) close(sock);
|
|
return err;
|
|
}
|
|
|
|
int get_intf_address(unsigned long *addr){
|
|
int err = 0;
|
|
char *devs[] = {"cloudbr0", "eth0", "eth1", "eth2", "wlan0", "wlan1", NULL};
|
|
char **dev;
|
|
|
|
for(dev = devs; *dev; dev++){
|
|
err = get_dev_address(*dev, addr);
|
|
if(err == 0) goto exit;
|
|
}
|
|
err = -ENOSYS;
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/** Get our own address. So we can ignore broadcast traffic
|
|
* we sent ourselves.
|
|
*
|
|
* @param addr
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int get_self_addr(struct sockaddr_in *addr){
|
|
int err = 0;
|
|
unsigned long saddr;
|
|
|
|
err = get_intf_address(&saddr);
|
|
if(err) goto exit;
|
|
addr->sin_addr.s_addr = saddr;
|
|
err = 0;
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int eval_vnetd_mcaddr(Sxpr exp, IOStream *out, void *data){
|
|
int err = 0;
|
|
Vnetd *vnetd = data;
|
|
Sxpr oaddr = intern("addr");
|
|
Sxpr ottl = intern("ttl");
|
|
uint32_t addr;
|
|
int ttl;
|
|
|
|
err = child_addr(exp, oaddr, &addr);
|
|
if(err < 0) goto exit;
|
|
vnetd_set_mcast_addr(vnetd, addr);
|
|
if(child_int(exp, ottl, &ttl) == 0){
|
|
vnetd->ttl = ttl;
|
|
}
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_eval_io(Vnetd *vnetd, Parser *parser, SxprEval *defs,
|
|
IOStream *in, IOStream *out){
|
|
int err = 0;
|
|
char buf[1024];
|
|
int k, n = sizeof(buf) - 1;
|
|
|
|
for( ; ; ){
|
|
k = IOStream_read(in, buf, n);
|
|
if(k < 0){
|
|
err = k;
|
|
goto exit;
|
|
}
|
|
err = Parser_input(parser, buf, k);
|
|
if(err < 0) goto exit;
|
|
while(Parser_ready(parser)){
|
|
Sxpr exp = Parser_get_val(parser);
|
|
if(NONEP(exp)) break;
|
|
err = vnet_eval_defs(defs, exp, out, vnetd);
|
|
if(err) goto exit;
|
|
}
|
|
if(Parser_at_eof(parser)) break;
|
|
}
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_configure(Vnetd *vnetd, char *file){
|
|
int err = 0;
|
|
Parser *parser = NULL;
|
|
IOStream *io = NULL;
|
|
SxprEval defs[] = {
|
|
{ .name = intern("peer.add"), .fn = eval_peer_add },
|
|
{ .name = intern("varp.mcaddr"), .fn = eval_vnetd_mcaddr },
|
|
{ .name = intern("vnet.add"), .fn = eval_vnet_add },
|
|
{ .name = ONONE, .fn = NULL } };
|
|
|
|
parser = Parser_new();
|
|
io = file_stream_fopen(file, "rb");
|
|
if(!io){
|
|
err = -errno;
|
|
goto exit;
|
|
}
|
|
vnetd_eval_io(vnetd, parser, defs, io, iostdout);
|
|
exit:
|
|
if(io) IOStream_close(io);
|
|
Parser_free(parser);
|
|
return err;
|
|
}
|
|
|
|
#define OPT_MCADDR 'a'
|
|
#define KEY_MCADDR "varp_mcaddr"
|
|
#define DOC_MCADDR "<addr>\n\t VARP multicast address"
|
|
|
|
#define OPT_FILE 'f'
|
|
#define KEY_FILE "file"
|
|
#define DOC_FILE "<file>\n\t Configuration file to load"
|
|
|
|
#define OPT_HELP 'h'
|
|
#define KEY_HELP "help"
|
|
#define DOC_HELP "\n\tprint help"
|
|
|
|
#define OPT_VERSION 'v'
|
|
#define KEY_VERSION "version"
|
|
#define DOC_VERSION "\n\tprint version"
|
|
|
|
#define OPT_VERBOSE 'V'
|
|
#define KEY_VERBOSE "verbose"
|
|
#define DOC_VERBOSE "\n\tverbose flag"
|
|
|
|
/** Print a usage message.
|
|
* Prints to stdout if err is zero, and exits with 0.
|
|
* Prints to stderr if err is non-zero, and exits with 1.
|
|
*
|
|
* @param err error code
|
|
*/
|
|
static void usage(int err){
|
|
FILE *out = (err ? stderr : stdout);
|
|
|
|
fprintf(out, "Usage: %s [options]\n", PROGRAM);
|
|
fprintf(out, "-%c, --%s %s\n", OPT_MCADDR, KEY_MCADDR, DOC_MCADDR);
|
|
fprintf(out, "-%c, --%s %s\n", OPT_FILE, KEY_FILE, DOC_FILE);
|
|
fprintf(out, "-%c, --%s %s\n", OPT_VERBOSE, KEY_VERBOSE, DOC_VERBOSE);
|
|
fprintf(out, "-%c, --%s %s\n", OPT_VERSION, KEY_VERSION, DOC_VERSION);
|
|
fprintf(out, "-%c, --%s %s\n", OPT_HELP, KEY_HELP, DOC_HELP);
|
|
exit(err ? 1 : 0);
|
|
}
|
|
|
|
/** Short options. Options followed by ':' take an argument. */
|
|
static char *short_opts = (char[]){
|
|
OPT_MCADDR, ':',
|
|
OPT_FILE, ':',
|
|
OPT_HELP,
|
|
OPT_VERSION,
|
|
OPT_VERBOSE,
|
|
0 };
|
|
|
|
/** Long options. */
|
|
static struct option const long_opts[] = {
|
|
{ KEY_MCADDR, required_argument, NULL, OPT_MCADDR },
|
|
{ KEY_FILE, required_argument, NULL, OPT_FILE },
|
|
{ KEY_HELP, no_argument, NULL, OPT_HELP },
|
|
{ KEY_VERSION, no_argument, NULL, OPT_VERSION },
|
|
{ KEY_VERBOSE, no_argument, NULL, OPT_VERBOSE },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
static int vnetd_getopts(Vnetd *vnetd, int argc, char *argv[]){
|
|
int err = 0;
|
|
int key = 0;
|
|
int long_index = 0;
|
|
|
|
while(1){
|
|
key = getopt_long(argc, argv, short_opts, long_opts, &long_index);
|
|
if(key == -1) break;
|
|
switch(key){
|
|
case OPT_MCADDR: {
|
|
unsigned long addr;
|
|
err = get_inet_addr(optarg, &addr);
|
|
if(err) goto exit;
|
|
vnetd_set_mcast_addr(vnetd, addr);
|
|
break; }
|
|
case OPT_FILE:
|
|
err = vnetd_configure(vnetd, optarg);
|
|
if(err) goto exit;
|
|
break;
|
|
case OPT_HELP:
|
|
usage(0);
|
|
break;
|
|
case OPT_VERBOSE:
|
|
vnetd->verbose = true;
|
|
break;
|
|
case OPT_VERSION:
|
|
iprintf("> %s %s\n", PROGRAM, VERSION);
|
|
exit(0);
|
|
break;
|
|
default:
|
|
usage(EINVAL);
|
|
break;
|
|
}
|
|
}
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/** Initialise vnetd params.
|
|
*
|
|
* @param vnetd vnetd
|
|
*/
|
|
static int vnetd_init(Vnetd *vnetd, int argc, char *argv[]){
|
|
int err = 0;
|
|
|
|
// Use etherip-in-udp encapsulation.
|
|
etherip_in_udp = true;
|
|
|
|
*vnetd = (Vnetd){};
|
|
vnetd->port = htons(VARP_PORT);
|
|
vnetd->verbose = false;
|
|
vnetd->ttl = 1; // Default multicast ttl.
|
|
vnetd->etherip = true;
|
|
vnetd->udp_sock = -1;
|
|
vnetd->mcast_sock = -1;
|
|
vnetd->etherip_sock = -1;
|
|
vnetd_set_mcast_addr(vnetd, htonl(VARP_MCAST_ADDR));
|
|
vnetd->mcast_addr.sin_port = vnetd->port;
|
|
vnetd->unix_path = "/tmp/vnetd";
|
|
|
|
vnetd_getopts(vnetd, argc, argv);
|
|
|
|
err = get_self_addr(&vnetd->ucast_addr);
|
|
vnetd->ucast_addr.sin_port = vnetd->port;
|
|
dprintf("> mcaddr=%s\n", inet_ntoa(vnetd->mcast_addr.sin_addr));
|
|
dprintf("> addr =%s\n", inet_ntoa(vnetd->ucast_addr.sin_addr));
|
|
return err;
|
|
}
|
|
|
|
static void vnet_select(Vnetd *vnetd, SelectSet *set){
|
|
HashTable_for_decl(entry);
|
|
|
|
HashTable_for_each(entry, vnetd->vnet_table){
|
|
Vnet *vnet = entry->value;
|
|
struct net_device *dev = vnet->dev;
|
|
if(!dev) continue;
|
|
if(dev->tapfd < 0) continue;
|
|
SelectSet_add(set, dev->tapfd, SELECT_READ);
|
|
}
|
|
}
|
|
|
|
static void vnet_handle(Vnetd *vnetd, SelectSet *set){
|
|
HashTable_for_decl(entry);
|
|
|
|
HashTable_for_each(entry, vnetd->vnet_table){
|
|
Vnet *vnet = entry->value;
|
|
struct net_device *dev = vnet->dev;
|
|
if(!dev) continue;
|
|
if(dev->tapfd < 0) continue;
|
|
if(SelectSet_in_read(set, dev->tapfd)){
|
|
int n;
|
|
for(n = 64; n > 0; --n){
|
|
if(vnet_read(vnet) < 0) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int vnetd_handle_udp(Vnetd *vnetd, struct sockaddr_in *addr, int sock){
|
|
int err = 0, n = 0;
|
|
struct sockaddr_in peer, dest;
|
|
socklen_t peer_n = sizeof(peer), dest_n = sizeof(dest);
|
|
int flags = MSG_DONTWAIT;
|
|
struct sk_buff *skb = NULL;
|
|
|
|
dest = *addr;
|
|
n = skb_recv_udp(sock, flags, &peer, &peer_n, &dest, &dest_n, &skb);
|
|
if(n < 0){
|
|
err = n;
|
|
goto exit;
|
|
}
|
|
dprintf("> Received %d bytes from=%s:%d dest=%s:%d\n",
|
|
n,
|
|
inet_ntoa(peer.sin_addr), htons(peer.sin_port),
|
|
inet_ntoa(dest.sin_addr), htons(dest.sin_port));
|
|
if(peer.sin_addr.s_addr == vnetd_intf_addr(vnetd)){
|
|
dprintf("> Ignoring message from self.\n");
|
|
goto exit;
|
|
}
|
|
if(dest.sin_addr.s_addr == vnetd_mcast_addr(vnetd)){
|
|
vnet_forward_send(skb);
|
|
}
|
|
err = varp_handle_message(skb);
|
|
|
|
exit:
|
|
if(skb) kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_handle_etherip(Vnetd *vnetd, struct sockaddr_in *addr, int sock){
|
|
int err = 0, n = 0;
|
|
struct sockaddr_in peer, dest;
|
|
socklen_t peer_n = sizeof(peer), dest_n = sizeof(dest);
|
|
int flags = 0;
|
|
struct sk_buff *skb = NULL;
|
|
|
|
dest = *addr;
|
|
n = skb_recv_raw(sock, flags, &peer, &peer_n, &dest, &dest_n, &skb);
|
|
if(n < 0){
|
|
err = n;
|
|
goto exit;
|
|
}
|
|
dprintf("> Received %d bytes from=%s:%d dest=%s:%d\n",
|
|
n,
|
|
inet_ntoa(peer.sin_addr), htons(peer.sin_port),
|
|
inet_ntoa(dest.sin_addr), htons(dest.sin_port));
|
|
if(peer.sin_addr.s_addr == vnetd_intf_addr(vnetd)){
|
|
dprintf("> Ignoring message from self.\n");
|
|
goto exit;
|
|
}
|
|
err = etherip_protocol_recv(skb);
|
|
exit:
|
|
if(skb) kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
typedef struct ConnClient {
|
|
Vnetd *vnetd;
|
|
Parser *parser;
|
|
} ConnClient;
|
|
|
|
static int conn_handle_fn(Conn *conn, int mode){
|
|
int err;
|
|
ConnClient *client = conn->data;
|
|
char data[1024] = {};
|
|
int k;
|
|
int done = false;
|
|
|
|
k = IOStream_read(conn->in, data, sizeof(data));
|
|
if(k < 0){
|
|
err = k;
|
|
goto exit;
|
|
}
|
|
if(!client->parser){
|
|
err = -ENOSYS;
|
|
goto exit;
|
|
}
|
|
if((k == 0) && Parser_at_eof(client->parser)){
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = Parser_input(client->parser, data, k);
|
|
if(err < 0) goto exit;
|
|
while(Parser_ready(client->parser)){
|
|
Sxpr sxpr = Parser_get_val(client->parser);
|
|
err = vnet_eval(sxpr, conn->out, NULL);
|
|
if(err) goto exit;
|
|
done = true;
|
|
}
|
|
if(done || Parser_at_eof(client->parser)){
|
|
// Close at EOF.
|
|
err = -EIO;
|
|
}
|
|
exit:
|
|
if(err < 0){
|
|
Parser_free(client->parser);
|
|
client->parser = NULL;
|
|
}
|
|
return (err < 0 ? err : 0);
|
|
}
|
|
|
|
static int vnetd_handle_unix(Vnetd *vnetd, int sock){
|
|
int err;
|
|
ConnClient *client = NULL;
|
|
Conn *conn = NULL;
|
|
struct sockaddr_un peer = {};
|
|
socklen_t peer_n = sizeof(peer);
|
|
int peersock;
|
|
|
|
peersock = accept(sock, (struct sockaddr *)&peer, &peer_n);
|
|
if(peersock < 0){
|
|
perror("accept");
|
|
err = -errno;
|
|
goto exit;
|
|
}
|
|
// We want non-blocking i/o.
|
|
fcntl(peersock, F_SETFL, O_NONBLOCK);
|
|
client = ALLOCATE(ConnClient);
|
|
client->vnetd = vnetd;
|
|
client->parser = Parser_new();
|
|
conn = Conn_new(conn_handle_fn, client);
|
|
err = Conn_init(conn, peersock, SOCK_STREAM, SELECT_READ,
|
|
(struct sockaddr_in){});
|
|
if(err) goto exit;
|
|
vnetd->conns = ConnList_add(vnetd->conns, conn);
|
|
exit:
|
|
if(err){
|
|
Conn_close(conn);
|
|
close(peersock);
|
|
}
|
|
if(err < 0) wprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
static void vnetd_select(Vnetd *vnetd, SelectSet *set){
|
|
SelectSet_add(set, vnetd->unix_sock, SELECT_READ);
|
|
SelectSet_add(set, vnetd->udp_sock, SELECT_READ);
|
|
SelectSet_add(set, vnetd->mcast_sock, SELECT_READ);
|
|
if(vnetd->etherip_sock >= 0){
|
|
SelectSet_add(set, vnetd->etherip_sock, SELECT_READ);
|
|
}
|
|
vnet_select(vnetd, set);
|
|
ConnList_select(vnetd->conns, set);
|
|
}
|
|
|
|
static void vnetd_handle(Vnetd *vnetd, SelectSet *set){
|
|
if(SelectSet_in_read(set, vnetd->unix_sock)){
|
|
vnetd_handle_unix(vnetd, vnetd->unix_sock);
|
|
}
|
|
if(SelectSet_in_read(set, vnetd->udp_sock)){
|
|
int n;
|
|
|
|
for(n = 256; n > 0; --n){
|
|
if(vnetd_handle_udp(vnetd, &vnetd->udp_sock_addr, vnetd->udp_sock) < 0){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(SelectSet_in_read(set, vnetd->mcast_sock)){
|
|
vnetd_handle_udp(vnetd, &vnetd->mcast_sock_addr, vnetd->mcast_sock);
|
|
}
|
|
if((vnetd->etherip_sock >= 0) &&
|
|
SelectSet_in_read(set, vnetd->etherip_sock)){
|
|
vnetd_handle_etherip(vnetd, &vnetd->etherip_sock_addr, vnetd->etherip_sock);
|
|
}
|
|
vnet_handle(vnetd, set);
|
|
vnetd->conns = ConnList_handle(vnetd->conns, set);
|
|
}
|
|
|
|
/** Counter for timer alarms.
|
|
*/
|
|
static unsigned timer_alarms = 0;
|
|
|
|
static int vnetd_main(Vnetd *vnetd){
|
|
int err = 0;
|
|
SelectSet _set = {}, *set = &_set;
|
|
struct timeval _timeout = {}, *timeout = &_timeout;
|
|
|
|
vnetd->vnet_table = vnet_table;
|
|
|
|
for( ; ; ){
|
|
timeout->tv_sec = 0;
|
|
timeout->tv_usec = 500000;
|
|
SelectSet_zero(set);
|
|
vnetd_select(vnetd, set);
|
|
err = SelectSet_select(set, timeout);
|
|
if(err == 0) continue;
|
|
if(err < 0){
|
|
switch(errno){
|
|
case EINTR:
|
|
if(timer_alarms){
|
|
timer_alarms = 0;
|
|
process_timers();
|
|
}
|
|
continue;
|
|
case EBADF:
|
|
continue;
|
|
default:
|
|
perror("select");
|
|
goto exit;
|
|
}
|
|
}
|
|
vnetd_handle(vnetd, set);
|
|
}
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int getsockaddr(int sock, struct sockaddr_in *addr){
|
|
socklen_t addr_n = sizeof(struct sockaddr_in);
|
|
return getsockname(sock, (struct sockaddr*)addr, &addr_n);
|
|
}
|
|
|
|
static int vnetd_etherip_sock(Vnetd *vnetd){
|
|
int err = 0;
|
|
|
|
if(!vnetd->etherip) goto exit;
|
|
err = vnetd_raw_socket(vnetd, IPPROTO_ETHERIP,
|
|
(VSOCK_BROADCAST | VSOCK_MULTICAST),
|
|
vnetd_mcast_addr(vnetd),
|
|
&vnetd->etherip_sock);
|
|
if(err < 0) goto exit;
|
|
err = setsock_pktinfo(vnetd->etherip_sock, true);
|
|
if(err < 0) goto exit;
|
|
getsockaddr(vnetd->etherip_sock, &vnetd->etherip_sock_addr);
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_udp_sock(Vnetd *vnetd){
|
|
int err;
|
|
uint32_t mcaddr = vnetd_mcast_addr(vnetd);
|
|
|
|
err = create_socket(SOCK_DGRAM, INADDR_ANY, vnetd->port,
|
|
(VSOCK_BIND | VSOCK_REUSE),
|
|
&vnetd->udp_sock);
|
|
if(err < 0) goto exit;
|
|
err = setsock_pktinfo(vnetd->udp_sock, true);
|
|
if(err < 0) goto exit;
|
|
getsockaddr(vnetd->udp_sock, &vnetd->udp_sock_addr);
|
|
vnetd->mcast_sock_addr.sin_addr.s_addr = vnetd_intf_addr(vnetd);
|
|
|
|
err = create_socket(SOCK_DGRAM, mcaddr, vnetd_mcast_port(vnetd),
|
|
(VSOCK_REUSE | VSOCK_BROADCAST | VSOCK_MULTICAST),
|
|
&vnetd->mcast_sock);
|
|
if(err < 0) goto exit;
|
|
err = setsock_pktinfo(vnetd->udp_sock, true);
|
|
if(err < 0) goto exit;
|
|
err = setsock_multicast(vnetd->mcast_sock, INADDR_ANY, mcaddr);
|
|
if(err < 0) goto exit;
|
|
err = setsock_multicast_ttl(vnetd->mcast_sock, vnetd->ttl);
|
|
if(err < 0) goto exit;
|
|
getsockaddr(vnetd->mcast_sock, &vnetd->mcast_sock_addr);
|
|
vnetd->mcast_sock_addr.sin_addr.s_addr = mcaddr;
|
|
|
|
exit:
|
|
if(err < 0){
|
|
close(vnetd->udp_sock);
|
|
close(vnetd->mcast_sock);
|
|
vnetd->udp_sock = -1;
|
|
vnetd->mcast_sock = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_raw_sock(Vnetd *vnetd){
|
|
int err;
|
|
|
|
err = vnetd_raw_socket(vnetd, IPPROTO_RAW,
|
|
(VSOCK_BROADCAST),
|
|
vnetd_mcast_addr(vnetd),
|
|
&vnetd->raw_sock);
|
|
if(err){
|
|
close(vnetd->raw_sock);
|
|
vnetd->raw_sock = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int vnetd_unix_sock(Vnetd *vnetd){
|
|
int err = 0;
|
|
struct sockaddr_un addr = { .sun_family = AF_UNIX };
|
|
socklen_t addr_n;
|
|
|
|
vnetd->unix_sock = socket(addr.sun_family, SOCK_STREAM, 0);
|
|
if(vnetd->unix_sock < 0){
|
|
err = -errno;
|
|
perror("unix socket");
|
|
goto exit;
|
|
}
|
|
unlink(vnetd->unix_path);
|
|
strcpy(addr.sun_path, vnetd->unix_path);
|
|
addr_n = sizeof(addr) - sizeof(addr.sun_path) + strlen(vnetd->unix_path) + 1;
|
|
err = bind(vnetd->unix_sock, (struct sockaddr *)&addr, addr_n);
|
|
if(err < 0){
|
|
err = -errno;
|
|
perror("unix bind");
|
|
goto exit;
|
|
}
|
|
err = listen(vnetd->unix_sock, 5);
|
|
if(err < 0){
|
|
err = -errno;
|
|
perror("unix listen");
|
|
}
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/** Handle SIGPIPE.
|
|
*
|
|
* @param code signal code
|
|
* @param info signal info
|
|
* @param data
|
|
*/
|
|
static void sigaction_SIGPIPE(int code, siginfo_t *info, void *data){
|
|
dprintf("> SIGPIPE\n");
|
|
}
|
|
|
|
/** Handle SIGALRM.
|
|
*
|
|
* @param code signal code
|
|
* @param info signal info
|
|
* @param data
|
|
*/
|
|
static void sigaction_SIGALRM(int code, siginfo_t *info, void *data){
|
|
timer_alarms++;
|
|
}
|
|
|
|
/** Type for signal handling functions. */
|
|
typedef void SignalAction(int code, siginfo_t *info, void *data);
|
|
|
|
/** Install a handler for a signal.
|
|
*
|
|
* @param signum signal
|
|
* @param action handler
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
static int catch_signal(int signum, SignalAction *action){
|
|
int err = 0;
|
|
struct sigaction sig = {};
|
|
dprintf(">\n");
|
|
sig.sa_sigaction = action;
|
|
sig.sa_flags = SA_SIGINFO;
|
|
err = sigaction(signum, &sig, NULL);
|
|
if(err){
|
|
err = -errno;
|
|
perror("sigaction");
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int main(int argc, char *argv[]){
|
|
int err = 0;
|
|
|
|
err = tunnel_module_init();
|
|
if(err < 0) goto exit;
|
|
err = vnet_init();
|
|
if(err < 0) goto exit;
|
|
err = vnetd_init(vnetd, argc, argv);
|
|
if(err < 0) goto exit;
|
|
err = catch_signal(SIGPIPE, sigaction_SIGPIPE);
|
|
if(err < 0) goto exit;
|
|
err = catch_signal(SIGALRM, sigaction_SIGALRM);
|
|
if(err < 0) goto exit;
|
|
err = vnetd_etherip_sock(vnetd);
|
|
if(err < 0) goto exit;
|
|
err = vnetd_udp_sock(vnetd);
|
|
if(err < 0) goto exit;
|
|
err = vnetd_raw_sock(vnetd);
|
|
if(err < 0) goto exit;
|
|
err = vnetd_unix_sock(vnetd);
|
|
if(err < 0) goto exit;
|
|
err = vnetd_main(vnetd);
|
|
exit:
|
|
return (err ? 1 : 0);
|
|
}
|