| /* |
| * |
| * PPP library with GLib integration |
| * |
| * Copyright (C) 2009-2011 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <arpa/inet.h> |
| #include <glib.h> |
| |
| #include "gatutil.h" |
| #include "gatppp.h" |
| #include "ppp.h" |
| |
| #define IPCP_SUPPORTED_CODES ((1 << PPPCP_CODE_TYPE_CONFIGURE_REQUEST) | \ |
| (1 << PPPCP_CODE_TYPE_CONFIGURE_ACK) | \ |
| (1 << PPPCP_CODE_TYPE_CONFIGURE_NAK) | \ |
| (1 << PPPCP_CODE_TYPE_CONFIGURE_REJECT) | \ |
| (1 << PPPCP_CODE_TYPE_TERMINATE_REQUEST) | \ |
| (1 << PPPCP_CODE_TYPE_TERMINATE_ACK) | \ |
| (1 << PPPCP_CODE_TYPE_CODE_REJECT)) |
| |
| enum ipcp_option_types { |
| IP_ADDRESSES = 1, |
| IP_COMPRESSION_PROTO = 2, |
| IP_ADDRESS = 3, |
| MOBILE_IPV4 = 4, |
| PRIMARY_DNS_SERVER = 129, |
| PRIMARY_NBNS_SERVER = 130, |
| SECONDARY_DNS_SERVER = 131, |
| SECONDARY_NBNS_SERVER = 132, |
| }; |
| |
| /* We request IP_ADDRESS, PRIMARY/SECONDARY DNS & NBNS */ |
| #define MAX_CONFIG_OPTION_SIZE 5*6 |
| |
| #define REQ_OPTION_IPADDR 0x01 |
| #define REQ_OPTION_DNS1 0x02 |
| #define REQ_OPTION_DNS2 0x04 |
| #define REQ_OPTION_NBNS1 0x08 |
| #define REQ_OPTION_NBNS2 0x10 |
| |
| #define MAX_IPCP_FAILURE 100 |
| |
| struct ipcp_data { |
| guint8 options[MAX_CONFIG_OPTION_SIZE]; |
| guint16 options_len; |
| guint8 req_options; |
| guint32 local_addr; |
| guint32 peer_addr; |
| guint32 dns1; |
| guint32 dns2; |
| guint32 nbns1; |
| guint32 nbns2; |
| gboolean is_server; |
| }; |
| |
| #define FILL_IP(options, req, type, var) \ |
| if (req) { \ |
| options[len] = type; \ |
| options[len + 1] = 6; \ |
| memcpy(options + len + 2, var, 4); \ |
| \ |
| len += 6; \ |
| } \ |
| |
| static void ipcp_generate_config_options(struct ipcp_data *ipcp) |
| { |
| guint16 len = 0; |
| |
| FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_IPADDR, |
| IP_ADDRESS, &ipcp->local_addr); |
| FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_DNS1, |
| PRIMARY_DNS_SERVER, &ipcp->dns1); |
| FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_DNS2, |
| SECONDARY_DNS_SERVER, &ipcp->dns2); |
| FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_NBNS1, |
| PRIMARY_NBNS_SERVER, &ipcp->nbns1); |
| FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_NBNS2, |
| SECONDARY_NBNS_SERVER, &ipcp->nbns2); |
| |
| ipcp->options_len = len; |
| } |
| |
| static void ipcp_reset_client_config_options(struct ipcp_data *ipcp) |
| { |
| ipcp->req_options = REQ_OPTION_IPADDR | REQ_OPTION_DNS1 | |
| REQ_OPTION_DNS2 | REQ_OPTION_NBNS1 | |
| REQ_OPTION_NBNS2; |
| |
| ipcp->local_addr = 0; |
| ipcp->peer_addr = 0; |
| ipcp->dns1 = 0; |
| ipcp->dns2 = 0; |
| ipcp->nbns1 = 0; |
| ipcp->nbns2 = 0; |
| |
| ipcp_generate_config_options(ipcp); |
| } |
| |
| static void ipcp_reset_server_config_options(struct ipcp_data *ipcp) |
| { |
| if (ipcp->local_addr != 0) |
| ipcp->req_options = REQ_OPTION_IPADDR; |
| else |
| ipcp->req_options = 0; |
| |
| ipcp_generate_config_options(ipcp); |
| } |
| |
| void ipcp_set_server_info(struct pppcp_data *pppcp, guint32 peer_addr, |
| guint32 dns1, guint32 dns2) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| |
| ipcp->peer_addr = peer_addr; |
| ipcp->dns1 = dns1; |
| ipcp->dns2 = dns2; |
| } |
| |
| static void ipcp_up(struct pppcp_data *pppcp) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| char local[INET_ADDRSTRLEN]; |
| char peer[INET_ADDRSTRLEN]; |
| char dns1[INET_ADDRSTRLEN]; |
| char dns2[INET_ADDRSTRLEN]; |
| struct in_addr addr; |
| |
| memset(local, 0, sizeof(local)); |
| addr.s_addr = ipcp->local_addr; |
| inet_ntop(AF_INET, &addr, local, INET_ADDRSTRLEN); |
| |
| memset(peer, 0, sizeof(peer)); |
| addr.s_addr = ipcp->peer_addr; |
| inet_ntop(AF_INET, &addr, peer, INET_ADDRSTRLEN); |
| |
| memset(dns1, 0, sizeof(dns1)); |
| addr.s_addr = ipcp->dns1; |
| inet_ntop(AF_INET, &addr, dns1, INET_ADDRSTRLEN); |
| |
| memset(dns2, 0, sizeof(dns2)); |
| addr.s_addr = ipcp->dns2; |
| inet_ntop(AF_INET, &addr, dns2, INET_ADDRSTRLEN); |
| |
| ppp_ipcp_up_notify(pppcp_get_ppp(pppcp), local[0] ? local : NULL, |
| peer[0] ? peer : NULL, |
| dns1[0] ? dns1 : NULL, |
| dns2[0] ? dns2 : NULL); |
| } |
| |
| static void ipcp_down(struct pppcp_data *pppcp) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| |
| if (ipcp->is_server) |
| ipcp_reset_server_config_options(ipcp); |
| else |
| ipcp_reset_client_config_options(ipcp); |
| |
| pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len); |
| ppp_ipcp_down_notify(pppcp_get_ppp(pppcp)); |
| } |
| |
| static void ipcp_finished(struct pppcp_data *pppcp) |
| { |
| ppp_ipcp_finished_notify(pppcp_get_ppp(pppcp)); |
| } |
| |
| static void ipcp_rca(struct pppcp_data *pppcp, |
| const struct pppcp_packet *packet) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| struct ppp_option_iter iter; |
| |
| if (ipcp->is_server) |
| return; |
| |
| ppp_option_iter_init(&iter, packet); |
| |
| while (ppp_option_iter_next(&iter) == TRUE) { |
| const guint8 *data = ppp_option_iter_get_data(&iter); |
| |
| switch (ppp_option_iter_get_type(&iter)) { |
| case IP_ADDRESS: |
| memcpy(&ipcp->local_addr, data, 4); |
| break; |
| case PRIMARY_DNS_SERVER: |
| memcpy(&ipcp->dns1, data, 4); |
| break; |
| case PRIMARY_NBNS_SERVER: |
| memcpy(&ipcp->nbns1, data, 4); |
| break; |
| case SECONDARY_DNS_SERVER: |
| memcpy(&ipcp->dns2, data, 4); |
| break; |
| case SECONDARY_NBNS_SERVER: |
| memcpy(&ipcp->nbns2, data, 4); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static void ipcp_rcn_nak(struct pppcp_data *pppcp, |
| const struct pppcp_packet *packet) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| struct ppp_option_iter iter; |
| |
| if (ipcp->is_server) |
| return; |
| |
| ppp_option_iter_init(&iter, packet); |
| |
| while (ppp_option_iter_next(&iter) == TRUE) { |
| const guint8 *data = ppp_option_iter_get_data(&iter); |
| |
| switch (ppp_option_iter_get_type(&iter)) { |
| case IP_ADDRESS: |
| ipcp->req_options |= REQ_OPTION_IPADDR; |
| memcpy(&ipcp->local_addr, data, 4); |
| break; |
| case PRIMARY_DNS_SERVER: |
| ipcp->req_options |= REQ_OPTION_DNS1; |
| memcpy(&ipcp->dns1, data, 4); |
| break; |
| case PRIMARY_NBNS_SERVER: |
| ipcp->req_options |= REQ_OPTION_NBNS1; |
| memcpy(&ipcp->nbns1, data, 4); |
| break; |
| case SECONDARY_DNS_SERVER: |
| ipcp->req_options |= REQ_OPTION_DNS2; |
| memcpy(&ipcp->dns2, data, 4); |
| break; |
| case SECONDARY_NBNS_SERVER: |
| ipcp->req_options |= REQ_OPTION_NBNS2; |
| memcpy(&ipcp->nbns2, data, 4); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| ipcp_generate_config_options(ipcp); |
| pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len); |
| } |
| |
| static void ipcp_rcn_rej(struct pppcp_data *pppcp, |
| const struct pppcp_packet *packet) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| struct ppp_option_iter iter; |
| |
| ppp_option_iter_init(&iter, packet); |
| |
| while (ppp_option_iter_next(&iter) == TRUE) { |
| switch (ppp_option_iter_get_type(&iter)) { |
| case IP_ADDRESS: |
| ipcp->req_options &= ~REQ_OPTION_IPADDR; |
| break; |
| case PRIMARY_DNS_SERVER: |
| ipcp->req_options &= ~REQ_OPTION_DNS1; |
| break; |
| case PRIMARY_NBNS_SERVER: |
| ipcp->req_options &= ~REQ_OPTION_NBNS1; |
| break; |
| case SECONDARY_DNS_SERVER: |
| ipcp->req_options &= ~REQ_OPTION_DNS2; |
| break; |
| case SECONDARY_NBNS_SERVER: |
| ipcp->req_options &= ~REQ_OPTION_NBNS2; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| ipcp_generate_config_options(ipcp); |
| pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len); |
| } |
| |
| static enum rcr_result ipcp_server_rcr(struct ipcp_data *ipcp, |
| const struct pppcp_packet *packet, |
| guint8 **new_options, guint16 *new_len) |
| { |
| struct ppp_option_iter iter; |
| guint8 nak_options[MAX_CONFIG_OPTION_SIZE]; |
| guint16 len = 0; |
| guint8 *rej_options = NULL; |
| guint16 rej_len = 0; |
| guint32 addr; |
| |
| ppp_option_iter_init(&iter, packet); |
| |
| while (ppp_option_iter_next(&iter) == TRUE) { |
| const guint8 *data = ppp_option_iter_get_data(&iter); |
| guint8 type = ppp_option_iter_get_type(&iter); |
| |
| switch (type) { |
| case IP_ADDRESS: |
| memcpy(&addr, data, 4); |
| |
| FILL_IP(nak_options, |
| addr != ipcp->peer_addr || addr == 0, |
| type, &ipcp->peer_addr); |
| break; |
| case PRIMARY_DNS_SERVER: |
| memcpy(&addr, data, 4); |
| |
| FILL_IP(nak_options, addr != ipcp->dns1 || addr == 0, |
| type, &ipcp->dns1); |
| break; |
| case SECONDARY_DNS_SERVER: |
| memcpy(&addr, data, 4); |
| |
| FILL_IP(nak_options, addr != ipcp->dns2 || addr == 0, |
| type, &ipcp->dns2); |
| break; |
| default: |
| /* Reject */ |
| if (rej_options == NULL) { |
| guint16 max_len = ntohs(packet->length) - 4; |
| rej_options = g_new0(guint8, max_len); |
| } |
| |
| if (rej_options != NULL) { |
| guint8 opt_len = |
| ppp_option_iter_get_length(&iter); |
| |
| rej_options[rej_len] = type; |
| rej_options[rej_len + 1] = opt_len + 2; |
| memcpy(rej_options + rej_len + 2, |
| data, opt_len); |
| rej_len += opt_len + 2; |
| } |
| break; |
| } |
| } |
| |
| if (rej_len > 0) { |
| *new_len = rej_len; |
| *new_options = rej_options; |
| |
| return RCR_REJECT; |
| } |
| |
| if (len > 0) { |
| *new_len = len; |
| *new_options = g_memdup(nak_options, len); |
| |
| return RCR_NAK; |
| } |
| |
| return RCR_ACCEPT; |
| } |
| |
| static enum rcr_result ipcp_client_rcr(struct ipcp_data *ipcp, |
| const struct pppcp_packet *packet, |
| guint8 **new_options, guint16 *new_len) |
| { |
| guint8 *options = NULL; |
| struct ppp_option_iter iter; |
| guint8 len = 0; |
| |
| ppp_option_iter_init(&iter, packet); |
| |
| while (ppp_option_iter_next(&iter) == TRUE) { |
| const guint8 *data = ppp_option_iter_get_data(&iter); |
| guint8 type = ppp_option_iter_get_type(&iter); |
| |
| switch (type) { |
| case IP_ADDRESS: |
| memcpy(&ipcp->peer_addr, data, 4); |
| |
| if (ipcp->peer_addr != 0) |
| break; |
| |
| /* |
| * Reject IP_ADDRESS if peer sends us 0 (expecting |
| * us to provide its IP address) |
| */ |
| |
| /* fall through */ |
| default: |
| if (options == NULL) { |
| guint16 max_len = ntohs(packet->length) - 4; |
| options = g_new0(guint8, max_len); |
| } |
| |
| if (options != NULL) { |
| guint8 opt_len = |
| ppp_option_iter_get_length(&iter); |
| |
| options[len] = type; |
| options[len + 1] = opt_len + 2; |
| memcpy(options + len + 2, data, opt_len); |
| len += opt_len + 2; |
| } |
| |
| break; |
| } |
| } |
| |
| if (len > 0) { |
| *new_len = len; |
| *new_options = options; |
| |
| return RCR_REJECT; |
| } |
| |
| return RCR_ACCEPT; |
| } |
| |
| static enum rcr_result ipcp_rcr(struct pppcp_data *pppcp, |
| const struct pppcp_packet *packet, |
| guint8 **new_options, guint16 *new_len) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(pppcp); |
| |
| if (ipcp->is_server) |
| return ipcp_server_rcr(ipcp, packet, new_options, new_len); |
| else |
| return ipcp_client_rcr(ipcp, packet, new_options, new_len); |
| } |
| |
| struct pppcp_proto ipcp_proto = { |
| .proto = IPCP_PROTO, |
| .name = "ipcp", |
| .supported_codes = IPCP_SUPPORTED_CODES, |
| .this_layer_up = ipcp_up, |
| .this_layer_down = ipcp_down, |
| .this_layer_finished = ipcp_finished, |
| .rca = ipcp_rca, |
| .rcn_nak = ipcp_rcn_nak, |
| .rcn_rej = ipcp_rcn_rej, |
| .rcr = ipcp_rcr, |
| }; |
| |
| struct pppcp_data *ipcp_new(GAtPPP *ppp, gboolean is_server, guint32 ip) |
| { |
| struct ipcp_data *ipcp; |
| struct pppcp_data *pppcp; |
| |
| ipcp = g_try_new0(struct ipcp_data, 1); |
| if (ipcp == NULL) |
| return NULL; |
| |
| /* |
| * Some 3G modems use repeated IPCP NAKs as the way of stalling |
| * util sending us the client IP address. So we increase the |
| * default number of NAKs we accept before start treating them |
| * as rejects. |
| */ |
| pppcp = pppcp_new(ppp, &ipcp_proto, FALSE, MAX_IPCP_FAILURE); |
| if (pppcp == NULL) { |
| g_free(ipcp); |
| return NULL; |
| } |
| |
| pppcp_set_data(pppcp, ipcp); |
| ipcp->is_server = is_server; |
| |
| if (is_server) { |
| ipcp->local_addr = ip; |
| ipcp_reset_server_config_options(ipcp); |
| } else |
| ipcp_reset_client_config_options(ipcp); |
| |
| pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len); |
| |
| return pppcp; |
| } |
| |
| void ipcp_free(struct pppcp_data *data) |
| { |
| struct ipcp_data *ipcp = pppcp_get_data(data); |
| |
| g_free(ipcp); |
| pppcp_free(data); |
| } |