#include "slirp.h"
#include "proxy_common.h"
#include "android_utils.h"  /* for dprint */
#include "android.h"

#define  D(...)   VERBOSE_PRINT(slirp,__VA_ARGS__)
#define  DN(...)  do { if (VERBOSE_CHECK(slirp)) dprintn(__VA_ARGS__); } while (0)

/* host address */
struct in_addr our_addr;
/* host dns address */
struct in_addr dns_addr[DNS_ADDR_MAX];
int            dns_addr_count;

/* host loopback address */
struct in_addr loopback_addr;

/* address for slirp virtual addresses */
struct in_addr special_addr;
/* virtual address alias for host */
struct in_addr alias_addr;

const uint8_t special_ethaddr[6] = {
    0x52, 0x54, 0x00, 0x12, 0x35, 0x00
};

uint8_t client_ethaddr[6] = {
    0x52, 0x54, 0x00, 0x12, 0x34, 0x56
};

int do_slowtimo;
int link_up;
struct timeval tt;
FILE *lfd;

/* XXX: suppress those select globals */
fd_set *global_readfds, *global_writefds, *global_xfds;

char slirp_hostname[33];

int slirp_add_dns_server(struct in_addr  new_dns_addr)
{
    if (dns_addr_count >= DNS_ADDR_MAX)
        return -1;

    dns_addr[dns_addr_count++] = new_dns_addr;
    return 0;
}


#ifdef _WIN32

int slirp_get_system_dns_servers()
{
    FIXED_INFO *FixedInfo=NULL;
    ULONG    BufLen;
    DWORD    ret;
    IP_ADDR_STRING *pIPAddr;
    struct in_addr tmp_addr;

    if (dns_addr_count > 0)
        return dns_addr_count;

    FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof(FIXED_INFO));
    BufLen = sizeof(FIXED_INFO);

    if (ERROR_BUFFER_OVERFLOW == GetNetworkParams(FixedInfo, &BufLen)) {
        if (FixedInfo) {
            GlobalFree(FixedInfo);
            FixedInfo = NULL;
        }
        FixedInfo = GlobalAlloc(GPTR, BufLen);
    }

    if ((ret = GetNetworkParams(FixedInfo, &BufLen)) != ERROR_SUCCESS) {
        printf("GetNetworkParams failed. ret = %08x\n", (u_int)ret );
        if (FixedInfo) {
            GlobalFree(FixedInfo);
            FixedInfo = NULL;
        }
        return -1;
    }

    D( "DNS Servers:");
    pIPAddr = &(FixedInfo->DnsServerList);
    while (pIPAddr && dns_addr_count < DNS_ADDR_MAX) {
        D( "  %s", pIPAddr->IpAddress.String );
        inet_aton(pIPAddr->IpAddress.String, &tmp_addr);
        if (tmp_addr.s_addr == loopback_addr.s_addr)
            tmp_addr = our_addr;
        if (dns_addr_count < DNS_ADDR_MAX)
            dns_addr[dns_addr_count++] = tmp_addr;
        pIPAddr = pIPAddr->Next;
    }

    if (FixedInfo) {
        GlobalFree(FixedInfo);
        FixedInfo = NULL;
    }
    if (dns_addr_count <= 0)
        return -1;

    return dns_addr_count;
}

#else

int slirp_get_system_dns_servers(void)
{
    char buff[512];
    char buff2[256];
    FILE *f;
    struct in_addr tmp_addr;

    if (dns_addr_count > 0)
        return dns_addr_count;

#ifdef CONFIG_DARWIN
    /* on Darwin /etc/resolv.conf is a symlink to /private/var/run/resolv.conf
     * in some siutations, the symlink can be destroyed and the system will not
     * re-create it. Darwin-aware applications will continue to run, but "legacy"
     * Unix ones will not.
     */
     f = fopen("/private/var/run/resolv.conf", "r");
     if (!f)
        f = fopen("/etc/resolv.conf", "r");  /* desperate attempt to sanity */
#else
    f = fopen("/etc/resolv.conf", "r");
#endif
    if (!f)
        return -1;

    DN("emulator: IP address of your DNS(s): ");
    while (fgets(buff, 512, f) != NULL) {
        if (sscanf(buff, "nameserver%*[ \t]%256s", buff2) == 1) {
            if (!inet_aton(buff2, &tmp_addr))
                continue;
            if (tmp_addr.s_addr == loopback_addr.s_addr)
                tmp_addr = our_addr;
            if (dns_addr_count < DNS_ADDR_MAX) {
                dns_addr[dns_addr_count++] = tmp_addr;
                if (dns_addr_count > 1)
                    DN(", ");
                DN("%s", inet_ntoa(tmp_addr));
            } else {
                DN("(more)");
                break;
            }
        }
    }
    DN("\n");
    fclose(f);

    if (!dns_addr_count)
        return -1;

    return dns_addr_count;
}

