#ifdef USE_L2TP /* l2tp_tunnel.cc * * Copyright 2004, 2007 Benjamin C. R. LaHaise, All Rights Reserved. * Permission is hereby granted to copy under the terms of the GPLv2 * or later. See the file LICENSE for details. */ #include "l2tpd.h" #include "l2tp_tunnel.h" #include "packet.h" #include "l2tp_linux.h" #include "aps_if.h" #include #include #include #include #include #include #include #include #include #include #include "pretty_dump.h" #include "md5.h" unsigned num_est_tunnels; unsigned num_tunnels; unsigned num_est_sessions; unsigned num_sessions; unsigned l2tp_default_tunnel_csum_payload; l2tp_packet_t pkt; l2tp_tunnel_t::l2tp_tunnel_t(l2tp_peer_t *_peer, u16 tunnel) { num_tunnels++; tunnel_state = L2TP_Tunnel_Idle; peer = _peer; memset(sessions, 0, sizeof(sessions)); memset(assigned_sessions, 0, sizeof(assigned_sessions)); tunnel_id = peer_tunnel_id = 0; tunnel_Ns = tunnel_Nr = 0; call_serial_number = 0; need_zlb = 0; tunnel_aborted = 0; free_session_id = 0; nr_free_session_ids = 65535; if (!tunnel) tunnel = peer->alloc_tunnel_id(this); fprintf(stderr, "peer %s: new tunnel: %5d\n", _peer->peer_ip(), tunnel); tunnel_id = tunnel; get_random(challenge_bytes, sizeof challenge_bytes); if (tunnel_id) this->set_csum_payload(l2tp_default_tunnel_csum_payload); } l2tp_tunnel_t::~l2tp_tunnel_t() { for (unsigned i=0; i<65536; i++) { if (sessions[i]) delete sessions[i]; } peer->remove_tunnel_id(tunnel_id, this); num_tunnels--; } const char *l2tp_tunnel_t::get_state(void) { static const char *states[] = { "idle", "wait-ctl-reply", "wait-ctl-conn", "established", }; return states[tunnel_state]; } void l2tp_tunnel_t::dump_sessions(ctrlfd_t *cfd, int verbose) { int i; cfd->printf("\t\tpeer(%5d) local(%5d) state(%s)%c", peer_tunnel_id, tunnel_id, get_state(), verbose ? ' ' : '\n'); if (verbose) cfd->printf("Ns(%u) Nr(%u)\n", tunnel_Ns, tunnel_Nr); cfd->printf("\t\tsessions {\n"); for (i=0; i<65536; i++) { l2tp_session_t *session = sessions[i]; if (session) session->dump_session(cfd); } cfd->printf("\t\t}\n"); dfs_l2tp_tx_queue_t(cfd); } void l2tp_tunnel_t::clean_up(const char *reason) { int i; fprintf(stderr, "l2tp_tunnel_t: clean_up!\n"); stop_hello_timer(); for (i=0; i<65536; i++) { if (sessions[i]) { sessions[i]->SetDownReason(reason); delete sessions[i]; } sessions[i] = NULL; } peer_tunnel_id = 0; tunnel_state = L2TP_Tunnel_Idle; tunnel_Ns = 0; tunnel_Nr = 0; need_zlb = 0; free_session_id = 1; nr_free_session_ids = 65535; delete this; } int l2tp_tunnel_t::alloc_session_id(void) { u16 id = (free_session_id + rand()) & 65535; for (int i=0; i<65536; i++) { if (!id) id = 1; if (!sessions[id]) { free_session_id = id + 1; nr_free_session_ids--; return id; } id++; } return -1; } void l2tp_tunnel_t::tx_timed_out(void) { clean_up("tunnel transmit timed out"); } const struct AVPInfo RecognizedAVPs[MaxAVPs] = { { 2, "MessageType" }, { 2, "ResultCode" }, { 2, "ProtocolVersion" }, { 4, "FramingCapabilities" }, { 4, "BearerCapabilities" }, { 8, "TieBreaker" }, { 2, "FirmwareRevision" }, { 1, "HostName" }, { -1, "VendorName" }, { 2, "AssignedTunnelID" }, { 2, "ReceiveWindowSize" }, { -1, "Challenge" }, { 3, "CauseCode" }, { 16, "ChallengeResponse" }, { 2, "AssignedSessionID" }, { 4, "CallSerialNumber" }, { 4, "MinimumBPS" }, { 4, "MaximumBPS" }, { 4, "BearerType" }, { 4, "FramingType" }, { 0, "Reserved20" }, { -1, "CalledNumber", 1, 0 }, { -1, "CallingNumber", 1, 0 }, { 0, "SubAddress" }, { 4, "ConnectSpeed" }, { 4, "PhysicalChannelID" }, { -1, "InitialReceivedLCPConfReq" }, /* FIXME: not used */ { -1, "LastSentLCPConfReq" }, /* FIXME: not used */ { -1, "LastReceivedLCPConfReq" }, /* FIXME: not used */ { 2, "ProxyAuthenType", 0, 1 }, { -1, "ProxyAuthenName", 1 }, { -1, "ProxyAuthenChallenge", 0, 1 }, /* FIXME: not used */ { 2, "ProxyAuthenID", 0, 1 }, { -1, "ProxyAuthenResponse", 1 }, { 0, "CallErrors" }, { 4, "ACCM" }, { -1, "RandomVector" }, { 0, "PrivateGroupID" }, { 4, "RxConnectSpeed" }, { 0, "SequencingRequired" }, }; int l2tp_packet_t::decode_avp(u16 vendor, u16 type, u16 length, u16 *ptr) { /* we only support bog standard AVPs */ if (vendor) return -1; if (type >= MaxAVPs) return -1; if (!RecognizedAVPs[type].min_len || length < RecognizedAVPs[type].min_len) return -1; AVPs[type] = ptr; nr_avps ++; if (vendor == 0 && RecognizedAVPs[type].log_text) { char name[256]; if (length > 255) length = 255; strncpy(name, (char *)ptr, length); name[length] = 0; fprintf(stderr, "decode_avp: len=%d %s = '%s'\n", length, RecognizedAVPs[type].name, name); } if (vendor == 0 && RecognizedAVPs[type].log_hex) { fprintf(stderr, "decode_avp: len=%d %s\n", length, RecognizedAVPs[type].name); pretty_dump(length, (u8 *)ptr); } return 0; } int l2tp_packet_t::decode_hidden_avp(l2tp_peer_t *peer, u16 vendor, u16 type, u16 length, u16 *ptr) { u16 type_nw = htons(type); u16 *random_avp = AVPs[AVP_RandomVector]; u16 random_len; MD5_CTX md5; u8 clast[16]; unsigned i; if (!random_avp || !peer->m_secret_len) return -1; random_len = ntohs(random_avp[-3]) & L2TP_AVP_LENGTH; fprintf(stderr, "random_len: %d\n", (int)random_len); if (random_len < (6 + 2)) return -1; random_len -= 6; MD5Init(&md5); MD5Update(&md5, (u8*)&type_nw, 2); MD5Update(&md5, (u8*)peer->m_secret, peer->m_secret_len); MD5Update(&md5, (u8*)random_avp, random_len); MD5Final(&md5); if (length > 16) memcpy(clast, ptr, 16); for (i=0; im_secret, peer->m_secret_len); MD5Update(&md5, clast, 16); MD5Final(&md5); if ((length - i) > 16) memcpy(clast, ((u8*)ptr)+i, 16); } ((u8 *)ptr)[i] ^= md5.digest[i & 15]; } pretty_dump(length, (unsigned char *)ptr); u16 new_length = ntohs(ptr[0]); length -= 2; if (new_length <= 0 || new_length > length) return -1; ptr[-2] = htons(new_length + 6); // compensate for avp_length() return decode_avp(vendor, type, new_length, ptr+1); } int l2tp_packet_t::parse_packet(l2tp_peer_t *peer, u16 *data, int size) { reset(); //pretty_dump(size, (unsigned char *)data); this->raw_packet = data; this->raw_length = size; Flags = ntohs(*data++); if (Flags & L2TPF_L) Length = ntohs(*data++); else Length = size; if (Length > size) return -1; if (size < Length) { fprintf(stderr, "parse_packet: clipping size(%d) from Length(%d)\n", size, Length); size = Length; } Tunnel = ntohs(*data++); Session = ntohs(*data++); if (Flags & L2TPF_S) { Ns = ntohs(*data++); Nr = ntohs(*data++); } else { Ns = -1; Nr = -1; } if (Flags & L2TPF_O) { u16 offset = *data++; data = (u16 *)((char *)data + offset); } if (((char *)data - (char *)raw_packet) > (long)size) { fprintf(stderr, "packet too small (%ld) > size(%ld)", (long)((char *)data - (char *)raw_packet), (long)size); return -1; } size -= (char *)data - (char *)raw_packet; Length -= (char *)data - (char *)raw_packet; DataPtr = data; /* that's it for a data packet */ if (!(Flags & L2TPF_T)) return 0; /* now we get to parse all the avps */ while (size >= 6) { u16 avp_lengthbits = ntohs(*data++); u16 avp_vendor = ntohs(*data++); u16 avp_type = ntohs(*data++); int avp_length = avp_lengthbits & L2TP_AVP_LENGTH; u16 *avp_value; if (avp_length < 6 || avp_length > size) { fprintf(stderr, "avp too short/long (%u vs %u).\n", avp_length, size); return -1; } size -= avp_length; if (avp_length > 6) avp_value = data; else avp_value = NULL; avp_length -= 6; data = (u16 *)((char *)data + avp_length); if (avp_lengthbits & L2TP_AVP_RESERVED) { fprintf(stderr, "reserved bits set, ignoring.\n"); continue; /* ignore as per RFC 2661 pg 13 */ } if (avp_lengthbits & L2TP_AVP_H) { fprintf(stderr, "hidden (%d %d 0x%x)\n", avp_vendor, avp_type, avp_lengthbits); if (decode_hidden_avp(peer, avp_vendor, avp_type, avp_length, avp_value)) { fprintf(stderr, "avp decode failed\n"); if (avp_lengthbits & L2TP_AVP_M) return -1; } continue; } if (decode_avp(avp_vendor, avp_type, avp_length, avp_value)) { fprintf(stderr, "decode_avp failed [%d/%d/%s]\n", avp_vendor, avp_type, (avp_type >= MaxAVPs) ? NULL : RecognizedAVPs[avp_type].name); if (avp_lengthbits & L2TP_AVP_M) { fprintf(stderr, "mandatory avp decode failed 0x%04x %d 0x%04x\n", avp_vendor, avp_type, avp_length); return -1; } } } if (size) fprintf(stderr, "parse_packet: %d bytes left\n", size); return 0; } int l2tp_packet_t::avp_length(int avp) { if (AVPs[avp]) { u16 val = ntohs(AVPs[avp][-3]); val &= L2TP_AVP_LENGTH; if (val >= 6) val -= 6; else val = 0; return val; } return 0; } char *l2tp_packet_t::avp_strdup(int avp) { int len = avp_length(avp); if (len > 0) { char *str = (char *)malloc(len + 1); memcpy(str, AVPs[avp], len); str[len] = 0; return str; } return NULL; } int l2tp_tunnel_t::build_challenge_rsp(u8 *where, unsigned char msgtype) { int len = pkt.avp_length(AVP_Challenge); if (len < 1) return 0; if (!peer->m_secret) { fprintf(stderr, "peer %s: no secret for challenge response\n", peer->peer_ip()); return 0; } MD5_CTX md5; MD5Init(&md5); MD5Update(&md5, &msgtype, 1); MD5Update(&md5, (unsigned char *)peer->m_secret, peer->m_secret_len); MD5Update(&md5, (unsigned char *)pkt.AVPs[AVP_Challenge], len); MD5Final(&md5); memcpy(where, md5.digest, 16); return 0x16; } int l2tp_tunnel_t::check_challenge_rsp(unsigned char msgtype) { int len = pkt.avp_length(AVP_ChallengeResponse); if (len < 16) return -1; MD5_CTX md5; MD5Init(&md5); MD5Update(&md5, &msgtype, 1); MD5Update(&md5, (unsigned char *)peer->m_secret, peer->m_secret_len); MD5Update(&md5, (unsigned char *)challenge_bytes, sizeof challenge_bytes); MD5Final(&md5); return memcmp(pkt.AVPs[AVP_ChallengeResponse], md5.digest, 16); } int l2tp_packet_t::is_avp_mandatory(int avp) { if (AVPs[avp]) { u16 val = ntohs(AVPs[avp][-3]); fprintf(stderr, "is_mandatory: 0x%04x\n", val); return !!(val & L2TP_AVP_M); } return 0; } void l2tp_tunnel_t::tunnel_abort(const char *msg) { fprintf(stderr, "tunnel_abort(%s)\n", msg); for (unsigned i=0; i<65536; i++) { l2tp_session_t *session = sessions[i]; if (session) { session->abort(msg); delete session; } } tunnel_aborted = 1; } void l2tp_tunnel_t::send_ZLB(void) { CPppPacket *pkt = prepare_pkt(0xc802, 0); need_zlb = 0; if (!peer_tunnel_id) { /* Prevent an invalid ZLB from going out */ fprintf(stderr, "send_ZLB: tried to send a ZLB with no tunnel id!\n"); delete pkt; return; } send_pkt(pkt, 0); } void l2tp_tunnel_t::add_hostname_avp(CPppPacket *pkt) { add_bytes_avp(pkt, 0x8006 + peer->l2tpd->host_name_len, AVP_HostName, (u8*)peer->l2tpd->host_name, peer->l2tpd->host_name_len); } void l2tp_tunnel_t::send_SCCRQ(void) { CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_SCCRQ); add_u16_avp(pkt, 0x8008, AVP_AssignedTunnelID, tunnel_id); add_u16_avp(pkt, 0x8008, AVP_ProtocolVersion, 0x0100); add_hostname_avp(pkt); add_u32_avp(pkt, 0x800a, AVP_FramingCapabilities, 3); if (peer->m_secret) { add_bytes_avp(pkt, 0x8006 + sizeof challenge_bytes, AVP_Challenge, challenge_bytes, sizeof challenge_bytes); } send_pkt(pkt); } void l2tp_tunnel_t::send_SCCRP(void) { u8 data[16]; int rsp = build_challenge_rsp(data, L2TP_CMSG_TYPE_SCCRP); CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_SCCRP); add_u16_avp(pkt, 0x8008, AVP_AssignedTunnelID, tunnel_id); add_u32_avp(pkt, 0x800a, AVP_FramingCapabilities, 3); add_u16_avp(pkt, 0x8008, AVP_ProtocolVersion, 0x0100); add_hostname_avp(pkt); if (rsp) add_bytes_avp(pkt, 0x8016, AVP_ChallengeResponse, data, 16); if (peer->m_secret) { add_bytes_avp(pkt, 0x8006 + sizeof challenge_bytes, AVP_Challenge, challenge_bytes, sizeof challenge_bytes); } send_pkt(pkt); } void l2tp_tunnel_t::send_CDN(u16 session_id, u16 peer_session_id, u16 result, l2tp_monitor_t *monitor) { if (tunnel_state == L2TP_Tunnel_Idle) return; CPppPacket *pkt = prepare_pkt(0xc802, peer_session_id); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_CDN); add_u16_avp(pkt, 0x8008, AVP_AssignedSessionID, session_id); add_u16_avp(pkt, 0x8008, AVP_ResultCode, result); send_pkt(pkt, 1, monitor, "CDN tx"); } void l2tp_session_t::send_CDN(u16 result) { if (session_state != L2TP_Session_Idle) { session_state = L2TP_Session_Idle; tunnel->send_CDN(session_id, peer_session_id, 1, m_monitor); } } void l2tp_tunnel_t::send_SCCCN(void) { u8 data[16]; int rsp = build_challenge_rsp(data, L2TP_CMSG_TYPE_SCCCN); CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_SCCCN); if (rsp) add_bytes_avp(pkt, 0x8016, AVP_ChallengeResponse, data, 16); send_pkt(pkt); } void l2tp_tunnel_t::send_StopCCN(void) { CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_StopCCN); add_u32_avp(pkt, 0x800a, AVP_FramingCapabilities, 3); add_u16_avp(pkt, 0x8008, AVP_AssignedTunnelID, tunnel_id); add_hostname_avp(pkt); add_u16_avp(pkt, 0x8008, AVP_ProtocolVersion, 0x0100); send_pkt(pkt); } void l2tp_tunnel_t::send_HELLO(void) { CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_HELLO); send_pkt(pkt); } CPppPacket *l2tp_tunnel_t::prepare_pkt(u16 flags, u16 session_id) { u16 data[6]; data[0] = htons(flags); data[1] = htons(0); /* len */ data[2] = htons(peer_tunnel_id); data[3] = htons(session_id); data[4] = htons(tunnel_Ns); data[5] = htons(tunnel_Nr); return new CPppPacket((u8 *)data, sizeof(data)); } void l2tp_tunnel_t::send_pkt(CPppPacket *pkt, int incr_seq, l2tp_monitor_t *monitor, const char *msg) { /* control packets *MUST* have a length field. */ ((u16 *)pkt->m_start)[1] = htons(pkt->GetLength()); tunnel_Ns += incr_seq; need_zlb = 0; if (debug) { fprintf(stderr, "l2tp_tunnel_t::send_pkt\n"); pretty_dump(pkt->GetLength(), (unsigned char *)pkt->m_start); } if (monitor) { l2tp_packet_t tmp_l2tp_pkt; tmp_l2tp_pkt.parse_packet(peer, (u16 *)pkt->m_start, pkt->GetLength()); monitor->monitor_l2tp_control_pkt(&tmp_l2tp_pkt, msg); } int ret = write(peer->udp_fd, pkt->m_start, pkt->GetLength()); if (ret <= 0) { fprintf(stderr, "send_pkt: write(%d) = %d, errno=%d\n", pkt->GetLength(), ret, errno); } if (incr_seq) tx_append(pkt); else delete pkt; } void l2tp_tunnel_t::tx_retransmit(CPppPacket *pkt) { u16 *data = (u16 *)pkt->m_start; data[5] = htons(tunnel_Nr); // update ack sequence number in the packet if (debug) fprintf(stderr, "tx_retransmit Ns=%d, Nr=%d\n", ntohs(data[4]), ntohs(data[5])); int ret = write(peer->udp_fd, pkt->m_start, pkt->GetLength()); if (ret <= 0) { fprintf(stderr, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); fprintf(stderr, "tx_retransmit: write(%d) = %d, errno=%d\n", pkt->GetLength(), ret, errno); } } int l2tp_tunnel_t::tunnel_check_auth(const char *which, int type) { if (peer->m_secret || peer->l2tpd->peer_must_auth) { if (!pkt.AVPs[AVP_ChallengeResponse]) { fprintf(stderr, "%s: tunnel(%d, %d) %s challenge response from peer %s\n", which, tunnel_id, peer_tunnel_id, "missing", peer->peer_ip()); tunnel_abort("no challenge response"); return 1; } if (check_challenge_rsp(type)) { fprintf(stderr, "%s: tunnel(%d, %d) %s challenge response from peer %s\n", which, tunnel_id, peer_tunnel_id, "failed", peer->peer_ip()); tunnel_abort("invalid challenge response"); return 1; } fprintf(stderr, "%s: tunnel(%d, %d) %s challenge response from peer %s\n", which, tunnel_id, peer_tunnel_id, "successful", peer->peer_ip()); } return 0; } void l2tp_tunnel_t::handle_SCCRQ(void) { if (!pkt.AVPs[AVP_AssignedTunnelID]) { tunnel_abort("sccrq: no tunnel id"); return; } u16 id = ntohs(*pkt.AVPs[AVP_AssignedTunnelID]); if (!id) { tunnel_abort("sccrq: assigned tunnel id is 0"); return; } if (id && peer_tunnel_id != id) { if (peer_tunnel_id) fprintf(stderr, "peer_tunnel_id changed? %d vs %d\n", peer_tunnel_id, id); peer_tunnel_id = id; struct sockaddr_l2tp sl; memset(&sl, 0, sizeof(sl)); sl.sl_tunnel = tunnel_id; sl.sl_peer_tunnel = peer_tunnel_id; if (connect(peer->l2tp_fd, (struct sockaddr *)&sl, sizeof(sl))) perror("SCCRQ: tunnel connect()"); } const char *reason = NULL; switch (tunnel_state) { case L2TP_Tunnel_Idle: send_SCCRP(); tunnel_state = L2TP_Tunnel_WaitCtlConn; break; case L2TP_Tunnel_Established: reason = "SCCRQ while established"; num_est_tunnels--; case L2TP_Tunnel_WaitCtlConn: if (!reason) reason = "SCCRQ while in WaitCtlConn"; send_StopCCN(); tunnel_state = L2TP_Tunnel_Idle; clean_up(reason); break; case L2TP_Tunnel_WaitCtlReply: default: fprintf(stderr, "handle_SCCRQ: unknown state\n"); } } void l2tp_tunnel_t::handle_SCCRP(void) { int id = -1; if (pkt.AVPs[AVP_AssignedTunnelID]) id = ntohs(*pkt.AVPs[AVP_AssignedTunnelID]); /* This has to be done before tunnel_check_auth() as we may need to * send an abort. */ if (L2TP_Tunnel_WaitCtlReply == tunnel_state) { // If we've opened a tunnel and the other end has responded // to our request, this is where we learn their tunnel id. if (!peer_tunnel_id) { if (id <= 0) { tunnel_abort("SCCRP: no tunnel id"); return; } peer_tunnel_id = id; } } if (tunnel_check_auth("SCCRP", L2TP_CMSG_TYPE_SCCRP)) return; switch (tunnel_state) { case L2TP_Tunnel_Established: num_est_tunnels--; case L2TP_Tunnel_Idle: case L2TP_Tunnel_WaitCtlConn: send_StopCCN(); tunnel_state = L2TP_Tunnel_Idle; clean_up("SCCRQ received"); break; case L2TP_Tunnel_WaitCtlReply: num_est_tunnels++; tunnel_state = L2TP_Tunnel_Established; send_SCCCN(); tunnel_up(); break; default: fprintf(stderr, "handle_SCCRP: unknown state\n"); } } void l2tp_tunnel_hello::TimerExpired(void) { send_HELLO(); Start(hello_interval); } void l2tp_tunnel_hello::start_hello_timer(void) { Start(hello_interval); } void l2tp_tunnel_t::stop_tunnel(void) { if (tunnel_state != L2TP_Tunnel_Idle) { send_StopCCN(); tunnel_state = L2TP_Tunnel_Idle; clean_up("shutdown due to stop-tunnel"); } } void l2tp_tunnel_t::tunnel_up(void) { start_hello_timer(); // Bring up any waiting sessions. for (int i=0; i<65536; i++) { l2tp_session_t *session = sessions[i]; if (session) session->tunnel_up(); } } void l2tp_tunnel_t::handle_SCCCN(void) { const char *reason = NULL; if (tunnel_check_auth("SCCCN", L2TP_CMSG_TYPE_SCCCN)) return; switch (tunnel_state) { case L2TP_Tunnel_Idle: clean_up("SCCCN received while idle"); break; case L2TP_Tunnel_Established: reason = "SCCCN received while established"; num_est_tunnels--; case L2TP_Tunnel_WaitCtlReply: if (!reason) reason = "SCCCN received while in WaitCtlReply"; send_StopCCN(); tunnel_state = L2TP_Tunnel_Idle; clean_up(reason); break; case L2TP_Tunnel_WaitCtlConn: num_est_tunnels++; tunnel_state = L2TP_Tunnel_Established; tunnel_up(); break; default: fprintf(stderr, "handle_SCCCN: unknown state\n"); } } void l2tp_tunnel_t::handle_StopCCN(void) { if (tunnel_state == L2TP_Tunnel_Established) num_est_tunnels--; tunnel_state = L2TP_Tunnel_Idle; send_ZLB(); tunnel_abort("StopCCN received"); } void l2tp_tunnel_t::handle_HELLO(void) { // implicit send_ZLB(); // or transmit another message need_zlb = 1; } void l2tp_tunnel_t::bring_up(void) { switch (tunnel_state) { case L2TP_Tunnel_Idle: // Bring up the control connection. tunnel_state = L2TP_Tunnel_WaitCtlReply; send_SCCRQ(); break; default: break; } } void l2tp_tunnel_t::open_session(ctrlfd_t *cfd, const char *site, int wait_for_hangup, char *called_number) { bring_up(); new l2tp_session_t(this, cfd, site, wait_for_hangup, called_number); } ////////////////////////////////////////////////////////// // Incoming call handling ////////////////////////////////////////////////////////// void l2tp_tunnel_t::send_ICRQ(u16 session_id, const char *called_number, const char *calling_number) { CPppPacket *pkt = prepare_pkt(0xc802, 0); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_ICRQ); add_u32_avp(pkt, 0x800a, AVP_CallSerialNumber, call_serial_number); add_u16_avp(pkt, 0x8008, AVP_AssignedSessionID, session_id); if (called_number) { int len = strlen(called_number); add_bytes_avp(pkt, 0x8006 + len, AVP_CalledNumber, (u8 *)called_number, len); } if (calling_number && calling_number[0]) { int len = strlen(calling_number); add_bytes_avp(pkt, 0x8006 + len, AVP_CallingNumber, (u8 *)calling_number, len); } call_serial_number ++; send_pkt(pkt); } void l2tp_tunnel_t::send_ICRP(l2tp_session_t *session) { CPppPacket *pkt = prepare_pkt(0xc802, session->peer_session_id); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_ICRP); add_u16_avp(pkt, 0x8008, AVP_AssignedSessionID, session->session_id); send_pkt(pkt, 1, session->m_monitor, "ICRP tx"); } void l2tp_tunnel_t::send_ICCN(u16 session_id, u32 txspeed, u32 framing, CLink *multihop, l2tp_session_t *multihop_session) { u16 auth = 0; CPppPacket *pkt = prepare_pkt(0xc802, session_id); add_u16_avp(pkt, 0x8008, AVP_MessageType, L2TP_CMSG_TYPE_ICCN); add_u32_avp(pkt, 0x800a, AVP_FramingType, framing); add_u32_avp(pkt, 0x800a, AVP_ConnectSpeed, txspeed); if (multihop && multihop->m_lcpProto.m_initialReceivedConfReq) { CPppPacket *p = multihop->m_lcpProto.m_initialReceivedConfReq; add_pkt_avp(pkt, 0x0006, AVP_InitialReceivedLCPConfReq, p); } if (multihop && multihop->m_lcpProto.m_lastSentConfReq) { CPppPacket *p = multihop->m_lcpProto.m_lastSentConfReq; add_pkt_avp(pkt, 0x0006, AVP_LastSentLCPConfReq, p); } if (multihop && multihop->m_lcpProto.m_lastReceivedConfReq) { CPppPacket *p = multihop->m_lcpProto.m_lastReceivedConfReq; add_pkt_avp(pkt, 0x0006, AVP_LastReceivedLCPConfReq, p); } if (multihop_session && (multihop_session->proxy_auth_type >= 0)) add_u16_avp(pkt, 0x0008, AVP_ProxyAuthenType, multihop_session->proxy_auth_type); if (multihop_session && (multihop_session->proxy_auth_id >= 0)) add_u16_avp(pkt, 0x0008, AVP_ProxyAuthenID, multihop_session->proxy_auth_id); if (multihop_session && multihop_session->proxy_auth_name) add_pkt_avp(pkt, 0x0006, AVP_ProxyAuthenName, multihop_session->proxy_auth_name); if (multihop_session && multihop_session->proxy_auth_challenge) add_pkt_avp(pkt, 0x0006, AVP_ProxyAuthenChallenge, multihop_session->proxy_auth_challenge); if (multihop_session && multihop_session->proxy_auth_response) add_pkt_avp(pkt, 0x0006, AVP_ProxyAuthenResponse, multihop_session->proxy_auth_response); if (multihop && (multihop->ifOptions.proto_id == PPP_PROTO_PAP)) { auth = 3; /* FIXME: this avp should be hidden */ unsigned int pass_len = strlen(multihop->ifOptions.passwd); if (pass_len) add_bytes_avp(pkt, 0x0006 + pass_len, AVP_ProxyAuthenResponse, (u8 *)multihop->ifOptions.passwd, pass_len); } if (multihop && (multihop->ifOptions.proto_id == PPP_PROTO_CHAP)) { auth = 2; /* FIXME: this avp should be hidden */ add_bytes_avp(pkt, 0x0006 + multihop->ifOptions.chap_lvalue_len, AVP_ProxyAuthenChallenge, multihop->ifOptions.chap_lvalue, multihop->ifOptions.chap_lvalue_len); } if (auth) { add_u16_avp(pkt, 0x0008, AVP_ProxyAuthenType, auth); add_u16_avp(pkt, 0x0008, AVP_ProxyAuthenID, multihop->ifOptions.auth_info); const char *user = multihop->get_user(); int user_len = user ? strlen(user) : 0; if (user_len > 0) { add_bytes_avp(pkt, 0x0006 + user_len, AVP_ProxyAuthenName, (u8 *)user, user_len); } } send_pkt(pkt); } void l2tp_tunnel_t::handle_ICRQ(void) { if (!pkt.AVPs[AVP_AssignedSessionID]) { fprintf(stderr, "handle_ICRQ: no AssignedSessionID\n"); return; } if (!pkt.AVPs[AVP_CallSerialNumber]) { fprintf(stderr, "handle_ICRQ: no CallSerialNumber\n"); return; } u16 peer_id = ntohs(*pkt.AVPs[AVP_AssignedSessionID]); l2tp_session_t *session = NULL; if (!pkt.Session) session = new l2tp_session_t(this, peer_id, pkt.Session); else { fprintf(stderr, "handle_ICRQ: bad session ids %d %d\n", peer_id, pkt.Session); return; } session->handle_ICRQ(&pkt); } void l2tp_tunnel_t::handle_ICRP(void) { if (!pkt.AVPs[AVP_AssignedSessionID]) { fprintf(stderr, "handle_ICRP: no AssignedSessionID\n"); return; } u16 peer_id = ntohs(*pkt.AVPs[AVP_AssignedSessionID]); l2tp_session_t *session = sessions[pkt.Session]; if (!session) { session = new l2tp_session_t(this, peer_id, pkt.Session); fprintf(stderr, "ICRP: made new session id %d peer %d\n", session->session_id, peer_id); } session->handle_ICRP(&pkt); } void l2tp_tunnel_t::handle_CDN(void) { if (!pkt.AVPs[AVP_AssignedSessionID]) { fprintf(stderr, "handle_CDN: no AssignedSessionID\n"); return; } u16 peer_session_id = ntohs(*pkt.AVPs[AVP_AssignedSessionID]); u16 session_id = pkt.Session; l2tp_session_t *session = sessions[session_id]; if (!session_id || !session) session = assigned_sessions[peer_session_id]; if (session) session->handle_CDN(&pkt); fprintf(stderr, "l2tp_tunnel_t::handle_CDN: %s session(%d) peer_session(%d)\n", session ? "yes" : "no", session_id, peer_session_id); } void l2tp_tunnel_t::handle_ICCN(void) { l2tp_session_t *session = sessions[pkt.Session]; if (!session) session = new l2tp_session_t(this, 0, pkt.Session); session->handle_ICCN(&pkt); } void l2tp_tunnel_t::handle_OCRQ(void) { if (!pkt.AVPs[AVP_AssignedSessionID]) { fprintf(stderr, "handle_OCRQ: no AssignedSessionID\n"); } u16 peer_id = ntohs(*pkt.AVPs[AVP_AssignedSessionID]); /* For now we don't implement outgoing calls -- send a CDN. */ send_CDN(0, peer_id, 3 /* Result = disc for admin reasons */, NULL); } ////////////////////////////////////////////////////////// typedef void (l2tp_tunnel_t::*l2tp_handle_packet_t)(void); #define MAX_l2tp_handle_packet_t 17 static l2tp_handle_packet_t packet_t_array[MAX_l2tp_handle_packet_t] = { NULL, // reserved &l2tp_tunnel_t::handle_SCCRQ, &l2tp_tunnel_t::handle_SCCRP, &l2tp_tunnel_t::handle_SCCCN, &l2tp_tunnel_t::handle_StopCCN, NULL, // 5 - reserved &l2tp_tunnel_t::handle_HELLO, &l2tp_tunnel_t::handle_OCRQ, NULL, // &l2tp_tunnel_t::handle_OCRP, NULL, // &l2tp_tunnel_t::handle_OCCN, &l2tp_tunnel_t::handle_ICRQ, &l2tp_tunnel_t::handle_ICRP, &l2tp_tunnel_t::handle_ICCN, NULL, // 13 - reserved &l2tp_tunnel_t::handle_CDN, NULL, // &l2tp_tunnel_t::handle_WEN, NULL, // &l2tp_tunnel_t::handle_SLI, }; void l2tp_tunnel_t::handle_control_packet(void) { l2tp_handle_packet_t func = NULL; int msgtype; if (!pkt.AVPs[AVP_MessageType]) { tunnel_abort("handle_control_packet: no MessageType AVP"); pretty_dump(pkt.raw_length, (unsigned char *)pkt.raw_packet); return; } msgtype = ntohs(pkt.AVPs[AVP_MessageType][0]); if (msgtype < MAX_l2tp_handle_packet_t) func = packet_t_array[msgtype]; if (func) (this->*func)(); else if (pkt.is_avp_mandatory(AVP_MessageType)) { char tmp[256]; sprintf(tmp, "handle_control_packet: unknown mandatory MessageType AVP %d\n", msgtype); tunnel_abort(tmp); } } void l2tp_tx_queue_t::TimerExpired(void) { l2tp_tunnel_t *tunnel = (l2tp_tunnel_t *)this; if (debug) fprintf(stderr, "tx_queue: TimerExpired() Ns=%d, Nr=%d\n", tunnel->tunnel_Ns, tunnel->tunnel_Nr); if (!IsEmpty()) { CQueueItem *q = Peek(); CPppPacket *pkt = (CPppPacket *)q; if (nr_retransmits > 10) { fprintf(stderr, "l2tp tx timeout\n"); tx_timed_out(); return; } nr_retransmits++; tx_retransmit(pkt); retransmit_interval *= 2; if (retransmit_interval > 800) retransmit_interval = 800; Start(retransmit_interval); } else retransmit_interval = 10; } void l2tp_tx_queue_t::discard_acked_packets(u16 seq) { unsigned last_acked = ~0U; if (IsEmpty()) { // fprintf(stderr, "discard_acked_packets: IsEmpty\n"); return; } if (debug) fprintf(stderr, "discard_acked_packets(%u)\n", seq); while (!IsEmpty()) { CQueueItem *q = Pull(); CPppPacket *pkt = (CPppPacket *)q; u16 *data = (u16 *)pkt->m_start; u16 pkt_Ns = ntohs(data[4]); u16 delta = seq - pkt_Ns; // this packet is ack'd, destroy it if (delta > 0 && delta < 0x7000) { nr_retransmits = 0; retransmit_interval = 10; if (debug) fprintf(stderr, "packet %d ack'd\n", pkt_Ns); last_acked = pkt_Ns; delete pkt; } else { if (debug) fprintf(stderr, "packet %d unack'd\n", pkt_Ns); Insert(q); if (((last_acked + 1) & 0xffff) == pkt_Ns) { tx_retransmit(pkt); Start(retransmit_interval); } break; } } } void l2tp_tunnel_t::handle_packet(l2tp_packet_t *the_pkt, union sockaddr_union *sau) { /* It is assumed that pkt.parse_packet() has been called */ assert(the_pkt == &pkt); if (debug) fprintf(stderr, "my tunnel is %d peer %d, packet %d.%d\n", tunnel_id, peer_tunnel_id, pkt.Tunnel, pkt.Session); if (pkt.Flags & L2TPF_T) { u16 delta = tunnel_Nr - pkt.Ns; if (debug) fprintf(stderr, "incoming packet Ns=%d, Nr=%d," " tunnel_Ns=%d, tunnel_Nr=%d\n", pkt.Ns, pkt.Nr, tunnel_Ns, tunnel_Nr); /* send a ZLB if this isn't an incoming zlb. */ if (pkt.nr_avps) need_zlb = 1; if (pkt.Ns == tunnel_Nr) { discard_acked_packets(pkt.Nr); if (pkt.nr_avps != 0) { need_zlb = 1; tunnel_Nr++; handle_control_packet(); } // ZLBs only ack packets, nothing else. } else if (delta < 0x7fff) { if (debug) fprintf(stderr, "sort of out of sequence pkt.Ns(%d) pkt.Nr(%d) vs tunnel_Ns(%d) tunnel_Nr(%d)\n", pkt.Ns, pkt.Nr, tunnel_Ns, tunnel_Nr); discard_acked_packets(pkt.Nr); } else fprintf(stderr, "out of sequence pkt.Ns(%d) pkt.Nr(%d) vs tunnel_Ns(%d) tunnel_Nr(%d)\n", pkt.Ns, pkt.Nr, tunnel_Ns, tunnel_Nr); discard_acked_packets(pkt.Nr); } else { l2tp_session_t *session = sessions[pkt.Session]; if (session) { if (session->m_monitor) session->m_monitor->monitor_l2tp_data_pkt( (u8 *)pkt.DataPtr, pkt.Length, "data rx"); session->handle_packet(&pkt); } else if (debug) fprintf(stderr, "data packet for unknown session %d?\n", pkt.Session); } if (tunnel_aborted) { delete this; return; } if (need_zlb) send_ZLB(); } ////////////////////////////////////////////////////////// // Session handling ////////////////////////////////////////////////////////// void l2tp_session_t::handle_ICRQ(l2tp_packet_t *pkt) { if (session_called_number) free(session_called_number); session_called_number = pkt->avp_strdup(AVP_CalledNumber); size_t len = pkt->avp_length(AVP_CallingNumber); if (len > 0) { if (sizeof(ifOptions.phone) <= len) len = sizeof(ifOptions.phone) - 1; memcpy(ifOptions.phone, pkt->AVPs[AVP_CallingNumber], len); ifOptions.phone[len] = 0; } switch (session_state) { case L2TP_Session_Idle: session_mode = Session_LNS; session_state = L2TP_Session_WaitConnect; tunnel->send_ICRP(this); break; case L2TP_Session_Established: case L2TP_Session_WaitTunnel: // FIXME: error? case L2TP_Session_WaitReply: case L2TP_Session_WaitConnect: disconnect_session("ICRQ received"); break; } } void l2tp_session_t::disconnect_session(const char *reason, int may_send_cdn) { SetDownReason(reason); if (session_state != L2TP_Session_Idle) { if (session_state == L2TP_Session_Established) num_est_sessions--; if (may_send_cdn) send_CDN(1); else session_state = L2TP_Session_Idle; clean_up(); if (multihop_session) { l2tp_session_t *other = multihop_session; multihop_session = NULL; multihop_link = NULL; multihop_clink = NULL; other->multihop_session = NULL; other->multihop_link = NULL; other->multihop_clink = NULL; other->disconnect_session(reason); } if (multihop_clink) { CLink *other = multihop_clink; multihop_clink = NULL; multihop_link = NULL; other->multihop_link = NULL; other->Hangup(); } } delete this; } void l2tp_session_t::kernel_multihop_up(CLink *multihop) { int err; int callid; callid = multihop->ch_ioctl(BIOCGETCALLID, 0); if (callid < 0) { perror("handle_ICRP: ch_ioctl(BIOCGETCALLID)"); return; } err = ch_ioctl(BIOC_SET_MULTIHOP, callid); if (err) perror("handle_ICRP: ch_ioctl(BIOC_SET_MULTIHOP)"); else fprintf(stderr, "handle_ICRP: multihop kernel mode callid=%d success\n", callid); } void l2tp_session_t::handle_ICRP(l2tp_packet_t *pkt) { CLink *multihop = NULL; int id = -1; char number[256]; monitor_control_pkt(pkt, "ICRP rx"); if (pkt->AVPs[AVP_AssignedSessionID]) id = ntohs(*pkt->AVPs[AVP_AssignedSessionID]); switch (session_state) { case L2TP_Session_WaitReply: if (!peer_session_id && id <= 0) { if (session_cfd) session_cfd->printf("icrp did not contain assigned session id avp\n"); goto cancel_it; } if (!peer_session_id) { peer_session_id = id; tunnel->assigned_sessions[id] = this; } if (session_cfd) session_cfd->printf("connected on icrp[%d]\n", id); session_state = L2TP_Session_Established; num_est_sessions++; struct l2tp_hardwire_info info; if (tunnel->get_hw_info(&info)) update_hw_peer(&info); if (multihop_session) multihop = multihop_session; if (multihop_clink) multihop = multihop_clink; if (multihop) { m_channel->state = CS_CONNECTED; SetTimeConnected(); kernel_multihop_up(multihop); } tunnel->send_ICCN(peer_session_id, 57600, 1, multihop, multihop_session); if (multihop) break; sprintf(number, "%s:%04x:%04x", sau_to_str(&tunnel->peer->remote_sau), session_id, peer_session_id); //Open(); //m_channel->state = CS_CONNECTED; //Up(); { Call *call = new Call(wait_for_hangup); call->ch = &ch; do_bdial(session_cfd, session_site ? session_site : "l2tp", number, call); break; } case L2TP_Session_Established: case L2TP_Session_Idle: case L2TP_Session_WaitConnect: case L2TP_Session_WaitTunnel: cancel_it: disconnect_session("ICRP received"); break; } } int l2tp_session_t::HardConnect(const char *number, u32 callType) { m_channel->state = CS_CONNECTED; if (!ifOptions.call) ifOptions.SetCall(new Call); SetTimeConnected(); ConnectComplete(0); Up(); return 0; } void l2tp_session_t::handle_CDN(l2tp_packet_t *pkt) { monitor_control_pkt(pkt, "CDN rx"); printf("CDN: state(%d) on channel(0x%04x, 0x%04x)\n", m_channel->state, session_id, peer_session_id); disconnect_session("CDN received", 0); } void l2tp_session_t::clean_up(const char *reason) { if (reason) SetDownReason(reason); Stop(); // kill hello timer Down(); session_mode = Session_None; } void l2tp_session_t::handle_ICCN(l2tp_packet_t *pkt) { switch (session_state) { case L2TP_Session_WaitTunnel: case L2TP_Session_WaitReply: case L2TP_Session_Idle: monitor_control_pkt(pkt, "ICCN rx (wait tunnel / wait reply / idle)"); clean_up("ICCN received"); break; case L2TP_Session_WaitConnect: num_est_sessions++; session_state = L2TP_Session_Established; struct l2tp_hardwire_info info; if (tunnel->get_hw_info(&info)) update_hw_peer(&info); m_channel->state = CS_CONNECTED; SetTimeConnected(); m_lcpProto.SetupICCN(pkt); m_iccn_pkt = pkt; proxy_auth_name = pkt->pkt_from_avp(AVP_ProxyAuthenName); proxy_auth_challenge = pkt->pkt_from_avp(AVP_ProxyAuthenChallenge); proxy_auth_response = pkt->pkt_from_avp(AVP_ProxyAuthenResponse); proxy_auth_type = pkt->avp_u16(AVP_ProxyAuthenType); proxy_auth_id = pkt->avp_u16(AVP_ProxyAuthenID); if (pkt->AVPs[AVP_ProxyAuthenName]) { char name[256]; int len = pkt->avp_length(AVP_ProxyAuthenName); strncpy(name, (const char *)pkt->AVPs[AVP_ProxyAuthenName], len); name[len] = 0; try_l2tp_monitor(this, name); monitor_control_pkt(pkt, "ICCN rx"); strncpy(CLink::ifOptions.user, name, sizeof CLink::ifOptions.user); if (!ifOptions.phone) sprintf(ifOptions.phone, "%s:%04x:%04x", sau_to_str(&tunnel->peer->remote_sau), session_id, peer_session_id); l2tp_tunnel_t *multihop; multihop = tunnel->peer->l2tpd->try_multihop(name); if (multihop) { fprintf(stderr, "handle_ICCN: multihop(%s)\n", name); multihop_session = new l2tp_session_t(multihop, this); m_iccn_pkt = NULL; break; } } ifOptions.SetCall(new Call); Up(); m_iccn_pkt = NULL; break; case L2TP_Session_Established: monitor_control_pkt(pkt, "ICCN rx (established)"); disconnect_session("ICCN received"); break; } } int l2tp_tunnel_t::set_csum_payload(unsigned do_csum) { struct l2tp_join_bundle join; join.tunnel = this->tunnel_id; join.session = 0; join.peer_tunnel = this->peer_tunnel_id; join.peer_session = 0; join.arg = do_csum; int ret = ioctl(this->peer->l2tp_fd, BIOC_L2TP_SET_TUNNEL_CSUM_PAYLOAD, &join); if (-1 == ret) { const char *why = strerror(errno); fprintf(stderr, "set_csum_payload: ioctl(%d, %u): %s\n", this->peer->l2tp_fd, do_csum, why); } return ret; } int l2tp_session_t::ch_ioctl(unsigned int cmd, unsigned long arg) { struct l2tp_join_bundle join; join.tunnel = tunnel->tunnel_id; join.session = session_id; join.peer_tunnel = tunnel->peer_tunnel_id; join.peer_session = peer_session_id; join.arg = arg; int ret = ioctl(tunnel->peer->l2tp_fd, cmd, &join); if (-1 == ret) { const char *why = strerror(errno); fprintf(stderr, "ch_ioctl: ioctl(0x%x, 0x%x, %lu): %s\n", tunnel->peer->l2tp_fd, cmd, arg, why); } return ret; } void l2tp_session_t::HardHangup(void) { ch.state = CS_DISCONNECTING; send_CDN(1); ConnectComplete(0x400); clean_up("HardHangup issued"); delete this; } int l2tp_session_t::HardOutput(CPppPacket *pkt, int pri) { pkt->Push16(0xff03); /* ACF */ pkt->Push16(peer_session_id); pkt->Push16(tunnel->peer_tunnel_id); pkt->Push16((pri ? L2TPF_P : 0) | L2TPF_Ver2); /* data packet, priority, v2 */ if (m_monitor) m_monitor->monitor_l2tp_data_pkt(pkt->m_start, pkt->GetLength(), "data tx"); int ret = write(tunnel->peer->udp_fd, pkt->m_start, pkt->GetLength()); if (ret == -1 && errno == ECONNREFUSED) ret = write(tunnel->peer->udp_fd, pkt->m_start, pkt->GetLength()); if (ret < 0 && errno != EAGAIN) perror("l2tp_session_t::HardOutput"); return ret; } void l2tp_session_t::dump_session(ctrlfd_t *cfd) { static const char *session_states[] = { "idle", "wait-connect", "established", "wait-tunnel", "wait-reply" }; static const char *session_modes[] = { "", "LAC", "LNS", }; cfd->printf("\t\t\tl2tp_session { peer(%5d) local(%5d) state(%s) mode(%s)", peer_session_id, session_id, session_states[session_state], session_modes[session_mode]); if (session_site) cfd->printf(" site(%s)", session_site); /* see also: l2tpd_t::find_l2tp_session() */ if (multihop_session) cfd->printf(" multihop(\"%d/%d:%d\")", multihop_session->tunnel->tunnel_id, multihop_session->peer_session_id, multihop_session->session_id); if (multihop_clink) cfd->printf(" multihop_clink(%s)", multihop_clink->m_channel->device_name); if (session_cfd) { cfd->my_putchar(' '); session_cfd->dump(cfd); } CLink *link = this; cfd->printf(" CLink {\n"); link->dump_CLink(cfd); cfd->printf("}\n"); cfd->printf("ch {\n"); ch.dump_full_state(cfd); cfd->printf("}\n"); cfd->printf("}\n"); } void l2tp_session_t::handle_packet(l2tp_packet_t *pkt) { CLink *link = this; if (debug) { fprintf(stderr, "session data packet: Length=%d\n", pkt->Length); pretty_dump(pkt->Length, (unsigned char*)pkt->DataPtr); } if (multihop_session) { if (multihop_session->session_state == L2TP_Session_Established) { pkt->DataPtr--; *pkt->DataPtr = htons(multihop_session->peer_session_id); pkt->DataPtr--; *pkt->DataPtr = htons(multihop_session->tunnel->peer_tunnel_id); pkt->DataPtr--; *pkt->DataPtr = htons(0x0002 | (pkt->Flags & L2TPF_P)); int ret = write(multihop_session->tunnel->peer->udp_fd, pkt->DataPtr, pkt->Length+6); if (-1 == ret) perror("multihop connection: write(udp)"); } else fprintf(stderr, "data packet on multihop not up\n"); return; } if (multihop_clink) { CPppPacket ppp_pkt((u8*)pkt->DataPtr, (unsigned)pkt->Length); multihop_clink->HardOutput(&ppp_pkt, !!(pkt->Flags & L2TPF_P)); return; } link->Input((u8 *)pkt->DataPtr, pkt->Length); } void l2tp_session_t::tunnel_up(void) { if (session_cfd) session_cfd->printf("tunnel is up\n"); if (L2TP_Session_WaitTunnel != session_state) return; session_state = L2TP_Session_WaitReply; session_mode = Session_LAC; if (multihop_session) tunnel->send_ICRQ(session_id, multihop_session->session_called_number, multihop_session->ifOptions.phone); else if (multihop_clink) tunnel->send_ICRQ(session_id, "FIXME", multihop_clink->ifOptions.phone); else tunnel->send_ICRQ(session_id, session_called_number, NULL); } void l2tp_session_t::abort(const char *msg) { SetDownReason(msg); if (session_cfd) session_cfd->printf("abort(%s)\n", msg); } void l2tp_session_t::GetConfig(void (*cbf)(void *, OptionSet_t *), void *obj, OptionSet_t *options) { l2tp_tunnel_t *multihop; LinkCancelGetConfig(); if (multihop_session) { fprintf(stderr, "l2tp_session_t::GetConfig -- already have multihop\n"); return; } multihop = tunnel->peer->l2tpd->try_multihop(options); if (multihop) { // shutdown lcp Down(); // make multihop go! multihop_session = new l2tp_session_t(multihop, this); return; } if (!options->phone[0]) sprintf(options->phone, "%s:%04x:%04x", sau_to_str(&tunnel->peer->remote_sau), session_id, peer_session_id); babd_GetConfig(cbf, obj, options); } l2tp_session_t::l2tp_session_t(l2tp_tunnel_t *tun, l2tp_session_t *in_session) { init_session(tun, 0, 0); multihop_session = in_session; session_state = L2TP_Session_WaitTunnel; if (tun->is_established()) tunnel_up(); else tun->bring_up(); } l2tp_session_t::l2tp_session_t(l2tp_tunnel_t *tun, CLink *in_link) { init_session(tun, 0, 0); multihop_clink = in_link; session_state = L2TP_Session_WaitTunnel; if (tun->is_established()) tunnel_up(); else tun->bring_up(); } const char *l2tp_session_t::get_multihop_name(void) { if (multihop_session) return multihop_session->get_name(); if (multihop_clink) return multihop_clink->get_name(); return CLink::get_multihop_name(); } const char *l2tp_session_t::get_user(void) { if (multihop_session && session_mode == Session_LAC) return multihop_session->get_user(); if (multihop_session && session_mode == Session_LNS) return CLink::ifOptions.user; if (multihop_clink) return multihop_clink->get_user(); return CLink::get_user(); } const char *l2tp_session_t::get_class(void) { if (multihop_session && session_mode == Session_LAC) return "l2tp/lac"; else if (multihop_session && session_mode == Session_LNS) return "l2tp/lns"; else return "l2tp"; } void l2tp_session_t::GotConfigTunnelGroup(OptionSet_t *os) { l2tp_tunnel_t *multihop = the_l2tpd->try_multihop(os); if (multihop) { m_lcpProto.Down(); multihop_session = new l2tp_session_t(multihop, this); multihop_link = multihop_session; return; } fprintf(stderr, "l2tp_session_t::GotConfigTunnelGroup: couldn't get multihop for ERX-Tunnel-Group(%s)\n", os->tunnel_group); Hangup(); } void l2tp_session_t::update_hw_peer(struct l2tp_hardwire_info *info) { info->hw_peer_session = htons(peer_session_id); if (ch_ioctl(BIOC_L2TP_HARDWIRE_SESSION, (unsigned long)info) != 0) perror("update_hw_peer: set hardwire info failed!"); else fprintf(stderr, "set session %u to hardwire dev %s\n", session_id, info->dev_name); fflush(stderr); } void l2tp_tunnel_t::update_hw_peer(struct l2tp_hardwire_info *info) { if (!is_established()) return; info->hw_peer_tunnel = htons(peer_tunnel_id); for (unsigned i = 0; i < 65536; i++) { l2tp_session_t *session = sessions[i]; if (!session) continue; if (!session->is_established()) continue; session->update_hw_peer(info); } } #endif