Apple removed high-level PPTP support in macOS Sierra from its network configuration system. However, the PPP internals are all still there, including /usr/sbin/pppd
and /etc/ppp/
.
How can I programmatically initiate a PPTP VPN connection on macOS Sierra / High Sierra using what's left?
This method creates a PPTP connection that doesn't send all traffic and doesn't override other DNS providers, meaning it works with multiple simultaneous VPN connections each having different DNS search domains, and closes it in an orderly fashion.
Not sending all traffic requires you to know the VPN subnet beforehand. If you don't, you must send all traffic (see below), since vanilla PPP/LCP has no means to tell the client its subnet (although theoretically the ip-up
and ip-down
scripts could guess it from the received IP address).
Save this perl as /usr/local/bin/pptp
:
#!/usr/bin/env perl
if (@ARGV) {
my $name = $ARGV[0];
if (length $name && -e "/etc/ppp/peers/$name") {
my $pid;
$SIG{"INT"} = "IGNORE";
die "fork: $!" unless defined ($pid = fork);
if ($pid) { # parent
$SIG{"INT"} = sub {
kill HUP => $pid;
};
wait;
exit;
} else { #child
$SIG{"INT"} = "DEFAULT";
exec "pppd", "call", $name;
exit;
}
} else {
print "Error: PPTP name: $name\n";
}
} else {
opendir my $d, "/etc/ppp/peers" or die "Cannot read /etc/ppp/peers";
while (readdir $d) {
print "$_\n" if !($_ eq "." || $_ eq "..");
}
closedir $d;
}
Run it as sudo pptp AcmeOffice
, where AcmeOffice
is the PPP connection name, and close it with a single Control-C/SIGINT.
In /etc/ppp/peers
, create the PPP connection file, in this example /etc/ppp/peers/AcmeOffice
:
plugin /System/Library/SystemConfiguration/PPPController.bundle/Contents/PlugIns/PPPDialogs.ppp
plugin PPTP.ppp
noauth
# debug
redialcount 1
redialtimer 5
idle 1800
#mru 1320
mtu 1320
receive-all
novj 0:0
ipcp-accept-local
ipcp-accept-remote
refuse-pap
refuse-chap
#refuse-chap-md5
refuse-eap
hide-password
#noaskpassword
#mppe-stateless
mppe-128
mppe-stateful
require-mppe
passive
looplocal
nodetach
# defaultroute
#replacedefaultroute
# ms-dns 8.8.8.8
# usepeerdns
noipdefault
# logfile /tmp/ppp.AcmeOffice.log
ipparam AcmeOffice
remoteaddress office.acme.com
user misteracme
password acme1234
The last 4 options are connection-specific. Note the password is stored cleartext. chown root:wheel
and chmod 600
is recommended. nodetach
, ipcp-accept-local
, ipcp-accept-remote
, noipdefault
are critical.
Since we're not becoming/replacing the default route, you must manually change your routing table. Add an AcmeOffice
entry to the /etc/ppp/ip-up
script:
#!/bin/sh
#params: interface-name tty-device speed local-IP-address remote-IP-address ipparam
PATH=$PATH:/sbin:/usr/sbin
case "$6" in
AcmeOffice)
route -n add -net 192.168.1.0/24 -interface "$1"
;;
AcmeLab)
route -n add -net 192.168.2.0/24 -interface "$1"
;;
AcmeOffshore)
route -n add -net 192.168.3.0/24 -interface "$1"
;;
VPNBook)
;;
*)
;;
esac
and your /etc/ppp/ip-down
script:
#!/bin/sh
#params: interface-name tty-device speed local-IP-address remote-IP-address ipparam
PATH=$PATH:/sbin:/usr/sbin
case "$6" in
AcmeOffice)
route -n delete -net 192.168.1.0/24 -interface "$1"
;;
AcmeLab)
route -n delete -net 192.168.2.0/24 -interface "$1"
;;
AcmeOffshore)
route -n delete -net 192.168.3.0/24 -interface "$1"
;;
VPNBook)
;;
*)
;;
esac
If the VPN has a DNS search domain (i.e. somehost.office.acme.com
), create a file in /etc/resolver/
named after the DNS suffix, like /etc/resolver/office.acme.com
, with contents like:
nameserver 192.168.1.1
domain office.acme.com
Note that this requires knowing the destination domain & nameserver beforehand. Theoretically ip-up
& ip-down
could create & delete this file on demand.
To send all traffic (& if you don't know the destination subnet), uncomment #defaultroute
in the PPP connection file and leave the ip-up
& ip-down
entries blank (e.g. the VPNBook
example). To override your DNS with the VPN's, uncomment usepeerdns
.