#endif

extern void  slirp_init_shapers();

void slirp_init(void)
{
#if DEBUG
    int          slirp_logmask = 0;
    char         slirp_logfile[512];

    {
        const char*  env = getenv( "ANDROID_SLIRP_LOGMASK" );
        if (env != NULL)
            slirp_logmask = atoi(env);
         else if (VERBOSE_CHECK(slirp))
            slirp_logmask = DEBUG_DEFAULT;
    }

    {
        char*  p   = slirp_logfile;
        char*  end = p + sizeof(slirp_logfile);

        p = bufprint_temp_file( p, end, "slirp.log" );
        if (p >= end) {
            dprint( "cannot create slirp log file in temporary directory" );
            slirp_logmask = 0;
        }
    }
    if (slirp_logmask) {
        dprint( "sending slirp logs with mask %x to %s", slirp_logmask, slirp_logfile );
        debug_init( slirp_logfile, slirp_logmask );
    }
#endif

    link_up = 1;

    if_init();
    ip_init();

    /* Initialise mbufs *after* setting the MTU */
    mbuf_init();

    /* set default addresses */
    inet_aton("127.0.0.1", &loopback_addr);

    if (dns_addr_count == 0) {
        if (slirp_get_system_dns_servers() < 0) {
            dns_addr[0]    = loopback_addr;
            dns_addr_count = 1;
            fprintf (stderr, "Warning: No DNS servers found\n");
        }
    }

    inet_aton(CTL_SPECIAL, &special_addr);
    alias_addr.s_addr = special_addr.s_addr | htonl(CTL_ALIAS);
    getouraddr();

    slirp_init_shapers();
}

#define CONN_CANFSEND(so) (((so)->so_state & (SS_FCANTSENDMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
#define CONN_CANFRCV(so) (((so)->so_state & (SS_FCANTRCVMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
#define UPD_NFDS(x) if (nfds < (x)) nfds = (x)

/*
 * curtime kept to an accuracy of 1ms
 */
#ifdef _WIN32
static void updtime(void)
{
    struct _timeb tb;

    _ftime(&tb);
    curtime = (u_int)tb.time * (u_int)1000;
    curtime += (u_int)tb.millitm;
}
#else
static void updtime(void)
{
	gettimeofday(&tt, 0);

	curtime = (u_int)tt.tv_sec * (u_int)1000;
	curtime += (u_int)tt.tv_usec / (u_int)1000;

	if ((tt.tv_usec % 1000) >= 500)
	   curtime++;
}
#endif

