#!/usr/bin/env python3 ############################################################################## # Keep the Realtek r8168 ethernet port working by resetting its driver # whenever it stops. # # Author: J S Worthington # Creation date: 19-Dec-2012 # Version: 1.1 # Date: 15-Jun-2016 ############################################################################## import syslog import subprocess import os, sys, socket, struct, select, time, signal ############################################################################## # Configuration # True to use debugging code, False otherwise. debug = False # Time to wait after a recovery (or startup) before trying to ping again. # Units: seconds recovery_delay = 10.0 # Target of the pings. This should be an IP address, as a DNS lookup may not # be possible if the ethernet port is down and also would cause delays. #ping_target = "10.0.2.253" # Websmart Gigabit ethernet router ping_target = "10.0.2.251" # erl.jsw.gen.nz router # Ping timeout. Units: ms. Remember to allow time for traffic congestion. ping_timeout = 100 # Ping size. Number of bytes of data to send in the ping. ping_size = 1 # Normal delay between pings when the last ping succeeded. Units: seconds if debug: long_delay = 3.0 else: long_delay = 60.0 # Short delay between pings when the last ping failed. Units: seconds short_delay = 1.0 # Maximum fast pings to try before deciding the ethernet port is not working. max_fast_pings = 3 ############################################################################## # Debug functions if debug: #import logging #logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) def debug_print(*args, **kwargs): #print("debug_print called") print(*args, **kwargs) #logging.debug(*args, **kwargs) sys.stdout.flush() else: def debug_print(*args, **kwargs): pass ############################################################################## # Python 3 ping from: # http://pastebin.com/4ZHR61BH # Cut down and altered by JSW 9-Dec-2012 """ A pure python ping implementation using raw sockets. Note that ICMP messages can only be sent from processes running as root (in Windows, you must run this script as 'Administrator'). Original Version from Matthew Dixon Cowles: -> ftp://ftp.visi.com/users/mdc/ping.py Rewrite by Jens Diemer: -> http://www.python-forum.de/post-69122.html#69122 Rewrite by George Notaras: -> http://www.g-loaded.eu/2009/10/30/python-ping/ Enhancements by Martin Falatic: -> http://www.falatic.com/index.php/39/pinging-with-python Edited by yokmp: I've done this because some lines doesn't work in Python3.2 and i needed it a bit more interactive. See the last lines for detail. =========================================================================== """ # ICMP parameters ICMP_ECHOREPLY = 0 # Echo reply (per RFC792) ICMP_ECHO = 8 # Echo request (per RFC792) ICMP_MAX_RECV = 2048 # Max size of incoming buffer MAX_SLEEP = 1000 def checksum(source_string): """ A port of the functionality of in_cksum() from ping.c Ideally this would act on the string as a series of 16-bit ints (host packed), but this works. Network data is big-endian, hosts are typically little-endian """ countTo = (int(len(source_string) / 2)) * 2 sum = 0 count = 0 # Handle bytes in pairs (decoding as short ints) loByte = 0 hiByte = 0 while count < countTo: if (sys.byteorder == "little"): loByte = source_string[count] hiByte = source_string[count + 1] else: loByte = source_string[count + 1] hiByte = source_string[count] sum = sum + (hiByte * 256 + loByte) count += 2 # Handle last byte if applicable (odd-number of bytes) # Endianness should be irrelevant in this case if countTo < len(source_string): # Check for odd length loByte = source_string[len(source_string) - 1] sum += loByte sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which # uses signed ints, but overflow is unlikely in ping) sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits sum += (sum >> 16) # Add carry from above (if any) answer = ~sum & 0xffff # Invert and truncate to 16 bits answer = socket.htons(answer) return answer def send_one_ping(mySocket, destIP, myID, mySeqNumber, numDataBytes): """ Send one ping to the given >destIP<. """ destIP = socket.gethostbyname(destIP) # Header is type (8), code (8), checksum (16), id (16), sequence (16) myChecksum = 0 # Make a dummy heder with a 0 checksum. header = struct.pack( "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber ) padBytes = [] startVal = 0x42 for i in range(startVal, startVal + (numDataBytes)): padBytes += [(i & 0xff)] # Keep chars in the 0-255 range data = bytes(padBytes) # Calculate the checksum on the data and the dummy header. myChecksum = checksum(header + data) # Checksum is in network order # Now that we have the right checksum, we put that in. It's just easier # to make up a new header than to stuff it into the dummy. header = struct.pack( "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber ) packet = header + data sendTime = time.time() try: mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP except socket.error as e: debug_print("General failure (%s)" % (e.args[1])) return return sendTime def receive_one_ping(mySocket, myID, timeout): """ Receive the ping from the socket. Timeout = in ms """ timeLeft = timeout / 1000 while True: # Loop while waiting for packet or timeout startedSelect = time.time() whatReady = select.select([mySocket], [], [], timeLeft) howLongInSelect = (time.time() - startedSelect) if whatReady[0] == []: # Timeout return None, 0, 0, 0, 0 timeReceived = time.time() recPacket, addr = mySocket.recvfrom(ICMP_MAX_RECV) ipHeader = recPacket[:20] iphVersion, iphTypeOfSvc, iphLength, \ iphID, iphFlags, iphTTL, iphProtocol, \ iphChecksum, iphSrcIP, iphDestIP = struct.unpack( "!BBHHHBBHII", ipHeader ) icmpHeader = recPacket[20:28] icmpType, icmpCode, icmpChecksum, \ icmpPacketID, icmpSeqNumber = struct.unpack( "!BBHHH", icmpHeader ) if icmpPacketID == myID: # Our packet dataSize = len(recPacket) - 28 return timeReceived, dataSize, iphSrcIP, icmpSeqNumber, iphTTL timeLeft = timeLeft - howLongInSelect if timeLeft <= 0: return None, 0, 0, 0, 0 def one_ping(destIP, timeout, mySeqNumber, numDataBytes): """ Returns either the delay (in ms) or None on timeout. """ delay = None try: # One could use UDP here, but it's obscure mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) except (socket.error, (errno, msg)): if errno == 1: # Operation not permitted - Add more information to traceback etype, evalue, etb = sys.exc_info() evalue = etype( "%s - Note that ICMP messages can only be sent from processes running as root." % evalue ) raise (etype, evalue, etb) debug_print("failed. (socket error: '%s')" % msg) raise # raise the original error my_ID = os.getpid() & 0xFFFF sentTime = send_one_ping(mySocket, destIP, my_ID, mySeqNumber, numDataBytes) if sentTime == None: mySocket.close() return delay recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout) mySocket.close() if recvTime: delay = (recvTime - sentTime) * 1000 debug_print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % ( dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay) ) else: delay = None debug_print("Request timed out.") if debug: global kill_ping if kill_ping != 0: kill_ping -= 1 debug_print("Ping killed") delay = None return delay ############################################################################## # Signal handler def handle_signal(signum, stack): global stop if signum in [1,2,3,15]: debug_print('Caught signal ', str(signum), ', stopping.', sep='') stop = True elif debug and signum == 20: global kill_ping kill_ping += 1 else: debug_print('Caught signal ', str(signum), ', ignoring.', sep='') ############################################################################## # Ping def ping(): global ping_target, ping_timeout, ping_id, ping_size ping.ping_id += 1 if ping.ping_id >= 100: ping.ping_id = 1 return one_ping(ping_target, ping_timeout, ping.ping_id, ping_size) ping.ping_id = 0 ############################################################################## # Main stop = False if debug: kill_ping = 0 # Set up signal handler. Catch all catchable signals and have them stop the # program. uncatchable = ['SIG_DFL', 'SIGSTOP', 'SIGKILL', 'SIG_BLOCK'] for i in [x for x in dir(signal) if x.startswith("SIG")]: if not i in uncatchable: signum = getattr(signal, i) signal.signal(signum, handle_signal) syslog.syslog("Daemon starting") # net-keepalived body ITER while not stop: # Recovery cycle SEQ # Recovery delay time.sleep(recovery_delay) debug_print("Awake!") # Ping response = ping() # OK period ITER while not stop and response: # OK period body SEQ # Slow pings period ITER while not stop and response: # Slow ping SEQ # Long delay time.sleep(long_delay) # Ping response = ping() # Slow ping END fast_pings_counter = max_fast_pings # Fast pings period ITER while not stop and not response and fast_pings_counter != 0: # Fast ping SEQ # Short delay time.sleep(short_delay) # Ping response = ping() fast_pings_counter -= 1 # Fast ping END # OK period body END # QUIT if stop if stop: break # Recovery SEQ if True : # Shut down r8168 driver debug_print("Shutting down r8168 driver") subprocess.call(["modprobe", "-r", "r8168"]) # QUIT if stop if stop: break # Restart r8168 driver debug_print("Restarting down r8168 driver") subprocess.call(["modprobe", "r8168"]) # QUIT if stop if stop: break # Restart smbd service debug_print("Restarting smbd service") subprocess.call(["restart", "smbd"]) syslog.syslog("Net connection recovered (r8168 driver reloaded)") # Recovery END # Recovery cycle END syslog.syslog("Daemon exiting") debug_print("Exiting")