int
main(int argc, char **argv)
{
	asyncns_t *asyncns;
	asyncns_query_t *query;
	struct addrinfo *result;
	struct pollfd pollfd = { .events = POLLIN };
	int status;

	asyncns = asyncns_new(10);
	assert(asyncns);
	assert(asyncns_getnqueries(asyncns) == 0);
	assert(asyncns_getnext(asyncns) == NULL);

	pollfd.fd = asyncns_fd(asyncns);
	assert(pollfd.fd > 2);

	query = asyncns_getaddrinfo(asyncns, "127.0.0.1", NULL, NULL);
	assert(query);
	assert(asyncns_getnqueries(asyncns) == 1);
	assert(asyncns_getnext(asyncns) == NULL);

	asyncns_cancel(asyncns, query);
	query = NULL;
	assert(asyncns_getnqueries(asyncns) == 0);
	assert(asyncns_getnext(asyncns) == NULL);

	query = asyncns_getaddrinfo(asyncns, "127.0.0.1", NULL, NULL);
	assert(query);
	assert(asyncns_getnqueries(asyncns) == 1);
	assert(asyncns_getnext(asyncns) == NULL);

	usleep(100000);
	status = poll(&pollfd, 1, 0);
	assert(status == 1);
	status = asyncns_wait(asyncns, 0);
	assert(status == 0);
	assert(asyncns_isdone(asyncns, query));
	assert(asyncns_getnqueries(asyncns) == 1);
	assert(asyncns_getnext(asyncns) == query);

	status = poll(&pollfd, 1, 100);
	assert(status == 0);

	status = asyncns_getaddrinfo_done(asyncns, query, &result);
	assert(asyncns_getnqueries(asyncns) == 0);

	/* Intuitively, this should not be needed but the docs state that
	 * a call to `asyncns_wait()` is necessary so that `asyncns_getnext()`
	 * provides meaningful results.
	 */
	status = asyncns_wait(asyncns, 0);
	assert(status == 0);

	/* There were two queries issued, one of which has been cancelled
	 * and the other has been freed afterwards. As none of them can be
	 * returned, the only meaningful result of `asyncns_getnext()` is
	 * NULL.
	 */
	assert(asyncns_getnext(asyncns) == NULL);

	asyncns_free(asyncns);
	asyncns_freeaddrinfo(result);

	return EXIT_SUCCESS;
}
int main (int argc, char **argv)
{

    int i, c;
    int pid_flags = 0;
    char *user = NULL;
    char *password = NULL;
    char *timeout = NULL;
    char *method = NULL;
    char *pid_path = NULL;
    char *conf_path = NULL;
    char *iface = NULL;

    int server_num = 0;
    const char *server_host[MAX_REMOTE_NUM];
    const char *server_port = NULL;

    int dns_thread_num = DNS_THREAD_NUM;

    int option_index = 0;
    static struct option long_options[] =
    {
        {"fast-open", no_argument, 0,  0 },
        {"port-start", required_argument, 0, 0 },
        {"port-end", required_argument, 0, 0 },
        {0,           0,           0,  0 }
    };

    opterr = 0;

    while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:c:i:d:a:uv",
                            long_options, &option_index)) != -1)
    {
        printf("option_index %d\n", option_index);
        switch (c)
        {
        case 0:

            if (option_index == 0)
            {
#ifdef TCP_FASTOPEN
                fast_open = 1;
                LOGD("using tcp fast open");
#else
                LOGE("tcp fast open is not supported by this environment");
#endif
            } else
            if (option_index == 1) 
            {
                start_port = atoi(optarg);
            } else
            if (option_index == 2) 
            {
                end_port = atoi(optarg);
            }

            break;
        case 's':
            server_host[server_num++] = optarg;
            break;
        case 'p':
            server_port = optarg;
            break;
        case 'k':
            password = optarg;
            break;
        case 'f':
            pid_flags = 1;
            pid_path = optarg;
            break;
        case 't':
            timeout = optarg;
            break;
        case 'm':
            method = optarg;
            break;
        case 'c':
            conf_path = optarg;
            break;
        case 'i':
            iface = optarg;
            break;
        case 'd':
            dns_thread_num = atoi(optarg);
            if (!dns_thread_num) FATAL("Invalid DNS thread number");
            break;
        case 'a':
            user = optarg;
            break;
        case 'u':
            udprelay = 1;
            break;
        case 'v':
            verbose = 1;
            break;
        }
    }

    printf("start %d end %d\n", start_port, end_port);

    if (opterr)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    if (conf_path != NULL)
    {
        jconf_t *conf = read_jconf(conf_path);
        if (server_num == 0)
        {
            server_num = conf->remote_num;
            for (i = 0; i < server_num; i++)
            {
                server_host[i] = conf->remote_addr[i].host;
            }
        }
        if (server_port == NULL) server_port = conf->remote_port;
        if (password == NULL) password = conf->password;
        if (method == NULL) method = conf->method;
        if (timeout == NULL) timeout = conf->timeout;
#ifdef TCP_FASTOPEN
        if (fast_open == 0) fast_open = conf->fast_open;
#endif
#ifdef HAVE_SETRLIMIT
        if (nofile == 0) nofile = conf->nofile;
        /*
         * no need to check the return value here since we will show
         * the user an error message if setrlimit(2) fails
         */
        if (nofile)
        {
            if (verbose)
            {
                LOGD("setting NOFILE to %d", nofile);
            }
            set_nofile(nofile);
        }
#endif
    }

    if ((start_port > 0 && end_port <= 0) || (start_port <= 0 && end_port > 0)) {
        printf("Both start_prot and end_port needs to be specified\n");
        usage();
        exit(EXIT_FAILURE);
    }

    if (server_port != NULL && start_port > 0) {
        printf("server port can't be set if you want to use a port range\n");
        usage();
        exit(EXIT_FAILURE);
    }

    if (server_num == 0 || (server_port == NULL && start_port <= 0) || password == NULL)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    if (timeout == NULL) timeout = "60";

    if (pid_flags)
    {
        USE_SYSLOG(argv[0]);
        daemonize(pid_path);
    }

    // ignore SIGPIPE
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    signal(SIGABRT, SIG_IGN);

    // setup asyncns
    asyncns_t *asyncns;
    if (!(asyncns = asyncns_new(dns_thread_num)))
    {
        FATAL("asyncns failed");
    }

    // setup keys
    LOGD("initialize ciphers... %s", method);
    int m = enc_init(password, method);

    // inilitialize ev loop
    struct ev_loop *loop = EV_DEFAULT;

    // inilitialize listen context
    struct listen_ctx listen_ctx_list[server_num + 1];

    // bind to each interface
    while (server_num > 0)
    {
        int index = --server_num;
        const char* host = server_host[index];
        int success = 1;
        int listenfd;

        if (start_port > 0) {
            server_port = itoa(start_port);
        }
        do {
            // Bind to port
            
            listenfd = create_and_bind(host, server_port);
            success = 1;
            if (listenfd < 0)
            {
                success = 0;
            }
            if (listen(listenfd, SOMAXCONN) == -1)
            {
                success = 0;
            }
            if (!success) {
                if (start_port < end_port) {
                    start_port++;
                    server_port = itoa(start_port);
                } else
                {
                    FATAL("Out of listen ports!");
                    exit(1);
                }
            }
        } while (!success);
        setnonblocking(listenfd);
        LOGD("server listening at port %s.", server_port);

        struct listen_ctx *listen_ctx = &listen_ctx_list[index + 1];

        // Setup proxy context
        listen_ctx->timeout = atoi(timeout);
        listen_ctx->asyncns = asyncns;
        listen_ctx->fd = listenfd;
        listen_ctx->method = m;
        listen_ctx->iface = iface;

        ev_io_init (&listen_ctx->io, accept_cb, listenfd, EV_READ);
        ev_io_start (loop, &listen_ctx->io);
    }

    // initialize the DNS
    struct listen_ctx *listen_ctx = &listen_ctx_list[0];
    int asyncnsfd = asyncns_fd(asyncns);
    listen_ctx->timeout = atoi(timeout);
    listen_ctx->asyncns = asyncns;
    listen_ctx->fd = asyncnsfd;
    listen_ctx->method = m;
    listen_ctx->iface = iface;
    ev_io_init (&listen_ctx->io, server_resolve_cb, asyncnsfd, EV_READ);
    ev_io_start (loop, &listen_ctx->io);

    // Setup UDP
    if (udprelay)
    {
        LOGD("udprelay enabled.");
        udprelay_init(server_host[0], server_port, dns_thread_num, m, listen_ctx->timeout, iface);
    }

    // setuid
    if (user != NULL)
        run_as(user);

    // start ev loop
    ev_run (loop, 0);
    return 0;
}
int init_udprelay(const char *server_host, const char *server_port,
#ifdef UDPRELAY_LOCAL
                  const char *remote_host, const char *remote_port,
#ifdef UDPRELAY_TUNNEL
                  const ss_addr_t tunnel_addr,
#endif
#endif
#ifdef UDPRELAY_REMOTE
                  int dns_thread_num,
#endif
                  int method, int timeout, const char *iface)
{
    // Inilitialize ev loop
    struct ev_loop *loop = EV_DEFAULT;

    // Inilitialize cache
    struct cache *conn_cache;
    cache_create(&conn_cache, MAX_UDP_CONN_NUM, free_cb);

    //////////////////////////////////////////////////
    // Setup server context

#ifdef UDPRELAY_REMOTE
    // setup asyncns
    asyncns_t *asyncns;
    if (!(asyncns = asyncns_new(dns_thread_num))) {
        FATAL("[udp] asyncns failed");
    }
    resolve_ctx = malloc(sizeof(struct resolve_ctx));

    int asyncnsfd = asyncns_fd(asyncns);
    ev_io_init(&resolve_ctx->io, query_resolve_cb, asyncnsfd, EV_READ);
    ev_io_start(loop, &resolve_ctx->io);

    resolve_ctx->asyncns = asyncns;
    resolve_ctx->asyncnsfd = asyncnsfd;
#endif

    // Bind to port
    int serverfd = create_server_socket(server_host, server_port);
    if (serverfd < 0) {
        FATAL("[udp] bind() error..");
    }
    setnonblocking(serverfd);

    server_ctx = new_server_ctx(serverfd);
    server_ctx->timeout = min(timeout, MAX_CONNECT_TIMEOUT);
    server_ctx->method = method;
    server_ctx->iface = iface;
    server_ctx->conn_cache = conn_cache;
#ifdef UDPRELAY_LOCAL
    server_ctx->remote_host = remote_host;
    server_ctx->remote_port = remote_port;
#ifdef UDPRELAY_TUNNEL
    server_ctx->tunnel_addr = tunnel_addr;
#endif
#endif
#ifdef UDPRELAY_REMOTE
    server_ctx->asyncns = asyncns;
#endif

    ev_io_start(loop, &server_ctx->io);

    return 0;
}