void slirp_select_fill(int *pnfds,
                       fd_set *readfds, fd_set *writefds, fd_set *xfds)
{
    struct socket *so, *so_next;
    struct timeval timeout;
    int nfds;
    int tmp_time;

    /* fail safe */
    global_readfds = NULL;
    global_writefds = NULL;
    global_xfds = NULL;

    nfds = *pnfds;
    /*
        * First, TCP sockets
        */
    do_slowtimo = 0;
    if (link_up) {
        /*
            * *_slowtimo needs calling if there are IP fragments
            * in the fragment queue, or there are TCP connections active
            */
        do_slowtimo = ((tcb.so_next != &tcb) ||
                    ((struct ipasfrag *)&ipq != (struct ipasfrag *)ipq.next));

        for (so = tcb.so_next; so != &tcb; so = so_next) {
            so_next = so->so_next;

               /*
                * See if we need a tcp_fasttimo
                */
            if (time_fasttimo == 0 && so->so_tcpcb->t_flags & TF_DELACK)
                time_fasttimo = curtime; /* Flag when we want a fasttimo */

               /*
                * NOFDREF can include still connecting to local-host,
                * newly socreated() sockets etc. Don't want to select these.
                */
            if (so->so_state & SS_NOFDREF || so->s == -1)
                continue;

                        /*
                         * don't register proxified socked connections here
                         */
                         if ((so->so_state & SS_PROXIFIED) != 0)
                            continue;

               /*
                * Set for reading sockets which are accepting
                */
            if (so->so_state & SS_FACCEPTCONN) {
                                FD_SET(so->s, readfds);
                UPD_NFDS(so->s);
                continue;
            }

               /*
                * Set for writing sockets which are connecting
                */
            if (so->so_state & SS_ISFCONNECTING) {
                FD_SET(so->s, writefds);
                UPD_NFDS(so->s);
                continue;
            }

               /*
                * Set for writing if we are connected, can send more, and
                * we have something to send
                */
            if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) {
                FD_SET(so->s, writefds);
                UPD_NFDS(so->s);
            }

               /*
                * Set for reading (and urgent data) if we are connected, can
                * receive more, and we have room for it XXX /2 ?
                */
            if (CONN_CANFRCV(so) && (so->so_snd.sb_cc < (so->so_snd.sb_datalen/2))) {
                FD_SET(so->s, readfds);
                FD_SET(so->s, xfds);
                UPD_NFDS(so->s);
            }
        }

        /*
            * UDP sockets
            */
        for (so = udb.so_next; so != &udb; so = so_next) {
            so_next = so->so_next;

            if ((so->so_state & SS_PROXIFIED) != 0)
                continue;

               /*
                * See if it's timed out
                */
            if (so->so_expire) {
                if (so->so_expire <= curtime) {
                    udp_detach(so);
                    continue;
                } else
                    do_slowtimo = 1; /* Let socket expire */
            }

               /*
                * When UDP packets are received from over the
                * link, they're sendto()'d straight away, so
                * no need for setting for writing
                * Limit the number of packets queued by this session
                * to 4.  Note that even though we try and limit this
                * to 4 packets, the session could have more queued
                * if the packets needed to be fragmented
                * (XXX <= 4 ?)
                */
            if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) {
                FD_SET(so->s, readfds);
                UPD_NFDS(so->s);
            }
        }
    }

       /*
        * Setup timeout to use minimum CPU usage, especially when idle
        */

       /*
        * First, see the timeout needed by *timo
        */
    timeout.tv_sec = 0;
    timeout.tv_usec = -1;
    /*
        * If a slowtimo is needed, set timeout to 500ms from the last
        * slow timeout. If a fast timeout is needed, set timeout within
        * 200ms of when it was requested.
        */
    if (do_slowtimo) {
        /* XXX + 10000 because some select()'s aren't that accurate */
        timeout.tv_usec = ((500 - (curtime - last_slowtimo)) * 1000) + 10000;
        if (timeout.tv_usec < 0)
            timeout.tv_usec = 0;
        else if (timeout.tv_usec > 510000)
            timeout.tv_usec = 510000;

        /* Can only fasttimo if we also slowtimo */
        if (time_fasttimo) {
            tmp_time = (200 - (curtime - time_fasttimo)) * 1000;
            if (tmp_time < 0)
                tmp_time = 0;

            /* Choose the smallest of the 2 */
            if (tmp_time < timeout.tv_usec)
                timeout.tv_usec = (u_int)tmp_time;
        }
    }

        /*
         * now, the proxified sockets
         */
        proxy_manager_select_fill(&nfds, readfds, writefds, xfds);

        *pnfds = nfds;
}

