From d4c9a04f89c239850bdcd21b133eb438dbaeef82 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Fri, 15 May 2020 18:03:45 -0700 Subject: [PATCH] use ACK/NAK request mechanism to request IPv4 address, (IPv4) DNS/NBNS addresses, and IPv6 interface identifiers The idea is that if we CONFREQ an all-zero value for one of these. PPP server/peer is supposed to: - CONFNAK with a value filled in, which we then re-CONFREQ to confirm. - CONFREJ if it can't supply a value (e.g. NBNS servers because it's not 1994 and we're not using Windows for Workgroups 3.11). Tested with F5, by overriding ppp->solicit_peerns=7, ppp->out_peer_addr.s_addr=0, ppp->out_ipv6_int_ident=0 in openconnect_ppp_new (even though F5 in fact sends these addresses in the XML config prior to PPP tunnel). The CONFREQ/CONFNAK/re-CONFREQ exchange is inefficient, requiring three rounds trips (request and reject, partial re-request and nak, confirming request and ack), but it works: Sending PPP IPCP Configure-Request packet (id 1, 34 bytes total) > 0000: f5 00 00 1e 80 21 01 01 00 1c 03 06 00 00 00 00 |.....!..........| > 0010: 81 06 00 00 00 00 82 06 00 00 00 00 83 06 00 00 |................| > 0020: 00 00 |..| ... Received proto 0x8021/id 1 Configure-Reject from server Server rejected IPCP request for NBNS[1] server Server rejected IPCP request for DNS[1] server Server rejected IPCP request for NBNS[0] server ... Sending our proto 0x8021/id 2 config request to server < 0000: f5 00 00 10 80 57 02 01 00 0e 01 0a e0 a7 1c fb |.....W..........| < 0010: 9e 55 00 00 |.U..| Sending PPP IPCP Configure-Request packet (id 2, 22 bytes total) > 0000: f5 00 00 12 80 21 01 02 00 10 03 06 00 00 00 00 |.....!..........| > 0010: 81 06 00 00 00 00 |......| No work to do; sleeping for 3000 ms... < 0000: f5 00 00 12 80 21 03 02 00 10 03 06 0a 00 00 17 |.....!..........| < 0010: 81 06 5a 9b 5c d1 |..Z.\.| Received proto 0x8021/id 2 Configure-Nak from server Server nak-offered IPv4 address: 10.0.0.23 Server nak-offered IPCP request for DNS[0] server: 90.155.92.209 ... Sending our proto 0x8021/id 3 config request to server Sending PPP IPCP Configure-Request packet (id 3, 16 bytes total) > 0000: f5 00 00 0c 80 21 01 03 00 0a 03 06 0a 00 00 17 |.....!..........| No work to do; sleeping for 3000 ms... < 0000: f5 00 00 0c 80 21 02 03 00 0a 03 06 0a 00 00 17 |.....!..........| Received proto 0x8021/id 3 Configure-Ack from server PPP state transition from OPENED to NETWORK Current PPP state: NETWORK (encap F5): in: asyncmap=0x00000000, lcp_opts=384, lcp_magic=0x04eb81f9, peer=1.1.1.1 out: asyncmap=0x00000000, lcp_opts=422, lcp_magic=0x70ac508f, peer=10.0.0.23, solicit_peerns=0 The purpose of the IPv6 interface identifier negotiation is unclear, but the F5 server does not accept a zero value, using CONFNAK to offer a new one. Signed-off-by: Daniel Lenski --- ppp.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++---------- ppp.h | 12 ++++++- 2 files changed, 105 insertions(+), 20 deletions(-) diff --git a/ppp.c b/ppp.c index c9131a1c..5751f178 100644 --- a/ppp.c +++ b/ppp.c @@ -212,6 +212,24 @@ int openconnect_ppp_new(struct openconnect_info *vpninfo, if (!ppp) return -ENOMEM; + /* Nameservers to request from peer + * (see https://tools.ietf.org/html/rfc1877#section-1) */ + ppp->solicit_peerns = 0; + if (!vpninfo->ip_info.dns[0]) + ppp->solicit_peerns |= IPCP_DNS0|IPCP_DNS1; + if (!vpninfo->ip_info.nbns[0]) + ppp->solicit_peerns |= IPCP_NBNS0|IPCP_NBNS1; + + /* Outgoing IPv4 address and IPv6 interface identifier (64 LSBs), + * if already configured via another mechanism */ + if (vpninfo->ip_info.addr) + ppp->out_peer_addr.s_addr = inet_addr(vpninfo->ip_info.addr); + if (vpninfo->ip_info.addr6) { + unsigned char *bytes[16]; + inet_pton(AF_INET6, vpninfo->ip_info.addr6, bytes); + memcpy(&ppp->out_ipv6_int_ident, bytes+8, 8); + } + ppp->encap = encap; switch (encap) { case PPP_ENCAP_F5: @@ -256,8 +274,8 @@ static void print_ppp_state(struct openconnect_info *vpninfo, int level) vpn_progress(vpninfo, level, _("Current PPP state: %s (encap %s):\n"), ppps_names[ppp->ppp_state], encap_names[ppp->encap]); vpn_progress(vpninfo, level, _(" in: asyncmap=0x%08x, lcp_opts=%d, lcp_magic=0x%08x, peer=%s\n"), ppp->in_asyncmap, ppp->in_lcp_opts, ntohl(ppp->in_lcp_magic), inet_ntoa(ppp->in_peer_addr)); - vpn_progress(vpninfo, level, _(" out: asyncmap=0x%08x, lcp_opts=%d, lcp_magic=0x%08x, peer=%s\n"), - ppp->out_asyncmap, ppp->out_lcp_opts, ntohl(ppp->out_lcp_magic), inet_ntoa(ppp->out_peer_addr)); + vpn_progress(vpninfo, level, _(" out: asyncmap=0x%08x, lcp_opts=%d, lcp_magic=0x%08x, peer=%s, solicit_peerns=%d\n"), + ppp->out_asyncmap, ppp->out_lcp_opts, ntohl(ppp->out_lcp_magic), inet_ntoa(ppp->out_peer_addr), ppp->solicit_peerns); } static int buf_append_ppp_tlv(struct oc_text_buf *buf, int tag, int len, const void *data) @@ -434,7 +452,7 @@ static int handle_config_request(struct openconnect_info *vpninfo, static int queue_config_request(struct openconnect_info *vpninfo, int proto) { struct oc_ppp *ppp = vpninfo->ppp; - unsigned char ipv6a[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + const uint32_t zero = 0; int ret, id; struct oc_ncp *ncp; struct oc_text_buf *buf; @@ -466,18 +484,20 @@ static int queue_config_request(struct openconnect_info *vpninfo, int proto) case PPP_IPCP: ncp = &ppp->ipcp; - if (vpninfo->ip_info.addr) - ppp->out_peer_addr.s_addr = inet_addr(vpninfo->ip_info.addr); - buf_append_ppp_tlv(buf, IPCP_IPADDR, 4, &ppp->out_peer_addr); + /* XX: send zero for IPv4/DNS/NBNS to request via NAK */ + buf_append_ppp_tlv(buf, IPCP_IPADDR, 4, &ppp->out_peer_addr.s_addr); + + /* XX: See ppp.h for why bitfields work here */ + for (int b=0; b<4; b++) + if (ppp->solicit_peerns & (1<ip6cp; - if (vpninfo->ip_info.addr6) - inet_pton(AF_INET6, vpninfo->ip_info.addr6, &ipv6a); - memcpy(&ppp->out_ipv6_int_ident, ipv6a+8, 8); /* last 8 bytes of addr6 */ + /* XX: unclear if any servers care about zeros here */ buf_append_ppp_tlv(buf, IP6CP_INT_ID, 8, &ppp->out_ipv6_int_ident); break; @@ -502,12 +522,13 @@ out: return ret; } -static int handle_config_reject(struct openconnect_info *vpninfo, - int proto, int id, unsigned char *payload, int len) +static int handle_config_rejnak(struct openconnect_info *vpninfo, + int proto, int id, int code, unsigned char *payload, int len) { struct oc_ppp *ppp = vpninfo->ppp; struct oc_ncp *ncp; unsigned char *p; + const char *action = (code == CONFREJ) ? "reject" : "nak"; /* XX: bad for translation */ switch (proto) { case PPP_LCP: ncp = &ppp->lcp; break; @@ -525,30 +546,84 @@ static int handle_config_reject(struct openconnect_info *vpninfo, switch (PROTO_TAG_LEN(proto, t, l-2)) { case PROTO_TAG_LEN(PPP_LCP, LCP_MRU, 2): vpn_progress(vpninfo, PRG_DEBUG, - _("Server rejected LCP MRU option\n")); + _("Server %sed LCP MRU option\n"), action); ppp->out_lcp_opts &= ~BIT_MRU; break; case PROTO_TAG_LEN(PPP_LCP, LCP_ASYNCMAP, 4): vpn_progress(vpninfo, PRG_DEBUG, - _("Server rejected LCP asyncmap option\n")); + _("Server %sed LCP asyncmap option\n"), action); ppp->out_asyncmap = ASYNCMAP_LCP; ppp->out_lcp_opts &= ~BIT_ASYNCMAP; break; case PROTO_TAG_LEN(PPP_LCP, LCP_MAGIC, 4): vpn_progress(vpninfo, PRG_DEBUG, - _("Server rejected LCP magic option\n")); + _("Server %sed LCP magic option\n"), action); ppp->out_lcp_opts &= ~BIT_MAGIC; break; case PROTO_TAG_LEN(PPP_LCP, LCP_PFCOMP, 0): vpn_progress(vpninfo, PRG_DEBUG, - _("Server rejected LCP PFCOMP option\n")); + _("Server %sed LCP PFCOMP option\n"), action); ppp->out_lcp_opts &= ~BIT_PFCOMP; break; case PROTO_TAG_LEN(PPP_LCP, LCP_ACCOMP, 0): vpn_progress(vpninfo, PRG_DEBUG, - _("Server reject LCP ACCOMP option\n")); - ppp->in_lcp_opts &= ~BIT_ACCOMP; + _("Server %sed LCP ACCOMP option\n"), action); + ppp->out_lcp_opts &= ~BIT_ACCOMP; + break; + case PROTO_TAG_LEN(PPP_IPCP, IPCP_IPADDR, 4): { + struct in_addr *a = (void *)(p + 2); + const char *s = inet_ntoa(*a); + if (code == CONFNAK && a->s_addr) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server nak-offered IPv4 address: %s\n"), s); + ppp->out_peer_addr = *a; + vpninfo->ip_info.addr = strdup(s); /* XX: need free and add_option() */ + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server %sed our IPv4 address or request: %s\n"), action, s); + return -EINVAL; + } + break; + } + case PROTO_TAG_LEN(PPP_IPCP, IPCP_xNS_BASE + 0, 4): + case PROTO_TAG_LEN(PPP_IPCP, IPCP_xNS_BASE + 1, 4): + case PROTO_TAG_LEN(PPP_IPCP, IPCP_xNS_BASE + 2, 4): + case PROTO_TAG_LEN(PPP_IPCP, IPCP_xNS_BASE + 3, 4): { + struct in_addr *a = (void *)(p + 2); + const char *s = inet_ntoa(*a); + /* XX: see ppp.h for why bitfields work here */ + if (code == CONFNAK && a->s_addr) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server nak-offered IPCP request for %s[%d] server: %s\n"), + (t&1) ? "DNS" : "NBNS", ((t&2)>>1), s); + /* XX: need free and add_option() */ + if (t & 1) + vpninfo->ip_info.dns[(t&2)>>1] = strdup(s); + else + vpninfo->ip_info.nbns[(t&2)>>1] = strdup(s); + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server %sed IPCP request for %s[%d] server\n"), action, + (t&1) ? "DNS" : "NBNS", ((t&2)>>1)); + } + /* Stop soliciting */ + ppp->solicit_peerns &= ~(1<<(t-IPCP_xNS_BASE)); + break; + } + case PROTO_TAG_LEN(PPP_IP6CP, IP6CP_INT_ID, 8): { + uint64_t *val = (void *)(p + 2); + if (code == CONFNAK && val != 0) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server nak-offered IPv6 interface identifier\n")); + memcpy(&ppp->out_ipv6_int_ident, val, 8); + /* XX: unclear what we should do to make an IPv6 address from this */ + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Server %sed our IPv6 interface identifier\n"), action); + return -EINVAL; + } break; + } default: vpn_progress(vpninfo, PRG_DEBUG, _("Server rejected unknown proto 0x%04x TLV (tag %d, len %d+2)\n"), @@ -627,10 +702,10 @@ static int handle_config_packet(struct openconnect_info *vpninfo, break; case CONFREJ: - ret = handle_config_reject(vpninfo, proto, id, p + 4, len - 4); + case CONFNAK: + ret = handle_config_rejnak(vpninfo, proto, id, code, p + 4, len - 4); break; - case CONFNAK: case CODEREJ: case PROTREJ: default: diff --git a/ppp.h b/ppp.h index 177a8719..048c12b9 100644 --- a/ppp.h +++ b/ppp.h @@ -79,6 +79,13 @@ #define IPCP_IPCOMP 2 #define IPCP_IPADDR 3 +/* RFC1877: DNS[0]=129, NBNS[0]=130, DNS[1]=131, NBNS[1]=132 */ +#define IPCP_xNS_BASE 129 +#define IPCP_DNS0 1 +#define IPCP_NBNS0 2 +#define IPCP_DNS1 4 +#define IPCP_NBNS1 8 + /* RFC5072 */ #define IP6CP_INT_ID 1 @@ -108,13 +115,16 @@ struct oc_ppp { struct in_addr out_peer_addr; uint64_t out_ipv6_int_ident; + int solicit_peerns; /* bitfield of DNS/NBNS to request */ + /* Incoming options */ - int exp_ppp_hdr_size; uint32_t in_asyncmap; int in_lcp_opts; int32_t in_lcp_magic; /* stored in on-the-wire order */ struct in_addr in_peer_addr; uint64_t in_ipv6_int_ident; + + int exp_ppp_hdr_size; /* predicted size of next PPP header */ }; #endif /* __OPENCONNECT_PPP_H__ */ -- 2.50.1