/* $USAGI: atmarp.c,v 1.4 2001/11/01 10:40:16 serizawa Exp $ */
/* atmarp.c - RFC1577 ATMARP control */
 
/* Written 1995-2000 by Werner Almesberger, EPFL-LRC/ICA */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if_arp.h>

#include <stdint.h>
#include <linux/atmarp.h>
#include <linux/atmclip.h>

#include "atm.h"
#include "atmd.h"
#include "atmarpd.h"


#define BUF_SIZE 4096


static int query_result(struct atmarp_req *reply)
{
    unsigned char *ipp = (unsigned char *) &reply->ip;
    char buf[MAX_ATM_ADDR_LEN+1];
    int error;

    printf("IP:  %d.%d.%d.%d\n",ipp[0],ipp[1],ipp[2],ipp[3]);
    if (!atmsvc_addr_in_use(reply->addr)) return 0;
    error = atm2text(buf,sizeof(buf),(struct sockaddr *) &reply->addr,
      A2T_PRETTY | A2T_NAME) < 0;
    if (error) strcpy(buf,"<invalid>");
    printf("ATM: %s\n",buf);
    return error ? 1 : 0;
}


static int send_request(struct atmarp_req *req)
{
    struct atmarp_req reply;
    int s,len;

    s = un_attach(ATMARP_SOCKET_PATH);
    if (s < 0) {
	perror("un_attach");
	exit(1);
    }
    if (write(s,req,sizeof(*req)) < 0) {
	perror("write");
	exit(1);
    }
    len = read(s,&reply,sizeof(reply));
    if (len < 0) {
	perror("read");
	exit(1);
    }
    if (req->type == art_query) return query_result(&reply);
    if (len != sizeof(int)) {
	fprintf(stderr,"bad read: %d != %d\n",len,sizeof(int));
	exit(1);
    }
    if (*(int *) &reply < 0) {
	fprintf(stderr,"atmarp: %s\n",strerror(-*(int *) &reply));
	exit(1);
    }
    return *(int *) &reply;
}


static int print_table(void)
{
    char buffer[BUF_SIZE];
    int fd,size;

    if ((fd = open(ATMARP_DUMP_DIR "/" ATMARP_DUMP_FILE,O_RDONLY)) < 0) {
	perror("open " ATMARP_DUMP_DIR "/" ATMARP_DUMP_FILE);
	return 1;
    }
    while ((size = read(fd,buffer,BUF_SIZE))) {
	if (size < 0) {
	    perror("read " ATMARP_DUMP_DIR "/" ATMARP_DUMP_FILE);
	    return 1;
	}
	if (write(0,buffer,size) < 0) {
	    perror("write stdout");
	    return 1;
	}
    }
    return 0;
}


static void usage(const char *name)
{
    fprintf(stderr,"usage: %s -a\n",name);
    fprintf(stderr,"%6s %s -c [[atm]N]\n","",name);
    fprintf(stderr,"%6s %s -q ip_addr [qos qos_spec] [sndbuf bytes]\n","",name);
    fprintf(stderr,"%6s %s -s ip_addr [itf.]vpi.vci [pcr value] [qos spec] "
      "[sndbuf bytes]\n%8s [temp] [pub] [null]\n","",name,"");
    fprintf(stderr,"%6s %s -s ip_addr atm_addr [pcr value] [qos spec] "
      "[sndbuf bytes] [temp]\n%8s [pub] [arpsrv]\n","",name,"");
    fprintf(stderr,"%6s %s -d ip_addr [arpsrv]\n","",name);
#if 0  /* undocumented */
    fprintf(stderr,"%6s %s -Q ip_addr\n","",name);
#endif
    fprintf(stderr,"%6s %s -V\n","",name);
    exit(1);
}