void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds)
{
    struct socket *so, *so_next;
    int ret;

    global_readfds = readfds;
    global_writefds = writefds;
    global_xfds = xfds;

	/* Update time */
	updtime();

	/*
	 * See if anything has timed out
	 */
	if (link_up) {
		if (time_fasttimo && ((curtime - time_fasttimo) >= 2)) {
			tcp_fasttimo();
			time_fasttimo = 0;
		}
		if (do_slowtimo && ((curtime - last_slowtimo) >= 499)) {
			ip_slowtimo();
			tcp_slowtimo();
			last_slowtimo = curtime;
		}
	}

	/*
	 * Check sockets
	 */
	if (link_up) {
		/*
		 * Check TCP sockets
		 */
		for (so = tcb.so_next; so != &tcb; so = so_next) {
			so_next = so->so_next;

			/*
			 * FD_ISSET is meaningless on these sockets
			 * (and they can crash the program)
			 */
			if (so->so_state & SS_NOFDREF || so->s == -1)
			   continue;

                        /*
                         * proxified sockets are polled later in this
                         * function.
                         */
                        if ((so->so_state & SS_PROXIFIED) != 0)
                            continue;

			/*
			 * Check for URG data
			 * This will soread as well, so no need to
			 * test for readfds below if this succeeds
			 */
			if (FD_ISSET(so->s, xfds))
			   sorecvoob(so);
			/*
			 * Check sockets for reading
			 */
			else if (FD_ISSET(so->s, readfds)) {
				/*
				 * Check for incoming connections
				 */
				if (so->so_state & SS_FACCEPTCONN) {
					tcp_connect(so);
					continue;
				} /* else */
				ret = soread(so);

				/* Output it if we read something */
				if (ret > 0)
				   tcp_output(sototcpcb(so));
			}

			/*
			 * Check sockets for writing
			 */
			if (FD_ISSET(so->s, writefds)) {
			  /*
			   * Check for non-blocking, still-connecting sockets
			   */
			  if (so->so_state & SS_ISFCONNECTING) {
			    /* Connected */
			    so->so_state &= ~SS_ISFCONNECTING;

			    ret = send(so->s, (char*)&ret, 0, 0);
			    if (ret < 0) {
			      /* XXXXX Must fix, zero bytes is a NOP */
			      if (errno == EAGAIN || errno == EWOULDBLOCK ||
				  errno == EINPROGRESS || errno == ENOTCONN)
				continue;

			      /* else failed */
			      so->so_state = SS_NOFDREF;
			    }
			    /* else so->so_state &= ~SS_ISFCONNECTING; */

			    /*
			     * Continue tcp_input
			     */
			    tcp_input((MBuf )NULL, sizeof(struct ip), so);
			    /* continue; */
			  } else
			    ret = sowrite(so);
			  /*
			   * XXXXX If we wrote something (a lot), there
			   * could be a need for a window update.
			   * In the worst case, the remote will send
			   * a window probe to get things going again
			   */
			}

			/*
			 * Probe a still-connecting, non-blocking socket
			 * to check if it's still alive
	 	 	 */
#ifdef PROBE_CONN
			if (so->so_state & SS_ISFCONNECTING) {
			  ret = recv(so->s, (char *)&ret, 0,0);

			  if (ret < 0) {
			    /* XXX */
			    if (errno == EAGAIN || errno == EWOULDBLOCK ||
				errno == EINPROGRESS || errno == ENOTCONN)
			      continue; /* Still connecting, continue */

			    /* else failed */
			    so->so_state = SS_NOFDREF;

			    /* tcp_input will take care of it */
			  } else {
			    ret = send(so->s, &ret, 0,0);
			    if (ret < 0) {
			      /* XXX */
			      if (errno == EAGAIN || errno == EWOULDBLOCK ||
				  errno == EINPROGRESS || errno == ENOTCONN)
				continue;
			      /* else failed */
			      so->so_state = SS_NOFDREF;
			    } else
			      so->so_state &= ~SS_ISFCONNECTING;

			  }
			  tcp_input((MBuf )NULL, sizeof(struct ip),so);
			} /* SS_ISFCONNECTING */
#endif
		}

		/*
		 * Now UDP sockets.
		 * Incoming packets are sent straight away, they're not buffered.
		 * Incoming UDP data isn't buffered either.
		 */
		for (so = udb.so_next; so != &udb; so = so_next) {
			so_next = so->so_next;

                        if ((so->so_state & SS_PROXIFIED) != 0)
                            continue;

			if (so->s != -1 && FD_ISSET(so->s, readfds)) {
                            sorecvfrom(so);
                        }
		}
	}

        /*
         * Now the proxified sockets
         */
        proxy_manager_poll(readfds, writefds, xfds);

	/*
	 * See if we can start outputting
	 */
	if (if_queued && link_up)
	   if_start();

	/* clear global file descriptor sets.
	 * these reside on the stack in vl.c
	 * so they're unusable if we're not in
	 * slirp_select_fill or slirp_select_poll.
	 */
	 global_readfds = NULL;
	 global_writefds = NULL;
	 global_xfds = NULL;
}

#define ETH_ALEN 6
#define ETH_HLEN 14

#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/

#define	ARPOP_REQUEST	1		/* ARP request			*/
#define	ARPOP_REPLY	2		/* ARP reply			*/

struct ethhdr
{
	unsigned char	h_dest[ETH_ALEN];	/* destination eth addr	*/
	unsigned char	h_source[ETH_ALEN];	/* source ether addr	*/
	unsigned short	h_proto;		/* packet type ID field	*/
};

struct arphdr
{
	unsigned short	ar_hrd;		/* format of hardware address	*/
	unsigned short	ar_pro;		/* format of protocol address	*/
	unsigned char	ar_hln;		/* length of hardware address	*/
	unsigned char	ar_pln;		/* length of protocol address	*/
	unsigned short	ar_op;		/* ARP opcode (command)		*/

