From c26ac3967e26edb95d13a7bf8e1dc8b358a9022b Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Tue, 8 Dec 2020 10:47:18 +0100 Subject: [PATCH] Apply patch provided by @rain2fog in #801 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pppd → tun interface + embedded PPP code --- src/http.c | 5 + src/io.c | 931 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/ipv4.h | 1 + src/tunnel.c | 141 +++++++- src/tunnel.h | 9 + 5 files changed, 1070 insertions(+), 17 deletions(-) diff --git a/src/http.c b/src/http.c index 58e8fc7a..ae01f3cf 100644 --- a/src/http.c +++ b/src/http.c @@ -825,6 +825,11 @@ static int parse_xml_config(struct tunnel *tunnel, const char *buffer) if (!gateway) log_warn("No gateway address, using interface for routing\n"); + if (tunnel->use_tun) { + tunnel->ipv4.ip_addr.s_addr = inet_addr(gateway); + tunnel->ipv4.peer_addr.s_addr = inet_addr("192.0.2.1"); + } + // The dns search string val = buffer; while ((val = xml_find('<', "dns", val, 2))) { diff --git a/src/io.c b/src/io.c index 577623b4..86600c56 100644 --- a/src/io.c +++ b/src/io.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -175,6 +176,845 @@ static os_semaphore_t sem_pppd_ready; static os_semaphore_t sem_if_config; static os_semaphore_t sem_stop_io; +struct lcp_header { + uint8_t code; +#define LCP_CONF_REQUEST 1 +#define LCP_CONF_ACK 2 +#define LCP_CONF_NAK 3 +#define LCP_CONF_REJECT 4 +#define LCP_TERM_REQUEST 5 +#define LCP_TERM_ACK 6 +#define LCP_CODE_REJECT 7 +#define LCP_PROT_REJECT 8 +#define LCP_ECHO_REQUEST 9 +#define LCP_ECHO_REPLY 10 +#define LCP_DISCARD 11 + uint8_t id; + uint16_t length; +}; + +const char *lcp_code_name[] = { + "", + "Configure-Request", + "Configure-Ack", + "Configure-Nak", + "Configure-Reject", + "Terminate-Request", + "Terminate-Ack", + "Code-Reject", + "Protocol-Reject", + "Echo-Request", + "Echo-Reply", + "Discard-Request", +}; +struct lcp_conf_request { + struct lcp_header header; +}; +struct conf_option { + uint8_t type; +#define LCP_COPT_MRU 1 +#define LCP_COPT_ACCM 2 +#define LCP_COPT_AUTH 3 +#define LCP_COPT_QUALITY 4 +#define LCP_COPT_MAGIC 5 +#define LCP_COPT_PFC 7 +#define LCP_COPT_ACFC 8 + uint8_t length; + uint8_t data[]; +}; +static void *co_data(struct conf_option *co) +{ + return co->data; +} + +struct conf_option_list { + struct conf_option *head; + struct conf_option *tail; +}; + +struct lcp_option_conf { + int flag; +#define LCP_OF_VALID 0x10000 + int len; + const char *name; +}; +int default_mru = 1534; +const struct lcp_option_conf lcp_valid_options[256] = { + [0] = { 0, 0, "RESERVED", }, + [1] = {LCP_OF_VALID, 4, "MRU", }, /* RFC 1661 */ + [2] = {LCP_OF_VALID, 6, "Async-Control-Character-Map",}, /* RFC 1172 */ + [3] = {LCP_OF_VALID, 4, "Auth-Protocol", }, + [4] = {LCP_OF_VALID, 4, "Quality-Protocol", }, + [5] = {LCP_OF_VALID, 6, "Magic-Num", }, + [6] = {LCP_OF_VALID, 6, "Link-Quality-Monitoring", }, /* RFC 1172 */ + [7] = {LCP_OF_VALID, 2, "Protocol-Field-Comp", }, + [8] = {LCP_OF_VALID, 2, "Addr&Ctrl-Field-Comp", }, +}; +struct lcp_option_value { + int flag; +#define LCP_OF_DEFAULT 0x20000 +#define LCP_OF_DISABLE 0x00100 +#define LCP_OF_LINK 0x000FF + int len; + union { + int boolean; + int number; + } u; + void *extra; +}; +int magic_seed = 0x64696E67; +/* +struct conf_option lcp_default[256] = { + [1] = {LCP_OF_DEFAULT, {.number = 1500}, }, + [2] = {LCP_OF_DEFAULT, {.number = 0xFFFFFFFF}, }, + [3] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, + [4] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, + [5] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, + [6] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, + [7] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, + [8] = {LCP_OF_DEFAULT | LCP_OF_DISABLE, }, +}; +*/ +struct conf_option *lcp_self[256] = { + NULL, +}; +struct conf_option *lcp_peer[256] = { + NULL, +}; + +static int lcp_id = 0; +int conf_option_get(struct conf_option **options, int type, void *data, int len) +{ + struct conf_option *opt = options[type]; + if (opt != NULL) { + int copy = opt->length - 2; + if (copy > len) { + copy = len; + } + memcpy(data, opt->data, copy); + return 0; + } + return -1; +} +int conf_option_set(struct conf_option **options, int type, int len, void *data) +{ + struct conf_option *opt = options[type]; + if (len == 0) { + options[type] = NULL; + free(opt); + return 0; + } + + if (opt == NULL) { + opt = malloc(len); + } else if (opt->length != len) { + opt = realloc(opt, len); + } + if (opt != NULL) { + options[type] = opt; + opt->type = type; + opt->length = len; + if (len > 2) { + memcpy(opt->data, data, len - 2); + } + } + return (opt == NULL) ? -1 : 0; +} +struct conf_option *conf_option_init(struct conf_option_list *optlist) +{ + int header = sizeof(struct lcp_header) + sizeof(uint16_t); + optlist->head = malloc(header + default_mru); + if (optlist->head) { + memset(optlist->head, 0, header + default_mru); + optlist->head = (struct conf_option *)(((uint8_t *)optlist->head) + header); + optlist->tail = optlist->head; + } + return optlist->head; +} +int conf_option_encode(struct conf_option_list *optlist, int type, int len, void *data) +{ + optlist->tail->type = type; + optlist->tail->length = len; + if (len > 2) { + memcpy(optlist->tail->data, data, len - 2); + } + optlist->tail = (struct conf_option *)(optlist->tail->data + len - 2); + return 0; +} +int conf_option_length(struct conf_option_list *optlist) +{ + int len = 0; + if (optlist) { + len = (uint8_t *)optlist->tail - (uint8_t *)optlist->head; + } + return len; +} +int conf_option_free(struct conf_option_list *optlist) +{ + if (optlist->head != NULL) { + int header = sizeof(struct lcp_header) + sizeof(uint16_t); + free((uint8_t *)optlist->head - header); + optlist->head = optlist->tail = NULL; + } + return 0; +} +int lcp_option_send(struct tunnel *tunnel, int id, int code, struct conf_option_list *optlist, int force) +{ + int ret = -1; + if (optlist && optlist->head) { + uint8_t *head = (uint8_t *)optlist->head; + struct lcp_header *header = ((struct lcp_header *)head) - 1; + unsigned short *ppp_type = ((unsigned short *)header) - 1; + int len = conf_option_length(optlist); + int hdrlen = sizeof(struct lcp_header) + sizeof(uint16_t); + + if (len > 0 || force) { + ssize_t pktsize; + struct ppp_packet *packet = NULL; + + *ppp_type = htons(PPP_LCP); + header->code = code; + header->id = id ? id : lcp_id ++; + header->length = htons(len + sizeof(*header)); + + pktsize = hdrlen + len; + packet = malloc(sizeof(*packet) + 6 + pktsize); + if (packet == NULL) { + goto out; + } + packet->len = pktsize; + memcpy(pkt_data(packet), ppp_type, pktsize); + + log_debug("%s ---> gateway (%lu bytes)\n", PPP_DAEMON, + packet->len); +#if HAVE_USR_SBIN_PPPD + log_packet("pppd: ", packet->len, pkt_data(packet)); +#else + log_packet("ppp: ", packet->len, pkt_data(packet)); +#endif + pool_push(&tunnel->pty_to_ssl_pool, packet); + } + ret = 0; + } + +out: + return ret; +} +int conf_request(struct tunnel *tunnel) +{ + int ret = 0; + struct conf_option_list request; + uint16_t mru = htons(default_mru); + int magic = htonl(magic_seed); + conf_option_init(&request); + conf_option_encode(&request, LCP_COPT_MRU, 4, &mru); + conf_option_encode(&request, LCP_COPT_MAGIC, 6, &magic); + ret = lcp_option_send(tunnel, 0, LCP_CONF_REQUEST, &request, 1); + conf_option_free(&request); + return ret; +} +int lcp_packet(struct tunnel *tunnel, void *packet, int len) +{ + struct lcp_header *header = packet; + + log_debug("packet %s\n", lcp_code_name[header->code]); + switch (header->code) { + case LCP_CONF_REQUEST: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + struct conf_option_list ack; + struct conf_option_list nack; + struct conf_option_list reject; + conf_option_init(&ack); + conf_option_init(&nack); + conf_option_init(&reject); + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + p += sprintf(p, "option %s: ", lcp_valid_options[co->type].name); + switch (co->type) { + case LCP_COPT_ACCM: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + conf_option_set(lcp_peer, co->type, co->length, co_data(co)); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + case LCP_COPT_AUTH: + switch (ntohs(*(uint16_t *)co_data(co))) { + case PPP_CHAP: + { + struct { + uint16_t chap; + uint8_t algo; + } *payload = (typeof(*payload) *)co_data(co); + p += sprintf(p, "CHAP %d", payload->algo); + break; + } + default: + p += sprintf(p, "%x", ntohs(*(uint16_t *)co_data(co))); + break; + } + conf_option_set(lcp_peer, co->type, co->length, co_data(co)); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + case LCP_COPT_MAGIC: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + conf_option_set(lcp_peer, co->type, co->length, co_data(co)); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + case LCP_COPT_PFC: + case LCP_COPT_ACFC: + conf_option_set(lcp_peer, co->type, co->length, co_data(co)); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + default: + conf_option_encode(&reject, co->type, co->length, co_data(co)); + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + if (header->code == LCP_CONF_REQUEST) { + int ret = -1; + ret = lcp_option_send(tunnel, header->id, LCP_CONF_ACK, &ack, 0); + if (ret < 0) { + log_error("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + ret = lcp_option_send(tunnel, header->id, LCP_CONF_NAK, &nack, 0); + if (ret < 0) { + log_error("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + ret = lcp_option_send(tunnel, header->id, LCP_CONF_REJECT, &reject, 0); + if (ret < 0) { + log_error("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + switch (tunnel->tun_state) { + case TUN_PPP_LCP: + log_debug("\n\nmove to establishment phase\n"); + tunnel->tun_state = TUN_PPP_IPCP; + default: + break; + } + } + conf_option_free(&ack); + conf_option_free(&nack); + conf_option_free(&reject); + + break; + } + case LCP_CONF_ACK: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + conf_option_set(lcp_self, co->type, co->length, co_data(co)); + p += sprintf(p, "option %s: ", lcp_valid_options[co->type].name); + switch (co->type) { + case LCP_COPT_MRU: + p += sprintf(p, "%d", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_ACCM: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_AUTH: + p += sprintf(p, "%x", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_MAGIC: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_PFC: + case LCP_COPT_ACFC: + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + + if (tunnel->tun_state == TUN_PPP_LCP) { + log_debug("\n\nentering authenticate phase\n"); + tunnel->tun_state = TUN_PPP_IPCP; + } + break; + } + case LCP_CONF_NAK: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + conf_option_set(lcp_self, co->type, 0, NULL); + p += sprintf(p, "option %s: ", lcp_valid_options[co->type].name); + switch (co->type) { + case LCP_COPT_MRU: + p += sprintf(p, "%d", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_ACCM: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_AUTH: + p += sprintf(p, "%x", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_MAGIC: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_PFC: + case LCP_COPT_ACFC: + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + break; + } + case LCP_CONF_REJECT: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + conf_option_set(lcp_self, co->type, 0, NULL); + p += sprintf(p, "option %s: ", lcp_valid_options[co->type].name); + switch (co->type) { + case LCP_COPT_MRU: + p += sprintf(p, "%d", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_ACCM: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_AUTH: + p += sprintf(p, "%x", ntohs(*(uint16_t *)co_data(co))); + break; + case LCP_COPT_MAGIC: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case LCP_COPT_PFC: + case LCP_COPT_ACFC: + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + break; + } + case LCP_TERM_REQUEST: + break; + case LCP_TERM_ACK: + break; + case LCP_CODE_REJECT: + break; + case LCP_PROT_REJECT: + break; + case LCP_ECHO_REQUEST: + break; + case LCP_ECHO_REPLY: + break; + case LCP_DISCARD: + break; + default: + /* ignore */ + break; + } + + return 0; +} + +struct ipcp_header { + uint8_t code; +#define IPCP_CONF_REQUEST 1 +#define IPCP_CONF_ACK 2 +#define IPCP_CONF_NAK 3 +#define IPCP_CONF_REJECT 4 +#define IPCP_TERM_REQUEST 5 +#define IPCP_TERM_ACK 6 +#define IPCP_CODE_REJECT 7 + uint8_t id; + uint16_t length; +}; + +#define IPCP_COPT_ADDRESSES 1 +#define IPCP_COPT_COMPRESS 2 +#define IPCP_COPT_ADDRESS 3 +#define IPCP_COPT_PRIMARY_DNS 129 +#define IPCP_COPT_SECONDARY_DNS 131 +const char *ipcp_valid_options[256] = { + [0] = "", + [1] = "IPCP Option addresses", + [2] = "IPCP Option compress", + [3] = "IPCP Option address", + [129] = "IPCP Option primary dns", + [131] = "IPCP Option secondary dns", +}; + +const char *ipcp_code_name[] = { + "", + "IPCP Configure-Request", + "IPCP Configure-Ack", + "IPCP Configure-Nak", + "IPCP Configure-Reject", + "IPCP Terminate-Request", + "IPCP Terminate-Ack", + "IPCP Code-Reject", +}; + +int nroutes = 0; +char **routes = NULL; + +uint32_t ip_address = 0; +uint32_t peer_address = 0; +uint32_t primary_dns = 0; +uint32_t secondary_dns = 0; + +int ipcp_add_route(struct tunnel *tunnel, uint32_t dst, uint32_t mask, uint32_t gw) +{ + int ret = 0; + struct rtentry rt; + struct sockaddr_in *sin = NULL; + + memset(&rt, 0, sizeof(rt)); + rt.rt_dev = tunnel->tun_iface; + rt.rt_flags = RTF_GATEWAY; + + sin = (struct sockaddr_in *)&rt.rt_dst; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = dst & htonl(mask); + + sin = (struct sockaddr_in *)&rt.rt_gateway; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = gw; + + sin = (struct sockaddr_in *)&rt.rt_genmask; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = htonl(mask); + + int sd = socket(AF_INET, SOCK_DGRAM, 0); + ret = ioctl(sd, SIOCADDRT, &rt); + if (ret == 0) { + log_debug("route add success\n"); + } else { + log_error("route add failed %d: %s\n", errno, strerror(errno)); + } + close(sd); + + return ret; +} +int ipcp_option_send(struct tunnel *tunnel, int id, int code, struct conf_option_list *optlist, int force) +{ + int ret = -1; + if (optlist && optlist->head) { + uint8_t *packet = (uint8_t *)optlist->head; + struct ipcp_header *header = ((struct ipcp_header *)packet) - 1; + unsigned short *ppp_type = ((unsigned short *)header) - 1; + int len = conf_option_length(optlist); + int hdrlen = sizeof(struct ipcp_header) + sizeof(uint16_t); + + if (len > 0 || force) { + ssize_t pktsize; + struct ppp_packet *packet = NULL; + + *ppp_type = htons(PPP_IPCP); + header->code = code; + header->id = id ? id : lcp_id ++; + header->length = htons(len + sizeof(*header)); + log_debug("send ipcp %d\n", len); + + pktsize = hdrlen + len; + packet = malloc(sizeof(*packet) + 6 + pktsize); + if (packet == NULL) { + goto out; + } + packet->len = pktsize; + memcpy(pkt_data(packet), ppp_type, pktsize); + + log_debug("%s ---> gateway (%lu bytes)\n", PPP_DAEMON, + packet->len); +#if HAVE_USR_SBIN_PPPD + log_packet("pppd: ", packet->len, pkt_data(packet)); +#else + log_packet("ppp: ", packet->len, pkt_data(packet)); +#endif + pool_push(&tunnel->pty_to_ssl_pool, packet); + } + ret = 0; + } + +out: + return ret; +} +int ipcp_packet(struct tunnel *tunnel, void *packet, int len) +{ + int ret = 0; + struct ipcp_header *header = packet; + + log_debug("packet %s\n", ipcp_code_name[header->code]); + switch (header->code) { + case IPCP_CONF_REQUEST: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + struct conf_option_list ack; + struct conf_option_list nack; + struct conf_option_list reject; + struct conf_option_list request; + conf_option_init(&ack); + conf_option_init(&nack); + conf_option_init(&reject); + conf_option_init(&request); + olen -= sizeof(struct ipcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + p += sprintf(p, "option %s: ", ipcp_valid_options[co->type]); + switch (co->type) { + case IPCP_COPT_ADDRESSES: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + case IPCP_COPT_COMPRESS: + conf_option_encode(&ack, co->type, co->length, co_data(co)); + break; + case IPCP_COPT_ADDRESS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + conf_option_encode(&ack, co->type, co->length, co_data(co)); + peer_address = *(uint32_t *)co_data(co); + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + if (header->code == IPCP_CONF_REQUEST) { + int ret = -1; + ret = ipcp_option_send(tunnel, header->id, IPCP_CONF_ACK, &ack, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + ret = ipcp_option_send(tunnel, header->id, IPCP_CONF_NAK, &nack, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + ret = ipcp_option_send(tunnel, header->id, IPCP_CONF_REJECT, &reject, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + + do { + uint32_t compress = htonl(0x002d0f01); + conf_option_encode(&request, IPCP_COPT_ADDRESS, 6, &ip_address); + conf_option_encode(&request, IPCP_COPT_COMPRESS, 6, &compress); + // conf_option_encode(&request, IPCP_COPT_PRIMARY_DNS, 6, &primary_dns); + // conf_option_encode(&request, IPCP_COPT_SECONDARY_DNS, 6, &secondary_dns); + ret = ipcp_option_send(tunnel, 0, IPCP_CONF_REQUEST, &request, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + } while (0); + } + conf_option_free(&ack); + conf_option_free(&nack); + conf_option_free(&reject); + conf_option_free(&request); + + break; + } + case IPCP_CONF_ACK: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + p += sprintf(p, "option %s: ", ipcp_valid_options[co->type]); + switch (co->type) { + case IPCP_COPT_ADDRESSES: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case IPCP_COPT_COMPRESS: + break; + case IPCP_COPT_ADDRESS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + ip_address = *(uint32_t *)co_data(co); + break; + case IPCP_COPT_PRIMARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + primary_dns = *(uint32_t *)co_data(co); + break; + case IPCP_COPT_SECONDARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + secondary_dns = *(uint32_t *)co_data(co); + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + + int tun_ifup(char *ifname, uint32_t ip_addr, uint32_t peer_addr); + tun_ifup(tunnel->tun_iface, ip_address, peer_address); + ipv4_set_tunnel_routes(tunnel); + break; + } + case IPCP_CONF_NAK: + { + int send_request = 0; + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + p += sprintf(p, "option %s: ", ipcp_valid_options[co->type]); + switch (co->type) { + case IPCP_COPT_ADDRESSES: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case IPCP_COPT_COMPRESS: + break; + case IPCP_COPT_ADDRESS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + if (ip_address != *(uint32_t *)co_data(co)) { + ip_address = *(uint32_t *)co_data(co); + send_request = 1; + } + break; + case IPCP_COPT_PRIMARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + if (primary_dns != *(uint32_t *)co_data(co)) { + primary_dns = *(uint32_t *)co_data(co); + send_request = 1; + } + break; + case IPCP_COPT_SECONDARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + if (secondary_dns != *(uint32_t *)co_data(co)) { + secondary_dns = *(uint32_t *)co_data(co); + send_request = 1; + } + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + + if (send_request) { + struct conf_option_list request; + conf_option_init(&request); + conf_option_encode(&request, IPCP_COPT_ADDRESS, 6, &ip_address); + // conf_option_encode(&request, IPCP_COPT_PRIMARY_DNS, 6, &primary_dns); + // conf_option_encode(&request, IPCP_COPT_SECONDARY_DNS, 6, &secondary_dns); + ret = ipcp_option_send(tunnel, 0, IPCP_CONF_REQUEST, &request, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + conf_option_free(&request); + } + break; + } + case IPCP_CONF_REJECT: + { + int olen = ntohs(header->length); + struct conf_option *co = NULL; + olen -= sizeof(struct lcp_header); + co = (struct conf_option *)(header + 1); + while (olen > 0) { + char buff[128]; + char *p = buff; + p += sprintf(p, "option %s: ", ipcp_valid_options[co->type]); + switch (co->type) { + case IPCP_COPT_ADDRESSES: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case IPCP_COPT_COMPRESS: + break; + case IPCP_COPT_ADDRESS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case IPCP_COPT_PRIMARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + case IPCP_COPT_SECONDARY_DNS: + p += sprintf(p, "%x", ntohl(*(uint32_t *)co_data(co))); + break; + default: + break; + } + log_debug("%s\n", buff); + olen -= co->length; + co = (struct conf_option *)((uint8_t *)co + co->length); + } + + do { + struct conf_option_list request; + conf_option_init(&request); + conf_option_encode(&request, IPCP_COPT_ADDRESS, 6, &ip_address); + // conf_option_encode(&request, IPCP_COPT_PRIMARY_DNS, 6, &primary_dns); + // conf_option_encode(&request, IPCP_COPT_SECONDARY_DNS, 6, &secondary_dns); + ret = ipcp_option_send(tunnel, 0, IPCP_CONF_REQUEST, &request, 0); + if (ret < 0) { + log_debug("send conf_ack failed %d: %s\n", errno, strerror(errno)); + exit(1); + } + } while (0); + break; + } + case IPCP_TERM_REQUEST: + break; + case IPCP_TERM_ACK: + break; + case IPCP_CODE_REJECT: + break; + default: + /* ignore */ + break; + } + + return 0; +} + + /* * Thread to read bytes from the pppd pty, convert them to ppp packets and add * them to the 'pty_to_ssl' pool. @@ -192,6 +1032,17 @@ static void *pppd_read(void *arg) log_debug("%s thread\n", __func__); + if (tunnel->use_tun) { + switch (tunnel->tun_state) { + case TUN_PPP_LCP: + conf_request(tunnel); + break; + case TUN_PPP_IPCP: + case TUN_PPP_SESSION: + break; + } + } + // Wait for pppd to be ready off_w = 0; while (1) { @@ -218,6 +1069,31 @@ static void *pppd_read(void *arg) SEM_POST(&sem_pppd_ready); first_time = 0; } + if (tunnel->use_tun) { + ssize_t pktsize; + struct ppp_packet *packet = NULL; + + pktsize = n + 2; + packet = malloc(sizeof(*packet) + 6 + pktsize); + if (packet == NULL) { + goto exit; + } + packet->len = pktsize; + pkt_data(packet)[0] = 0x00; + pkt_data(packet)[1] = 0x21; + memcpy(pkt_data(packet) + 2, buf, n); + + log_debug("%s ---> gateway (%lu bytes)\n", PPP_DAEMON, + packet->len); +#if HAVE_USR_SBIN_PPPD + log_packet("pppd: ", packet->len, pkt_data(packet)); +#else + log_packet("ppp: ", packet->len, pkt_data(packet)); +#endif + pool_push(&tunnel->pty_to_ssl_pool, packet); + continue; + } + off_w += n; // We have data in the buffer, there may be zero, one or many @@ -308,17 +1184,44 @@ static void *pppd_write(void *arg) // This waits until a packet has arrived from the gateway packet = pool_pop(&tunnel->ssl_to_pty_pool); - hdlc_bufsize = estimated_encoded_size(packet->len); - hdlc_buffer = malloc(hdlc_bufsize); - if (hdlc_buffer == NULL) { - log_error("malloc: %s\n", strerror(errno)); - break; - } - len = hdlc_encode(hdlc_buffer, hdlc_bufsize, - pkt_data(packet), packet->len); - if (len < 0) { - log_error("Failed to encode PPP packet into HDLC frame.\n"); - goto err_free_buf; + if (tunnel->use_tun) { + void *pkt_type = pkt_data(packet); + + hdlc_bufsize = len = packet->len; + switch (ntohs(*(uint16_t *)pkt_type)) { + case PPP_LCP: + lcp_packet(tunnel, pkt_data(packet) + 2, len - 2); + continue; + case PPP_IPCP: + ipcp_packet(tunnel, pkt_data(packet) + 2, len - 2); + continue; + case PPP_IP: + case PPP_IPV6: + break; + default: + goto out_free_packet; + } + + hdlc_buffer = malloc(packet->len); + if (hdlc_buffer == NULL) { + log_error("malloc: %s\n", strerror(errno)); + break; + } + + memcpy(hdlc_buffer, pkt_data(packet) + 2, packet->len - 2); + } else { + hdlc_bufsize = estimated_encoded_size(packet->len); + hdlc_buffer = malloc(hdlc_bufsize); + if (hdlc_buffer == NULL) { + log_error("malloc: %s\n", strerror(errno)); + break; + } + len = hdlc_encode(hdlc_buffer, hdlc_bufsize, + pkt_data(packet), packet->len); + if (len < 0) { + log_error("Failed to encode PPP packet into HDLC frame.\n"); + goto err_free_buf; + } } written = 0; @@ -349,6 +1252,7 @@ static void *pppd_write(void *arg) } free(hdlc_buffer); +out_free_packet: free(packet); continue; err_free_buf: @@ -497,6 +1401,11 @@ static void *ssl_read(void *arg) char line[ARRAY_SIZE("[xxx.xxx.xxx.xxx], ns [xxx.xxx.xxx.xxx, xxx.xxx.xxx.xxx], ns_suffix []") + MAX_DOMAIN_LENGTH]; set_tunnel_ips(tunnel, packet); + + if (tunnel->use_tun) { + int tun_ifup(char *ifname, uint32_t ip_addr, uint32_t peer_addr); + tun_ifup(tunnel->tun_iface, tunnel->ipv4.ip_addr.s_addr, 0); + } strcpy(line, "["); strncat(line, inet_ntoa(tunnel->ipv4.ip_addr), 15); strcat(line, "], ns ["); diff --git a/src/ipv4.h b/src/ipv4.h index fc1406b6..7fd492ec 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -57,6 +57,7 @@ struct rtentry { struct ipv4_config { struct in_addr ip_addr; + struct in_addr peer_addr; struct in_addr ns1_addr; struct in_addr ns2_addr; diff --git a/src/tunnel.c b/src/tunnel.c index 445e85ac..2c024dcc 100644 --- a/src/tunnel.c +++ b/src/tunnel.c @@ -62,6 +62,9 @@ #if HAVE_LIBUTIL_H #include #endif +#if HAVE_LINUX_IF_TUN_H +#include +#endif #include #include @@ -157,7 +160,127 @@ static int on_ppp_if_down(struct tunnel *tunnel) return 0; } -static int pppd_run(struct tunnel *tunnel) +#define TUN_PATH "/dev/net/tun" + +#define TUN_ASSERT(cond, fmt, ...) do { \ + if (! (cond)) { \ + log_info(fmt "\n", ##__VA_ARGS__); \ + exit(1); \ + } \ + } while (0) + +static int +tun_open(struct ifreq *ifr) +{ + int ret = 0; + int fd = -1; + + fd = open(TUN_PATH, O_RDWR, 0); + if (fd >= 0) { + ret = ioctl(fd, TUNSETIFF, (unsigned long)ifr); + if (ret < 0) { + log_error("ioctl: %s\n", strerror(errno)); + close(fd); + return ret; + } + log_info("interface <%s> created\n", ifr->ifr_name); + } else { + log_error("fcntl: %s\n", strerror(errno)); + } + + return fd; +} + +static int +__attribute__((unused)) +tun_close(int fd) +{ + return close(fd); +} + +int +tun_ifup(char *ifname, uint32_t ip_addr, uint32_t peer_addr) +{ + struct ifreq ifreq = { + .ifr_flags = IFF_UP, + }; + struct sockaddr_in *sin = NULL; + int fd = -1; + int ret = -1; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return fd; + } + strncpy(ifreq.ifr_name, ifname, IFNAMSIZ); + ret = ioctl(fd, SIOCGIFFLAGS, (unsigned long)&ifreq); + TUN_ASSERT(ret == 0, "ioctl get ifflags", 0); + if (ret == 0) { + ifreq.ifr_flags |= IFF_UP; + ret = ioctl(fd, SIOCSIFFLAGS, (unsigned long)&ifreq); + TUN_ASSERT(ret == 0, "ioctl set ifflags", 0); + } + + if (ip_addr != 0) { + sin = (struct sockaddr_in *)&ifreq.ifr_addr; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = ip_addr; + ret = ioctl(fd, SIOCSIFADDR, (unsigned long)&ifreq); + TUN_ASSERT(ret == 0, "ioctl set ifaddr err: %s", strerror(errno)); + log_info("setup <%s> ip addr to %s\n", ifname, inet_ntoa(sin->sin_addr)); + } + + if (peer_addr != 0) { + sin = (struct sockaddr_in *)&ifreq.ifr_addr; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = peer_addr; + ret = ioctl(fd, SIOCSIFDSTADDR, (unsigned long)&ifreq); + TUN_ASSERT(ret == 0, "ioctl set dst ifaddr", 0); + } + + close(fd); + + return ret; +} + +static int +__attribute__((unused)) +tun_setup(struct tunnel *tunnel) +{ + int flags = 0; + int tun_fd = -1; + struct ifreq ifreq = { + .ifr_flags = IFF_TUN | IFF_NO_PI, + }; + + tun_fd = tun_open(&ifreq); + if (tun_fd < 0) { + log_error("tun_open failed: %s\n", strerror(errno)); + return 1; + } + + flags = fcntl(tun_fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + if (fcntl(tun_fd, F_SETFL, flags | O_NONBLOCK) == -1) { + log_error("fcntl failed: %s\n", strerror(errno)); + return 1; + } + + strcpy(tunnel->tun_iface, ifreq.ifr_name); + tun_ifup(tunnel->tun_iface, 0, 0); + + tunnel->pppd_pid = -1; + tunnel->pppd_pty = tun_fd; + + return 0; +} + +static int +__attribute__((unused)) +pppd_run(struct tunnel *tunnel) { pid_t pid; int amaster; @@ -516,6 +639,7 @@ int ppp_interface_is_up(struct tunnel *tunnel) && strstr(ifa->ifa_name, tunnel->config->pppd_ifname) != NULL) || strstr(ifa->ifa_name, "ppp") != NULL + || strstr(ifa->ifa_name, "tun") != NULL #endif #if HAVE_USR_SBIN_PPP strstr(ifa->ifa_name, "tun") != NULL @@ -1292,7 +1416,8 @@ int run_tunnel(struct vpn_config *config) goto err_tunnel; // Step 3: get configuration - log_debug("Retrieving configuration\n"); + tunnel.use_tun = 1; + log_info("Retrieving configuration\n"); ret = auth_get_config(&tunnel); if (ret != 1) { log_error("Could not get VPN configuration (%s).\n", @@ -1302,13 +1427,17 @@ int run_tunnel(struct vpn_config *config) } // Step 4: run a pppd process - log_debug("Establishing the tunnel\n"); - ret = pppd_run(&tunnel); + log_info("Establishing the tunnel\n"); + if (tunnel.use_tun) { + ret = tun_setup(&tunnel); + } else { + ret = pppd_run(&tunnel); + } if (ret) goto err_tunnel; // Step 5: ask gateway to start tunneling - log_debug("Switch to tunneling mode\n"); + log_info("Switch to tunneling mode\n"); ret = http_send(&tunnel, "GET /remote/sslvpn-tunnel HTTP/1.1\r\n" "Host: sslvpn\r\n" @@ -1322,7 +1451,7 @@ int run_tunnel(struct vpn_config *config) tunnel.state = STATE_CONNECTING; // Step 6: perform io between pppd and the gateway, while tunnel is up - log_debug("Starting IO through the tunnel\n"); + log_info("Starting IO through the tunnel\n"); io_loop(&tunnel); log_debug("disconnecting\n"); diff --git a/src/tunnel.h b/src/tunnel.h index 31eb045f..af4d64e2 100644 --- a/src/tunnel.h +++ b/src/tunnel.h @@ -54,10 +54,17 @@ enum tunnel_state { STATE_DISCONNECTING }; +enum tun_ppp_state { + TUN_PPP_LCP, + TUN_PPP_IPCP, + TUN_PPP_SESSION, +}; + struct tunnel { struct vpn_config *config; enum tunnel_state state; + enum tun_ppp_state tun_state; char cookie[COOKIE_SIZE + 1]; struct ppp_packet_pool ssl_to_pty_pool; @@ -65,6 +72,8 @@ struct tunnel { pid_t pppd_pid; pid_t pppd_pty; + int use_tun; + char tun_iface[ROUTE_IFACE_LEN]; char ppp_iface[ROUTE_IFACE_LEN]; int ssl_socket;