#define EID_STR #include "proxy.h" #include #include #include #include #include #include #include "params.h" #include "conev.h" #include "extend.h" #include "error.h" #ifdef _WIN32 #include #include #define close(fd) closesocket(fd) #else #include #include #include #include #include #include #include #if defined(__linux__) && defined(__GLIBC__) extern int accept4(int, struct sockaddr *__restrict, socklen_t *__restrict, int); #endif #ifdef __linux__ /* For SO_ORIGINAL_DST only (which is 0x50) */ #include "linux/netfilter_ipv4.h" #ifndef IP6T_SO_ORIGINAL_DST #define IP6T_SO_ORIGINAL_DST SO_ORIGINAL_DST #endif #endif #endif int NOT_EXIT = 1; static void on_cancel(int sig) { NOT_EXIT = 0; } void map_fix(struct sockaddr_ina *addr, char f6) { struct { uint64_t o64; uint16_t o16; uint16_t t16; uint32_t o32; } *ipv6m = (void *)&addr->in6.sin6_addr; if (addr->sa.sa_family == AF_INET && f6) { addr->sa.sa_family = AF_INET6; ipv6m->o32 = *(uint32_t *)(&addr->in.sin_addr); ipv6m->o64 = 0; ipv6m->o16 = 0; ipv6m->t16 = 0xffff; } else if (!ipv6m->o64 && !ipv6m->o16 && ipv6m->t16 == 0xffff && !f6) { addr->sa.sa_family = AF_INET; const struct in_addr *sin_addr_ptr = (struct in_addr *) &ipv6m->o32; addr->in.sin_addr = *sin_addr_ptr; } } static inline char addr_equ( const struct sockaddr_ina *a, const struct sockaddr_ina *b) { if (a->sa.sa_family == AF_INET) { return *((uint32_t *)(&a->in.sin_addr)) == *((uint32_t *)(&b->in.sin_addr)); } return *((uint64_t *)(&a->in6.sin6_addr)) == *((uint64_t *)(&b->in6.sin6_addr)) && *((uint64_t *)(&a->in6.sin6_addr) + 1) == *((uint64_t *)(&b->in6.sin6_addr) + 1); } static inline int nb_socket(int domain, int type) { #ifdef __linux__ int fd = socket(domain, type | SOCK_NONBLOCK, 0); #else int fd = socket(domain, type, 0); #endif if (fd < 0) { uniperror("socket"); return -1; } #ifdef _WIN32 unsigned long mode = 1; if (ioctlsocket(fd, FIONBIO, &mode) < 0) { uniperror("ioctlsocket"); close(fd); return -1; } #else #ifndef __linux__ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { uniperror("fcntl"); close(fd); return -1; } #endif #endif return fd; } static int resolve(char *host, int len, struct sockaddr_ina *addr, int type) { struct addrinfo hints = {0}, *res = 0; hints.ai_socktype = type; hints.ai_flags = AI_ADDRCONFIG; hints.ai_family = params.ipv6 ? AF_UNSPEC : AF_INET; char rchar = host[len]; host[len] = '\0'; LOG(LOG_S, "resolve: %s\n", host); if (getaddrinfo(host, 0, &hints, &res) || !res) { host[len] = rchar; return -1; } if (res->ai_addr->sa_family == AF_INET6) addr->in6 = *(struct sockaddr_in6 *)res->ai_addr; else addr->in = *(struct sockaddr_in *)res->ai_addr; freeaddrinfo(res); host[len] = rchar; return 0; } static int auth_socks5(int fd, const char *buffer, ssize_t n) { if (n <= 2 || (uint8_t)buffer[1] != (n - 2)) { return -1; } uint8_t c = S_AUTH_BAD; for (long i = 2; i < n; i++) if (buffer[i] == S_AUTH_NONE) { c = S_AUTH_NONE; break; } uint8_t a[2] = { S_VER5, c }; if (send(fd, a, sizeof(a), 0) < 0) { uniperror("send"); return -1; } return c != S_AUTH_BAD ? 0 : -1; } static int resp_s5_error(int fd, int e) { struct s5_rep s5r = { .ver = 0x05, .code = (uint8_t )e, .atp = S_ATP_I4 }; return send(fd, (char *)&s5r, sizeof(s5r), 0); } static int resp_error(int fd, int e, int flag) { if (flag == FLAG_S4) { struct s4_req s4r = { .cmd = e ? S4_ER : S4_OK }; return send(fd, (char *)&s4r, sizeof(s4r), 0); } else if (flag == FLAG_S5) { switch (unie(e)) { case 0: e = S_ER_OK; break; case ECONNREFUSED: e = S_ER_CONN; break; case EHOSTUNREACH: case ETIMEDOUT: e = S_ER_HOST; break; case ENETUNREACH: e = S_ER_NET; break; default: e = S_ER_GEN; } return resp_s5_error(fd, e); } #ifdef __linux__ if (params.transparent && (e == ECONNREFUSED || e == ETIMEDOUT)) { struct linger l = { .l_onoff = 1 }; if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) { uniperror("setsockopt SO_LINGER"); return -1; } } #endif return 0; } static int s4_get_addr(const char *buff, size_t n, struct sockaddr_ina *dst) { if (n < sizeof(struct s4_req) + 1) { return -1; } struct s4_req *r = (struct s4_req *)buff; if (r->cmd != S_CMD_CONN) { return -1; } if (ntohl(r->i4.s_addr) <= 255) { if (!params.resolve || buff[n - 1] != 0) { return -1; } char *id_end = strchr(buff + sizeof(*r), 0); if (!id_end) { return -1; } int len = (buff + n - id_end) - 2; if (len < 3 || len > 255) { return -1; } if (resolve(id_end + 1, len, dst, SOCK_STREAM)) { LOG(LOG_E, "not resolved: %.*s\n", len, id_end + 1); return -1; } } else { dst->in.sin_family = AF_INET; dst->in.sin_addr = r->i4; } dst->in.sin_port = r->port; return 0; } static int s5_get_addr(const char *buffer, size_t n, struct sockaddr_ina *addr, int type) { if (n < S_SIZE_MIN) { LOG(LOG_E, "ss: request too small\n"); return -S_ER_GEN; } struct s5_req *r = (struct s5_req *)buffer; size_t o = (r->atp == S_ATP_I4 ? S_SIZE_I4 : (r->atp == S_ATP_ID ? r->id.len + S_SIZE_ID : (r->atp == S_ATP_I6 ? S_SIZE_I6 : 0))); if (n < o) { LOG(LOG_E, "ss: bad request\n"); return -S_ER_GEN; } switch (r->atp) { case S_ATP_I4: addr->in.sin_family = AF_INET; addr->in.sin_addr = r->i4; break; case S_ATP_ID: if (!params.resolve) { return -S_ER_ATP; } if (r->id.len < 3 || resolve(r->id.domain, r->id.len, addr, type)) { LOG(LOG_E, "not resolved: %.*s\n", r->id.len, r->id.domain); return -S_ER_HOST; } break; case S_ATP_I6: if (!params.ipv6) return -S_ER_ATP; else { addr->in6.sin6_family = AF_INET6; addr->in6.sin6_addr = r->i6; } } memcpy(&addr->in.sin_port, &buffer[o - 2], sizeof(uint16_t)); return o; } static int s5_set_addr(char *buffer, size_t n, const struct sockaddr_ina *addr, char end) { struct s5_req *r = (struct s5_req *)buffer; if (n < S_SIZE_I4) { return -1; } if (addr->sa.sa_family == AF_INET) { if (end) { r = (struct s5_req *)(buffer - S_SIZE_I4); } r->atp = S_ATP_I4; r->i4 = addr->in.sin_addr; r->p4 = addr->in.sin_port; return S_SIZE_I4; } else { if (n < S_SIZE_I6) { return -1; } if (end) { r = (struct s5_req *)(buffer - S_SIZE_I6); } r->atp = S_ATP_I6; r->i6 = addr->in6.sin6_addr; r->p6 = addr->in6.sin6_port; return S_SIZE_I6; } return 0; } static int remote_sock(struct sockaddr_ina *dst, int type) { if (params.baddr.sin6_family == AF_INET6) { map_fix(dst, 6); } else { map_fix(dst, 0); } if (dst->sa.sa_family != params.baddr.sin6_family) { LOG(LOG_E, "different addresses family\n"); return -1; } int sfd = nb_socket(dst->sa.sa_family, type); if (sfd < 0) { uniperror("socket"); return -1; } if (socket_mod(sfd) < 0) { close(sfd); return -1; } if (dst->sa.sa_family == AF_INET6) { int no = 0; if (setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&no, sizeof(no))) { uniperror("setsockopt IPV6_V6ONLY"); close(sfd); return -1; } } if (bind(sfd, (struct sockaddr *)¶ms.baddr, SA_SIZE(¶ms.baddr)) < 0) { uniperror("bind"); close(sfd); return -1; } return sfd; } int create_conn(struct poolhd *pool, struct eval *val, const struct sockaddr_ina *dst, int next) { struct sockaddr_ina addr = *dst; int sfd = remote_sock(&addr, SOCK_STREAM); if (sfd < 0) { return -1; } #ifdef __linux__ int syn_count = 1; if (setsockopt(sfd, IPPROTO_TCP, TCP_SYNCNT, (char *)&syn_count, sizeof(syn_count))) { uniperror("setsockopt TCP_SYNCNT"); close(sfd); return -1; } #ifdef TCP_FASTOPEN_CONNECT int yes = 1; if (params.tfo && setsockopt(sfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (char *)&yes, sizeof(yes))) { uniperror("setsockopt TCP_FASTOPEN_CONNECT"); close(sfd); return -1; } #endif #endif int one = 1; if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one))) { uniperror("setsockopt TCP_NODELAY"); close(sfd); return -1; } if (params.debug) { INIT_ADDR_STR((*dst)); LOG(LOG_S, "new conn: fd=%d, pair=%d, addr=%s:%d\n", sfd, val->fd, ADDR_STR, ntohs(dst->in.sin_port)); } int status = connect(sfd, &addr.sa, SA_SIZE(&addr)); if (status == 0 && params.tfo) { LOG(LOG_S, "TFO supported!\n"); } if (status < 0 && get_e() != EINPROGRESS && get_e() != EAGAIN) { uniperror("connect"); close(sfd); return -1; } struct eval *pair = add_event(pool, next, sfd, POLLOUT); if (!pair) { close(sfd); return -1; } if (mod_etype(pool, val, 0) < 0) { uniperror("mod_etype"); return -1; } val->pair = pair; pair->pair = val; #ifdef __NetBSD__ pair->in6 = addr.in6; #else pair->in6 = dst->in6; #endif pair->flag = FLAG_CONN; val->type = EV_IGNORE; return 0; } static int udp_associate(struct poolhd *pool, struct eval *val, const struct sockaddr_ina *dst) { struct sockaddr_ina addr = *dst; int ufd = remote_sock(&addr, SOCK_DGRAM); if (ufd < 0) { return -1; } struct eval *pair = add_event(pool, EV_UDP_TUNNEL, ufd, POLLIN); if (!pair) { close(ufd); return -1; } if (dst->in6.sin6_port != 0) { if (connect(ufd, &addr.sa, SA_SIZE(&addr)) < 0) { uniperror("connect"); del_event(pool, pair); return -1; } pair->in6 = addr.in6; } // socklen_t sz = sizeof(addr); if (getsockname(val->fd, &addr.sa, &sz)) { uniperror("getsockname"); return -1; } addr.in.sin_port = 0; int cfd = nb_socket(addr.sa.sa_family, SOCK_DGRAM); if (cfd < 0) { uniperror("socket"); del_event(pool, pair); return -1; } if (bind(cfd, &addr.sa, SA_SIZE(&addr)) < 0) { uniperror("bind"); del_event(pool, pair); close(cfd); return -1; } struct eval *client = add_event(pool, EV_UDP_TUNNEL, cfd, POLLIN); if (!client) { del_event(pool, pair); close(cfd); return -1; } if (params.debug) { INIT_ADDR_STR((*dst)); LOG(LOG_S, "udp associate: fds=%d,%d,%d addr=%s:%d\n", ufd, cfd, val->fd, ADDR_STR, ntohs(dst->in.sin_port)); } val->type = EV_IGNORE; val->pair = client; client->pair = pair; pair->pair = val; client->flag = FLAG_CONN; client->in6 = val->in6; client->in6.sin6_port = 0; sz = sizeof(addr); if (getsockname(cfd, &addr.sa, &sz)) { uniperror("getsockname"); return -1; } struct s5_req s5r = { .ver = 0x05 }; int len = s5_set_addr((char *)&s5r, sizeof(s5r), &addr, 0); if (len < 0) { return -1; } if (send(val->fd, (char *)&s5r, len, 0) < 0) { uniperror("send"); return -1; } if (mod_etype(pool, val, 0)) { uniperror("mod_etype"); return -1; } return 0; } #ifdef __linux__ static inline int transp_conn(struct poolhd *pool, struct eval *val) { struct sockaddr_ina remote, self; socklen_t rlen = sizeof(remote), slen = sizeof(self); if (getsockopt(val->fd, IPPROTO_IP, SO_ORIGINAL_DST, &remote, &rlen) != 0) { if (getsockopt(val->fd, IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, &remote, &rlen) != 0) { uniperror("getsockopt SO_ORIGINAL_DST"); return -1; } } if (getsockname(val->fd, &self.sa, &slen) < 0) { uniperror("getsockname"); return -1; } if (self.sa.sa_family == remote.sa.sa_family && self.in.sin_port == remote.in.sin_port && addr_equ(&self, &remote)) { LOG(LOG_E, "connect to self, ignore\n"); return -1; } int error = connect_hook(pool, val, &remote, EV_CONNECT); if (error) { uniperror("connect_hook"); return -1; } return 0; } #endif static int on_accept(struct poolhd *pool, const struct eval *val) { struct sockaddr_ina client; struct eval *rval; while (1) { socklen_t len = sizeof(client); #ifdef __linux__ int c = accept4(val->fd, &client.sa, &len, SOCK_NONBLOCK); #else int c = accept(val->fd, &client.sa, &len); #endif if (c < 0) { if (get_e() == EAGAIN || get_e() == EINPROGRESS) break; uniperror("accept"); return -1; } LOG(LOG_S, "accept: fd=%d\n", c); #ifndef __linux__ #ifdef _WIN32 unsigned long mode = 1; if (ioctlsocket(c, FIONBIO, &mode) < 0) { uniperror("ioctlsocket"); #else if (fcntl(c, F_SETFL, O_NONBLOCK) < 0) { uniperror("fcntl"); #endif close(c); continue; } #endif int one = 1; if (setsockopt(c, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one))) { uniperror("setsockopt TCP_NODELAY"); close(c); continue; } if (!(rval = add_event(pool, EV_REQUEST, c, POLLIN))) { close(c); continue; } rval->in6 = client.in6; #ifdef __linux__ if (params.transparent && transp_conn(pool, rval) < 0) { del_event(pool, rval); continue; } #endif } return 0; } static int on_tunnel(struct poolhd *pool, struct eval *val, char *buffer, size_t bfsize, int etype) { ssize_t n = 0; struct eval *pair = val->pair; if (etype & POLLOUT) { LOG(LOG_S, "pollout (fd=%d)\n", val->fd); val = pair; pair = val->pair; } if (val->buff.data) { if (etype & POLLHUP) { return -1; } n = val->buff.size - val->buff.offset; ssize_t sn = tcp_send_hook(pair, val->buff.data + val->buff.offset, n, n); if (sn < 0) { uniperror("send"); return -1; } if (sn < n) { val->buff.offset += sn; return 0; } free(val->buff.data); val->buff.data = 0; val->buff.size = 0; val->buff.offset = 0; if (mod_etype(pool, val, POLLIN) || mod_etype(pool, pair, POLLIN)) { uniperror("mod_etype"); return -1; } } do { n = tcp_recv_hook(pool, val, buffer, bfsize); //if (n < 0 && get_e() == EAGAIN) { if (n == 0) { break; } if (n < 0) { return -1; } ssize_t sn = tcp_send_hook(pair, buffer, bfsize, n); if (sn < 0) { uniperror("send"); return -1; } if (sn < n) { LOG(LOG_S, "send: %zd != %zd (fd=%d)\n", sn, n, pair->fd); assert(!(val->buff.size || val->buff.offset)); val->buff.size = n - sn; if (!(val->buff.data = malloc(n - sn))) { uniperror("malloc"); return -1; } memcpy(val->buff.data, buffer + sn, n - sn); if (mod_etype(pool, val, 0) || mod_etype(pool, pair, POLLOUT)) { uniperror("mod_etype"); return -1; } break; } } while (n == bfsize); return 0; } static int on_udp_tunnel(struct eval *val, char *buffer, size_t bfsize) { char *data = buffer; size_t data_len = bfsize; if (val->flag != FLAG_CONN) { data += S_SIZE_I6; data_len -= S_SIZE_I6; } struct sockaddr_ina addr = {0}; struct eval *pair = val->flag == FLAG_CONN ? val->pair : val->pair->pair; do { socklen_t asz = sizeof(addr); ssize_t n = recvfrom(val->fd, data, data_len, 0, &addr.sa, &asz); if (n < 1) { if (n && get_e() == EAGAIN) break; uniperror("recv udp"); return -1; } val->recv_count += n; if (val->round_sent == 0) { val->round_count++; val->round_sent += n; pair->round_sent = 0; } ssize_t ns; if (val->flag == FLAG_CONN) { if (!val->in6.sin6_port) { if (!addr_equ(&addr, (struct sockaddr_ina *)&val->in6)) { return 0; } if (connect(val->fd, &addr.sa, SA_SIZE(&addr)) < 0) { uniperror("connect"); return -1; } val->in6 = addr.in6; } if (*(data + 2) != 0) { // frag continue; } int offs = s5_get_addr(data, n, &addr, SOCK_DGRAM); if (offs < 0) { LOG(LOG_E, "udp parse error\n"); return -1; } if (!pair->in6.sin6_port) { if (params.baddr.sin6_family == AF_INET6) { map_fix(&addr, 6); } if (params.baddr.sin6_family != addr.sa.sa_family) { return -1; } if (connect(pair->fd, &addr.sa, SA_SIZE(&addr)) < 0) { uniperror("connect"); return -1; } pair->in6 = addr.in6; } ns = udp_hook(pair, data + offs, bfsize - offs, n - offs, (struct sockaddr_ina *)&pair->in6); } else { map_fix(&addr, 0); memset(buffer, 0, S_SIZE_I6); int offs = s5_set_addr(data, S_SIZE_I6, &addr, 1); if (offs < 0 || offs > S_SIZE_I6) { return -1; } ns = send(pair->fd, data - offs, offs + n, 0); } if (ns < 0) { uniperror("sendto"); return -1; } } while(1); return 0; } static inline int on_request(struct poolhd *pool, struct eval *val, char *buffer, size_t bfsize) { struct sockaddr_ina dst = {0}; ssize_t n = recv(val->fd, buffer, bfsize, 0); if (n < 1) { if (n) uniperror("ss recv"); return -1; } int error = 0; if (*buffer == S_VER5) { if (val->flag != FLAG_S5) { if (auth_socks5(val->fd, buffer, n)) { return -1; } val->flag = FLAG_S5; return 0; } if (n < S_SIZE_MIN) { LOG(LOG_E, "ss: request too small (%zd)\n", n); return -1; } struct s5_req *r = (struct s5_req *)buffer; int s5e = 0; switch (r->cmd) { case S_CMD_CONN: s5e = s5_get_addr(buffer, n, &dst, SOCK_STREAM); if (s5e >= 0) { error = connect_hook(pool, val, &dst, EV_CONNECT); } break; case S_CMD_AUDP: if (params.udp) { s5e = s5_get_addr(buffer, n, &dst, SOCK_DGRAM); if (s5e >= 0) { error = udp_associate(pool, val, &dst); } break; } default: LOG(LOG_E, "ss: unsupported cmd: 0x%x\n", r->cmd); s5e = -S_ER_CMD; } if (s5e < 0) { if (resp_s5_error(val->fd, -s5e) < 0) uniperror("send"); return -1; } } else if (*buffer == S_VER4) { val->flag = FLAG_S4; error = s4_get_addr(buffer, n, &dst); if (error) { if (resp_error(val->fd, error, FLAG_S4) < 0) uniperror("send"); return -1; } error = connect_hook(pool, val, &dst, EV_CONNECT); } else { LOG(LOG_E, "ss: invalid version: 0x%x (%zd)\n", *buffer, n); return -1; } if (error) { int en = get_e(); if (resp_error(val->fd, en ? en : error, val->flag) < 0) uniperror("send"); LOG(LOG_S, "ss error: %d\n", en); return -1; } return 0; } static inline int on_connect(struct poolhd *pool, struct eval *val, int e) { int error = 0; socklen_t len = sizeof(error); if (e) { if (getsockopt(val->fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len)) { uniperror("getsockopt SO_ERROR"); return -1; } } else { if (mod_etype(pool, val, POLLIN) || mod_etype(pool, val->pair, POLLIN)) { uniperror("mod_etype"); return -1; } int t = params.auto_level <= AUTO_NOBUFF ? EV_TUNNEL : EV_FIRST_TUNNEL; val->type = t; val->pair->type = t; } if (resp_error(val->pair->fd, error, val->pair->flag) < 0) { uniperror("send"); return -1; } return e ? -1 : 0; } static void close_conn(struct poolhd *pool, struct eval *val) { struct eval *cval = val; do { LOG(LOG_S, "close: fd=%d (pair=%d), recv: %zd, rounds: %d\n", cval->fd, cval->pair ? cval->pair->fd : -1, cval->recv_count, cval->round_count); cval = cval->pair; } while (cval && cval != val); del_event(pool, val); } int event_loop(int srvfd) { size_t bfsize = params.bfsize; struct poolhd *pool = init_pool(params.max_open * 2 + 1); if (!pool) { close(srvfd); return -1; } if (!add_event(pool, EV_ACCEPT, srvfd, POLLIN)) { destroy_pool(pool); close(srvfd); return -1; } char *buffer = malloc(params.bfsize); if (!buffer) { uniperror("malloc"); destroy_pool(pool); return -1; } struct eval *val; int i = -1, etype; while (NOT_EXIT) { val = next_event(pool, &i, &etype); if (!val) { if (get_e() == EINTR) continue; uniperror("(e)poll"); break; } assert(val->type >= 0 && val->type < sizeof(eid_name)/sizeof(*eid_name)); LOG(LOG_L, "new event: fd: %d, evt: %s, mod_iter: %llu\n", val->fd, eid_name[val->type], val->mod_iter); switch (val->type) { case EV_ACCEPT: if ((etype & POLLHUP) || on_accept(pool, val)) NOT_EXIT = 0; continue; case EV_REQUEST: if ((etype & POLLHUP) || on_request(pool, val, buffer, bfsize)) close_conn(pool, val); continue; case EV_FIRST_TUNNEL: if (on_first_tunnel(pool, val, buffer, bfsize, etype)) close_conn(pool, val); continue; case EV_TUNNEL: if (on_tunnel(pool, val, buffer, bfsize, etype)) close_conn(pool, val); continue; case EV_UDP_TUNNEL: if (on_udp_tunnel(val, buffer, bfsize)) close_conn(pool, val); continue; case EV_CONNECT: if (on_connect(pool, val, etype & POLLERR)) close_conn(pool, val); continue; case EV_IGNORE: if (etype & (POLLHUP | POLLERR | POLLRDHUP)) close_conn(pool, val); continue; default: LOG(LOG_E, "???\n"); NOT_EXIT = 0; } } LOG(LOG_S, "exit\n"); free(buffer); destroy_pool(pool); return 0; } int listen_socket(const struct sockaddr_ina *srv) { int srvfd = nb_socket(srv->sa.sa_family, SOCK_STREAM); if (srvfd < 0) { uniperror("socket"); return -1; } int opt = 1; if (setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) == -1) { uniperror("setsockopt"); close(srvfd); return -1; } if (bind(srvfd, &srv->sa, SA_SIZE(srv)) < 0) { uniperror("bind"); close(srvfd); return -1; } if (listen(srvfd, 10)) { uniperror("listen"); close(srvfd); return -1; } return srvfd; } int run(const struct sockaddr_ina *srv) { #ifdef SIGPIPE if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) uniperror("signal SIGPIPE!"); #endif signal(SIGINT, on_cancel); signal(SIGTERM, on_cancel); int fd = listen_socket(srv); if (fd < 0) { return -1; } return event_loop(fd); }