	 /*
	  *	 Ethernet looks like this : This bit is variable sized however...
	  */
	unsigned char		ar_sha[ETH_ALEN];	/* sender hardware address	*/
	unsigned char		ar_sip[4];		/* sender IP address		*/
	unsigned char		ar_tha[ETH_ALEN];	/* target hardware address	*/
	unsigned char		ar_tip[4];		/* target IP address		*/
};

void arp_input(const uint8_t *pkt, int pkt_len)
{
    struct ethhdr *eh = (struct ethhdr *)pkt;
    struct arphdr *ah = (struct arphdr *)(pkt + ETH_HLEN);
    uint8_t arp_reply[ETH_HLEN + sizeof(struct arphdr)];
    struct ethhdr *reh = (struct ethhdr *)arp_reply;
    struct arphdr *rah = (struct arphdr *)(arp_reply + ETH_HLEN);
    int ar_op;

    ar_op = ntohs(ah->ar_op);
    switch(ar_op) {
    case ARPOP_REQUEST:
        if (!memcmp(ah->ar_tip, &special_addr, 3)) {
            if ( CTL_IS_DNS(ah->ar_tip[3]) || ah->ar_tip[3] == CTL_ALIAS)
                goto arp_ok;
            return;
        arp_ok:
            /* XXX: make an ARP request to have the client address */
            memcpy(client_ethaddr, eh->h_source, ETH_ALEN);

            /* ARP request for alias/dns mac address */
            memcpy(reh->h_dest, pkt + ETH_ALEN, ETH_ALEN);
            memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 1);
            reh->h_source[5] = ah->ar_tip[3];
            reh->h_proto = htons(ETH_P_ARP);

            rah->ar_hrd = htons(1);
            rah->ar_pro = htons(ETH_P_IP);
            rah->ar_hln = ETH_ALEN;
            rah->ar_pln = 4;
            rah->ar_op = htons(ARPOP_REPLY);
            memcpy(rah->ar_sha, reh->h_source, ETH_ALEN);
            memcpy(rah->ar_sip, ah->ar_tip, 4);
            memcpy(rah->ar_tha, ah->ar_sha, ETH_ALEN);
            memcpy(rah->ar_tip, ah->ar_sip, 4);
            slirp_output(arp_reply, sizeof(arp_reply));
        }
        break;
    default:
        break;
    }
}

void slirp_input(const uint8_t *pkt, int pkt_len)
{
    MBuf m;
    int proto;

    if (pkt_len < ETH_HLEN)
        return;

    proto = ntohs(*(uint16_t *)(pkt + 12));
    switch(proto) {
    case ETH_P_ARP:
        arp_input(pkt, pkt_len);
        break;
    case ETH_P_IP:
        m = mbuf_alloc();
        if (!m)
            return;
        /* Note: we add to align the IP header */
        m->m_len = pkt_len + 2;
        memcpy(m->m_data + 2, pkt, pkt_len);

        m->m_data += 2 + ETH_HLEN;
        m->m_len -= 2 + ETH_HLEN;

        ip_input(m);
        break;
    default:
        break;
    }
}

/* output the IP packet to the ethernet device */
void if_encap(const uint8_t *ip_data, int ip_data_len)
{
    uint8_t buf[1600];
    struct ethhdr *eh = (struct ethhdr *)buf;

    if (ip_data_len + ETH_HLEN > sizeof(buf))
        return;

    memcpy(eh->h_dest, client_ethaddr, ETH_ALEN);
    memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 1);
    /* XXX: not correct */
    eh->h_source[5] = CTL_ALIAS;
    eh->h_proto = htons(ETH_P_IP);
    memcpy(buf + sizeof(struct ethhdr), ip_data, ip_data_len);
    slirp_output(buf, ip_data_len + ETH_HLEN);
}

int slirp_redir(int is_udp, int host_port,
                struct in_addr guest_addr, int guest_port)
{
    if (is_udp) {
        if (!udp_listen(htons(host_port), guest_addr.s_addr,
                        htons(guest_port), 0))
            return -1;
    } else {
        if (!solisten(htons(host_port), guest_addr.s_addr,
                      htons(guest_port), 0))
            return -1;
    }
    return 0;
}

int  slirp_unredir(int  is_udp, int  host_port)
{
    if (is_udp)
        return udp_unlisten( host_port );
    else
        return sounlisten( host_port );
}

