Restarting OpenVPN



  • I would like to automatically restart OpenVPN every night by using cron.

    What command should I use to restart OpenVPN? I saw an old post that says to use this command:

    echo "" | php -q
    

    From what I can tell, it seems to work. Is this the best command to use? Or is there a better way?


  • Rebel Alliance Developer Netgate

    Why would you want to do that?

    That command should do it, and is probably the easiest way to accomplish that, but still seems like an odd thing to do.



  • Hi Jimp,

    That's a good question. I hope the following answer makes sense.

    Our main office has two internet connections. One is very fast and the other is relatively slow and is meant as a backup connection.

    Our satellite offices' VPN tunnels are configured to always connect to the fast connection first, then the slow one if the fast one is down.

    Sometimes in the middle of the night, our fast connection goes down for a minute or two. During that time, the satellite offices' VPN tunnels automatically switch over to the slow connection. Sometimes we discover this days later when a user complains that the VPN has been slow for several days. Then we have to manually restart the tunnels to get them to use the fast connection again.

    If we automatically restart the tunnels every night, then the longest someone would be on the slow connection would be 24 hours.

    If you have a better idea for automatically switching the tunnels back to the fast connection, I'm all ears.



  • OpenVPN has a network interface of some kind that can be used to control it. I am 99% sure that pfSense uses this feature for itself to check on status and just generally control it, because when I tried to add the options to make it listen on the IP and port I wanted, all the pfSense built-in stuff broke.

    I would love to know what IP/port it's set to listen to by default in pfSense, because I want to do something similar (basically, I want to stop the VPN when certain conditions are met, and start it again on other conditions). Jim, do you know?


  • Rebel Alliance Developer Netgate

    Yeah I suppose that might be needed there. I don't think there is a way to make that happen inside of OpenVPN. Some Googling suggests that others do this (on other platforms) by shutting down the other server processes when they want to, so you're probably doing about the best you can.


  • Rebel Alliance Developer Netgate

    @Briantist:

    OpenVPN has a network interface of some kind that can be used to control it. I am 99% sure that pfSense uses this feature for itself to check on status and just generally control it, because when I tried to add the options to make it listen on the IP and port I wanted, all the pfSense built-in stuff broke.

    I would love to know what IP/port it's set to listen to by default in pfSense, because I want to do something similar (basically, I want to stop the VPN when certain conditions are met, and start it again on other conditions). Jim, do you know?

    It doesn't listen on a port like that, we use a unix socket (/var/etc/openvpn/<something>.sock)</something>



  • @jimp:

    Yeah I suppose that might be needed there. I don't think there is a way to make that happen inside of OpenVPN. Some Googling suggests that others do this (on other platforms) by shutting down the other server processes when they want to, so you're probably doing about the best you can.

    Yup, I googled around too, hoping that OpenVPN had a built-in feature to restart the tunnels periodically. It seems the answer is "no". Pity. I can't be the only person in the world who needs that feature.  :-[



  • I should still be able to use that then as long as I'm controlling it from pfSense then, as opposed to an outside machine, right? I'm thinking I would install NRPE, and use a cron job to run one of the NRPE scripts, and send control commands to OpenVPN based on the results. Any thoughts? Thanks for the quick response.


  • Rebel Alliance Developer Netgate

    @cyboc:

    @jimp:

    Yeah I suppose that might be needed there. I don't think there is a way to make that happen inside of OpenVPN. Some Googling suggests that others do this (on other platforms) by shutting down the other server processes when they want to, so you're probably doing about the best you can.

    Yup, I googled around too, hoping that OpenVPN had a built-in feature to restart the tunnels periodically. It seems the answer is "no". Pity. I can't be the only person in the world who needs that feature.  :-[
    [/quote]

    Yeah, it seems like it would be a nice feature, but in reality that would involve the client periodically retrying the server connection on its own, potentially disrupting the tunnel there. Favoring stability over speed is usually a good choice, but it would be nice to have the option.


  • Rebel Alliance Developer Netgate

    @Briantist:

    I should still be able to use that then as long as I'm controlling it from pfSense then, as opposed to an outside machine, right? I'm thinking I would install NRPE, and use a cron job to run one of the NRPE scripts, and send control commands to OpenVPN based on the results. Any thoughts? Thanks for the quick response.

    Probably best to split that off into its own thread.



  • @cyboc:

    @jimp:

    Yeah I suppose that might be needed there. I don't think there is a way to make that happen inside of OpenVPN. Some Googling suggests that others do this (on other platforms) by shutting down the other server processes when they want to, so you're probably doing about the best you can.

    Yup, I googled around too, hoping that OpenVPN had a built-in feature to restart the tunnels periodically. It seems the answer is "no". Pity. I can't be the only person in the world who needs that feature.  :-[
    [/quote]
    I found a way. You have to know which VPN server you're using (pfSense seems to assign them sequential numbers, server1, server2, etc.).

    Then in cron you can use this:

    
    nc -U /var/etc/openvpn/serverX.sock %signal SUGHUP%exit%
    
    

    The X in serverX.sock is the server number I referenced above. The exit is probably not necessary, at least that's what it seems in my testing, but I put it in anyway (the OpenVPN management interface only supports one connection at a time, so the OpenVPN status page in the web GUI is inoperable while you are connected).

    HTH



  • Hi Brian,

    Thanks for the tip.

    After thinking more about the requirements, I have refined them a bit. Instead of restarting the VPN client every night, I am going to restart the VPN whenever I detect that it is connected to the VPN server's secondary (slow) IP address instead of the primary (fast) IP address.

    The primary IP address is specified by the Server host or address setting on the VPN client config screen (i.e. in the server_addr setting in the pfSense XML config file). The secondary IP address is specified by the remote setting inside the Advanced setting on the VPN client config screen (i.e. in the custom_options setting in the pfSense XML config file).

    Here is a hackish, unfinished, unpolished PHP script that I am currently working on for this purpose. Please don't flame saying the code sucks. I know it sucks. It's not done yet. So far it seems to work okay in my testing.

    require_once('config.inc');
    include('openvpn.inc');
    
    // Read in all of the OpenVPN client configs.
    global $config;
    if (!is_array($config['openvpn']['openvpn-client']))
    {
      return;
    }
    
    // Find client config for London VPN server
    $found_config = false;
    $name = "London";
    foreach ($config['openvpn']['openvpn-client'] as & $settings)
    {
      if (stripos($settings['description'], $name) !== false)
      {
         $found_config = true;
         break;
      }
    }
    
    // If client config not found, exit.
    if (!$found_config)
    {
      echo "Did not find client VPN config for \"$name\".\n";
      exit;
    }
    
    // Print the client config.
    echo "vpnid: {$settings['vpnid']}\n";
    echo "description: {$settings['description']}\n";
    echo "server_addr: {$settings['server_addr']}\n";   
    echo "custom_options: {$settings['custom_options']}\n";
    
    // Resolve the configured host name to an IP address.
    // This is the VPN server's primary IP.
    $ip = gethostbyname($settings['server_addr']);
    echo "VPN server primary IP: $ip\n";
    
    // Tokenize the custom_options to get the VPN 
    // server's secondary IP.
    $tok = strtok($settings['custom_options'], " \n\t;");
    while ($tok !== false) {
      if (strcasecmp($tok, 'remote') == 0)
      {
        $tok = strtok(" \n\t;");
        if ($tok !== false)
        {
          $ip_secondary = gethostbyname($tok);
          echo "VPN server secondary IP: $ip_secondary\n";    
        }
      }
      $tok = strtok(" \n\t;");
    }
    
    // Find the status for the VPN client.
    $found_status = false;
    $clients = openvpn_get_active_clients();
    foreach ($clients as $client) 
    {
      if (stripos($client['name'], $name) !== false)
      {
        $found_status = true;
        break;
      }
    }
    
    // If client status not found, exit.
    if (!$found_config)
    {
      echo "Did not find VPN client status for \"$name\".\n";
      exit;
    }
    
    // Print the client status
    echo "name: {$client['name']}\n";
    echo "mgmt: {$client['mgmt']}\n";
    echo "status: {$client['status']}\n";
    echo "remote_host: {$client['remote_host']}";
    
    // See if the client is connected to a secondary IP.
    // Note: $client['remote_host'] has trailing white space that
    // we must trim before comparing to $ip
    $remote_host = rtrim($client['remote_host']);
    if ($client['status'] == 'up' && $remote_host !== $ip)
    {
      echo "The VPN client is connected to the server's secondary IP.\n";
    }
    else 
    {
      echo "The VPN client is NOT connected to the server's secondary IP.\n";
      echo "NOT restarting VPN.\n";
      exit;
    }
    
    // If we got this far, we will want to restart the VPN client
    // so it connects to primary IP instead of secondary IP.
    
    // Before trying to connect to primary IP, we have to make sure
    // we can ping it. If we can't ping it, there's no sense in
    // trying to connect to it. Furthermore, that is probably
    // the reason why the client is currently connected to the 
    // secondary IP.
    exec("ping -t 10 -o $ip", $output, $return_var);
    echo "ping return_var: $return_var\n";
    
    // A return value of 0 means at least one ping was replied to.
    // In other words, the IP address is up and we should be
    // able to restart to VPN to connect to it.
    if ($return_var == 0)
    {
      echo "Restarting VPN.\n";
      openvpn_restart('client', $settings);
      echo "Finished restarting VPN.\n";
    }
    ?>
    

    As you can see, the script makes use of the handy config variables in config.inc and the handy functions in openvpn.inc. Rather than hard-coding the client vpnid number in my script, I scan all of the client configs for a description containing 'London'; that will be the client that connects to our London server.

    When finished, I will store this script somewhere in the pfSense filesystem (any suggestions as to an appropriate directory to store it in?). Then I will call it periodically with cron, perhaps once per hour.

    HTH



  • Neat! For my purposes, I am actually doing more than just restarting, and I considered using PHP but I was really looking for something that didn't rely on external files. Basically, I want to be able to restore a config backup and not have to restore any random files to get this working again. I don't like that I am hard coding the server number, but I'm okay with it for now.

    I looked into the add-on package that lets you enter PHP code in the web gui (PHP Service) and runs it every second, but it was buggy and didn't work right, so I promptly uninstalled it. Please post your final solution; I'm interested, even if it's just to run the PHP via cron.



  • I haven't tried it yet but I think the Shellcmd package might let you echo some PHP code to a file in /tmp on system startup. Then you should be able to call that script from cron. This should allow you to store the script (somewhat indirectly) inside the exported config file. Again, I haven't tried this yet but it seems like it could do the job.



  • Meh. I tried Shellcmd. The GUI for it only lets you enter one line. Not really convenient for what I wanted to do.



  • Brian,

    I am running the embedded version of pfSense. I just tried storing my custom script in /scripts. Then I rebooted. Low and behold, after rebooting, my script was still in /scripts. I expected that it would have been wiped out by a reboot but I was wrong. That's good. Now I can store my script there. It's not quite the same as storing it in the XML config file but at least I can keep all my scripts in one place.



  • Yeah the embedded version won't delete your files on you. You might have some trouble writing things if you don't mount the filesystem as RW first, but anyway that's not what I was worried about. I am doing something that will be used at multiple locations, and I just don't want to have backup additional files with the config that I would then have to put back in place in the event of a restore.

    From what I could tell there is no way to do multiple lines with echo in the bourne shell, so you would need to install bash (another dependency I don't want). It just happens that cron does have a way to send mult-line input (the % character) so that helped me out there.



  • /scripts already seems to be mounted writeable. I did not have to do a remount-rw. :)



  • Brian,

    I have revised the code a bit. For example, I now write some messages to the system log by calling the logger command. I ran this script once per hour last night using cron. It seems to work fine so far. Still requires more testing. For example, I will have to pull the plug on our primary internet connection to force the VPN client to switchover to the secondary IP (i.e. backup connection). Then I will have to verify that this script forces the connection back to the primary IP.

    Here's the current version of the script:

    #!/usr/local/bin/php -f
    require_once('config.inc');
    include('openvpn.inc');
    
    // Read in all of the OpenVPN client configs.
    global $config;
    if (!is_array($config['openvpn']['openvpn-client']))
    {
      return;
    }
    
    // Find client config for London VPN server
    $found_config = false;
    $name = "London";
    foreach ($config['openvpn']['openvpn-client'] as & $settings)
    {
      if (stripos($settings['description'], $name) !== false)
      {
         $found_config = true;
         break;
      }
    }
    
    // If client config not found, exit.
    if (!$found_config)
    {
      echo "Did not find client VPN config for \"$name\".\n";
      exit;
    }
    
    // Print the client config.
    echo "vpnid: {$settings['vpnid']}\n";
    echo "description: {$settings['description']}\n";
    echo "server_addr: {$settings['server_addr']}\n";   
    echo "custom_options: {$settings['custom_options']}\n";
    
    // Resolve the configured host name to an IP address.
    // This is the VPN server's primary IP.
    $ip = gethostbyname($settings['server_addr']);
    echo "VPN server primary IP: $ip\n";
    
    // Tokenize the custom_options to get the VPN 
    // server's secondary IP.
    $tok = strtok($settings['custom_options'], " \n\t;");
    while ($tok !== false) {
      if (strcasecmp($tok, 'remote') == 0)
      {
        $tok = strtok(" \n\t;");
        if ($tok !== false)
        {
          $ip_secondary = gethostbyname($tok);
          echo "VPN server secondary IP: $ip_secondary\n";    
        }
      }
      $tok = strtok(" \n\t;");
    }
    
    // Find the status for the VPN client.
    $found_status = false;
    $clients = openvpn_get_active_clients();
    foreach ($clients as $client) 
    {
      if (stripos($client['name'], $name) !== false)
      {
        $found_status = true;
        break;
      }
    }
    
    // If client status not found, exit.
    if (!$found_config)
    {
      echo "Did not find VPN client status for \"$name\".\n";
      exit;
    }
    
    // Print the client status
    echo "name: {$client['name']}\n";
    echo "mgmt: {$client['mgmt']}\n";
    echo "status: {$client['status']}\n";
    echo "remote_host: {$client['remote_host']}";
    
    // See if the client is connected to a secondary IP.
    // Note: $client['remote_host'] has trailing white space that
    // we must trim before comparing to $ip
    $remote_host = rtrim($client['remote_host']);
    if ($client['status'] == 'up' && $remote_host == $ip)
    {
      $msg = "The $name VPN client is connected to $ip (VPN server primary IP). Not restarting client.\n";
      echo $msg;
      exec("logger '$msg'");
      exit;
    }
    else if ($client['status'] == 'up')
    {
      $msg = "The $name VPN client is connected to $ip (VPN server secondary IP).\n";
      echo $msg;
      exec("logger '$msg'");
    }
    else
    {
      $msg = "The $name VPN client is not connected. Current status is {$client['status']}.\n";
      echo $msg;
      exec("logger '$msg'");
      exit;
    }
    
    // If we got this far, we will want to restart the VPN client
    // so it connects to primary IP instead of secondary IP.
    
    // Before trying to connect to primary IP, we have to make sure
    // we can ping it. If we can't ping it, there's no sense in
    // trying to connect to it. Furthermore, that is probably
    // the reason why the client is currently connected to the 
    // secondary IP.
    exec("ping -t 10 -o $ip", $output, $return_var);
    echo "ping return_var: $return_var\n";
    
    // A return value of 0 means at least one ping was replied to.
    // In other words, the IP address is up and we should be
    // able to restart to VPN to connect to it.
    if ($return_var == 0)
    {
      $msg = "VPN server primary IP is up. Restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
      openvpn_restart('client', $settings);
      $msg = "Finished restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
    }
    else
    {
      $msg = "VPN server primary IP is down. Not restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
    }
    ?>
    


  • Having just read this thread i something think you're shooting on sparrows with cannons. (Writing scripts, changing php).

    Is it not feasible to have 2 OpenVPN tunnels up with two different tranfer subnets.
    Each VPN tunnel on it's own WAN.

    When both tunnels are up a failover-pool forces all traffic to the fast connection.
    Should it go down, the backup connection will be used.
    If the connection comes back up everything switches to the fast connection again.

    Should asymetric routing occur (for whatever reasons) traffic could even be NATed into the tunnel to ensure the answer comes back the same tunnel.



  • Hi GruensFroeschli,

    Thank you for your comments.

    @GruensFroeschli:

    Is it not feasible to have 2 OpenVPN tunnels up with two different tranfer subnets.
    Each VPN tunnel on it's own WAN.

    We don't have 2 tunnels. We have only 1. We use OpenVPN's standard way of connecting to redundant server IPs. That is, our VPN client is configured with two remote settings in the config file. For example:

    
    remote fast.foo.com 1194
    remote slow.foo.com 1194
    
    

    Note that we do not use the –remote-random setting in our config. Therefore, the client will always try the first remote IP first and the second remote IP next. If the first IP is up, the client connects to that one but if it is down, the client connects to the second IP.

    @GruensFroeschli:

    When both tunnels are up a failover-pool forces all traffic to the fast connection.
    Should it go down, the backup connection will be used.

    Yes, I absolutely agree. Our current configuration already does that.

    @GruensFroeschli:

    If the connection comes back up everything switches to the fast connection again.

    I have never seen an OpenVPN client connection switch back to the first remote IP after connecting to the second remote IP. As far as I know, there is NO mechanism in OpenVPN to keep trying the first IP after connecting to the second IP and then automatically switch back to the first one when it is up. If someone can tell me the name of the OpenVPN setting to enable this behaviour, I'm all ears.

    We have successfully been running our VPN server like this for months. When the primary IP goes down, the clients automatically switch over to the secondary IP after a short timeout period (about 1 minute). All of this happens without any intervention required by me. That's good!

    However, since the server side's secondary internet connection is way slower than the primary internet connection, we don't like to run the VPN on the secondary connection for long periods of time.

    We usually don't realize that the VPN clients have switched over to the secondary IP until a user calls a couple of days later to say that the VPN seems kind of slow. When we get that phone call, we manually restart the VPN client to force a reconnection to the primary IP.

    The purpose of my script is to automatically switch the VPN clients back to the primary IP when it comes back up.

    Note that my script runs only on the client side, not on the server side. Note also that we run our VPN in UDP mode. Perhaps the behaviour of the failover pool is different for TCP mode?

    If someone can suggest a simpler way of automatically switching the OpenVPN client back to the primary IP after connecting to the secondary IP, I'm all ears. (And by suggest, I mean, showing the exact config settings or code to accomplish this).



  • You misunderstand me.
    My suggestion is to not have a single OpenVPN connection with two remote entries,
    but to actually have two OpenVPN connections with only one remote entry.

    Let pfSense handle the switching between the tunnels.
    Or is this OpenVPN client not a pfSense?

    With pfSense you can create failover-pools under "System –> Routing"



  • Yes, do use failover pools. For example, we use failover pools at our main office, the office with the two internet connections (which is also the office where the VPN server is). With the failover pool, users at our main office are automatically switched over the the secondary internet connection when the primary internet connection goes down. Thus, they can keep browsing the web, sending email, etc, even when the primary internet connection goes down.

    Are you suggesting is to user failover pools on the client side (i.e. at our remote offices), which has only one internet connection? If so, how do I make the two tunnels appear as gateways on the Failover group configuration screen? I have never heard of this. If you have a step-by-step configuration for this, I'd like to see it. When I try to add a gateway using the Edit Gateway screen, the only interfaces I see are the actual WAN and LAN interfaces, not any sort of OpenVPN "pseudo" interface.

    Even if it is possible to have the tunnels appear as gateways in the failover group configuration screen, I'm not sure that would work for our requirements. We do NOT want ALL of the client's traffic to go over the VPN. We want inter-office traffic to go over the VPN but we want all other traffic (web browsing, etc) to go over the client's own internet connection. We have only limited bandwidth at the main office and we don't want all of the web browsing traffic from the several remote offices to go over the VPN.

    Again, if you have an actual working example, it would be helpful. Otherwise your comments will not really help me solve the problem, which has already been solved by my script.



  • Well not a step by step for this exact scenario but i'll write something up here:

    (Everything assuming you're running 2.0)

    Server:
    1: Create on the server side two OpenVPN instances and bind one to each WAN.
    If not specified, OpenVPN will bind to all interfaces. You can specify by setting the "–local host" in the "Advanced" configuration field.
    Make sure that you use two different transfer-subnets for these servers. (eg. 172.21.0.1/24 and 172.22.0.1/24)

    2: Assign the created OpenVPN clients as interface (Interface --> assign).
    Enable these newly added OPTs and set the IP configuration to "none".

    3: Go to "System --> Routing" and create two gateways. One for each client-side IP inside the tunnel.
    (If you're using the above subnets, that would be 172.21.0.2 and 172.22.0.2)

    4: Create a failover-pool under the tab "Groups" (main-VPN Tier1, backup-VPN Tier2)

    5: Create on your LAN tab firewall-rules.
    #1: allow, source: LAN-subnet, destination: off_site_subnet, gateway: failoverpool
    #2: allow, source: LAN-subnet, destination: any, gateway: default

    Client:
    1: Create two clients on the pfSense, one pointing to each WAN on your server-side.

    2: Assign the created OpenVPN clients as interface (Interface --> assign).
    Enable these newly added OPTs and set the IP configuration to "none".

    3: Go to "System --> Routing" and create two gateways. One for each server-side IP inside the tunnel.
    (If you're using the above subnets, that would be 172.21.0.1 and 172.22.0.1)

    4: Create a failover-pool under the tab "Groups" (main-VPN Tier1, backup-VPN Tier2)

    5: Create on your LAN tab firewall-rules.
    #1: allow, source: LAN-subnet, destination: main_site_subnet, gateway: failoverpool
    #2: allow, source: LAN-subnet, destination: any, gateway: default.

    With such a setup, the failoverpool is responsible for sending traffic via the correct WAN and no longer the OpenVPN client.

    Addendum:
    I'm not sure if OpenVPN-traffic for WAN2 will actually leave via WAN2.
    If it doesn't, a workaround would be that you bind the two OpenVPN server instances on different ports to the LAN interface and create two portforwards to the LAN interface.
    With NAT involved i know that traffic will leave via the correct WAN.

    Another thing i see which might be problematic.
    Traffic from the pfSense itself also needs to use the failover-pools.
    I never actually did this, but from what i read you can do this via the floating rules.
    Maybe someone else can help you on this.

    Also if one of the WANs goes down and a connection has been established on the second WAN and then the first WAN comes back up, it could be that you have asymetric routing.
    If NAT is not a problem for your setup you could NAT all traffic into the VPN tunnel.
    For the other side this would effectively hide the subnet behind the pfSense and traffic would always flow correctly.
    To configure this go to "Firewall --> NAT --> outbound" and enable manual rule generation.
    Now create a rule for both WANs and the assigned OpenVPN interfaces.



  • Thank you very much for that procedure GruensFroeschli! When I have a few spare hours, I'll try that technique in a lab.

    As for my script, below is a new version with a couple of changes. First, the "VPN client is connected to <ip address="">" message was printing the wrong IP address. Second, I modified the code that does the restart of the openvpn process. Instead of calling the openvpn_restart function in the file /etc/inc/openvpn.inc, I mimicked the restart code in /usr/local/www/status_services.php, which seems to be more reliable. In my testing, the openvpn_restart function sometimes failed to start the new openvpn process after terminating the old process. The main difference between the two seems to be in the way they wait for the old process to terminate.

    More testing is required but it seems to work right now.

    #!/usr/local/bin/php -f
    require_once('config.inc');
    include('openvpn.inc');
    
    // Read in all of the OpenVPN client configs.
    global $config;
    if (!is_array($config['openvpn']['openvpn-client']))
    {
      return;
    }
    
    // Find client config for London VPN server
    $found_config = false;
    $name = "London";
    foreach ($config['openvpn']['openvpn-client'] as & $settings)
    {
      if (stripos($settings['description'], $name) !== false)
      {
         $found_config = true;
         break;
      }
    }
    
    // If client config not found, exit.
    if (!$found_config)
    {
      echo "Did not find client VPN config for \"$name\".\n";
      exit;
    }
    
    // Print the client config.
    echo "vpnid: {$settings['vpnid']}\n";
    echo "description: {$settings['description']}\n";
    echo "server_addr: {$settings['server_addr']}\n";   
    echo "custom_options: {$settings['custom_options']}\n";
    
    // Resolve the configured host name to an IP address.
    // This is the VPN server's primary IP.
    $ip_primary = gethostbyname($settings['server_addr']);
    echo "VPN server primary IP: $ip_primary\n";
    
    // Tokenize the custom_options to get the VPN 
    // server's secondary IP.
    $tok = strtok($settings['custom_options'], " \n\t;");
    while ($tok !== false) {
      if (strcasecmp($tok, 'remote') == 0)
      {
        $tok = strtok(" \n\t;");
        if ($tok !== false)
        {
          $ip_secondary = gethostbyname($tok);
          echo "VPN server secondary IP: $ip_secondary\n";    
        }
      }
      $tok = strtok(" \n\t;");
    }
    
    // Find the status for the VPN client.
    $found_status = false;
    $clients = openvpn_get_active_clients();
    foreach ($clients as $client) 
    {
      if (stripos($client['name'], $name) !== false)
      {
        $found_status = true;
        break;
      }
    }
    
    // If client status not found, exit.
    if (!$found_config)
    {
      echo "Did not find VPN client status for \"$name\".\n";
      exit;
    }
    
    // Print the client status
    echo "name: {$client['name']}\n";
    echo "mgmt: {$client['mgmt']}\n";
    echo "status: {$client['status']}\n";
    echo "remote_host: {$client['remote_host']}";
    
    // See if the client is connected to a secondary IP.
    // Note: $client['remote_host'] has trailing white space that
    // we must trim before comparing to $ip_primary
    $remote_host = rtrim($client['remote_host']);
    if ($client['status'] == 'up' && $remote_host == $ip_primary)
    {
      $msg = "The $name VPN client is connected to $remote_host (VPN server primary IP). Not restarting client.\n";
      echo $msg;
      exec("logger '$msg'");
      exit;
    }
    else if ($client['status'] == 'up')
    {
      $msg = "The $name VPN client is connected to $remote_host (VPN server secondary IP).\n";
      echo $msg;
      exec("logger '$msg'");
    }
    else
    {
      $msg = "The $name VPN client is not connected. Current status is {$client['status']}.\n";
      echo $msg;
      exec("logger '$msg'");
      exit;
    }
    
    // If we got this far, we will want to restart the VPN client
    // so it connects to primary IP instead of secondary IP.
    
    // Before trying to connect to primary IP, we have to make sure
    // we can ping it. If we can't ping it, there's no sense in
    // trying to connect to it. Furthermore, that is probably
    // the reason why the client is currently connected to the 
    // secondary IP.
    exec("ping -t 10 -o $ip_primary", $output, $return_var);
    echo "ping return_var: $return_var\n";
    
    // A return value of 0 means at least one ping was replied to.
    // In other words, the IP address is up and we should be
    // able to restart to VPN to connect to it.
    if ($return_var == 0)
    {
      $msg = "VPN server primary IP is up. Restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
    
      // Restart the VPN client by mimicking the technique used in
      // the file /usr/local/www/status_services.php
      //openvpn_restart('client', $settings);
      $configfile = "{$g['varetc_path']}/openvpn/client{$settings['vpnid']}.conf";
      $pidfile =    "{$g['varrun_path']}/openvpn_client{$settings['vpnid']}.pid";
      if (file_exists($configfile)) {
        $msg = "Terminating openvpn using pidfile: $pidfile\n";
        echo $msg;
        exec("logger '$msg'");
        killbypid($pidfile);
        // wait for process to terminate
        sleep(1);
        $msg = "Starting new openvpn process using config file: $configfile\n";
        echo $msg;
        exec("logger '$msg'");
        mwexec_bg("/usr/local/sbin/openvpn --config {$configfile}");
      }
    
      $msg = "Finished restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
    }
    else
    {
      $msg = "VPN server primary IP is down. Not restarting $name VPN client.\n";
      echo $msg;
      exec("logger '$msg'");
    }
    ?>
    ```</ip>


  • Hi cyboc,

    Very nice work right here. I have try your solution today (we have similar dual WAN config) and it is very simple indeed! Contrary to what GruensFroeschli said, I thinks his solution is the one that is "shooting on sparrows with cannons." Heck, his solution hasn't even been tried yet!

    One suggestion for your code however. There is problem with these two lines:

    $ip_primary = gethostbyname($settings['server_addr']);
    // ...
    if ($client['status'] == 'up' && $remote_host == $ip_primary)
    

    You assuming that PHP function gethostbyname will work correctly every time. However, I had temporary problem with my ISP DNS server today. It was down. So when I try to do the lookup with gethostbyname it fails (timeout). So instead of returning IP address, it returns the hostname I was looking up. So, expression $remote_host == $ip_primary will return false eventhough hostname should resolve to that $remote_host IP if lookup was working. So, VPN as a result will restart when it shouldn't. See it?

    To explain with example, let's say foo.com should resolve to 192.168.1.1 and $remote_host is currently 192.168.1.1 and that is the primary IP. If lookup works, $remote_host == $ip_primary will return true and VPN will not restart (because 192.168.1.1 equals 192.168.1.1). This is what we want if connection currently on primary IP (is good). But if lookup fails, expression will return false and VPN will restart when it should not (192.168.1.1 is NOT equal to foo.com). Get it?

    So maybe if lookup fails, you should just abort? Just suggestion here.



  • @Philay:

    You assuming that PHP function gethostbyname will work correctly every time. However, I had temporary problem with my ISP DNS server today. It was down. So when I try to do the lookup with gethostbyname it fails (timeout). So instead of returning IP address, it returns the hostname I was looking up.

    Philay, good catch. You are right. The documentation for gethostbyname does say that it "Returns the IPv4 address or a string containing the unmodified hostname on failure."

    Well when I started this thread, I did say that I'm not a PHP guy. :)

    I will fix this. Thanks!

    BTW, I think you are too hard on GruensFroeschli. I think his solution is fine and will probably work. I just don't have time to try it right now.



  • Philay,

    Here is another new version. As you suggested, I have added a check for DNS lookup errors. I have also turned the client name (full or partial match) into a parameter instead of hard-coding it (usage: restart-vpn-client.php <full or="" partial="" vpn="" client="" name="">). I also made some minor changes to the output.

    #!/usr/local/bin/php -f
    require_once('config.inc');
    include('openvpn.inc');
    
    if ($argc != 2)
    {
      echo "Usage: " . basename($argv[0]) . " <full or="" partial="" vpn="" client="" name="">\n";
      exit;
    }
    $name = $argv[1];
    
    $msg = "Checking whether the $name VPN client needs a restart.";
    echo $msg."\n";
    exec("logger '$msg'");
    
    // Read in all of the OpenVPN client configs.
    global $config;
    if (!is_array($config['openvpn']['openvpn-client']))
    {
      return;
    }
    
    // Find client config for given name
    $found_config = false;
    foreach ($config['openvpn']['openvpn-client'] as & $settings)
    {
      if (stripos($settings['description'], $name) !== false)
      {
         $found_config = true;
         break;
      }
    }
    
    // If client config not found, exit.
    if (!$found_config)
    {
      echo "Did not find client VPN config for \"$name\".\n";
      exit;
    }
    
    // Print the client config.
    echo "\n----Client configuration----\n";
    echo "vpnid: {$settings['vpnid']}\n";
    echo "description: {$settings['description']}\n";
    echo "server_addr: {$settings['server_addr']}\n";   
    echo "custom_options: {$settings['custom_options']}\n\n";
    
    // Resolve the configured host name to an IP address.
    // This is the VPN server's primary IP.
    if (is_ipaddr($settings['server_addr']))
    {
      $ip_primary = $settings['server_addr'];
    }
    else
    {
      $ip_primary = gethostbyname($settings['server_addr']);
      if ($ip_primary == $settings['server_addr'])
      {
        echo "DNS lookup failed for VPN server primary hostname ({$settings['server_addr']}). Exiting.\n";
        exit;
      }
    }
    echo "VPN server primary IP: $ip_primary\n";
    
    // Tokenize the custom_options to get the VPN 
    // server's secondary IP.
    $tok = strtok($settings['custom_options'], " \n\t;");
    while ($tok !== false) 
    {
      if (strcasecmp($tok, 'remote') == 0)
      {
        $tok = strtok(" \n\t;");
        if ($tok !== false)
        {
          if (is_ipaddr($tok))
          {
            $ip_secondary = $tok;
          }
          else
          {
            $ip_secondary = gethostbyname($tok);
            if ($ip_secondary == $tok)
            {
              echo "DNS lookup failed for VPN server secondary hostname ($tok). Exiting.\n";
              exit;
            }
          }
          echo "VPN server secondary IP: $ip_secondary\n";    
        }
      }
      $tok = strtok(" \n\t;");
    }
    
    // Find the status for the VPN client.
    $found_status = false;
    $clients = openvpn_get_active_clients();
    foreach ($clients as $client) 
    {
      if (stripos($client['name'], $name) !== false)
      {
        $found_status = true;
        break;
      }
    }
    
    // If client status not found, exit.
    if (!$found_config)
    {
      echo "Did not find VPN client status for \"$name\".\n";
      exit;
    }
    
    // Print the client status
    echo "\n----Client status----\n";
    echo "name: {$client['name']}\n";
    echo "mgmt: {$client['mgmt']}\n";
    echo "status: {$client['status']}\n";
    echo "remote_host: {$client['remote_host']}\n";
    
    // See if the client is connected to a secondary IP.
    // Note: $client['remote_host'] has trailing white space that
    // we must trim before comparing to $ip_primary
    $remote_host = rtrim($client['remote_host']);
    if ($client['status'] == 'up' && $remote_host == $ip_primary)
    {
      $msg = "The $name VPN client is connected to $remote_host (VPN server primary IP). Not restarting client.";
      echo $msg."\n";
      exec("logger '$msg'");
      exit;
    }
    else if ($client['status'] == 'up')
    {
      $msg = "The $name VPN client is connected to $remote_host (VPN server secondary IP).";
      echo $msg."\n";
      exec("logger '$msg'");
    }
    else
    {
      $msg = "The $name VPN client is not connected. Current status is {$client['status']}.";
      echo $msg."\n";
      exec("logger '$msg'");
      exit;
    }
    
    // If we got this far, we will want to restart the VPN client
    // so it connects to primary IP instead of secondary IP.
    
    // Before trying to connect to primary IP, we have to make sure
    // we can ping it. If we can't ping it, there's no sense in
    // trying to connect to it. Furthermore, that is probably
    // the reason why the client is currently connected to the 
    // secondary IP.
    exec("ping -t 10 -o $ip_primary", $output, $return_var);
    echo "ping return_var: $return_var\n";
    
    // A return value of 0 means at least one ping was replied to.
    // In other words, the IP address is up and we should be
    // able to restart to VPN to connect to it.
    if ($return_var == 0)
    {
      $msg = "VPN server primary IP is up. Restarting $name VPN client.";
      echo $msg."\n";
      exec("logger '$msg'");
    
      // Restart the VPN client by mimicking the technique used in
      // the file /usr/local/www/status_services.php
      //openvpn_restart('client', $settings);
      $configfile = "{$g['varetc_path']}/openvpn/client{$settings['vpnid']}.conf";
      $pidfile =    "{$g['varrun_path']}/openvpn_client{$settings['vpnid']}.pid";
      if (file_exists($configfile)) 
      {
        $msg = "Terminating openvpn using pidfile: $pidfile";
        echo $msg."\n";
        exec("logger '$msg'");
        killbypid($pidfile);
        // wait for process to terminate
        sleep(1);
        $msg = "Starting new openvpn process using config file: $configfile";
        echo $msg."\n";
        exec("logger '$msg'");
        mwexec_bg("/usr/local/sbin/openvpn --config {$configfile}");
      }
    
      $msg = "Finished restarting $name VPN client.";
      echo $msg."\n";
      exec("logger '$msg'");
    }
    else
    {
      $msg = "VPN server primary IP is down. Not restarting $name VPN client.";
      echo $msg."\n";
      exec("logger '$msg'");
    }
    ?></full>
    ```</full>


  • Okay, seems is working a little better now with DNS. Hoping for more improvements soon.


Log in to reply