/* * lcp.cc - Self explanitory * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: lcp.cc,v 1.11 2004/08/22 20:57:30 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. */ #include "kernel.h" #include "debug.h" #include "lcp.h" #include "link.h" #include "aps_if.h" #include "ctrlfd.h" #include "l2tp_tunnel.h" #include "l2tp_linux.h" int deny_pfc = 1; // These protocol codes must not be rejected by the other side // or the result is terminal. Any other codes can be rejected unsigned char fatalCodes[] = { LCP_CODE_CONFREQ, LCP_CODE_CONFACK, LCP_CODE_CONFREJ, LCP_CODE_CONFNAK, LCP_CODE_TERMREQ, LCP_CODE_TERMACK, LCP_CODE_DISCREQ, LCP_CODE_ECHOREQ, LCP_CODE_ECHORPLY, LCP_CODE_CODEREJ, LCP_CODE_PROTOREJ, '\0' }; // These protocol IDs must not be rejected by peer or the result // is terminal. Other protocols can be rejected. unsigned short fatalProtos[] = { PPP_PROTO_LCP, PPP_PROTO_MLCP, 0x0000 }; const char *fatalProtos2Name[] = { "LCP", "MLCP", "unknown" }; class LcpEchoTimer : public CTimer { protected: LcpEchoTimer *drain_into; int timeout; CQueue q; public: LcpEchoTimer(int default_timeout = 10, LcpEchoTimer *dest = NULL) { timeout = default_timeout; drain_into = dest; Start(timeout); } ~LcpEchoTimer() { // Hack: LcpEchoTimer's destructor gets called at exit() // time. This results in the Queue trying to delete all // CLcpProtocol which currently are on the queue. Avoid // a valgrind warning by clearing the CQueue. while (q.Pull()) ; } void Add(CLcpProtocol *lcp) { q.Append(lcp); if (!IsActive()) Start(timeout); } void Remove(CLcpProtocol *lcp) { q.Remove(lcp); } void TimerExpired (void) { if (drain_into) { for (CQueueItem *qi = q.Pull(); qi; qi = q.Pull()) { CLcpProtocol *lcp = (CLcpProtocol *)qi; drain_into->Add(lcp); } return; } CQueueItem *qi = q.Pull(); if (qi) { CLcpProtocol *lcp = (CLcpProtocol *)qi; lcp->SndEchoReq(); } if (!q.IsEmpty()) Start(timeout); } }; static LcpEchoTimer echoTimer(300); /* 3.00s */ static LcpEchoTimer slowEchoTimer(10000, &echoTimer); /* 100s */ #if 0 case LongLcpEchoTimer : public BaseLcpEchoTimer { void TimerExpired (void) {} { CQueueItem *qi; while ((qi = q.Pull()) { CLcpProtocol *lcp = (CLcpProtocol *)qi; lcp->EchoTimeout(); } // Always restart the timer, as this one is slow Start(timeout); } } static LcpEchoTimer longEchoTimer(Mode_Long, 100*60); #endif void CLcpProtocol::EchoTimeout() { this->Unlink(); m_echoTimer.Stop(); if (m_echoSent >= 3) { m_echoIntervalTimer.Stop(); m_parent->Close(-2, "LCP: timeout sending Echo Requests."); return; } /* Don't kick us onto the global timer if we have an interval timer. */ if (m_echoInterval) return; /* use a short timeout if we dropped echo packets */ if (m_echoSent) echoTimer.Add(this); else slowEchoTimer.Add(this); } int CLcpProtocol::SndEchoReq(unsigned size) { CPppPacket pkt; const unsigned min_len = PPP_HEADER_LENGTH + LCP_HEADER_LENGTH + sizeof(u32); /* Use default size if none specified */ if (!size) { size = min_len; if (m_parent->m_lpolicy.Valid(P_ECHO_LEN)) size = m_parent->m_lpolicy.Get(P_ECHO_LEN); } if (size < min_len) size = min_len; pkt.Reserve(size); size -= min_len; pkt.Push32(m_parent->m_lpolicy.Get(P_MAGIC)); pkt.Push16(1 + LCP_HEADER_LENGTH + sizeof(u32) + 4 + 8 + size); pkt.Push8(++m_lastSentIdent); pkt.Push8(LCP_CODE_ECHOREQ); pkt.Push16(PPP_PROTO_LCP); pkt.Put8(m_lastSentIdent); struct timeval tv; gettimeofday(&tv, NULL); pkt.Put64(tv.tv_sec); pkt.Put32(tv.tv_usec); if (size > 0) { for (unsigned i = 0; i < size; i++) pkt.Put8((u8)i); } m_lastEchoIdent = m_lastSentIdent; m_echoSent++; m_parent->Output(&pkt, 1); m_echoTimer.Start(m_echoTime); return m_lastSentIdent; } static void echoTimeout(void *data) { CLcpProtocol *lcp = (CLcpProtocol *)data; lcp->EchoTimeout(); } void echoIntervalTimer(void *data) { CLcpProtocol *lcp = (CLcpProtocol *)data; lcp->SndEchoReq(); lcp->m_echoIntervalTimer.Start(lcp->m_echoInterval); } CLcpProtocol::CLcpProtocol() : CFsmProtocol() { m_restartTime = 3; m_protocolName = "LCP"; m_parent = NULL; m_lcpPing = NULL; m_initialReceivedConfReq = NULL; m_lastSentConfReq = NULL; m_lastReceivedConfReq = NULL; m_echoTimer.SetFunction(echoTimeout, this); m_echoIntervalTimer.SetFunction(echoIntervalTimer, this); m_lastEchoIdent = 0; m_echoSent = 0; m_rxPackets = 0; m_mruNaks = 0; m_restartedForMRRU = 0; } CLcpProtocol::~CLcpProtocol() { while (m_lcpPing) delete m_lcpPing; m_echoTimer.Stop(); m_echoIntervalTimer.Stop(); this->Unlink(); if (m_initialReceivedConfReq) delete m_initialReceivedConfReq; if (m_lastSentConfReq) delete m_lastSentConfReq; if (m_lastReceivedConfReq) delete m_lastReceivedConfReq; } /* GetOptionFromPacketU16 * Extract a u16 option value from a LCP packet fragment. Return * values >= 0 are the option's value, while < 0 indicates an error. */ int GetOptionFromPacketU16(CPppPacket *packet, u8 wanted_type) { CPppPacket *tmpPkt = new CPppPacket(packet); int ret = -1; if (!tmpPkt) return -2; packet = tmpPkt; while (packet->GetLength() >= 2) { u8 type = packet->Pull8(); u8 len = packet->Pull8(); if (type == wanted_type) { ret = -3; if (len < 4) goto out; ret = packet->Pull16(); goto out; } packet->Trim(len - 2); } ret = -4; out: delete packet; return ret; } int GetMRRUFromPacket(CPppPacket *packet) { return GetOptionFromPacketU16(packet, LCP_OPT_MRRU); } void CLcpProtocol::Input(CPppPacket *packet) { DebugEnter("CLcpProtocol::Input()"); CPppPacket *tmpPkt = new CPppPacket(packet); u8 code = packet->Pull8(); u8 ident = packet->Pull8(); u16 len = packet->Pull16(); if (len < 4 || (int)packet->GetLength() < (len - 4)) { delete tmpPkt; return; /* bad packet */ } m_rxPackets++; // Process the packet switch(code) { case LCP_CODE_CONFREQ: if (!m_initialReceivedConfReq) m_initialReceivedConfReq = new CPppPacket(tmpPkt); if (m_lastReceivedConfReq) delete m_lastReceivedConfReq; m_lastReceivedConfReq = tmpPkt; tmpPkt = NULL; m_lastRcvdIdent = ident; RcvConfReq(packet); break; case LCP_CODE_CONFACK: if(ident != m_lastSentIdent) { // Drop packet Log(LF_INFO|LF_PROTO, "LCP ConfAck, Bad ident...dropping"); break; } RcvConfAck(packet); break; case LCP_CODE_CONFNAK: if(ident != m_lastSentIdent) { // Drop packet Log(LF_INFO|LF_PROTO, "LCP ConfNak, Bad ident...dropping"); break; } RcvConfNak(packet); break; case LCP_CODE_CONFREJ: if(ident != m_lastSentIdent) { // Drop packet Log(LF_INFO|LF_PROTO, "LCP ConfRej, Bad ident...dropping"); break; } RcvConfRej(packet); break; case LCP_CODE_TERMREQ: m_lastRcvdIdent = ident; RcvTermReq(); break; case LCP_CODE_TERMACK: if(ident != m_lastSentIdent) { // Drop packet Log(LF_INFO|LF_PROTO, "LCP TermAck, Bad ident...dropping"); break; } RcvTermAck(); break; case LCP_CODE_CODEREJ: RcvCodeReject(packet); break; case LCP_CODE_PROTOREJ: RcvProtoReject(packet); break; case LCP_CODE_ECHOREQ: m_lastRcvdIdent = ident; RcvEchoReq(packet); break; case LCP_CODE_ECHORPLY: { lcp_ping *ping = m_lcpPing; TimeVal tv; packet->Pull8(); /* ident */ packet->Pull32(); /* MAGIC */ u64 secs = packet->Pull64(); u32 usecs = packet->Pull32(); TimeVal got(secs, usecs); tv.GetTimeOfDay(); tv = tv - got; while (ping) { lcp_ping *next = ping->m_next; ping->got_ping(ident, tv.tv); ping = next; } if (m_lastEchoIdent == ident) m_echoSent = 0; else Log(LF_ERROR|LF_PROTO, "%s: LCP: received echo reply ident=%02x expected %02x", m_parent->m_channel->device_name, ident, m_lastEchoIdent); break; } case LCP_CODE_DISCREQ: // Discard Log(LF_INFO|LF_PROTO, "LCP DiscReq, Discarding packet"); break; default: Log(LF_INFO|LF_PROTO, "LCP Rcvd-Unknown-Code %d, Rejecting", code); SndCodeReject(packet); RcvUnknownCode(packet); break; } if (tmpPkt) delete tmpPkt; DebugVoidReturn; } // // Receive a Configuration-Request from the peer. Simultaniously build // ack, nak and reject responses while we go, sending the appropriate // response and discarding the others. Also, build a set of acked options // and copy them to the link configuration if we send an ack // void CLcpProtocol::RcvConfReq(CPppPacket *packet) { u8 addr[LCP_OPT_EPD_ADDR_LEN + 256]; const char *port = m_parent->m_channel->device_name; policy_t theirs; policy_t &want = m_parent->m_policy; DebugEnter("CLcpProtocol::RcvConfReq()"); // // Reserve header space in the responses // m_ackPkt.Clear(); m_nakPkt.Clear(); m_rejPkt.Clear(); m_ackPkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); m_nakPkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); m_rejPkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); theirs.Clear(); while(packet->GetLength()) { u8 type = packet->Pull8(); u8 len = packet->Pull8(); switch(type) { #if 0 case 0: if (len > 2) packet->Trim(len - 2); break; #endif case LCP_OPT_AUTH: { u16 auth_proto = packet->Pull16(); if (auth_proto == PPP_PROTO_CHAP) { if (len != LCP_OPT_CHAP_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len > 2 ? len : 2); if (len > 2) { packet->Pull(&m_rejPkt, len - 2); } Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [CHAP], Invalid size (%d)", port, len); } else { u8 digest = packet->Pull8(); if (digest != LCP_OPT_CHAP_MD5) { // We do MD5 only, suggest it m_nakPkt.Put8(type); m_nakPkt.Put8(LCP_OPT_PAP_LEN); m_nakPkt.Put16(PPP_PROTO_PAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [CHAP->PAP]", port); } else { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put16(PPP_PROTO_CHAP); m_ackPkt.Put8(LCP_OPT_CHAP_MD5); theirs.Set(P_AUTH, PPP_PROTO_CHAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [CHAP]", port); } } } else if (auth_proto == PPP_PROTO_PAP) { if (len != LCP_OPT_PAP_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len > 2 ? len : 2); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [PAP], Invalid size (%d)", port, len); } else { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put16(PPP_PROTO_PAP); theirs.Set(P_AUTH, PPP_PROTO_PAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [PAP]", port); } } else { // Unknown authentication protocol, NAK and suggest PAP as a minimum m_nakPkt.Put8(type); m_nakPkt.Put8(LCP_OPT_PAP_LEN); if (len > 2) { packet->Trim(len - 2); m_nakPkt.Put16(PPP_PROTO_PAP); } Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [AUTH] Unknown protocol (%#x)", port, auth_proto); } break; } case LCP_OPT_MRU: { // Maximum-Receive-Unit if (len != LCP_OPT_MRU_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len > 2 ? len : 2); if (len > 2) packet->Pull(&m_rejPkt, len - 2); m_rejPkt.Print(); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRU], Invalid size (%d)", port, len); break; } u16 mru = packet->Pull16(); CPppPacket *wpkt; if (mru > m_parent->m_hard_mru || (mru < LCP_OPT_MRU_MIN && mru > m_parent->m_hard_mru)) { Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRU(%d)]->[MRU(%d)]", port, mru, m_parent->DefaultMRU()); wpkt = &m_nakPkt; mru = m_parent->m_hard_mru; } else { Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRU(%d)]", port, mru); wpkt = &m_ackPkt; } theirs.Set(P_MRU, mru); wpkt->Put8(type); wpkt->Put8(len); wpkt->Put16(mru); break; } case LCP_OPT_MAGIC: { if(len != LCP_OPT_MAGIC_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len > 2 ? len : 2); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [Magic], Invalid size (%d)", port, len); break; } u32 magic = packet->Pull32(); if (magic == want.Get(P_MAGIC)) { Log(LF_WARN|LF_PROTO, "%s: Possible Loopback detected.", port); m_parent->m_loop_count++; if(m_parent->m_loop_count > MAX_LOOP_COUNT) { Log(LF_ERROR|LF_PROTO, "%s: Loopback confirmed, closing connection", port); m_parent->Close(-2, "LCP: Connection looped back"); goto rcr_cleanup; } // Nak the magic number option in case he is just using the same // number as us, give him another suggestion m_nakPkt.Put8(type); m_nakPkt.Put8(len); m_nakPkt.Put32(magic); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [Magic(%#x)]", port, magic); } else { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put32(magic); theirs.Set(P_MAGIC, magic); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [Magic(%#x)]", port, magic); } break; } case LCP_OPT_PFC: if(len != LCP_OPT_PFC_LEN || deny_pfc) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [PFC], Invalid size (%d)", port, len); break; } // We can do PFC m_ackPkt.Put8(type); m_ackPkt.Put8(len); theirs.Set(P_PFC, 1); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [PFC]", port); break; case LCP_OPT_ACFC: if(len != LCP_OPT_ACFC_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [ACFC], Invalid size (%d)", port, len); break; } // We can do ACFC m_ackPkt.Put8(type); m_ackPkt.Put8(len); theirs.Set(P_ACFC, 1); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [ACFC]", port); break; case LCP_OPT_ACCM: { if(len != LCP_OPT_ACCM_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [ACCM], Invalid size (%d)", port, len); break; } u32 accm = packet->Pull32(); m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put32(accm); theirs.Set(P_ACCM, accm); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [ACCM(%#x)]", port, accm); break; } case LCP_OPT_MRRU: { if(len != LCP_OPT_MRRU_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRRU], Invalid size (%d)", port, len); break; } u16 mrru = packet->Pull16(); if (want.Valid(P_WANT_REJ_MRRU)) { m_rejPkt.Put8(type); m_rejPkt.Put8(len); m_rejPkt.Put16(mrru); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRRU(%d)]", port, mrru); } else if (mrru >= LCP_OPT_MRU_MIN) { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put16(mrru); theirs.Set(P_MRRU, mrru); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRRU(%d)]", port, mrru); } else { m_nakPkt.Put8(type); m_nakPkt.Put8(len); m_nakPkt.Put16(m_parent->DefaultMRU()); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [MRRU(%d)]->[MRRU(%d)]", port, mrru, m_parent->DefaultMRU()); } break; } case LCP_OPT_SSN: if(len != LCP_OPT_SSN_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); m_rejPkt.Print(); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [SSNHF], Invalid size (%d)", port, len); break; } // We'll do SSNHF m_ackPkt.Put8(type); m_ackPkt.Put8(len); theirs.Set(P_SSN, 1); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [SSNHF]", port); break; case LCP_OPT_EPD: { if (len < LCP_OPT_EPD_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [EPD], Invalid size (%d)", port, len); break; } u8 aClass = packet->Pull8(); packet->Pull(addr, len - 3); if (aClass) { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put8(aClass); m_ackPkt.Put(addr, len - 3); theirs.Set(P_EPD_CLASS, aClass); memcpy(theirs.epd_addr, addr, len - 3); theirs.Set(P_EPD_LENGTH, len - 3); } else { m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put8(aClass); m_ackPkt.Put(addr, len - 3); } Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [EPD(%d)]", port, aClass); break; } case LCP_OPT_LDISC: { if (len != LCP_OPT_LDISC_LEN) { // Wrong size, reject option m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); m_rejPkt.Print(); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [LDISC], Invalid size (%d)", port, len); break; } u16 ldisc = packet->Pull16(); m_ackPkt.Put8(type); m_ackPkt.Put8(len); m_ackPkt.Put16(ldisc); theirs.Set(P_LDISC, ldisc); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [LDISC(%d)]", port, ldisc); break; } default: // Reject options we don't know m_rejPkt.Put8(type); m_rejPkt.Put8(len); if (len > 2) packet->Pull(&m_rejPkt, len - 2); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: (%d), Unknown option", port, type); break; } } if (!theirs.Valid(P_MRU) && want.Valid(P_MRU) && want.Get(P_MRU) != m_parent->DefaultMRU()) { /* Work around DLink routers which will never accept an MRU. */ if (++m_mruNaks < 3) { theirs.Set(P_MRU, want.Get(P_MRU)); m_nakPkt.Put8(LCP_OPT_MRU); m_nakPkt.Put8(LCP_OPT_MRU_LEN); m_nakPkt.Put16(want.Get(P_MRU)); } } if (m_restartedForMRRU && (!theirs.Valid(P_MRRU) || (theirs.Get(P_MRRU) != want.Get(P_MRRU)))) { m_nakPkt.Put8(LCP_OPT_MRRU); m_nakPkt.Put8(LCP_OPT_MRRU_LEN); m_nakPkt.Put16(1500); } if(m_rejPkt.GetLength()) { // Send the reject packet m_rejPkt.Push16(m_rejPkt.GetLength() + LCP_HEADER_LENGTH); m_rejPkt.Push8(m_lastRcvdIdent); m_rejPkt.Push8(LCP_CODE_CONFREJ); m_rejPkt.Push16(PPP_PROTO_LCP); CFsmProtocol::RcvBadConfReq(); } else if(m_nakPkt.GetLength()) { // Send the nak packet m_nakPkt.Push16(m_nakPkt.GetLength() + LCP_HEADER_LENGTH); m_nakPkt.Push8(m_lastRcvdIdent); m_nakPkt.Push8(LCP_CODE_CONFNAK); m_nakPkt.Push16(PPP_PROTO_LCP); CFsmProtocol::RcvBadConfReq(); } else { // Send the ack packet m_ackPkt.Push16(m_ackPkt.GetLength() + LCP_HEADER_LENGTH); m_ackPkt.Push8(m_lastRcvdIdent); m_ackPkt.Push8(LCP_CODE_CONFACK); m_ackPkt.Push16(PPP_PROTO_LCP); m_parent->m_rpolicy.Set(theirs); CFsmProtocol::RcvGoodConfReq(); } rcr_cleanup: DebugVoidReturn; } void CLcpProtocol::SndConfAck() { DebugEnter("CLcpProtocol::SndConfAck"); Log(LF_INFO|LF_PROTO|LF_TX, "%s: LCP Send ConfAck: len = %d, ident = %d", m_parent->m_channel->device_name, m_ackPkt.GetLength(), m_lastRcvdIdent); m_parent->Output(&m_ackPkt); DebugVoidReturn; } void CLcpProtocol::SndConfNak() { DebugEnter("CLcpProtocol::SndConfNak"); if (m_rejPkt.GetLength()) { Log(LF_INFO|LF_PROTO|LF_TX, "%s: LCP Send ConfRej: len = %d, ident = %d", m_parent->m_channel->device_name, m_rejPkt.GetLength(), m_lastRcvdIdent); m_parent->Output(&m_rejPkt); } else { Log(LF_INFO|LF_PROTO|LF_TX, "%s: LCP Send ConfNak: len = %d, ident = %d", m_parent->m_channel->device_name, m_nakPkt.GetLength(), m_lastRcvdIdent); m_parent->Output(&m_nakPkt); } DebugVoidReturn; } // // RcvConfNak() - Receive a Configure-Nak from the peer // Adjust config options we ask for and reject any options // that the peer has tossed in that we dont understand. Call // the base class function to update the state machine. // void CLcpProtocol::RcvConfNak(CPppPacket *packet) { u8 digest; u8 addr[LCP_OPT_EPD_ADDR_LEN]; const char *port = m_parent->m_channel->device_name; policy_t &want = m_parent->m_npolicy; policy_t new_opts; // Options we want as modified here DebugEnter("CLcpProtocol::RcvConfNak()"); new_opts.Set(m_parent->m_npolicy); while (packet->GetLength()) { u8 type = packet->Pull8(); u8 len = packet->Pull8(); switch(type) { case LCP_OPT_AUTH: { u16 auth_proto = packet->Pull16(); if (auth_proto == PPP_PROTO_CHAP) { if (len != LCP_OPT_CHAP_LEN) { // Wrong size, drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [CHAP], Invalid size (%d)", port, len); goto out; } digest = packet->Pull8(); if (digest != LCP_OPT_CHAP_MD5) { // We do MD5 only, so drop down to PAP new_opts.Set(P_AUTH, PPP_PROTO_PAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: Unknown digest(%#x) [CHAP->PAP]", port, digest); } else { new_opts.Set(P_AUTH, PPP_PROTO_CHAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfReq: [CHAP]", port); } } else if (auth_proto == PPP_PROTO_PAP) { if (len != LCP_OPT_PAP_LEN) { // Wrong size, drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [PAP], Invalid size (%d)", port, len); goto out; } new_opts.Set(P_AUTH, PPP_PROTO_PAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [PAP]", port); } else { // Unknown authentication protocol, NAK and suggest PAP as a minimum if (len > 2) packet->Trim(len - 2); new_opts.Set(P_AUTH, PPP_PROTO_PAP); Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [AUTH] Unknown protocol (%#x)", port, auth_proto); } break; } case LCP_OPT_MRU: { if (len != LCP_OPT_MRU_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "<7>%s: LCP ConfNak: [MRU], Invalid Length (%d)", port, len); goto out; } u16 sugg_mru = packet->Pull16(); // The MRU we asked for is unacceptable, just use the default u16 mru = m_parent->m_hard_mru < m_parent->DefaultMRU() ? m_parent->m_hard_mru : m_parent->DefaultMRU(); if (sugg_mru < mru && sugg_mru >= 576) mru = sugg_mru; new_opts.Set(P_MRU, mru); Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [MRU], using default (%u)", port, mru); break; } case LCP_OPT_MAGIC: { if (len != LCP_OPT_MAGIC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [Magic], Invalid Length (%d)", port, len); goto out; } u32 magic = packet->Pull32(); // See if we're looped back if (m_parent->m_policy.Get(P_MAGIC) == magic) { Log(LF_INFO|LF_RX, "%s: LCP ConfNak: Possible Loopback", port); m_parent->m_loop_count++; if (m_parent->m_loop_count > MAX_LOOP_COUNT) { Log(LF_ERROR|LF_PROTO, "%s: Loopback confirmed, closing connection", port); m_parent->Close(-2, "Connection looped back"); goto out; } } break; } case LCP_OPT_PFC: if(len != LCP_OPT_PFC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [PFC], Invalid Length", port); goto out; } // This should really be a rej, so it must be a request if (want.Get(P_PFC) || deny_pfc) { // This should be a reject! Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [PFC]", port); new_opts.Set(P_PFC, 0); } else { // They want it? Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [PFC] will try", port); new_opts.Set(P_PFC, 1); } break; case LCP_OPT_ACFC: if (len != LCP_OPT_ACFC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [ACFC], Invalid Length", port); goto out; } if (want.Get(P_ACFC)) { // Should be a reject Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [ACFC]", port); new_opts.Set(P_ACFC, 0); } else { // They want it? Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [ACFC] will try", port); new_opts.Set(P_ACFC, 1); } break; case LCP_OPT_ACCM: { if (len != LCP_OPT_ACCM_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [ACCM], Invalid Length", port); goto out; } u32 accm = packet->Pull32(); // And what they want to our ACCM accm |= new_opts.Get(P_ACCM); new_opts.Set(P_ACCM, accm); Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [ACCM], now %#x", port, accm); break; } case LCP_OPT_MRRU: if(len != LCP_OPT_MRRU_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [MRRU], Invalid Length", port); goto out; } new_opts.Set(P_MRRU, packet->Pull16()); break; case LCP_OPT_SSN: if(len != LCP_OPT_SSN_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [SSNHF], Invalid Length", port); goto out; } if (want.Get(P_SSN)) { // Should be REJ! Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [SSNHF]", port); new_opts.Set(P_SSN, 0); } else { // They want it Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [SSNHF] will try", port); new_opts.Set(P_SSN, 1); } break; case LCP_OPT_EPD: { if (len < LCP_OPT_EPD_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [EPD], Invalid Length", port); goto out; } u8 aClass = packet->Pull8(); packet->Pull(addr, len - 3); // Our class is no good but this shouldn't happen?? Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [EPD(%d)(%s)]", port, aClass, addr); want.Set(P_EPD_CLASS, 0); break; } case LCP_OPT_LDISC: { if (len != LCP_OPT_LDISC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [LDISC], Invalid Length", port); goto out; } packet->Pull16(); /* discard ldisc */ // The LDISC we asked for is unacceptable, no BoD Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [LDISC], disabling", port); new_opts.Set(P_LDISC, 0); break; } default: // Must ignore everything else! if (len > 2) packet->Trim(len - 2); break; } } // If we get here the packet must good and we can set the new options m_parent->m_npolicy.Set(new_opts); // Tell the state machine CFsmProtocol::RcvConfNak(); out: DebugVoidReturn; } // // // void CLcpProtocol::RcvConfRej(CPppPacket *packet) { policy_t new_opts; const char *port = m_parent->m_channel->device_name; u8 addr[LCP_OPT_EPD_ADDR_LEN]; DebugEnter("CLcpProtocol::RcvConfRej()"); Log(LF_INFO|LF_PROTO|LF_RX, "%s: LCP Rcvd ConfRej -->", port); new_opts.Set(m_parent->m_npolicy); while(packet->GetLength()) { u8 type = packet->Pull8(); u8 len = packet->Pull8(); if (len < 2) { Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: Dropping packet -- Invalid Length", port); goto out; } switch(type) { case LCP_OPT_PFC: if (len != LCP_OPT_PFC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [PFC], Invalid Length", port); goto out; } new_opts.Set(P_PFC, 0); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [PFC]", port); break; case LCP_OPT_ACFC: if (len != LCP_OPT_ACFC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [ACFC], Invalid Length", port); goto out; } new_opts.Set(P_ACFC, 0); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [ACFC]", port); break; case LCP_OPT_MRRU: if (len != LCP_OPT_MRRU_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [MRRU], Invalid Length", port); goto out; } packet->Pull16(); // Don't do MP new_opts.Set(P_MRRU, 0); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [MRRU], Disabling MP", port); break; case LCP_OPT_SSN: if (len != LCP_OPT_SSN_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [SSNHF], Invalid Length", port); goto out; } new_opts.Set(P_SSN, 0); break; case LCP_OPT_EPD: if (len < LCP_OPT_EPD_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [EPD], Invalid Length", port); goto out; } packet->Pull8(); packet->Pull(addr, len - 3); new_opts.Set(P_EPD_CLASS, 0); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [EPD]", port); break; case LCP_OPT_LDISC: if(len != LCP_OPT_LDISC_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [LDISC], Invalid Length", port); goto out; } packet->Pull16(); // Use the default new_opts.Set(P_LDISC, 0); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [LDISC], Disabling BACP", port); break; case LCP_OPT_MRU: if(len != LCP_OPT_MRU_LEN) { // Drop packet Log(LF_INFO|LF_PROTO, "%s: LCP ConfNak: [MRU], Invalid Length (%d)", port, len); goto out; } packet->Pull16(); // Pull off the mru // The MRU we asked for is unacceptable, just use the default new_opts.Set(P_MRU, m_parent->m_hard_mru < m_parent->DefaultMRU() ? m_parent->m_hard_mru : m_parent->DefaultMRU()); Log(LF_INFO|LF_PROTO, "%s: LCP ConfRej: [MRU], using default (%d)", port, m_parent->DefaultMRU()); break; default: // Fatal option rejected, close the link Log(LF_DEBUG|LF_PROTO, "Option? %d len %d", type, len); m_parent->Close(-2, "LCP: Fatal option rejected"); goto out; } } m_parent->m_npolicy.Set(new_opts); // Same state transition as Nak CFsmProtocol::RcvConfNak(); out: DebugVoidReturn; } // // // void CLcpProtocol::RcvConfAck(CPppPacket *) { DebugEnter("CLcpProtocol::RcvConfAck()"); memcpy(&m_parent->m_lpolicy, &m_parent->m_npolicy, sizeof(policy_t)); CFsmProtocol::RcvConfAck(); DebugVoidReturn; } // // // void CLcpProtocol::SndTermAck() { CPppPacket ackPkt; DebugEnter("CLcpProtocol::SndTermAck()"); ackPkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); ackPkt.Push16(4); ackPkt.Push8(m_lastRcvdIdent); ackPkt.Push8(LCP_CODE_TERMACK); ackPkt.Push16(PPP_PROTO_LCP); m_parent->Output(&ackPkt); DebugVoidReturn; } void CLcpProtocol::SndConfReq() { CPppPacket c; policy_t &want = m_parent->m_npolicy; const char *port = m_parent->m_channel->device_name; DebugEnter("CLcpProtocol::SndConfReq()"); if (!cntConfig) { Log(LF_DEBUG|LF_PROTO, "Config attempts failed: Closing"); m_parent->Close(-2, "LCP:Max config attempts reached: Check LCP options"); DebugVoidReturn; } // Reserving header space in packet c.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); Log(LF_INFO|LF_PROTO, "%s: m_hard_mru = %d", port, m_parent->m_hard_mru); if (want.Get(P_MRU) && (want.Get(P_MRU) != LCP_OPT_MRU_DEFAULT)) { Log(LF_INFO|LF_PROTO, "%s: Adding option MRU %d", port, want.Get(P_MRU)); c.Put8(LCP_OPT_MRU); c.Put8(LCP_OPT_MRU_LEN); c.Put16(want.Get(P_MRU)); } if (want.Get(P_AUTH) == PPP_PROTO_CHAP) { Log(LF_INFO|LF_PROTO, "%s: Adding option CHAP", port); c.Put8(LCP_OPT_AUTH_CHAP); c.Put8(LCP_OPT_CHAP_LEN); c.Put16(PPP_PROTO_CHAP); c.Put8(LCP_OPT_CHAP_MD5); } else if (want.Get(P_AUTH) == PPP_PROTO_PAP) { Log(LF_INFO|LF_PROTO, "%s: Adding option PAP", port); c.Put8(LCP_OPT_AUTH_PAP); c.Put8(LCP_OPT_PAP_LEN); c.Put16(PPP_PROTO_PAP); } if (want.Get(P_MAGIC)) { Log(LF_INFO|LF_PROTO, "%s :Adding option MAGIC %#x", port, want.Get(P_MAGIC)); c.Put8(LCP_OPT_MAGIC); c.Put8(LCP_OPT_MAGIC_LEN); c.Put32(want.Get(P_MAGIC)); } if (want.Get(P_PFC) && deny_pfc) want.Set(P_PFC, 0); if (want.Get(P_PFC)) { Log(LF_INFO|LF_PROTO, "%s: Adding option PFC", port); c.Put8(LCP_OPT_PFC); c.Put8(LCP_OPT_PFC_LEN); } if (want.Get(P_ACFC)) { Log(LF_INFO|LF_PROTO, "%s: Adding option ACFC", port); c.Put8(LCP_OPT_ACFC); c.Put8(LCP_OPT_ACFC_LEN); } if (want.Get(P_ACCM)) { Log(LF_INFO|LF_PROTO, "%s: Adding option ACCM %#x", port, want.Get(P_ACCM)); c.Put8(LCP_OPT_ACCM); c.Put8(LCP_OPT_ACCM_LEN); c.Put32(want.Get(P_ACCM)); } if (want.Get(P_MRRU)) { Log(LF_INFO|LF_PROTO, "%s: Adding option MRRU %d", port, want.Get(P_MRRU)); c.Put8(LCP_OPT_MRRU); c.Put8(LCP_OPT_MRRU_LEN); c.Put16(want.Get(P_MRRU)); if (want.Get(P_SSN)) { Log(LF_INFO|LF_PROTO, "%s: Adding option SSN", port); c.Put8(LCP_OPT_SSN); c.Put8(LCP_OPT_SSN_LEN); } u8 epd_class = want.Get(P_EPD_CLASS); u8 epd_length = want.Get(P_EPD_LENGTH); if (epd_class) { Log(LF_INFO|LF_PROTO, "%s: Adding option EPD class %d", port, epd_class); c.Put8(LCP_OPT_EPD); c.Put8(LCP_OPT_EPD_LEN + epd_length); c.Put8(epd_class); c.Put(want.epd_addr, epd_length); } } if (want.Get(P_LDISC)) { Log(LF_INFO|LF_PROTO, "%s: Adding option LDISC %#x", port, want.Get(P_LDISC)); c.Put8(LCP_OPT_LDISC); c.Put8(LCP_OPT_LDISC_LEN); c.Put16(want.Get(P_LDISC)); } // Decrement the config restart count if (cntConfig) { Log(LF_DEBUG|LF_PROTO, "Decrementing cntConfig(%d)", cntConfig); cntConfig--; } Log(LF_DEBUG|LF_PROTO, "cntConfig: %d", cntConfig); c.Push16(c.GetLength() + LCP_HEADER_LENGTH); c.Push8(++m_lastSentIdent); c.Push8(CONFREQ); if (m_lastSentConfReq) delete m_lastSentConfReq; m_lastSentConfReq = new CPppPacket(&c); c.Push16(PPP_PROTO_LCP); m_parent->Output(&c); DebugVoidReturn; } void CLcpProtocol::SndTermReq() { CPppPacket tPkt; DebugEnter("CLcpProtocol::SndTermReq()"); fprintf(stderr, "Sending Term Req\n"); if (!cntTerminate) { Log(LF_DEBUG|LF_PROTO, "Attempted %d times to terminate connection", maxTerminate); m_parent->Close(-2, "LCP: Peer not responding to Terminate Request"); DebugVoidReturn; } // Decrement the terminate request count if (cntTerminate) cntTerminate--; // Send the request tPkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); tPkt.Push16(LCP_HEADER_LENGTH); tPkt.Push8(++m_lastSentIdent); tPkt.Push8(LCP_CODE_TERMREQ); tPkt.Push16(PPP_PROTO_LCP); m_parent->Output(&tPkt); DebugVoidReturn; } void CLcpProtocol::ThisLayerFinished(const char *why) { char tmp[1024]; strcpy(tmp, "LCP: "); strncat(tmp, why, sizeof(tmp)-32); if (!m_rxPackets) strcat(tmp, " -- no packets received"); DebugEnter("CLcpProtocol::ThisLayerFinished()"); //printk("CLcpProtocol::ThisLayerFinished() -- Hangup channel %s\n", m_parent->m_channel->device_name); m_parent->Close(-2, tmp); m_parent->Hangup(); DebugVoidReturn; } void CLcpProtocol::SetupICCN(l2tp_packet_t *iccn) { /* Do a bit of housekeeping. This needs to be done on Up() rather * than at down time, as ThisLayerDown() gets called to shut LCP * when a multihop session activates. */ if (m_initialReceivedConfReq) delete m_initialReceivedConfReq; m_initialReceivedConfReq = NULL; if (m_lastSentConfReq) delete m_lastSentConfReq; m_lastSentConfReq = NULL; if (m_lastReceivedConfReq) delete m_lastReceivedConfReq; m_lastReceivedConfReq = NULL; /* In case of a multihop session, copy data we may need to send * out again or use later. */ if (iccn) { m_initialReceivedConfReq = iccn->pkt_from_avp(AVP_InitialReceivedLCPConfReq); m_lastSentConfReq = iccn->pkt_from_avp(AVP_LastSentLCPConfReq); m_lastReceivedConfReq = iccn->pkt_from_avp(AVP_LastReceivedLCPConfReq); } } void CLcpProtocol::Up() { if (!m_parent->m_lpolicy.Valid(P_ECHO) || !m_parent->m_lpolicy.Valid(P_ECHO_INTERVAL)) { m_parent->m_lpolicy.Set(P_ECHO, 500); m_parent->m_lpolicy.Set(P_ECHO_INTERVAL, 600); } if (m_restartedForMRRU) { CFsmProtocol::Up(); return; } if (m_parent->m_iccn_pkt && m_parent->m_iccn_pkt->AVPs[AVP_ProxyAuthenName] && m_parent->m_iccn_pkt->AVPs[AVP_ProxyAuthenType] && ntohs(*m_parent->m_iccn_pkt->AVPs[AVP_ProxyAuthenType]) == 3) { /* FIXME: this is a giant hack that has not been verified * to be complete. Seems to work, though. */ SetState(ST_OPENED); m_parent->m_phase = PHASE_AUTHENTICATE; m_parent->m_lpolicy.Set(P_AUTH, PPP_PROTO_PAP); ThisLayerUp(); } else CFsmProtocol::Up(); } void CLcpProtocol::RestartLCPForMRRU(OptionSet_t *os) { u32 mrru = 0; if (!m_parent->ifOptions.pol || !m_parent->ifOptions.pol->Valid(P_MRRU) || !(mrru = m_parent->ifOptions.pol->Get(P_MRRU))) goto out; CFsmProtocol::Down(); m_restartedForMRRU = 1; if (os->pol) { m_parent->m_npolicy.Merge(*os->pol); m_parent->m_policy.Merge(*os->pol); } m_parent->m_npolicy.Set(P_MRRU, mrru); m_parent->m_policy.Set(P_MRRU, mrru); out: CFsmProtocol::Up(); } void CLcpProtocol::ThisLayerUp() { DebugEnter("CLcpProtocol::ThisLayerUp()"); /* start doing our echo probes */ m_echoSent = 0; m_echoTime = m_parent->m_lpolicy.Get(P_ECHO); m_echoInterval = m_parent->m_lpolicy.Get(P_ECHO_INTERVAL); if (m_parent->ifOptions.pol) { if (m_parent->ifOptions.pol->Valid(P_ECHO)) m_echoTime = m_parent->ifOptions.pol->Get(P_ECHO); if (m_parent->ifOptions.pol->Valid(P_ECHO_INTERVAL)) m_echoInterval = m_parent->ifOptions.pol->Get(P_ECHO_INTERVAL); } if (m_echoTime) { if (!m_echoInterval) { echoTimer.Add(this); } else { fprintf(stderr, "using local interval timer of %d\n", m_echoInterval); m_echoIntervalTimer.Start(m_echoInterval); } } m_parent->ch_ioctl(BIOC_SET_MTU, m_parent->m_rpolicy.Get(P_MRU)); // Proceed to the authentication phase if there is authentication to do // otherwise go straight to the network phase Log(LF_DEBUG|LF_PROTO, "Local Auth %#x, Remote Auth %#x", m_parent->m_lpolicy.Get(P_AUTH), m_parent->m_rpolicy.Get(P_AUTH)); if (m_parent->m_lpolicy.Get(P_AUTH) || m_parent->m_rpolicy.Get(P_AUTH)) { // Authentication is required, bring up the authentication protocols m_parent->m_phase = PHASE_AUTHENTICATE; m_parent->m_papProto.Up(); m_parent->m_chapProto.Up(); } else { m_parent->Ready(); } DebugVoidReturn; } void CLcpProtocol::ThisLayerDown() { DebugEnter("CLcpProtocol::ThisLayerDown()"); this->Unlink(); m_echoTimer.Stop(); m_echoIntervalTimer.Stop(); m_lastEchoIdent = 0xffff; m_rxPackets = 0; m_mruNaks = 0; m_parent->m_phase = PHASE_TERMINATE; /* close the link */ //m_parent->Close(); DebugVoidReturn; } void CLcpProtocol::SndCodeReject(CPppPacket *packet) { CPppPacket cr; DebugEnter("CLcpProtocol::SndCodeReject()"); cr.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH); // Truncate the packet if necessary if(packet->GetLength() + LCP_HEADER_LENGTH > m_parent->m_rpolicy.Get(P_MRU)) { packet->Chop(packet->GetLength() + LCP_HEADER_LENGTH - m_parent->m_rpolicy.Get(P_MRU)); } cr.Put(packet); cr.Push16(packet->GetLength() + LCP_HEADER_LENGTH + PPP_HEADER_LENGTH); cr.Push8(m_lastRcvdIdent); cr.Push8(LCP_CODE_CODEREJ); cr.Push16(PPP_PROTO_LCP); m_parent->Output(&cr); DebugVoidReturn; } void CLcpProtocol::SndEchoReply(CPppPacket *packet) { u32 magic = packet->Pull32(); CPppPacket rpl_pkt; DebugEnter("CLcpProtocol::SndEchoReply()"); if (magic != 0 && magic == m_parent->m_lpolicy.Get(P_MAGIC)) { // Loopback m_parent->m_loop_count++; if(m_parent->m_loop_count > MAX_LOOP_COUNT) { Log(LF_ERROR|LF_PROTO, "%s: Loopback confirmed, closing connection", m_parent->m_channel->device_name); m_parent->Close(-2, "LCP: Connection looped back"); } DebugVoidReturn; } rpl_pkt.Reserve(PPP_HEADER_LENGTH + LCP_HEADER_LENGTH + sizeof(u32)); rpl_pkt.Push32(m_parent->m_lpolicy.Get(P_MAGIC)); rpl_pkt.Push16(packet->GetLength() + LCP_HEADER_LENGTH + sizeof(u32)); rpl_pkt.Push8(m_lastRcvdIdent); rpl_pkt.Push8(LCP_CODE_ECHORPLY); rpl_pkt.Push16(PPP_PROTO_LCP); rpl_pkt.Put(packet); m_parent->Output(&rpl_pkt, 1); DebugVoidReturn; } // // We received an LCP code-reject packet // void CLcpProtocol::RcvCodeReject(CPppPacket *pkt) { int i = 0; u8 code; DebugEnter("CLcpProtocol::RcvCodeReject()"); code = *((const u8 *)pkt); // Determine if the code is fatal or not while(fatalCodes[i] != '\0') { if(fatalCodes[i] == code) { // Fatal, close the link Log(LF_INFO|LF_PROTO, "%s: Rcv'd fatal LCP code reject code=%d", m_parent->m_channel->device_name, code); m_parent->Close(-2, "LCP: Received fatal code reject"); DebugVoidReturn; } i++; } // The rejected code is not fatal, reconfigure to stop // sending the code? CFsmProtocol::RcvCodeReject(); DebugVoidReturn; } // // We received an LCP protocol-reject packet // void CLcpProtocol::RcvProtoReject(CPppPacket *pkt) { int i = 0; u16 id = pkt->Pull16(); DebugEnter("CLcpProtocol::RcvProtoReject()"); /* MCLP rejects should just continue on without trying to negotiate * MLCP. * HACK: some modems send a Reject with 0x0101 to 0x010a when they * receive MLCP packets. Use such a packet as a sign to continue to * work around this damage. */ if (id == PPP_PROTO_MLCP || (0x0101 <= id && id <= 0x010a)) { if (m_parent->m_phase == PHASE_MLCP) { m_parent->m_mlcpProto.Down(); m_parent->Ready(); return; } } if (id == PPP_PROTO_IPV6CP) { if (m_parent->m_interface) m_parent->m_interface->RcvRejIpv6(); return; } // Determine if the protocol is fatal or not while (fatalProtos[i] != '\0') { if(fatalProtos[i] == id) { char tmp[128]; // Fatal, close the link sprintf(tmp, "LCP: Fatal protocol %s (0x%04x) rejected", fatalProtos2Name[i], id); m_parent->Close(-2, tmp); DebugVoidReturn; } i++; } // The rejected protocol is not fatal, reconfigure to stop // sending the protocol CFsmProtocol::RcvCodeReject(); DebugVoidReturn; } void CLcpProtocol::RejectProtocol(CPppPacket *packet) { CPppPacket rejPkt; u16 proto = packet->Pull16(); u16 len = packet->GetLength(); DebugEnter("CLcpProtocol::RejectProtocol()"); u16 mru = m_parent->m_rpolicy.Get(P_MRU); if (!mru) len = len > 1492 ? 1492 : len; else if (len + 8 > mru) len = mru > 8 ? mru - 8 : 0; packet->Pull(&rejPkt, len); rejPkt.Push16(proto); rejPkt.Push16(rejPkt.GetLength() + 4); rejPkt.Push8(m_lastSentIdent++); rejPkt.Push8(LCP_CODE_PROTOREJ); rejPkt.Push16(PPP_PROTO_LCP); m_parent->Output(&rejPkt); DebugVoidReturn; } lcp_ping::lcp_ping(ctrlfd_t *cfd, CLink *link, unsigned size) { m_success = 0; m_cfd = cfd; if (cfd) cfd->die_notify(this); m_link = link; m_next = link->m_lcpProto.m_lcpPing; if (m_next) m_next->m_prevp = &m_next; m_prevp = &link->m_lcpProto.m_lcpPing; *m_prevp = this; int id = link->m_lcpProto.SndEchoReq(size); if (id < 0) { cfd->printf("Invalid size %u\n", size); delete this; return; } m_ident = id; if (cfd) cfd->printf("send seq %d\n", m_ident); Start(500); } lcp_ping::~lcp_ping() { Stop(); *m_prevp = m_next; if (m_next) m_next->m_prevp = m_prevp; if (m_cfd) { m_cfd->die_unnotify(this); m_cfd->done(m_success ? 0 : 2); } } void lcp_ping::TimerExpired(void) { if (m_cfd) m_cfd->printf("timed out\n"); delete this; } void lcp_ping::got_ping(int ident, struct timeval delta) { if (ident != m_ident) return; got_good_ping(ident, delta); } void lcp_ping::got_good_ping(int ident, struct timeval delta) { if (m_cfd) m_cfd->printf("seq %d in %lu.%06lus\n", ident, (unsigned long)delta.tv_sec, (unsigned long)delta.tv_usec); m_success = 1; delete this; } void lcp_ping::ctrlfd_die_notification(ctrlfd_t *cfd) { m_cfd = NULL; delete this; } lcp_probe_ping_t::lcp_probe_ping_t(lcp_probe_t *probe, CLink *link) : lcp_ping(NULL, link) { m_probe = probe; } void lcp_probe_ping_t::got_good_ping(int ident, struct timeval delta) { m_probe->ping_complete(this, 1); delete this; } void lcp_probe_ping_t::TimerExpired(void) { m_probe->ping_complete(this, 0); delete this; } lcp_probe_t::lcp_probe_t(lcp_probe_complete_t *req, const char *user) { m_req = req; m_outstanding = 0; m_nr_alive = 0; m_nr_dead = 0; fprintf(stderr, "lcp_probe_t user(%s)\n", user); CLink *link; /* FIXME */ for (link=linkListHead; link; link = link->m_next) { if (link->m_phase <= PHASE_AUTHENTICATE) continue; if (strcmp(link->ifOptions.user, user)) continue; m_outstanding++; new lcp_probe_ping_t(this, link); } if (!m_outstanding) { m_req->lcp_probe_complete(m_nr_alive, m_nr_dead); delete this; } } void lcp_probe_t::ping_complete(lcp_probe_ping_t *ping, int alive) { if (alive) m_nr_alive++; else m_nr_dead++; if (!--m_outstanding) { m_req->lcp_probe_complete(m_nr_alive, m_nr_dead); delete this; } } void lcp_probe_user(const char *user) { lcp_probe_complete_t *req = new lcp_probe_complete_t; fprintf(stderr, "lcp_probe_user(%s) => %p\n", user, req); new lcp_probe_t(req, user); }