/* Copyright 2001, 2002 Jeff Dike and others
* Copyright 2003 Renzo Davoli (modified for daemon and vde)
* Licensed under the GPL
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#ifndef __FreeBSD__
#include <stdint.h>
#endif
#include <getopt.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <unistd.h>
#include <syslog.h>
#include <libgen.h>
#ifdef __FreeBSD__
#include <string.h>
#else
#include <endian.h>
#endif
#include "vde.h"
#include "switch.h"
#include "port.h"
#include "hash.h"
#ifdef TUNTAP
#include "tuntap.h"
#endif
#ifdef notdef
#include <stddef.h>
#endif
#include <stdarg.h>
static int hub = 0;
static char *prog;
static int daemonize = 0;
static int logok = 0;
void printlog(int priority, const char *format, ...)
{
va_list arg;
va_start (arg, format);
if (logok)
vsyslog(priority,format,arg);
else {
fprintf(stderr,"%s: ",prog);
vfprintf(stderr,format,arg);
fprintf(stderr,"\n");
}
va_end (arg);
}
enum request_type { REQ_NEW_CONTROL };
#define SWITCH_MAGIC 0xfeedface
struct request_v1 {
uint32_t magic;
enum request_type type;
union {
struct {
unsigned char addr[ETH_ALEN];
struct sockaddr_un name;
} new_control;
} u;
};
struct request_v3 {
uint32_t magic;
uint32_t version;
enum request_type type;
struct sockaddr_un sock;
};
union request {
struct request_v1 v1;
struct request_v3 v3;
};
static char *ctl_socket = VDESTDSOCK;
static char *data_socket = NULL;
static struct sockaddr_un data_sun;
static void cleanup(int x,void* data)
{
if(unlink(ctl_socket) < 0){
printlog(LOG_WARNING,"Couldn't remove control socket '%s' : %s", ctl_socket, strerror(errno));
}
if((data_socket != NULL) && (unlink(data_socket) < 0)){
printlog(LOG_WARNING,"Couldn't remove data socket '%s' : %s", data_socket, strerror(errno));
}
}
#ifdef __FreeBSD__
static void cleanupwrapper()
{
cleanup(0, NULL);
}
#endif
void **g_fdsdata = NULL;
int g_nfds = 0;
int g_minfds = 0;
static struct pollfd *fds = NULL;
static int maxfds = 0;
static int nfds = 0;
static void add_fd(int fd)
{
struct pollfd *p;
if(nfds == maxfds){
maxfds = maxfds ? 2 * maxfds : 8;
if((fds = realloc(fds, maxfds * sizeof(struct pollfd))) == NULL){
printlog(LOG_ERR,"realloc fds %s",strerror(errno));
exit(1);
}
if((g_fdsdata = realloc(g_fdsdata, maxfds * sizeof(void *))) == NULL){
printlog(LOG_ERR,"realloc fdsdata %s",strerror(errno));
exit(1);
}
}
p = &fds[nfds];
p->fd = fd;
p->events = POLLIN;
g_fdsdata[nfds]=NULL;
nfds++;
g_nfds=nfds;
}
static void remove_fd(int fd)
{
int i;
for(i = 0; i < nfds; i++){
if(fds[i].fd == fd) break;
}
if(i == nfds){
printlog(LOG_WARNING,"remove_fd : Couldn't find descriptor %d", fd);
} else {
memmove(&fds[i], &fds[i + 1], (maxfds - i - 1) * sizeof(struct pollfd));
memmove(&g_fdsdata[i], &g_fdsdata[i + 1], (maxfds - i - 1) * sizeof(void *));
nfds--;
g_nfds=nfds;
}
}
static void sig_handler(int sig)
{
printlog(LOG_ERR,"Caught signal %d, cleaning up and exiting", sig);
cleanup(1,NULL);
signal(sig, SIG_DFL);
kill(getpid(), sig);
}
static void close_descriptor(int i, int fd)
{
close_port(i,fd);
close(fd);
remove_fd(fd);
}
static void new_port_v1_v3(int i, int fd, enum request_type type_group,
struct sockaddr_un *sock, int data_fd)
{
int n, err;
enum request_type type = type_group & 0xff;
int group=type_group >> 8;
// group
switch(type){
case REQ_NEW_CONTROL:
err = setup_sock_port(i, fd, sock, data_fd, group);
if(err) return;
n = write(fd, &data_sun, sizeof(data_sun));
if(n != sizeof(data_sun)){
printlog(LOG_WARNING,"Sending data socket name %s",strerror(errno));
close_descriptor(i, fd);
}
break;
default:
printlog(LOG_WARNING,"Bad request type : %d", type);
close_descriptor(i, fd);
}
}
static void new_port(int i, int fd, int data_fd)
{
union request req;
int len;
len = read(fd, &req, sizeof(req));
if(len < 0){
if(errno != EAGAIN){
printlog(LOG_WARNING,"Reading request %s", strerror(errno));
close_descriptor(i, fd);
}
return;
}
else if(len == 0){
printlog(LOG_WARNING,"EOF from new port");
close_descriptor(i, fd);
return;
}
if(req.v1.magic == SWITCH_MAGIC){
if(req.v3.version == 3)
new_port_v1_v3(i,fd, req.v3.type, &req.v3.sock, data_fd);
else if(req.v3.version > 2 || req.v3.version == 2)
printlog(LOG_ERR, "Request for a version %d port, which this "
"vde_switch doesn't support", req.v3.version);
else new_port_v1_v3(i, fd, req.v1.type, &req.v1.u.new_control.name, data_fd);
}
else {
printlog(LOG_WARNING,"V0 request not supported");
close_descriptor(i, fd);
return;
}
}
void accept_connection(int fd)
{
struct sockaddr addr;
int len, new;
len = sizeof(addr);
new = accept(fd, &addr, &len);
if(new < 0){
printlog(LOG_WARNING,"accept %s",strerror(errno));
return;
}
if(fcntl(new, F_SETFL, O_NONBLOCK) < 0){
printlog(LOG_WARNING,"fcntl - setting O_NONBLOCK %s",strerror(errno));
close(new);
return;
}
add_fd(new);
}
int still_used(struct sockaddr_un *sun)
{
int test_fd, ret = 1;
if((test_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0){
printlog(LOG_ERR,"socket %s",strerror(errno));
exit(1);
}
if(connect(test_fd, (struct sockaddr *) sun, sizeof(*sun)) < 0){
if(errno == ECONNREFUSED){
if(unlink(sun->sun_path) < 0){
printlog(LOG_ERR,"Failed to removed unused socket '%s': %s",
sun->sun_path,strerror(errno));
}
ret = 0;
}
else printlog(LOG_ERR,"connect %s",strerror(errno));
}
close(test_fd);
return(ret);
}
int bind_socket(int fd, const char *name, struct sockaddr_un *sock_out)
{
struct sockaddr_un sun;
sun.sun_family = AF_UNIX;
strncpy(sun.sun_path, name, sizeof(sun.sun_path));
if(bind(fd, (struct sockaddr *) &sun, sizeof(sun)) < 0){
if((errno == EADDRINUSE) && still_used(&sun)) return(EADDRINUSE);
else if(bind(fd, (struct sockaddr *) &sun, sizeof(sun)) < 0){
printlog(LOG_ERR,"bind %s",strerror(errno));
return(EPERM);
}
}
if(sock_out != NULL) *sock_out = sun;
return(0);
}
void bind_data_socket(int fd, struct sockaddr_un *sun)
{
struct timeval tv;
sun->sun_family = AF_UNIX;
#ifdef __FreeBSD__
gettimeofday(&tv, NULL);
snprintf(sun->sun_path, sizeof(sun->sun_path), "%s.%i.%li",
VDEDATSOCK, getpid(), tv.tv_usec);
data_socket = strdup(sun->sun_path);
#else
struct {
char zero;
int pid;
int usecs;
} name;
name.zero = 0;
name.pid = getpid();
gettimeofday(&tv, NULL);
name.usecs = tv.tv_usec;
memcpy(sun->sun_path, &name, sizeof(name));
#endif
if(bind(fd, (struct sockaddr *) sun, sizeof(*sun)) < 0){
printlog(LOG_ERR,"Binding to data socket %s",strerror(errno));
exit(1);
}
}
void bind_sockets(int ctl_fd, const char *ctl_name, int data_fd)
{
int err, used=0;
err = bind_socket(ctl_fd, ctl_name, NULL);
if(err == 0){
bind_data_socket(data_fd, &data_sun);
return;
}
else if(err == EADDRINUSE) used = 1;
if(used){
fprintf(stderr, "The control socket '%s' has another server "
"attached to it\n", ctl_name);
fprintf(stderr, "You can either\n");
fprintf(stderr, "\tremove '%s'\n", ctl_name);
fprintf(stderr, "\tor rerun with a different, unused filename for a "
"socket\n");
}
else
fprintf(stderr, "The control socket '%s' exists, isn't used, but couldn't "
"be removed\n", ctl_name);
exit(1);
}
static void Usage(void)
{
#ifdef TUNTAP
fprintf(stderr, "Usage : %s [ -sock control-socket ] [ -tap tuntap-device ] [ -hub ] [-daemon]\n" , prog);
#else
fprintf(stderr, "Usage : %s [ -sock control-socket ] [ -hub ] [-daemon]\n", prog);
#endif
exit(1);
}
int main(int argc, char **argv)
{
int connect_fd, data_fd, n, i, /*new,*/ one = 1;
char *tap_dev = NULL;
#ifdef TUNTAP
int tap_fd = -1;
#endif
#ifdef __FreeBSD__
atexit(cleanupwrapper);
#else
on_exit(cleanup, NULL);
#endif
prog = argv[0];
/* option parsing */
{
int c;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"sock", 1, 0, 's'},
{"vdesock", 1, 0, 's'},
{"unix", 1, 0, 's'},
{"tap", 1, 0, 't'},
{"daemon", 0, 0, 'd'},
{"hub", 0, 0, 'x'},
{"help",0,0,'h'},
{0, 0, 0, 0}
};
c = getopt_long_only (argc, argv, "s:t:dxh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 's':
ctl_socket=strdup(optarg);
break;
case 't':
#ifdef TUNTAP
tap_dev=strdup(optarg);
#else
fprintf(stderr, "-tap isn't supported since TUNTAP isn't enabled\n");
Usage();
#endif
break;
case 'x':
printlog(LOG_INFO,"s will be a hub instead of a switch", prog);
hub = 1;
break;
case 'd':
daemonize=1;
break;
case 'h':
default:
Usage();
}
}
if(optind < argc)
Usage();
}
if((connect_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0){
printlog(LOG_ERR,"socket: %s",strerror(errno));
exit(1);
}
if(setsockopt(connect_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
sizeof(one)) < 0){
printlog(LOG_ERR,"setsockopt: %s",strerror(errno));
exit(1);
}
if(fcntl(connect_fd, F_SETFL, O_NONBLOCK) < 0){
printlog(LOG_ERR,"Setting O_NONBLOCK on connection fd: %s",strerror(errno));
exit(1);
}
if((data_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0){
printlog(LOG_ERR,"socket: %s",strerror(errno));
exit(1);
}
if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0){
printlog(LOG_ERR,"Setting O_NONBLOCK on data fd %s",strerror(errno));
exit(1);
}
bind_sockets(connect_fd, ctl_socket, data_fd);
if(listen(connect_fd, 15) < 0){
printlog(LOG_ERR,"listen: %s",strerror(errno));
exit(1);
}
if(signal(SIGINT, sig_handler) < 0) {
printlog(LOG_ERR,"Setting handler for SIGINT: %s",strerror(errno));
}
hash_init();
if (daemonize) {
openlog(basename(prog), LOG_PID, 0);
logok=1;
syslog(LOG_INFO,"UML_SWITCH started");
}
printlog(LOG_INFO,"attached to unix socket '%s'", ctl_socket);
if(isatty(0) && ! daemonize)
add_fd(0);
add_fd(connect_fd);
add_fd(data_fd);
g_minfds=g_nfds;
#ifdef TUNTAP
if(tap_dev != NULL) tap_fd = open_tap(tap_dev);
if(tap_fd > -1) {
add_fd(tap_fd);
setup_port(g_nfds-1, tap_fd, send_tap, NULL, 0, 0);
}
#endif
if (daemonize && daemon(0, 1)) {
printlog(LOG_ERR,"daemon: %s",strerror(errno));
exit(1);
}
while(1){
char buf[128];
n = poll(fds, nfds, -1);
if(n < 0){
if(errno == EINTR) continue;
printlog(LOG_WARNING,"poll %s",strerror(errno));
break;
}
for(i = 0; i < nfds; i++){
if(fds[i].revents == 0) continue;
if(fds[i].fd == 0){
if(fds[i].revents & POLLHUP){
printlog(LOG_WARNING,"EOF on stdin, cleaning up and exiting");
exit(0);
}
n = read(0, buf, sizeof(buf));
if(n < 0){
printlog(LOG_WARNING,"Reading from stdin %s",strerror(errno));
break;
}
else if(n == 0){
printlog(LOG_WARNING,"EOF on stdin, cleaning up and exiting");
exit(0);
}
}
else if(fds[i].fd == connect_fd){
if(fds[i].revents & POLLHUP){
printlog(LOG_WARNING,"Error on connection fd");
continue;
}
accept_connection(connect_fd);
}
else if(fds[i].fd == data_fd) handle_sock_data(data_fd, hub);
#ifdef TUNTAP
else if(fds[i].fd == tap_fd) handle_tap_data(i, tap_fd, hub);
#endif
else {
if (g_fdsdata[i] == NULL)
new_port(i,fds[i].fd, data_fd);
else
if (handle_sock_direct_data(i, fds[i].fd, hub))
close_descriptor(i, fds[i].fd);
}
}
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1