Advertisement
beechfuzz

eap_proxy.py

Aug 23rd, 2019
742
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 24.06 KB | None | 0 0
  1. #!/usr/bin/env python
  2. """
  3. usage: eap_proxy [-h] [--ping-gateway] [--ignore-when-wan-up] [--ignore-start]
  4.                 [--ignore-logoff] [--restart-dhcp] [--set-mac]
  5.                 [--vlan-id VLAN_ID] [--daemon] [--pidfile PIDFILE] [--syslog]
  6.                 [--promiscuous] [--debug] [--debug-packets]
  7.                 IF_WAN IF_ROUTER
  8.  
  9. positional arguments:
  10.  IF_WAN                interface of the AT&T ONT/WAN
  11.  IF_ROUTER             interface of the AT&T router
  12.  
  13. optional arguments:
  14.  -h, --help            show this help message and exit
  15.  
  16. checking whether WAN is up:
  17.  --ping-gateway        normally the WAN is considered up if the IF_WAN VLAN
  18.                        has an address; this option additionally requires that
  19.                        there is a default route gateway that responds to a
  20.                        ping
  21.  
  22. ignoring router packets:
  23.  --ignore-when-wan-up  ignore router packets when WAN is up (see --ping-
  24.                        gateway)
  25.  --ignore-start        always ignore EAPOL-Start from router
  26.  --ignore-logoff       always ignore EAPOL-Logoff from router
  27.  
  28. configuring IF_WAN VLAN:
  29.  --restart-dhcp        check whether WAN is up after receiving EAP-Success on
  30.                        IF_WAN VLAN (see --ping-gateway); if not, restart
  31.                        dhclient on IF_WAN VLAN
  32.  --set-mac             set IF_WAN VLAN MAC (ether) address to router's MAC
  33.                        address
  34.  --vlan-id VLAN_ID     set IF_WAN VLAN ID (default is 0)
  35.  
  36. daemonization:
  37.  --daemon              become a daemon; implies --syslog
  38.  --pidfile PIDFILE     record pid to PIDFILE
  39.  --syslog              log to syslog instead of stderr
  40.  
  41. debugging:
  42.  --promiscuous         place interfaces into promiscuous mode instead of
  43.                        multicast
  44.  --debug               enable debug-level logging
  45.  --debug-packets       print packets in hex format to assist with debugging;
  46.                        implies --debug
  47. """
  48. # pylint:disable=invalid-name,missing-docstring
  49. import argparse
  50. import array
  51. import atexit
  52. import ctypes
  53. import ctypes.util
  54. import logging
  55. import logging.handlers
  56. import os
  57. import random
  58. import re
  59. import select
  60. import signal
  61. import socket
  62. import struct
  63. import subprocess
  64. import sys
  65. import time
  66. import traceback
  67. from collections import namedtuple
  68. from fcntl import ioctl
  69. from functools import partial
  70.  
  71. ### Constants
  72.  
  73. EAP_MULTICAST_ADDR = (0x01, 0x80, 0xc2, 0x00, 0x00, 0x03)
  74. ETH_P_PAE = 0x888e  # IEEE 802.1X (Port Access Entity)
  75. IFF_PROMISC = 0x100
  76. PACKET_ADD_MEMBERSHIP = 1
  77. PACKET_MR_MULTICAST = 0
  78. PACKET_MR_PROMISC = 1
  79. SIOCGIFADDR = 0x8915
  80. SIOCGIFFLAGS = 0x8913
  81. SIOCSIFFLAGS = 0x8914
  82. SOL_PACKET = 263
  83.  
  84. ### Sockets / Network Interfaces
  85.  
  86. class struct_packet_mreq(ctypes.Structure):
  87.     # pylint:disable=too-few-public-methods
  88.     _fields_ = (
  89.         ("mr_ifindex", ctypes.c_int),
  90.         ("mr_type", ctypes.c_ushort),
  91.         ("mr_alen", ctypes.c_ushort),
  92.         ("mr_address", ctypes.c_ubyte * 8))
  93.  
  94.  
  95. if_nametoindex = ctypes.CDLL(ctypes.util.find_library('c')).if_nametoindex
  96.  
  97.  
  98. def addsockaddr(sock, address):
  99.     """Configure physical-layer multicasting or promiscuous mode for `sock`.
  100.       If `addr` is None, promiscuous mode is configured. Otherwise `addr`
  101.       should be a tuple of up to 8 bytes to configure that multicast address.
  102.    """
  103.     # pylint:disable=attribute-defined-outside-init
  104.     mreq = struct_packet_mreq()
  105.     mreq.mr_ifindex = if_nametoindex(getifname(sock))
  106.     if address is None:
  107.         mreq.mr_type = PACKET_MR_PROMISC
  108.     else:
  109.         mreq.mr_type = PACKET_MR_MULTICAST
  110.         mreq.mr_alen = len(address)
  111.         mreq.mr_address = address
  112.     sock.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, mreq)
  113.  
  114.  
  115. def rawsocket(ifname, poll=None, promisc=False):
  116.     """Return raw socket listening for 802.1X packets on `ifname` interface.
  117.       The socket is configured for multicast mode on EAP_MULTICAST_ADDR.
  118.       Specify `promisc` to enable promiscuous mode instead.
  119.       Provide `poll` object to register socket to it POLLIN events.
  120.    """
  121.     s = socket.socket(
  122.         socket.PF_PACKET,  # pylint:disable=no-member
  123.         socket.SOCK_RAW,
  124.         socket.htons(ETH_P_PAE))
  125.     s.bind((ifname, 0))
  126.     addsockaddr(s, None if promisc else EAP_MULTICAST_ADDR)
  127.     if poll is not None:
  128.         poll.register(s, select.POLLIN)  # pylint:disable=no-member
  129.     return s
  130.  
  131.  
  132. def getifname(sock):
  133.     """Return interface name of `sock`"""
  134.     return sock.getsockname()[0]
  135.  
  136.  
  137. def getifaddr(ifname):
  138.     """Return IP addr of `ifname` interface in 1.2.3.4 notation
  139.       or None if no IP is assigned or other IOError occurs.
  140.    """
  141.     # pylint:disable=attribute-defined-outside-init
  142.     ifreq = "%-32s" % (ifname + "\0")
  143.     try:
  144.         result = ioctl(socket.socket(), SIOCGIFADDR, ifreq)
  145.     except IOError:
  146.         return None
  147.     return socket.inet_ntoa(result[20:24])
  148.  
  149.  
  150. def getifhwaddr(ifname):
  151.     """Return MAC address for `ifname` as a packed string."""
  152.     with open("/sys/class/net/%s/address" % ifname) as f:
  153.         s = f.readline()
  154.     octets = s.split(':')
  155.     return ''.join(chr(int(x, 16)) for x in octets)
  156.  
  157.  
  158. def getdefaultgatewayaddr():
  159.     """Return IP of default route gateway (next hop) in 1.2.3.4 notation
  160.       or None if there is not default route.
  161.    """
  162.     search = re.compile(r"^\S+\s+00000000\s+([0-9a-fA-F]{8})").search
  163.     with open("/proc/net/route") as f:
  164.         for line in f:
  165.             m = search(line)
  166.             if m:
  167.                 hexaddr = m.group(1)
  168.                 octets = (hexaddr[i:i + 2] for i in xrange(0, 7, 2))
  169.                 if sys.byteorder == "little":
  170.                     octets = reversed(list(octets))
  171.                 ipaddr = '.'.join(str(int(octet, 16)) for octet in octets)
  172.                 return ipaddr
  173.     return None
  174.  
  175. ### Ping
  176.  
  177. def ipchecksum(packet):
  178.     """Return IP checksum of `packet`"""
  179.     # c.f. https://tools.ietf.org/html/rfc1071
  180.     arr = array.array('H', packet + '\0' if len(packet) % 2 else packet)
  181.     chksum = sum(arr)
  182.     chksum = (chksum >> 16) + (chksum & 0xffff)  # add high and low 16 bits
  183.     chksum += chksum >> 16  # add carry
  184.     chksum = ~chksum & 0xffff  # invert and truncate
  185.     return socket.htons(chksum)  # per RFC 1071
  186.  
  187.  
  188. def pingaddr(ipaddr, data='', timeout=1.0, strict=False):
  189.     """Return True if `ipaddr` replies to an ICMP ECHO request within
  190.       `timeout` seconds else False. Provide optional `data` to include in
  191.       the request. Any reply from `ipaddr` will suffice. Use `strict` to
  192.       accept only a reply matching the request.
  193.    """
  194.     # pylint:disable=too-many-locals
  195.     # construct packet
  196.     if len(data) > 2000:
  197.         raise ValueError("data too large")
  198.     icmp_struct = struct.Struct("!BBHHH")
  199.     echoid = os.getpid() & 0xffff
  200.     seqnum = random.randint(0, 0xffff)
  201.     chksum = ipchecksum(icmp_struct.pack(8, 0, 0, echoid, seqnum) + data)
  202.     packet = icmp_struct.pack(8, 0, chksum, echoid, seqnum) + data
  203.     # send it and check reply
  204.     sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, 1)
  205.     sock.sendto(packet, (ipaddr, 1))
  206.     t0 = time.time()
  207.     while time.time() - t0 < timeout:
  208.         ready, __, __ = select.select([sock], (), (), timeout)
  209.         if not ready:
  210.             return False
  211.         packet, peer = sock.recvfrom(2048)
  212.         if peer[0] != ipaddr:
  213.             continue
  214.         if not strict:
  215.             return True
  216.         # verify it's a reply to the packet we just sent
  217.         packet = packet[20:]  # strip IP header
  218.         fields = icmp_struct.unpack(packet[:8])
  219.         theirs = fields[-2:] + (packet[8:],)
  220.         if theirs == (echoid, seqnum, data):
  221.             return True
  222.     return False
  223.  
  224. ### Helpers
  225.  
  226. def strbuf(buf):
  227.     """Return `buf` formatted as a hex dump (like tcpdump -xx)."""
  228.     out = []
  229.     for i in xrange(0, len(buf), 16):
  230.         octets = (ord(x) for x in buf[i:i + 16])
  231.         pairs = []
  232.         for octet in octets:
  233.             pad = '' if len(pairs) % 2 else ' '
  234.             pairs.append("%s%02x" % (pad, octet))
  235.         out.append("0x%04x: %s" % (i, '' .join(pairs)))
  236.     return '\n'.join(out)
  237.  
  238.  
  239. def strmac(mac):
  240.     """Return packed string `mac` formatted like aa:bb:cc:dd:ee:ff."""
  241.     return ':'.join("%02x" % ord(b) for b in mac[:6])
  242.  
  243.  
  244. def strexc():
  245.     """Return current exception formatted as a single line suitable
  246.       for logging.
  247.    """
  248.     try:
  249.         exc_type, exc_value, tb = sys.exc_info()
  250.         if exc_type is None:
  251.             return ''
  252.         # find last frame in this script
  253.         lineno, func = 0, ''
  254.         for frame in traceback.extract_tb(tb):
  255.             if frame[0] != __file__:
  256.                 break
  257.             lineno, func = frame[1:3]
  258.         return "exception in %s line %s (%s: %s)" % (
  259.             func, lineno, exc_type.__name__, exc_value)
  260.     finally:
  261.         del tb
  262.  
  263.  
  264. def killpidfile(pidfile, signum):
  265.     """Send `signum` to PID recorded in `pidfile`.
  266.       Return PID if successful, else return None.
  267.    """
  268.     try:
  269.         with open(pidfile) as f:
  270.             pid = int(f.readline())
  271.         os.kill(pid, signum)
  272.         return pid
  273.     except (EnvironmentError, ValueError):
  274.         pass
  275.  
  276.  
  277. def checkpidfile(pidfile):
  278.     """Check whether a process is running with the PID in `pidfile`.
  279.       Return PID if successful, else return None.
  280.    """
  281.     return killpidfile(pidfile, 0)
  282.  
  283.  
  284. def safe_unlink(path):
  285.     """rm -f `path`"""
  286.     try:
  287.         os.unlink(path)
  288.     except EnvironmentError:
  289.         pass
  290.  
  291.  
  292. def writepidfile(pidfile):
  293.     """Write current pid to `pidfile`."""
  294.     with open(pidfile, 'w') as f:
  295.         f.write("%s\n" % os.getpid())
  296.  
  297.     # NOTE: called on normal Python exit, but not on SIGTERM.
  298.     @atexit.register
  299.     def removepidfile(_remove=os.remove):  # pylint:disable=unused-variable
  300.         try:
  301.             _remove(pidfile)
  302.         except Exception:  # pylint:disable=broad-except
  303.             pass
  304.  
  305.  
  306. def daemonize():
  307.     """Convert process into a daemon."""
  308.     if os.fork():
  309.         sys.exit(0)
  310.     os.chdir("/")
  311.     os.setsid()
  312.     os.umask(0)
  313.     if os.fork():
  314.         sys.exit(0)
  315.     sys.stdout.flush()
  316.     sys.stderr.flush()
  317.     nullin = open('/dev/null', 'r')
  318.     nullout = open('/dev/null', 'a+')
  319.     nullerr = open('/dev/null', 'a+', 0)
  320.     os.dup2(nullin.fileno(), sys.stdin.fileno())
  321.     os.dup2(nullout.fileno(), sys.stdout.fileno())
  322.     os.dup2(nullerr.fileno(), sys.stderr.fileno())
  323.  
  324.  
  325. def make_logger(use_syslog=False, debug=False):
  326.     """Return new logging.Logger object."""
  327.     if use_syslog:
  328.         formatter = logging.Formatter("eap_proxy[%(process)d]: %(message)s")
  329.         formatter.formatException = lambda *__: ''  # no stack trace to syslog
  330.         SysLogHandler = logging.handlers.SysLogHandler
  331.         handler = SysLogHandler("/dev/log", facility=SysLogHandler.LOG_LOCAL7)
  332.         handler.setFormatter(formatter)
  333.     else:
  334.         formatter = logging.Formatter("[%(asctime)s]: %(message)s")
  335.         handler = logging.StreamHandler()
  336.         handler.setFormatter(formatter)
  337.  
  338.     logger = logging.getLogger("eap_proxy")
  339.     if debug:
  340.         logger.setLevel(logging.DEBUG)
  341.     else:
  342.         logger.setLevel(logging.INFO)
  343.     logger.addHandler(handler)
  344.     return logger
  345.  
  346. ### EdgeOS
  347.  
  348.  
  349. class EdgeOS(object):
  350.     def __init__(self, log):
  351.         self.log = log
  352.  
  353.     def run(self, *args):
  354.         try:
  355.             return 0, subprocess.check_output(args)
  356.         except subprocess.CalledProcessError as ex:
  357.             self.log.warn("%s exited %d", args, ex.returncode)
  358.             return ex.returncode, ex.output
  359.  
  360.     def run_vyatta_interfaces(self, name, *args):
  361.         self.run(
  362.             "/opt/vyatta/sbin/vyatta-interfaces.pl", "--dev", name, *args)
  363.  
  364.     def restart_dhclient(self, name):
  365.         # This isn't working:
  366.         # self.run_vyatta_interfaces(name, "--dhcp", "release")
  367.         # self.run_vyatta_interfaces(name, "--dhcp", "renew")
  368.         # The "renew" command emits:
  369.         #   eth0.0 is not using DHCP to get an IP address
  370.         # So we emulate it ourselves.
  371.         self.stop_dhclient(name)
  372.         self.start_dhclient(name)
  373.  
  374.     @staticmethod
  375.     def dhclient_pathnames(ifname):
  376.         """Return tuple of (-cf, -pf, and -lf) arg values for dhclient."""
  377.         filename = ifname.replace('.', '_')
  378.         return (
  379.             "/var/run/dhclient_%s.conf" % filename,    # -cf
  380.             "/var/run/dhclient_%s.pid" % filename,     # -pf
  381.             "/var/run/dhclient_%s.leases" % filename)  # -lf
  382.  
  383.     def stop_dhclient(self, ifname):
  384.         """Stop dhclient on `ifname` interface."""
  385.         # Emulates vyatta-interfaces.pl's behavior
  386.         cf, pf, lf = self.dhclient_pathnames(ifname)
  387.         self.run(
  388.             "/sbin/dhclient", "-q",
  389.             "-cf", cf,
  390.             "-pf", pf,
  391.             "-lf", lf,
  392.             "-r", ifname)
  393.         safe_unlink(pf)
  394.  
  395.     def start_dhclient(self, ifname):
  396.         """Start dhclient on `ifname` interface"""
  397.         # Emulates vyatta-interfaces.pl's behavior
  398.         cf, pf, lf = self.dhclient_pathnames(ifname)
  399.         killpidfile(pf, signal.SIGTERM)
  400.         safe_unlink(pf)
  401.         self.run(
  402.             "/sbin/dhclient", "-q", "-nw",
  403.             "-cf", cf,
  404.             "-pf", pf,
  405.             "-lf", lf,
  406.             ifname)
  407.  
  408.     def setmac(self, ifname, mac):
  409.         """Set interface `ifname` mac to `mac`, which may be either a packed
  410.           string or in "aa:bb:cc:dd:ee:ff" format."""
  411.         # untested, perhaps I should use /bin/ip or ioctl instead.
  412.         if len(mac) == 6:
  413.             mac = strmac(mac)
  414.         self.run_vyatta_interfaces(ifname, "--set-mac", mac)
  415.  
  416.     @staticmethod
  417.     def getmac(ifname):
  418.         """Return MAC address for `ifname` as a packed string."""
  419.         return getifhwaddr(ifname)
  420.  
  421. ### EAP frame/packet decoding
  422. # c.f. https://github.com/the-tcpdump-group/tcpdump/blob/master/print-eap.c
  423.  
  424. class EAPFrame(namedtuple("EAPFrame", "dst src version type length packet")):
  425.     __slots__ = ()
  426.     _struct = struct.Struct("!6s6sHBBH")  # includes ethernet header
  427.     TYPE_PACKET = 0
  428.     TYPE_START = 1
  429.     TYPE_LOGOFF = 2
  430.     TYPE_KEY = 3
  431.     TYPE_ENCAP_ASF_ALERT = 4
  432.     _types = {
  433.         TYPE_PACKET: "EAP packet",
  434.         TYPE_START: "EAPOL start",
  435.         TYPE_LOGOFF: "EAPOL logoff",
  436.         TYPE_KEY: "EAPOL key",
  437.         TYPE_ENCAP_ASF_ALERT: "Encapsulated ASF alert"
  438.     }
  439.  
  440.     @classmethod
  441.     def from_buf(cls, buf):
  442.         unpack, size = cls._struct.unpack, cls._struct.size
  443.         dst, src, etype, ver, ptype, length = unpack(buf[:size])
  444.         if etype != ETH_P_PAE:
  445.             raise ValueError("invalid ethernet type: 0x%04x" % etype)
  446.         if ptype == cls.TYPE_PACKET:
  447.             packet = EAPPacket.from_buf(buf[size:size + length])
  448.         else:
  449.             packet = None
  450.         return cls(dst, src, ver, ptype, length, packet)
  451.  
  452.     @property
  453.     def type_name(self):
  454.         return self._types.get(self.type, "???")
  455.  
  456.     @property
  457.     def is_start(self):
  458.         return self.type == self.TYPE_START
  459.  
  460.     @property
  461.     def is_logoff(self):
  462.         return self.type == self.TYPE_LOGOFF
  463.  
  464.     @property
  465.     def is_success(self):
  466.         return self.packet and self.packet.is_success
  467.  
  468.     def __str__(self):
  469.         return "%s > %s, %s (%d) v%d, len %d%s" % (
  470.             strmac(self.src), strmac(self.dst),
  471.             self.type_name, self.type, self.version, self.length,
  472.             ", " + str(self.packet) if self.packet else '')
  473.  
  474.  
  475. class EAPPacket(namedtuple("EAPPacket", "code id length data")):
  476.     __slots__ = ()
  477.     _struct = struct.Struct("!BBH")
  478.     REQUEST, RESPONSE, SUCCESS, FAILURE = 1, 2, 3, 4
  479.     _codes = {
  480.         REQUEST: "Request",
  481.         RESPONSE: "Response",
  482.         SUCCESS: "Success",
  483.         FAILURE: "Failure"
  484.     }
  485.  
  486.     @classmethod
  487.     def from_buf(cls, buf):
  488.         unpack, size = cls._struct.unpack, cls._struct.size
  489.         code, id_, length = unpack(buf[:size])
  490.         data = buf[size:size + length - 4]
  491.         return cls(code, id_, length, data)
  492.  
  493.     @property
  494.     def code_name(self):
  495.         return self._codes.get(self.code, "???")
  496.  
  497.     @property
  498.     def is_success(self):
  499.         return self.code == self.SUCCESS
  500.  
  501.     def __str__(self):
  502.         return "%s (%d) id %d, len %d [%d]" % (
  503.             self.code_name, self.code, self.id, self.length, len(self.data))
  504.  
  505. ### EAP Proxy
  506.  
  507. class EAPProxy(object):
  508.  
  509.     def __init__(self, args, log):
  510.         self.args = args
  511.         self.os = EdgeOS(log)
  512.         self.log = log
  513.  
  514.     def proxy_forever(self):
  515.         log = self.log
  516.         while True:
  517.             try:
  518.                 log.info("proxy_loop starting")
  519.                 self.proxy_loop()
  520.             except KeyboardInterrupt:
  521.                 return
  522.             except Exception as ex:  # pylint:disable=broad-except
  523.                 log.warn("%s; restarting in 10 seconds", strexc(), exc_info=ex)
  524.             else:
  525.                 log.warn("proxy_loop exited; restarting in 10 seconds")
  526.             time.sleep(10)
  527.  
  528.     def proxy_loop(self):
  529.         args = self.args
  530.         poll = select.poll()  # pylint:disable=no-member
  531.         s_rtr = rawsocket(args.if_rtr, poll=poll, promisc=args.promiscuous)
  532.         s_wan = rawsocket(args.if_wan, poll=poll, promisc=args.promiscuous)
  533.         socks = {s.fileno(): s for s in (s_rtr, s_wan)}
  534.         on_poll_event = partial(self.on_poll_event, s_rtr=s_rtr, s_wan=s_wan)
  535.  
  536.         while True:
  537.             ready = poll.poll()
  538.             for fd, event in ready:
  539.                 on_poll_event(socks[fd], event)
  540.  
  541.     def on_poll_event(self, sock_in, event, s_rtr, s_wan):
  542.         log = self.log
  543.         ifname = getifname(sock_in)
  544.         if event != select.POLLIN:  # pylint:disable=no-member
  545.             raise IOError("[%s] unexpected poll event: %d" % (ifname, event))
  546.  
  547.         buf = sock_in.recv(2048)
  548.  
  549.         if self.args.debug_packets:
  550.             log.debug("%s: recv %d bytes:\n%s", ifname, len(buf), strbuf(buf))
  551.  
  552.         eap = EAPFrame.from_buf(buf)
  553.         log.debug("%s: %s", ifname, eap)
  554.  
  555.         if sock_in == s_rtr:
  556.             sock_out = s_wan
  557.             self.on_router_eap(eap)
  558.             if self.should_ignore_router_eap(eap):
  559.                 log.debug("%s: ignoring %s", ifname, eap)
  560.                 return
  561.         else:
  562.             sock_out = s_rtr
  563.             self.on_wan_eap(eap)
  564.  
  565.         log.info("%s: %s > %s", ifname, eap, getifname(sock_out))
  566.         nbytes = sock_out.send(buf)
  567.         log.debug("%s: sent %d bytes", getifname(sock_out), nbytes)
  568.  
  569.  
  570.     def should_ignore_router_eap(self, eap):
  571.         args = self.args
  572.         if args.ignore_start and eap.is_start:
  573.             return True
  574.         if args.ignore_logoff and eap.is_logoff:
  575.             return True
  576.         if args.ignore_when_wan_up:
  577.             return self.check_wan_is_up()
  578.         return False
  579.  
  580.     def on_router_eap(self, eap):
  581.         args = self.args
  582.         if not args.set_mac:
  583.             return
  584.  
  585.         if_vlan = "%s.%d" % (args.if_wan, args.vlan_id)
  586.         if self.os.getmac(if_vlan) == eap.src:
  587.             return
  588.  
  589.         self.log.info("%s: setting mac to %s", if_vlan, strmac(eap.src))
  590.         self.os.setmac(if_vlan, eap.src)
  591.  
  592.     def on_wan_eap(self, eap):
  593.         if not self.should_restart_dhcp(eap):
  594.             return
  595.         args = self.args
  596.         if_vlan = "%s.%d" % (args.if_wan, args.vlan_id)
  597.         self.log.info("%s: restarting dhclient", if_vlan)
  598.         self.os.restart_dhclient(if_vlan)
  599.  
  600.     def should_restart_dhcp(self, eap):
  601.         if self.args.restart_dhcp and eap.is_success:
  602.             return not self.check_wan_is_up()
  603.         return False
  604.  
  605.     def check_wan_is_up(self):
  606.         args, log = self.args, self.log
  607.         if_vlan = "%s.%d" % (args.if_wan, args.vlan_id)
  608.         ipaddr = getifaddr(if_vlan)
  609.         if ipaddr:
  610.             log.debug("%s: %s", if_vlan, ipaddr)
  611.             return self.ping_gateway() if args.ping_gateway else True
  612.         log.debug("%s: no IP address", if_vlan)
  613.         return False
  614.  
  615.     def ping_gateway(self):
  616.         log = self.log
  617.         ipaddr = getdefaultgatewayaddr()
  618.         if not ipaddr:
  619.             log.debug("ping: no default route gateway")
  620.             return False
  621.         rv = pingaddr(ipaddr)
  622.         log.debug("ping: %s %s", ipaddr, "success" if rv else "failed")
  623.         return rv
  624.  
  625. ### Main
  626.  
  627. def parse_args():
  628.     p = argparse.ArgumentParser("eap_proxy")
  629.  
  630.     # interface arguments
  631.     p.add_argument(
  632.         "if_wan", metavar="IF_WAN", help="interface of the AT&T ONT/WAN")
  633.     p.add_argument(
  634.         "if_rtr", metavar="IF_ROUTER", help="interface of the AT&T router")
  635.  
  636.     # checking whether WAN is up
  637.     g = p.add_argument_group("checking whether WAN is up")
  638.     g.add_argument(
  639.         "--ping-gateway", action="store_true", help=
  640.         "normally the WAN is considered up if the IF_WAN VLAN has an address; "
  641.         "this option additionally requires that there is a default route "
  642.         "gateway that responds to a ping")
  643.  
  644.     # ignoring packet options
  645.     g = p.add_argument_group("ignoring router packets")
  646.     g.add_argument(
  647.         "--ignore-when-wan-up", action="store_true", help=
  648.         "ignore router packets when WAN is up (see --ping-gateway)")
  649.     g.add_argument(
  650.         "--ignore-start", action="store_true", help=
  651.         "always ignore EAPOL-Start from router")
  652.     g.add_argument(
  653.         "--ignore-logoff", action="store_true", help=
  654.         "always ignore EAPOL-Logoff from router")
  655.  
  656.     # configuring IF_WAN VLAN options
  657.     g = p.add_argument_group("configuring IF_WAN VLAN")
  658.     g.add_argument(
  659.         "--restart-dhcp", action="store_true", help=
  660.         "check whether WAN is up after receiving EAP-Success on IF_WAN VLAN "
  661.         "(see --ping-gateway); if not, restart dhclient on IF_WAN VLAN")
  662.     g.add_argument(
  663.         "--set-mac", action="store_true", help=
  664.         "set IF_WAN VLAN MAC (ether) address to router's MAC address")
  665.     g.add_argument(
  666.         "--vlan-id", type=int, default=0, help=
  667.         "set IF_WAN VLAN ID (default is 0)")
  668.  
  669.     # daemonization options
  670.     g = p.add_argument_group("daemonization")
  671.     g.add_argument(
  672.         "--daemon", action="store_true", help=
  673.         "become a daemon; implies --syslog")
  674.     g.add_argument("--pidfile", help="record pid to PIDFILE")
  675.     g.add_argument(
  676.         "--syslog", action="store_true", help=
  677.         "log to syslog instead of stderr")
  678.  
  679.     # debugging options
  680.     g = p.add_argument_group("debugging")
  681.     g.add_argument(
  682.         "--promiscuous", action="store_true", help=
  683.         "place interfaces into promiscuous mode instead of multicast")
  684.     g.add_argument(
  685.         "--debug", action="store_true", help=
  686.         "enable debug-level logging")
  687.     g.add_argument(
  688.         "--debug-packets", action="store_true", help=
  689.         "print packets in hex format to assist with debugging; "
  690.         "implies --debug")
  691.  
  692.     args = p.parse_args()
  693.     if args.daemon:
  694.         args.syslog = True
  695.     if args.debug_packets:
  696.         if args.syslog:
  697.             p.error("--debug-packets not allowed with --syslog")
  698.         args.debug = True
  699.     return args
  700.  
  701.  
  702. def main():
  703.     args = parse_args()
  704.     log = make_logger(args.syslog, args.debug)
  705.  
  706.     if args.pidfile:
  707.         pid = checkpidfile(args.pidfile)
  708.         if pid:
  709.             log.error("eap_proxy already running with pid %s?", pid)
  710.             return 1
  711.  
  712.     if args.daemon:
  713.         try:
  714.             daemonize()
  715.         except Exception:  # pylint:disable=broad-except
  716.             log.exception("could not become daemon: %s", strexc())
  717.             return 1
  718.  
  719.     # ensure cleanup (atexit, etc) occurs when we're killed via SIGTERM
  720.     def on_sigterm(signum, __):
  721.         log.info("exiting on signal %d", signum)
  722.         raise SystemExit(0)
  723.  
  724.     signal.signal(signal.SIGTERM, on_sigterm)
  725.  
  726.     if args.pidfile:
  727.         try:
  728.             writepidfile(args.pidfile)
  729.         except EnvironmentError:  # pylint:disable=broad-except
  730.             log.exception("could not write pidfile: %s", strexc())
  731.  
  732.     EAPProxy(args, log).proxy_forever()
  733.  
  734.  
  735. if __name__ == "__main__":
  736.     sys.exit(main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement