route add no longer works when I connected to VPN via cisco anyconnect client
Turns out it was Cisco AnyConnect client that is monitoring routing table.
The C++ function CHostConfigMgr::StartInterfaceAndRouteMonitoring()
was doing the job. You might either modify the function to make it return immediately (and fix the checksum verification in vpnagentd) or try this solution with a new function name _ZN14CHostConfigMgr32StartInterfaceAndRouteMonitoringEv
Intro and other options
It's been like 5.5 years, so I'm mostly leaving this answer for people, trying to solve same problems with modern Cisco Anyconnect 4.x. In my case Anyconnect was wrapping traffic to local Kubernetes cluster brought up by Minikube on macOS.
Methods like _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv
or __ZN25CInterfaceRouteMonitorMac20routeCallbackHandlerEv
described in:
- https://superuser.com/a/1174704/465915
- https://superuser.com/a/870752/465915
Does not seem to work any longer. Also, a lot of guides are related to fixing OS X firewall represented in previous versions by ipfw
utility like here, so they are irrelevant.
Patching vpnagentd
In 2019 we're still battling both problems for companies avoid split routing and bring up unreasonable firewall rules. Here the fix.
You need to patch calling of method
CHostConfigMgr::StartInterfaceAndRouteMonitoring()
in vpnagentd
binary, which in my version located at 0x09cbf6
. It's a simple jmp qword
command, which is never being returned to this place, so single nop
can be enough. However, it could be worth completely wiping the command with 6 nop
s.
Here the Python script which can automate this procedure for you, however any disassembly utility can help you there. In my original research and hack I used radare2
which is quite handy for people who do not perform such actions on a daily basis:
#!/usr/bin/python3
MAGIC_OFFSET = "0x09cbf6"
MAGIC_BYTE = 144
def eff_anyconnect(file):
print("Opening {} to patch it".format(file))
with open(file, "rb+") as f:
print("Going to {}".format(MAGIC_OFFSET))
print("Current command to call method for watching our routing table")
f.seek(int(MAGIC_OFFSET, 16))
print(hex(int.from_bytes(f.read(6), "big")))
f.seek(int(MAGIC_OFFSET, 16))
f.write(bytes([MAGIC_BYTE]))
print("NOP any longer:")
f.seek(int(MAGIC_OFFSET, 16))
print(hex(int.from_bytes(f.read(6), "big")))
eff_anyconnect("/your/path/to/cisco/bin/vpnagentd")
Next step, after you patch the binary, you should kill current vpnagent
process and reconnect to your VPN. You might find that desired routes are still affected, but the hack above will unlock the routing table, so you can override routes.
Routes
I would suggest adding -static
ones, those are not interfered by AnyConnect at all, while non-static still being duplicated by tunneled ones. I have no good solution here, for my case single static route was enough:
sudo route -n delete $(minikube ip)
sudo route -n add $(minikube ip) -interface bridge100 -static
Firewall
Final, firewall step. That's pretty straightforward, you need to check what are the rules denying or allowing only AnyConnect tagged ones. In my case, everything what is not tagged was blocked, so I created a file with following entries:
nat on utun1 proto {tcp, udp, icmp} from 192.168.64.0/24 to any -> utun1
pass in log on bridge0 inet all flags S/SA keep state tag cisco_anyconnect_vpn_pass
pass in log on bridge100 inet all flags S/SA keep state tag cisco_anyconnect_vpn_pass
Mind the following:
utun1
is your AnyConnect interfacebridge0
andbridge100
are your Minikube/Docker bridges. For some reason AnyConnect renames bridges.192.168.64.0/24
is your Minikube subnet.
Then run:
sudo pfctl -e enable packet-filtering
sudo pfctl -f <your_file_with_rules> -v
From now on, until next reconnect you should be good in terms of routes and firewall.