Specify network interface to use

Hi!
A suggestion for a feature: the ability to specify which network interface(s) to use for backup.

Use case: I want to allow the backup to run when I am connected to a wired (ethernet) network, but don’t want it to run when just connected to a wireless (WiFi) network, or tethered to my phone. As a bonus, you could allow specifying which WiFi networks to use/not use. This may be useful for people who have limits on certain types of their network connections or vast speed differences between connections.

(I have previously tested Arq (https://www.arqbackup.com/) which allows for this.)

If this feature already exists and I can’t find it, just let me know!

Thanks!
Q

1 Like

There is no such thing right now, but another request for it here:

This is not natively supported, but as a workaround you can use the --run-script-before-required option to do one or more checks before the backup operation is started. To get started, see the script in this thread:

Scripts like this will be more usable when Duplicati accepts more than 2 error levels (0=OK, 1=Fail backup), which seems to be planned for the near future:

1 Like

Thanks for the quick replies and options. I’ll try to mess around with writing a simple script for macOS (which is what I’m on). If I come up with something that works I’ll post it for others too.

Since it sound like people might be digging around in that part of the code, it might also be nice to support exclusion of connections flagged as metered.

I don’t know about Linux / MacOS but I know it can be done in Windows 10 as some people flag all their connections as metered to avoid getting forced updates. :scream:

OK, first draft of script to do what I wanted on MacOS.
(Note: I’m definitely not even close to a professional programmer, so I’m happy for any suggestions to make this better, fix bugs, etc.)
I’m posting here in case it’s helpful to anyone else.

#!/bin/bash

# Duplicati script to only allow over certain network interfaces / wifi networks.
# Use the command line option --run-script-before-required with the script.
# If this script exists with errorlevel 0 then Duplicati runs.  If it exits with errorlevel 1 it doesn't run.

# INSTRUCTIONS: (2 steps)
#
# 1.  Inside the parenthesis here put (in quotes) the list of all network interfaces that you want to ALLOW Duplicati to run over.)
# To get the list of network interface names, in the terminal run the command:
#        networksetup -listnetworkserviceorder
# The string next to the numbers in parenthesis is what you want.
#  E.g.:  (4) Wi-Fi   ---->  The network interface name is wifi
#
# Note:  To allow all interfaces leave the array empty.

allowed_services=(  )
# Example:  allowed_services=("Wi-Fi" "Ethernet")

# 2.  Inside parenthesis here put (in quotes) the names of WiFi networks you want to allow.
# (Technical note:  Assuming that the Wi-Fi interface is called "Wi-Fi")
#
# Note:  To allow all Wi-Fi networks leave the array empty.

allowed_wifi=( )
# Example:  allowed_wifi=("Home_Network" "School WiFi")


#  That's it.  You should be good to go.

#-----------------------------------------------------------------------

# Helper functions here

# Helper function gets the current network service that is active.
# Taken then edited from here:
# https://apple.stackexchange.com/questions/191879/how-to-find-the-currently-connected-network-service-from-the-command-line
get_current_service_name () {

  local services=$(networksetup -listnetworkserviceorder | grep 'Hardware Port')

  local found_it=0
  # Need to do this because we only want to return the FIRST interface that's active.  Otherwise it may return the wrong thing.  E.g., you're connected to wifi and ethernet.  Ethernet is listed first in the order of services, so is what network traffic is actually using.  Without this found_it variable this function would actually return WiFi.
  while read line && [ $found_it -eq 0 ]; do
      local sname=$(echo $line | awk -F  "(, )|(: )|[)]" '{print $2}')
      local sdev=$(echo $line | awk -F  "(, )|(: )|[)]" '{print $4}')
      #echo "Current service: $sname, $sdev, $currentservice"

      if [ -n "$sdev" ]; then
          local ifout="$(ifconfig $sdev 2>/dev/null)"
          echo "$ifout" | grep 'status: active' > /dev/null 2>&1
          local rc="$?"
          if [ "$rc" -eq 0 ]; then
              local currentservice="$sname"
              local currentdevice="$sdev"
              # currentmac=$(echo "$ifout" | awk '/ether/{print $2}')
              found_it=1
          fi
      fi
  done <<< "$(echo "$services")"

  if [ -n "$currentservice" ]; then
      echo $currentservice
  else
      >&2 echo "Could not find current service"
      exit 1
  fi
}

# Helper function gets the current Wi-Fi network name.
get_wifi_name () {
  # Credit to:  https://stackoverflow.com/questions/4481005/get-wireless-ssid-through-shell-script-on-mac-os-x
  # To get wifi name:
  local current_wifi=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | awk '/ SSID/ {print substr($0, index($0, $2))}')
  if [ -n "$current_wifi" ]; then
      echo $current_wifi
  fi
}

# Function to check if an array contains a specific value
# Credit to:  https://stackoverflow.com/questions/3685970/check-if-a-bash-array-contains-a-value
containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

#  End helper functions
#-----------------------------------------------------------------------

#
#  Actual program
#

# We start off with the exit code being 1.  Then modify it below if we succeed.
exit_code=1

# If allowed_services array is empty, then we allow anything, so can just exit with level 0.
if [ ${#allowed_services[@]} -eq 0 ]; then
  exit_code=0
else
  # Get the current network interface then see if it's in the allowed list.
  current_interface=$(get_current_service_name)
  if (containsElement "$current_interface" "${allowed_services[@]}"); then
    # We know the interface is allowed.  Now if it's WiFi check if the WiFi network is allowed
    if [ "$current_interface" == "Wi-Fi" ] && [ ${#allowed_wifi[@]} -ne 0 ]; then
      current_wifi_network=$(get_wifi_name)
      if (containsElement "$current_wifi_network" "${allowed_wifi[@]}"); then
        exit_code=0
      else
        exit_code=1
      fi
    else
      # It's not Wi-Fi so we can exit with success
      exit_code=0
    fi
  fi
fi

exit $exit_code
3 Likes

I did I quick test with a backup job (on Macbook Air) and it seems to be working to filter a specific Wi-Fi. Just a note, I had to specify “Wi-Fi” interface (1) and also its SSID (2). Initially I thought that I didn’t need to specify the interface because Macbook Air doesn’t have ethernet, but after failing to work, I investigated the result of “networksetup -listnetworkserviceorder” command and saw that it actually resulted in 3 items (Wi-Fi, Bluetooth and Thunderbolt Bridge). So, leaving “allow_services=()” empty in my case would always exit with 0 code, because of those additional interfaces.

I’ll be using this script to filter my backup set to a LAN computer only when I’m connected to a home Wi-Fi. Thanks @Quadari !

1 Like

Thanks so much for this baseline script. I’m also not a scripter/programmer in any sense but I’ve taken your script and translated it into a script for PowerShell on Windows. (Specifically Windows 10 PowerShell). It’s important to note that the default security Policy does NOT allow scripts to be run, so you have to either set the security policy to allow scripts OR run a .bat file that calls the script. I’ve posted the .bat file script that I use below.

Note also that Windows 10 supports identifying certain networks as “metered” connections, so at the end of the script I’ve also included a code snipped to test for a metered connection.

# Original Script by Quadari on duplicati.com
# Modifications made 2018-04-20 to run on PowerShell.
# Duplicati script to only allow over certain network interfaces / wifi networks.
# Use the command line option --run-script-before-required with the script.
# If this script exists with errorlevel 0 then Duplicati runs.  If it exits with errorlevel 1 it doesn't run.

# INSTRUCTIONS: (2 steps)
#
# 1.  Inside the parenthesis here put (in quotes) the list of all network interfaces that you want to ALLOW Duplicati to run over.)
# To get the list of network interface names, in PowerShell run the command:
#        get-netadapter -physical
# (note you can find which networks are UP by using  get-netadapter -physical | where status -eq 'up'
#
# Note:  To allow all interfaces leave the array empty.
$allowed_services=@("Wi-Fi","Ethernet")
# Example:  allowed_services=("Wi-Fi","Ethernet")


# 2.  Inside parenthesis here put (in quotes) the names of WiFi networks you want to allow.
# (Technical note:  Assuming that the Wi-Fi interface is called "Wi-Fi")
#
# Note:  To allow all Wi-Fi networks leave the array empty.
$allowed_wifi=@("WifiName1","otherWiFi")
# Example:  allowed_wifi=("Home_Network","School WiFi")


#  That's it.  You should be good to go.

#-----------------------------------------------------------------------

# Helper functions here



# Function to check if an array contains a specific value
function containsElement ($my_alias, $my_array) {
#echo $my_alias
foreach ($a in $my_array) {
#echo $a 
	if ($a -eq $my_alias) { 
		
		return $true	
		}
} 
  return $false
}

#  End helper functions
#-----------------------------------------------------------------------

#
#  Actual program
#

# We start off with the exit code being 1.  Then modify it below if we succeed.
$exit_code=1


# If allowed_services array is empty, then we allow anything, so can just exit with level 0.
if($allowed_services -eq $null ) {
  $exit_code=0
}
else 
{
	# Get the current network interface then see if it's in the allowed list.
	$iface_alias = (get-netconnectionProfile).InterfaceAlias
	$my_test = (containsElement $iface_alias $allowed_services)
	if ($my_test) 
	{
		# We know the interface is allowed.  Now if it's WiFi check if the WiFi network is allowed
		# and check to see if the allowed wifi list is not empty.
		if (($iface_alias -eq "Wi-Fi") -and ($allowed_wifi -ne $null)) 
		{
			$iface_name =(get-netconnectionProfile).Name
			$my_test = (containsElement $iface_name $allowed_wifi)
			if ($my_test) 
			{
				$exit_code=0 
			}
			else 
			{
				$exit_code=1 
			}
		}
		else 
		{
		  # It's not Wi-Fi so we can exit with success
		  $exit_code=0 
		}
		
	}
}

#Additional Section to test for Metered Connection
#If all you care about is metered connection, consider using this only.
# from https://blogs.technet.microsoft.com/heyscriptingguy/2013/08/02/more-messing-around-with-wireless-settings-with-powershell/

[void][Windows.Networking.Connectivity.NetworkInformation,Windows,ContentType=WindowsRuntime]
$connectionProfile = [Windows.Networking.Connectivity.NetworkInformation]::GetInternetConnectionProfile()
$connectionCost = $connectionProfile.GetConnectionCost()
$networkCostType = $connectionCost.NetworkCostType

if ($networkCostType -ne "Unrestricted")
{
$exit_code=1
}

exit $exit_code

And here’s the .bat file script that I used

@echo off
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& 'C:\Users\yourname\PathToFile\NetworkCheckerScript.ps1'";
exit %errorlevel%

Good luck everyone!

1 Like