diff --git a/configure.ac b/configure.ac index 7bccdfe4..140f9b3c 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,8 @@ AC_CHECK_HEADERS([net/route.h], [], AC_MSG_ERROR([Required header not found]), [ # Checks for optional header files. AC_CHECK_HEADERS([ \ libutil.h \ +linux/if_ppp.h \ +linux/if_tun.h \ mach/mach.h \ pty.h \ semaphore.h \ diff --git a/src/http.c b/src/http.c index 133ffc65..6526953c 100644 --- a/src/http.c +++ b/src/http.c @@ -834,6 +834,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 388a005e..7c64a71f 100644 --- a/src/io.c +++ b/src/io.c @@ -35,6 +35,9 @@ #include #include #include +#if HAVE_LINUX_IF_PPP_H +#include +#endif #include #include @@ -175,6 +178,894 @@ 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; +}; + +static int magic_seed = 0x64696E67; + +struct conf_option *lcp_self[256] = { + NULL, +}; +struct conf_option *lcp_peer[256] = { + NULL, +}; + +static int lcp_id; + +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(const struct conf_option_list *optlist) +{ + if (optlist) + return (uint8_t *)optlist->tail - (uint8_t *)optlist->head; + else + return 0; +} + +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; + const size_t hdrlen = sizeof(struct lcp_header) + sizeof(uint16_t); + const int len = conf_option_length(optlist); + + if (len > 0 || force) { + const ssize_t pktsize = hdrlen + len; + 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)); + + 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: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct lcp_header); + 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); + 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 = 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(EXIT_FAILURE); + } + 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(EXIT_FAILURE); + } + 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(EXIT_FAILURE); + } + 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: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct lcp_header); + + 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: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct lcp_header); + + 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: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct lcp_header); + + 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", +}; + + +static uint32_t ip_address; +static uint32_t peer_address; +static uint32_t primary_dns; +static uint32_t secondary_dns; + +int ipcp_add_route(struct tunnel *tunnel, uint32_t dst, uint32_t mask, uint32_t gw) +{ + int ret = 0; + int sd = -1; + 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); + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + log_error("socket: %s\n", strerror(errno)); + return sd; + } + + 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)); + if (close(sd)) + log_warn("close: %s\n", strerror(errno)); + + 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; + const size_t hdrlen = sizeof(struct ipcp_header) + sizeof(uint16_t); + const int len = conf_option_length(optlist); + + if (len > 0 || force) { + const ssize_t pktsize = hdrlen + len; + 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); + + 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: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct ipcp_header); + 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); + + 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(EXIT_FAILURE); + } + 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(EXIT_FAILURE); + } + 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(EXIT_FAILURE); + } + + 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(EXIT_FAILURE); + } + } 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); + } + + tun_ifup(tunnel->tun_iface, ip_address, peer_address); + ipv4_set_tunnel_routes(tunnel); + break; + } + case IPCP_CONF_NAK: { + int send_request = 0; + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length) - sizeof(struct lcp_header); + + 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(EXIT_FAILURE); + } + conf_option_free(&request); + } + break; + } + case IPCP_CONF_REJECT: { + struct conf_option *co = (struct conf_option *)(header + 1); + int olen = ntohs(header->length); + + olen -= sizeof(struct lcp_header); + 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(EXIT_FAILURE); + } + } 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 +1083,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 +1120,29 @@ static void *pppd_read(void *arg) SEM_POST(&sem_pppd_ready); first_time = 0; } + if (tunnel->use_tun) { + ssize_t pktsize = n + 2; + struct ppp_packet *packet = NULL; + + 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 +1233,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 +1301,7 @@ static void *pppd_write(void *arg) } free(hdlc_buffer); +out_free_packet: free(packet); continue; err_free_buf: @@ -497,6 +1450,10 @@ 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) + 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 6bc36971..c3d5094f 100644 --- a/src/tunnel.c +++ b/src/tunnel.c @@ -60,6 +60,9 @@ #if HAVE_LIBUTIL_H #include #endif +#if HAVE_LINUX_IF_TUN_H +#include +#endif #include #include @@ -155,6 +158,122 @@ static int on_ppp_if_down(struct tunnel *tunnel) return 0; } +static const char TUN_PATH[] = "/dev/net/tun"; + +#define TUN_ASSERT(cond, fmt, ...) do { \ + if (!(cond)) { \ + log_info(fmt "\n", ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } \ + } 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, 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 tun_close(int fd) +{ + return close(fd); +} + +int tun_ifup(const char *ifname, uint32_t ip_addr, uint32_t peer_addr) +{ + struct ifreq ifreq = { + .ifr_flags = IFF_UP, + }; + int fd = -1; + int ret = -1; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return fd; + + TUN_ASSERT(strlen(ifname) < IFNAMSIZ, "ifname length"); + strcpy(ifreq.ifr_name, ifname); + + ret = ioctl(fd, SIOCGIFFLAGS, &ifreq); + TUN_ASSERT(ret == 0, "ioctl get ifflags"); + if (ret == 0) { + ifreq.ifr_flags |= IFF_UP; + ret = ioctl(fd, SIOCSIFFLAGS, &ifreq); + TUN_ASSERT(ret == 0, "ioctl set ifflags"); + } + + if (ip_addr != 0) { + struct sockaddr_in *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, &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) { + struct sockaddr_in *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, &ifreq); + TUN_ASSERT(ret == 0, "ioctl set dst ifaddr"); + } + + close(fd); + + return ret; +} + +static int 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)); + tun_close(tun_fd); + 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 pppd_run(struct tunnel *tunnel) { pid_t pid; @@ -514,7 +633,8 @@ int ppp_interface_is_up(struct tunnel *tunnel) #if HAVE_USR_SBIN_PPPD ((tunnel->config->pppd_ifname && strstr(ifa->ifa_name, tunnel->config->pppd_ifname) != NULL) || - strstr(ifa->ifa_name, "ppp") != 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 && @@ -666,6 +786,8 @@ static int tcp_connect(struct tunnel *tunnel) >= IF_NAMESIZE) { log_error("interface name too long\n"); goto err_post_socket; + } else { + strcpy(ifr.ifr_name, tunnel->config->iface_name); } ifr.ifr_addr.sa_family = AF_INET; if (ioctl(handle, SIOCGIFADDR, &ifr) == -1) { @@ -1300,7 +1422,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", @@ -1310,8 +1433,11 @@ 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; diff --git a/src/tunnel.h b/src/tunnel.h index 31eb045f..6c790f6b 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; @@ -84,6 +93,8 @@ struct token { int ppp_interface_is_up(struct tunnel *tunnel); +int tun_ifup(const char *ifname, uint32_t ip_addr, uint32_t peer_addr); + int ssl_connect(struct tunnel *tunnel); int run_tunnel(struct vpn_config *config);