##############################################################################
#
#  			Copyright 2023 Cornelius Keck.
#			      All Rights Reserved
#
#
#  System        : 
#  Module        : 
#  Object Name   : $RCSfile$
#  Revision      : $Revision$
#  Date          : $Date$
#  Author        : $Author$
#  Created By    : Cornelius Keck
#  Created       : Fri May 5 13:04:21 2023
#  Last Modified : <230614.1320>
#
#  Description	
#
#  Notes
#
#  History
#	
##############################################################################
#
#  Copyright (c) 2023 Cornelius Keck.
# 
#  All Rights Reserved.
# 
#  This  document  may  not, in  whole  or in  part, be  copied,  photocopied,
#  reproduced,  translated,  or  reduced to any  electronic  medium or machine
#  readable form without prior written consent from Cornelius Keck.
#
##############################################################################

# ping some IP. Return 1 for we got something back, 0 if not.

proc pingThis { ipAddr } {
    
    set targetIsUp 0
    
    set fd [ open "| /bin/ping -c 20 -i 1 $ipAddr" ]
    
    # read lines until we get EOF
    
    while { ! [ eof $fd ] } {
        
        # read one line, eval if it contains something
        
        if { [ gets $fd oneline ] >= 0 } {
            
            puts $oneline
            
            # if there is a network hiccup, we are done immediately.
            
            if { [ string first "connect: Network is unreachable" $oneline ] >= 0 } {
                break
            }
            
            # last line of a ping contains statistics. That line contains strrings
            # "packets transmitted" and "packet loss".
            
            if { [ string first "packets transmitted," $oneline ] >= 0 && [ string first "packet loss" $oneline ] >= 0 } {
                # we have statistics. If none of the pings came back, then the string "100% packet loss is present.
                # If not, the router is up.
                if { [ string first "100% packet loss" $oneline ] < 0 } {
                    # packet loss is not 100%, target is up.
                    set targetIsUp 1
                    break
                }
            }
        }
    }
    catch { close $fd }
    
    return $targetIsUp
}


# there are two lists here. First one is any set of known, typically up IPs,
# like DNS servers hosted by Cloudflare, Google and Plan-9. If all of these
# are down, chances are we our uplink is down, so don't even try anything
# further.

set pingPublic "1.1.1.1 8.8.8.8 9.9.9.9"

# second list is a set of our own servers. If they are all down, but we have
# an uplink, then chances are the ONT is toast, good reason to open the door

set pingMine "1.2.3.4 1.2.3.5 1.2.3.6 1.2.3.7 1.2.3.8 1.2.3.9 1.2.3.10"

# set router and rendezvous host. Do not use a hostname, in case DNS is flaky

set ROUTER 192.168.0.1
set RHOST 1.2.3.4

# wake up router
# Open a pipe to a ping again the wireless router, for 10 ping 2 seconds
# apart, Might take a couple pings for the radio link to wake up. No rush
# jobs here!

set haveUplink 0
set haveMine 0

# find the inside IP address of your router. netstat -an might tell you that
# one as the destination for your default route. Probably starts with 192.168,
# 172.16 or just 10. Most likely candidates start with 192.168, end in 0.1,
# 0.254, 1.1, or 1.254.

set routerIsUp [ pingThis $ROUTER ]

if { $routerIsUp } {
    
    # if the router is sane, check if the router has an uplink, by pinging a couple
    # of well-known targets. If none of them responds, then chances are that the
    # hotspot does not LTE. Bad hotspot. If there is a response, then break out of
    # this loop.
    
    foreach target $pingPublic {
        set haveUplink [ pingThis $target ]
        if { $haveUplink } {
            break
        }
    }
}

if { $haveUplink } {
    
    # if the router and uplink are sane, check my outside-facing main uplink IPs.
    # Easy in my case, because static IPs. Changing this for DHCP, which tends to
    # change only if your CPE is not reachable for a bit, like after a power outage.
    
    # Determination is different from one above against public targets. We count
    # the number of reachable targets, and open the link if some of them are not
    # reachable, with some being more than one.
    
    foreach target $pingMine {
        incr haveMine [ pingThis $target ]
    }
}

# find out if autodial.exp is already running. If so, do not spin up another one.
# This is similar to the ping thing above, but we are looking for something like
# /usr/bin/expect /home/pi/Expect/autodial.exp
# If your expect binary or autodial.exp are in different locations, adjust as needed.

set haveAutoDial 0
set fd [ open "| /usr/bin/ps -ef" ]
while { ! [ eof $fd ] } {
    if { [ gets $fd oneline ] > 0 } {
        set expectAt [ string first "/usr/bin/expect" $oneline ]
        set autodialAt [ string first "/home/pi/Expect/autodial.exp" $oneline ]
        if { $expectAt >= 0 && $autodialAt > $expectAt } {
            
            puts "$oneline"
            # autodial is already running. Remember that,
            # do not break out of this loop, but rather
            # eat the rest.
            set haveAutoDial 1
        }
    }  
}

catch { close $fd }

puts "haveAutoDial: $haveAutoDial"

if { $routerIsUp && $haveUplink && ! $haveAutoDial && $haveMine < 3 } {
    
    # router and uplink are good, but not enough of mine respond,
    # and autodial is not running, so commence evasive action,
    # open the gates of doom, after making sure doom is online.
    
    if { [ pingThis $RHOST ] } {
        
        puts "line is down, opening door"
        
        # insert more evasive action here.
        
        # create .stay-on, to make sure the tunnel stays open as long as main line is down.
        
        set fd [ open $env(HOME)/.stay-on w ]
        puts $fd [ clock seconds ]
        close $fd
        
        # spin up the backdoor dialer in th background.
        
        exec nohup /usr/bin/expect /home/pi/Expect/autodial.exp > /home/pi/Expect/autodial.log 2>&1 &
    } else {
        puts "$RHOST does not respond."
    }
} elseif { [ file isfile $env(HOME)/.stay-on ] } {
    
    # line is up. remove .stay-on to close down.
    
    puts "line is back, closing door"
    catch { file delete -force $env(HOME)/.stay-on }
} else {
    puts "line is up, nothing to do"
}