int main(int argc,char **argv)
{
#ifndef CONFIG_ATM_IPV6
    struct atmarp_req req;
    int c,i,num;
    char *here,*end;
#endif /* ifndef CONFIG_ATM_IPV6 */

#ifdef CONFIG_ATM_IPV6
    fprintf(stderr,"%s is not consistent with the kernel option: CONFIG_ATM_IPV6. \n"
      "If you want to enable atmarp, please off CONFIG_ATM_IPV6.\n",argv[0]);
    exit(1);
#else  /* i.e., !ifdef CONFIG_ATM_IPV6 */
    req.type = 0;
    while ((c = getopt(argc,argv,"acdqQsV")) != EOF)
	switch (c) {
	    case 'a':
		if (argc != optind || req.type) usage(argv[0]);
		req.type = art_table;
		/* (void) send_request(&req); @@@ fix this later */
		return print_table();
	    case 'c':
		if (req.type) usage(argv[0]);
		req.type = art_create;
		break;
	    case 'd':
		if (req.type) usage(argv[0]);
		req.type = art_delete;
		break;
	    case 'q':
		if (req.type) usage(argv[0]);
		req.type = art_qos;
		break;
	    case 'Q':
		if (req.type) usage(argv[0]);
		req.type = art_query;
		break;
	    case 's':
		if (req.type) usage(argv[0]);
		req.type = art_set;
		break;
	    case 'V':
		printf("%s\n",VERSION);
		return 0;
	    default:
		usage(argv[0]);
	}
    switch (req.type) {
	case art_create:
	    if (argc == optind) req.itf = -1;
	    else {
		if (argc != optind+1) usage(argv[0]);
		here = argv[optind];
		if (strlen(here) > 3 && !strncmp(here,"atm",3)) here += 3;
		req.itf = strtoul(here,&end,10);
		if (*end || (here[0] == '0' && here[1])) {
		    usage(argv[0]);
		    return 1;
		}
	    }
	    num = send_request(&req);
	    if (req.itf == -1) printf("atm%d\n",num);
	    return 0;
	case art_qos:
	    if (argc < optind+1) usage(argv[0]);
	    /* fall through */
	case art_set:
	    if (argc < optind+2) usage(argv[0]);
	    break;
	case art_query:
	    if (argc != optind+1) usage(argv[0]);
	    break;
	case art_delete:
	    if (argc < optind+1) usage(argv[0]);
	    break;
	default:
	    usage(argv[0]);
    }
    req.ip = text2ip(argv[optind],NULL,T2I_NAME | T2I_ERROR);
    if (req.ip == INADDR_NONE) return 1;
    req.flags = ATF_PERM;
    if (req.type == art_qos) {
	memset(&req.qos,0,sizeof(req.qos));
	req.sndbuf = 0;
	for (i = optind+1; i < argc; i++)
	    if (!strcmp(argv[i],"qos")) {
		    if (++i >= argc) usage(argv[0]);
		    if (text2qos(argv[i],&req.qos,0)) usage(argv[0]);
		}
	    else if (!strcmp(argv[i],"sndbuf")) {
		    if (++i >= argc) usage(argv[0]);
		    req.sndbuf = strtol(argv[i],&end,0);
		    if (*end) usage(argv[0]);
		}
	    else if (i != optind+1 || argc != optind+2 ||
		  text2qos(argv[optind+1],&req.qos,0)) usage(argv[0]);
    }
    if (req.type == art_set) {
	memset(&req.qos,0,sizeof(req.qos));
	req.sndbuf = 0;
	for (i = optind+2; i < argc; i++)
	    if (!strcmp(argv[i],"temp")) req.flags &= ~ATF_PERM;
	    else if (!strcmp(argv[i],"pub")) req.flags |= ATF_PUBL;
	    else if (!strcmp(argv[i],"null")) req.flags |= ATF_NULL;
	    else if (!strcmp(argv[i],"arpsrv")) req.flags |= ATF_ARPSRV;
	    else if (!strcmp(argv[i],"qos")) {
		    if (++i >= argc) usage(argv[0]);
		    if (text2qos(argv[i],&req.qos,0)) usage(argv[0]);
		}
	    else if (!strcmp(argv[i],"sndbuf")) {
		    if (++i >= argc) usage(argv[0]);
		    req.sndbuf = strtol(argv[i],&end,0);
		    if (*end) usage(argv[0]);
		}
	    else if (!strcmp(argv[i],"pcr")) {
		    if (++i >= argc) usage(argv[0]);
		    req.qos.txtp.traffic_class = req.qos.rxtp.traffic_class =
		      ATM_CBR;
		    req.qos.txtp.max_pcr = req.qos.rxtp.max_pcr =
		      strtol(argv[i],&end,0);
		    if (*end) usage(argv[0]);
		}
	    else usage(argv[0]);
	if (text2atm(argv[optind+1],(struct sockaddr *) &req.addr,
	  sizeof(req.addr),T2A_NAME) < 0) {
	    fprintf(stderr,"%s: invalid ATM address\n",argv[optind+1]);
	    return 1;
	}
    }
    if (req.type == art_delete && optind+1 < argc) {
	if (optind+2 < argc || strcmp(argv[optind+1],"arpsrv")) usage(argv[0]);
	req.flags |= ATF_ARPSRV;
    }
    if (!req.qos.aal) req.qos.aal = ATM_AAL5;
    send_request(&req);
#endif /* ifdef CONFIG_ATM_IPV6 */
    return 0;
}
