1011 lines
29 KiB
Lua

--- A minimalistic PPPoE (Point-to-point protocol over Ethernet)
-- library, implementing basic support for PPPoE
-- Discovery and Configuration requests. The PPPoE protocol is ethernet based
-- and hence does not use any IPs or port numbers.
--
-- The library contains a number of classes to support packet creation,
-- parsing and sending/receiving responses. The classes are:
-- o LCP - Contains classes to build and parse PPPoE LCP requests and
-- responses.
--
-- o PPPoE - Contains classes to build and parse PPPoE requests and
-- responses.
--
-- o Comm - Contains some basic functions for sending and receiving
-- LCP and PPPoE requests and responses.
--
-- o Helper- The Helper class serves as the main interface between scripts
-- and the library.
--
--
-- @author Patrik Karlsson <patrik@cqure.net>
--
local rand = require "rand"
local nmap = require "nmap"
local packet = require "packet"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
_ENV = stdnse.module("pppoe", stdnse.seeall)
EtherType = {
PPPOE_DISCOVERY = 0x8863,
PPPOE_SESSION = 0x8864,
}
-- A Class to handle the Link Control Protocol LCP
LCP = {
ConfigOption = {
RESERVED = 0,
MRU = 1,
AUTH_PROTO = 3,
QUAL_PROTO = 4,
MAGIC_NUMBER = 5,
PROTO_COMPR = 7,
ACFC = 8,
-- Value has already been encoded, treat it as a byte stream
RAW = -1,
-- Creates a new config option
-- @param option number containing the configuration option
-- @param value containing the configuration option value
-- @param raw string containing the configuration options raw value
-- @return o new instance of ConfigOption
new = function(self, option, value, raw)
local o = {
option = option,
value = value,
raw = raw,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the ConfigOption
-- class
-- @param data string containing raw bytes to parse
-- @return o instance of ConfigOption
parse = function(data)
local opt, pos, len = {}, 1, 0
opt.option, len, pos = string.unpack("BB", data, pos)
opt.raw, pos = string.unpack("c" .. ( len - 2 ), data, pos)
-- MRU
if ( 1 == opt.option ) then
opt.value = string.unpack(">I2", opt.raw)
end
return LCP.ConfigOption:new(opt.option, opt.value, opt.raw)
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
-- MRU
if ( self.raw ) then
return string.pack(">BB", self.option, #self.raw + 2) .. self.raw
elseif( 1 == self.option ) then
return string.pack(">BBI2", 1, 4, self.value)
else
error( ("Unsupported configuration option %d"):format(self.option) )
end
end,
},
-- A class to hold multiple ordered config options
ConfigOptions = {
new = function(self, options)
local o = {
options = options or {},
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Adds a new config option to the table
-- @param option instance of ConfigOption
add = function(self, option)
table.insert(self.options, option)
end,
-- Gets a config option by ID
-- @param opt number containing the configuration option to retrieve
-- @return v instance of ConfigOption
getById = function(self, opt)
for _, v in ipairs(self.options) do
if ( v.option == opt ) then
return v
end
end
end,
-- Returns all config options in an ordered table
-- @return tab table containing all configuration options
getTable = function(self)
local tab = {}
for _, v in ipairs(self.options) do
table.insert(tab, v)
end
return tab
end,
-- Parses a byte stream and builds a new instance of the ConfigOptions
-- class
-- @param data string containing raw bytes to parse
-- @return o instance of ConfigOption
parse = function(data)
local options = LCP.ConfigOptions:new()
local pos, opt, opt_val, len
repeat
opt, len, pos = string.unpack(">BB", data, pos)
if ( 0 == opt ) then break end
opt_val, pos = string.unpack("c"..len, data, (pos - 2))
options:add(LCP.ConfigOption.parse(opt_val))
until( pos == #data )
return options
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
local str = ""
for _, v in ipairs(self.options) do
str = str .. tostring(v)
end
return str
end,
},
ConfigOptionName = {
[0] = "Reserved",
[1] = "Maximum receive unit",
[3] = "Authentication protocol",
[4] = "Quality protocol",
[5] = "Magic number",
[7] = "Protocol field compression",
[8] = "Address and control field compression",
},
Code = {
CONFIG_REQUEST = 1,
CONFIG_ACK = 2,
CONFIG_NAK = 3,
TERMINATE_REQUEST = 5,
TERMINATE_ACK = 6,
},
-- The LCP Header
Header = {
-- Creates a new instance of the LCP header
-- @param code number containing the LCP code of the request
-- @param identifier number containing the LCP identifier
new = function(self, code, identifier)
local o = {
code = code,
identifier = identifier or 1,
length = 0,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the Header class
-- @param data string containing raw bytes to parse
-- @return o instance of ConfigOption
parse = function(data)
local header = LCP.Header:new()
header.code, header.identifier, header.length = string.unpack(">BBI2", data)
return header
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
return string.pack(">BBI2", self.code, self.identifier, self.length)
end,
},
ConfigRequest = {
-- Creates a new instance of the ConfigRequest class
-- @param identifier number containing the LCP identifier
-- @param options table of <code>LCP.ConfigOption</code> options
-- @return o instance of ConfigRequest
new = function(self, identifier, options)
local o = {
header = LCP.Header:new(LCP.Code.CONFIG_REQUEST, identifier),
options = LCP.ConfigOptions:new(options)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the ConfigRequest
-- class
-- @param data string containing raw bytes to parse
-- @return o instance of ConfigRequest
parse = function(data)
local req = LCP.ConfigRequest:new()
req.header = LCP.Header.parse(data)
req.options = LCP.ConfigOptions.parse(data:sub(#tostring(req.header) + 1))
return req
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
self.header.length = 4 + #tostring(self.options)
return tostring(self.header) .. tostring(self.options)
end,
},
ConfigNak = {
-- Creates a new instance of the ConfigNak class
-- @param identifier number containing the LCP identifier
-- @param options table of <code>LCP.ConfigOption</code> options
-- @return o instance of ConfigNak
new = function(self, identifier, options)
local o = {
header = LCP.Header:new(LCP.Code.CONFIG_NAK, identifier),
options = LCP.ConfigOptions:new(options),
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
self.header.length = 4 + #tostring(self.options)
return tostring(self.header) .. tostring(self.options)
end,
},
ConfigAck = {
-- Creates a new instance of the ConfigAck class
-- @param identifier number containing the LCP identifier
-- @param options table of <code>LCP.ConfigOption</code> options
-- @return o instance of ConfigNak
new = function(self, identifier, options)
local o = {
header = LCP.Header:new(LCP.Code.CONFIG_ACK, identifier),
options = LCP.ConfigOptions:new(options),
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the ConfigAck class
-- @param data string containing raw bytes to parse
-- @return o instance of ConfigRequest
parse = function(data)
local ack = LCP.ConfigAck:new()
ack.header = LCP.Header.parse(data)
ack.options = LCP.ConfigOptions.parse(data:sub(#tostring(ack.header) + 1))
return ack
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
self.header.length = 4 + #tostring(self.options)
return tostring(self.header) .. tostring(self.options)
end,
},
TerminateRequest = {
-- Creates a new instance of the TerminateRequest class
-- @param identifier number containing the LCP identifier
-- @return o instance of ConfigNak
new = function(self, identifier, data)
local o = {
header = LCP.Header:new(LCP.Code.TERMINATE_REQUEST, identifier),
data = data or "",
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the class instance to string
-- @return string containing the raw config option
__tostring = function(self)
self.header.length = 4 + #self.data
return tostring(self.header) .. self.data
end,
}
}
-- The PPPoE class
PPPoE = {
-- Supported PPPoE codes (requests/responses)
Code = {
SESSION_DATA = 0x00,
PADO = 0x07,
PADI = 0x09,
PADR = 0x19,
PADS = 0x65,
PADT = 0xa7,
},
-- Support PPPoE Tag types
TagType = {
SERVICE_NAME = 0x0101,
AC_NAME = 0x0102,
HOST_UNIQUE = 0x0103,
AC_COOKIE = 0x0104,
},
-- Table used to convert table IDs to Names
TagName = {
[0x0101] = "Service-Name",
[0x0102] = "AC-Name",
[0x0103] = "Host-Uniq",
[0x0104] = "AC-Cookie",
},
Header = {
-- Creates a new instance of the PPPoE header class
-- @param code number containing the PPPoE code
-- @param session number containing the PPPoE session
-- @return o instance of Header
new = function(self, code, session)
local o = {
version = 1,
type = 1,
code = code,
session = session or 0,
length = 0,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the class
-- @param data string containing raw bytes to parse
-- @return o instance of Header
parse = function(data)
local header = PPPoE.Header:new()
local vertyp
vertyp, header.code, header.session, header.length = string.unpack(">BBI2I2", data)
header.version = (vertyp >> 4)
header.type = (vertyp & 0x0F)
return header
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
local vertype = (self.version << 4) + self.type
return string.pack(">BBI2I2", vertype, self.code, self.session, self.length)
end,
},
-- The TAG NVP Class
Tag = {
-- Creates a new instance of the Tag class
-- @param tag number containing the tag type
-- @param value string/number containing the tag value
-- @return o instance of Tag
new = function(self, tag, value)
local o = { tag = tag, value = value or "" }
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
return string.pack(">I2s2", self.tag, self.value)
end,
},
PADI = {
-- Creates a new instance of the PADI class
-- @param tags table of <code>PPPoE.Tag</code> instances
-- @param value string/number containing the tag value
-- @return o instance of ConfigNak
new = function(self, tags)
local c = rand.random_string(8)
local o = {
header = PPPoE.Header:new(PPPoE.Code.PADI),
tags = tags or {
PPPoE.Tag:new(PPPoE.TagType.SERVICE_NAME),
PPPoE.Tag:new(PPPoE.TagType.HOST_UNIQUE, c)
}
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
local tags = ""
for _, tag in ipairs(self.tags) do
tags = tags .. tostring(tag)
end
self.header.length = #tags
return tostring(self.header) .. tags
end,
},
PADO = {
-- Creates a new instance of the PADO class
-- @return o instance of PADO
new = function(self)
local o = { tags = {} }
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the class
-- @param data string containing raw bytes to parse
-- @return o instance of PADO
parse = function(data)
local pado = PPPoE.PADO:new()
pado.header = PPPoE.Header.parse(data)
local pos = #tostring(pado.header) + 1
pado.data = data:sub(pos)
repeat
local tag, decoded, raw
tag, raw, pos = string.unpack(">I2s2", pos)
if ( PPPoE.TagDecoder[tag] ) then
decoded = PPPoE.TagDecoder[tag](raw)
else
stdnse.debug1("PPPoE: Unsupported tag (%d)", tag)
end
local t = PPPoE.Tag:new(tag, raw)
t.decoded = decoded
table.insert(pado.tags, t)
until( pos >= #data )
return pado
end,
},
PADR = {
-- Creates a new instance of the PADR class
-- @param tags table of <code>PPPoE.Tag</code> instances
-- @return o instance of PADR
new = function(self, tags)
local o = {
tags = tags or {},
header = PPPoE.Header:new(PPPoE.Code.PADR)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
local tags = ""
for _, tag in ipairs(self.tags) do
tags = tags .. tostring(tag)
end
self.header.length = #tags
return tostring(self.header) .. tags
end,
},
PADS = {
-- Creates a new instance of the PADS class
-- @return o instance of PADS
new = function(self)
local o = { tags = {} }
setmetatable(o, self)
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the class
-- @param data string containing raw bytes to parse
-- @return o instance of PADS
parse = function(data)
local pads = PPPoE.PADS:new()
pads.header = PPPoE.Header.parse(data)
local pos = #tostring(pads.header) + 1
pads.data = data:sub(pos)
return pads
end,
},
PADT = {
-- Creates a new instance of the PADT class
-- @param session number containing the PPPoE session
-- @return o instance of PADT
new = function(self, session)
local o = { header = PPPoE.Header:new(PPPoE.Code.PADT) }
setmetatable(o, self)
o.header.session = session
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the class
-- @param data string containing raw bytes to parse
-- @return o instance of PADI
parse = function(data)
local padt = PPPoE.PADT:new()
padt.header = PPPoE.Header.parse(data)
return padt
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
return tostring(self.header)
end,
},
SessionData = {
-- Creates a new instance of the SessionData class
-- @param session number containing the PPPoE session
-- @param data string containing the LCP data to send
-- @return o instance of ConfigNak
new = function(self, session, data)
local o = {
data = data or "",
header = PPPoE.Header:new(PPPoE.Code.SESSION_DATA)
}
setmetatable(o, self)
o.header.session = session
self.__index = self
return o
end,
-- Parses a byte stream and builds a new instance of the class
-- @param data string containing raw bytes to parse
-- @return o instance of SessionData
parse = function(data)
local sess = PPPoE.SessionData:new()
sess.header = PPPoE.Header.parse(data)
local pos = #tostring(sess.header) + 1 + 2
sess.data = data:sub(pos)
return sess
end,
-- Converts the instance to string
-- @return string containing the raw config option
__tostring = function(self)
-- 2 for the encapsulation
self.header.length = 2 + 4 + #self.data
return tostring(self.header) .. "\xC0\x21" .. self.data
end,
}
}
-- A bunch of tag decoders
PPPoE.TagDecoder = {}
PPPoE.TagDecoder.decodeHex = stdnse.tohex
PPPoE.TagDecoder.decodeStr = function(data) return data end
PPPoE.TagDecoder[PPPoE.TagType.SERVICE_NAME] = PPPoE.TagDecoder.decodeStr
PPPoE.TagDecoder[PPPoE.TagType.AC_NAME] = PPPoE.TagDecoder.decodeStr
PPPoE.TagDecoder[PPPoE.TagType.AC_COOKIE] = PPPoE.TagDecoder.decodeHex
PPPoE.TagDecoder[PPPoE.TagType.HOST_UNIQUE] = PPPoE.TagDecoder.decodeHex
-- The Comm class responsible for communication with the PPPoE server
Comm = {
-- Creates a new instance of the Comm class
-- @param iface string containing the interface name
-- @param src_mac string containing the source MAC address
-- @param dst_mac string containing the destination MAC address
-- @return o new instance of Comm
new = function(self, iface, src_mac, dst_mac)
local o = {
iface = iface,
src_mac = src_mac,
dst_mac = dst_mac,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Sets up the pcap receiving socket
-- @return status true on success
connect = function(self)
self.socket = nmap.new_socket()
self.socket:set_timeout(10000)
local mac = stdnse.format_mac(self.src_mac)
-- let's set a filter on PPPoE we can then check what packet is ours,
-- based on the HOST_UNIQUE tag, if we need to
self.socket:pcap_open(self.iface, 1500, false, "ether[0x0c:2] == 0x8863 or ether[0x0c:2] == 0x8864 and ether dst " .. mac)
return true
end,
-- Sends a packet
-- @param data class containing the request to send
-- @return status true on success, false on failure
send = function(self, data)
local eth_type = ( data.header.code == PPPoE.Code.SESSION_DATA ) and 0x8864 or 0x8863
local ether = self.dst_mac .. self.src_mac .. string.pack(">I2", eth_type)
local p = packet.Frame:new(ether .. tostring(data))
local sock = nmap.new_dnet()
if ( not(sock) ) then
return false, "Failed to create raw socket"
end
local status = sock:ethernet_open(self.iface)
-- we don't actually need to do this as the script simply crashes
-- if we don't have the right permissions at this point
if ( not(status) ) then
return false, "Failed to open raw socket"
end
status = sock:ethernet_send(p.frame_buf)
if ( not(status) ) then
return false, "Failed to send data"
end
sock:ethernet_close()
return true
end,
-- Receive a response from the server
-- @return status true on success, false on failure
-- @return response class containing the response or
-- err string on error
recv = function(self)
local status, _, l2, l3 = self.socket:pcap_receive()
-- if we got no response, just return false as there's
-- probably not really an error
if ( not(status) ) then
return false, "Did not receive any packets"
end
local header = PPPoE.Header.parse(l3)
local p = packet.Frame:new(l2..l3)
-- there's probably a more elegant way of doing this
if ( EtherType.PPPOE_DISCOVERY == p.ether_type ) then
if ( header.code == PPPoE.Code.PADO ) then
local pado = PPPoE.PADO.parse(l3)
pado.mac_srv = p.mac_src
return true, pado
elseif ( header.code == PPPoE.Code.PADS ) then
local pads = PPPoE.PADS.parse(l3)
return true, pads
elseif ( header.code == PPPoE.Code.PADT ) then
local pads = PPPoE.PADT.parse(l3)
return true, pads
end
elseif ( EtherType.PPPOE_SESSION == p.ether_type ) then
return true, PPPoE.SessionData.parse(l3)
end
return false, ("Received unsupported response, can't decode code (%d)"):format(header.code)
end,
-- Does an "exchange", ie, sends a request and waits for a response
-- @param data class containing the request to send
-- @return status true on success, false on failure
-- @return response class containing the response or
-- err string on error
exch = function(self, data)
local status, err = self:send(data)
if ( not(status) ) then
return false, err
end
local retries, resp = 3, nil
repeat
status, resp = self:recv()
if ( data.header and 0 == data.header.session ) then
return true, resp
elseif ( data.header and data.header.session == resp.header.session ) then
return true, resp
end
retries = retries - 1
until(retries == 0)
return false, "Failed to retrieve proper PPPoE response"
end,
}
-- The Helper class is the main script interface
Helper = {
-- Creates a new instance of Helper
-- @param iface string containing the name of the interface to use
-- @return o new instance on success, nil on failure
new = function(self, iface)
local o = {
iface = iface,
-- set the LCP identifier to 0
identifier = 0,
}
setmetatable(o, self)
self.__index = self
if ( not(nmap.is_privileged()) ) then
return nil, "The PPPoE library requires Nmap to be run in privileged mode"
end
-- get src_mac
local info = nmap.get_interface_info(iface)
if ( not(info) or not(info.mac) ) then
return nil, "Failed to get source MAC address"
end
o.comm = Comm:new(iface, info.mac)
return o
end,
-- Sets up the pcap socket for listening and does some other preparations
-- @return status true on success, false on failure
connect = function(self)
return self.comm:connect()
end,
-- Performs a PPPoE discovery initiation by sending a PADI request to the
-- ethernet broadcast address
-- @return status true on success, false on failure
-- @return pado instance of PADO on success, err string on failure
discoverInit = function(self)
local padi = PPPoE.PADI:new()
self.comm.dst_mac = ("\xFF"):rep(6)
local status, err = self.comm:send(padi)
if ( not(status) ) then
return false, err
end
-- wait for a pado
local pado, retries = nil, 3
repeat
status, pado = self.comm:recv()
if ( not(status) ) then
return status, pado
end
retries = retries - 1
until( pado.tags or retries == 0 )
if ( not(pado.tags) ) then
return false, "PADO response contained no tags"
end
local pado_host_unique
for _, tag in ipairs(pado.tags) do
if ( PPPoE.TagType.HOST_UNIQUE == tag.tag ) then
pado_host_unique = tag.raw
end
end
-- store the tags for later use
self.tags = pado.tags
self.comm.dst_mac = pado.mac_srv
if ( pado_host_unique and
pado_host_unique ~= padi.tags[PPPoE.TagType.HOST_UNIQUE] ) then
-- currently, we don't handle this, we probably should
-- in order to do so, we need to split the function exch
-- to recv and send
return false, "Got incorrect answer"
end
return true, pado
end,
-- Performs a Discovery Request by sending PADR to the PPPoE ethernet
-- address
-- @return status true on success, false on failure
-- @return pads instance of PADS on success
discoverRequest = function(self)
-- remove the AC-Name tag if there is one
local function getTag(tag)
for _, t in ipairs(self.tags) do
if ( tag == t.tag ) then
return t
end
end
end
local taglist = {
PPPoE.TagType.SERVICE_NAME,
PPPoE.TagType.HOST_UNIQUE,
PPPoE.TagType.AC_COOKIE
}
local tags = {}
for _, t in ipairs(taglist) do
if ( getTag(t) ) then
table.insert(tags, getTag(t))
end
end
local padr = PPPoE.PADR:new(tags)
local status, pads = self.comm:exch(padr)
if ( status ) then
self.session = pads.header.session
end
return status, pads
end,
-- Attempts to specify a method for authentication
-- If the server responds with another method it's NAK:ed and we try to set
-- our requested method instead. If this fails, we return a failure
-- @param method string containing one of the following methods:
-- <code>MSCHAPv1</code>, <code>MSCHAPv2</code> or <code>PAP</code>
-- @return status true on success, false on failure
-- err string containing error message on failure
setAuthMethod = function(self, method)
local AuthMethod = {
methods = {
{ name = "EAP", value = "\xC2\x27" },
{ name = "MSCHAPv1", value = "\xC2\x23\x80" },
{ name = "MSCHAPv2", value = "\xC2\x23\x81" },
{ name = "PAP", value = "\xC0\x23" },
}
}
AuthMethod.byName = function(name)
for _, m in ipairs(AuthMethod.methods) do
if ( m.name == name ) then
return m
end
end
end
AuthMethod.byValue = function(value)
for _, m in ipairs(AuthMethod.methods) do
if ( m.value == value ) then
return m
end
end
end
local auth_data = ( AuthMethod.byName(method) and AuthMethod.byName(method).value )
if ( not(auth_data) ) then
return false, ("Unsupported authentication mode (%s)"):format(method)
end
self.identifier = self.identifier + 1
-- First do a Configuration Request
local options = { LCP.ConfigOption:new(LCP.ConfigOption.MRU, 1492) }
local lcp_req = LCP.ConfigRequest:new(self.identifier, options)
local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req))
local status, resp = self.comm:exch(sess_req)
if ( not(status) or PPPoE.Code.SESSION_DATA ~= resp.header.code ) then
return false, "Unexpected packet type was received"
end
-- Make sure we got a Configuration Request in return
local lcp_header = LCP.Header.parse(resp.data)
if ( LCP.Code.CONFIG_REQUEST ~= lcp_header.code ) then
return false, ("Unexpected packet type was received (%d)"):format(lcp_header.code)
end
local config_req = LCP.ConfigRequest.parse(resp.data)
if ( not(config_req.options) ) then
return false, "Failed to retrieve any options from response"
end
local auth_proposed = config_req.options:getById(LCP.ConfigOption.AUTH_PROTO)
if ( auth_proposed.raw ~= auth_data ) then
local options = { LCP.ConfigOption:new(LCP.ConfigOption.AUTH_PROTO, nil, auth_data) }
local lcp_req = LCP.ConfigNak:new(self.identifier, options)
local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req))
local status, resp = self.comm:exch(sess_req)
if ( not(status) or PPPoE.Code.SESSION_DATA ~= resp.header.code ) then
return false, "Unexpected packet type was received"
end
-- Make sure we got a Configuration Request in return
local lcp_header = LCP.Header.parse(resp.data)
if ( LCP.Code.CONFIG_REQUEST ~= lcp_header.code ) then
return false, ("Unexpected packet type was received (%d)"):format(lcp_header.code)
end
config_req = LCP.ConfigRequest.parse(resp.data)
-- if the authentication methods match, send an ACK
if ( config_req.options:getById(LCP.ConfigOption.AUTH_PROTO).raw == auth_data ) then
-- The ACK is essential the Config Request, only with a different code
-- Do a dirty attempt to just replace the code and send the request back as an ack
self.identifier = self.identifier + 1
local lcp_req = LCP.ConfigAck:new(config_req.header.identifier, config_req.options:getTable())
local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req))
local status, resp = self.comm:send(sess_req)
return true
end
return false, "Authentication method was not accepted"
end
return false, "Failed to negotiate authentication mechanism"
end,
-- Sends a LCP Terminate Request and waits for an ACK
-- Attempts to do so 10 times before aborting
-- @return status true on success false on failure
close = function(self)
local tries = 10
repeat
if ( 0 == self.session ) then
break
end
local lcp_req = LCP.TerminateRequest:new(self.identifier)
local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req))
local status, resp = self.comm:exch(sess_req)
if ( status and resp.header and resp.header.code ) then
if ( PPPoE.Code.SESSION_DATA == resp.header.code ) then
local lcp_header = LCP.Header.parse(resp.data)
if ( LCP.Code.TERMINATE_ACK == lcp_header.code ) then
break
end
end
end
tries = tries - 1
until( tries == 0 )
self.comm:exch(PPPoE.PADT:new(self.session))
return true
end,
}
return _ENV;