/* atmtcp.c - control ATM on TCP emulation */

/* Written 1995,1997 by Werner Almesberger, EPFL-LRC */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <linux/atm.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= 0x20100
#include <linux/atm_tcp.h>
#else

struct atmtcp_hdr {
    unsigned short vpi;
    unsigned short vci;
    unsigned short length; /* ... of data part */
};

#endif


#define PORT 8402	/* old ATMTCP used 8401, but now the header is in
			   network byte order, so we're incompatible in most
			   cases */

#define MAX_PACKET ATM_MAX_AAL5_PDU+sizeof(struct atmtcp_hdr)


static int s_tcp,s_krn;


/*
 * This is a pretty poor implementation because we need to read twice for each
 * PDU. But then, atmtcp isn't exactly designed for throughput ...
 */


static void from_tcp(void)
{
    static char buf[MAX_PACKET];
    static int bytes = 0;
    int ret,size;

    size = sizeof(struct atmtcp_hdr)-bytes;
    if (size <= 0) size += ntohl(((struct atmtcp_hdr *) buf)->length);
    ret = read(s_tcp,buf+bytes,size);
    if (ret < 0) {
	perror("read from TCP");
	exit(1);
    }
    if (!ret) exit(0); /* EOF */
    bytes += ret;
    if (bytes < sizeof(struct atmtcp_hdr)) return;
    if (ntohl(((struct atmtcp_hdr *) buf)->length) > ATM_MAX_AAL5_PDU) {
	fprintf(stderr,"giant PDU (length = %ld) received\n",
	  ntohl(((struct atmtcp_hdr *) buf)->length));
	exit(1);
    }
    if (bytes < sizeof(struct atmtcp_hdr)+
      ntohl(((struct atmtcp_hdr *) buf)->length)) return;
    size = write(s_krn,buf,bytes);
    if (size < 0) perror("write to kernel");
    else if (size != bytes) {
	    fprintf(stderr,"bad write (%d != %d)\n",size,bytes);
	    exit(1);
	}
    bytes = 0;
}


static void to_tcp(void)
{
    char buf[MAX_PACKET];
    int ret,wrote;

    ret = read(s_krn,buf,MAX_PACKET);
    if (ret < 0) {
	perror("read from kernel");
	exit(1);
    }
    if (!ret) exit(0); /* we don't use that yet */
    if (ret < sizeof(struct atmtcp_hdr)) {
	fprintf(stderr,"kernel message too small (%d)\n",ret);
	exit(1);
    }
    if (ret != sizeof(struct atmtcp_hdr)+
      ntohl(((struct atmtcp_hdr *) buf)->length)) {
	fprintf(stderr,"invalid kernel message\n");
	exit(1);
    }
    wrote = write(s_tcp,buf,ret);
    if (wrote < 0) {
	perror("write to TCP");
	exit(1);
    }
    if (!wrote) exit(0); /* EOF */
    if (wrote != ret) {
	fprintf(stderr,"bad write (%d != %d)\n",wrote,ret);
	exit(1);
    }
}


static void usage(const char *name)
{
    fprintf(stderr,"%s [ -b ] -l [ <lclport> ]\n",name);
    fprintf(stderr,"%s [ -b ] -c <host> [ <rmtport> ]\n",name);
    exit(1);
}


int main(int argc,char **argv)
{
    struct sockaddr_in addr;
    const char *name;
    int background,listen_mode,port,itf;
    int s2;

    name = argv[0];
    background = 0;
    if (argc > 2 && !strcmp(argv[1],"-b")) {
	background = 1;
	argc--;
	argv++;
    }
    if (argc < 2) usage(name);
    listen_mode = !strcmp(argv[1],"-l");
    if (listen_mode) {
	if (argc > 3) usage(name);
	port = argc == 3 ? atoi(argv[2]) : -1;
    }
    else {
	if (argc > 4 || strcmp(argv[1],"-c")) usage(name);
	port = argc == 4 ? atoi(argv[3]) : -1;
    }
    if ((s_tcp = socket(PF_INET,SOCK_STREAM,0)) < 0) {
	perror("socket");
	return 1;
    }
    if (!port) usage(name);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port == -1 ?  PORT :  port);
    /*
     * Create atmtcp interface before setting up the TCP connection in order
     * to make assignment of interface numbers a bit more predictable.
     */
    if ((s_krn = socket(PF_ATMSVC,SOCK_DGRAM,0)) < 0) {
	perror("socket");
	return 1;
    }
    if ((itf = ioctl(s_krn,SIOCSIFATMTCP,0)) < 0) {
	perror("ioctl SIOCSIFATMTCP");
	return 1;
    }
    if (listen_mode) {
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(s_tcp,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
	    perror("bind");
	    return 1;
	}
	printf("Listening on port %d ...\n",ntohs(addr.sin_port));
	if (listen(s_tcp,5) < 0) {
	    perror("listen");
	    return 1;
	}
	if ((s2 = accept(s_tcp,NULL,NULL)) < 0) {
	    perror("accept");
	    return 1;
	}
	(void) close(s_tcp);
	s_tcp = s2;
    }
    else {
	struct hostent *h;

	h = gethostbyname(argv[2]);
	if (h)
	    memcpy(&addr.sin_addr.s_addr,h->h_addr,
	      sizeof(addr.sin_addr.s_addr));
	else if ((addr.sin_addr.s_addr = inet_addr(argv[2])) == -1) {
		herror(argv[2]);
		return 1;
	    }
	if (connect(s_tcp,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
	    perror("connect");
	    return 1;
	}
    }
    printf("Connected, interface %d\n",itf);
    if (background) {
	pid_t pid;

	pid = fork();
	if (pid < 0) {
	    perror("fork");
	    return 1;
	}
	if (pid) return 0;
    }
    while (1) {
	fd_set set;
	int ret;

	FD_ZERO(&set);
	FD_SET(s_tcp,&set);
	FD_SET(s_krn,&set);
	ret = select(s_tcp > s_krn ? s_tcp+1 : s_krn+1,&set,NULL,NULL,0);
	if (ret < 0) {
	    if (errno != EINTR) perror("select");
	}
	else {
	    if (FD_ISSET(s_tcp,&set)) from_tcp();
	    if (FD_ISSET(s_krn,&set)) to_tcp();
	}
    }
    return 0;
}
