Skip to content

Commit ac65b6a

Browse files
committed
KVM: add configurable MAC/IP script hook for static ARP/NDP and routes
Introduces a new agent.properties option `vm.network.macip.script` (absolute path, disabled by default) that BridgeVifDriver invokes on every NIC plug (VM start) and unplug (VM stop). This is very useful in EVPN+VXLAN environments as it can reduce BUM traffic. By setting static ARP/NDP entries bridges can be configured using 'neigh_suppress on' as the ARP/NDP entries are already set statically by CloudStack.
1 parent f49ab6b commit ac65b6a

3 files changed

Lines changed: 154 additions & 0 deletions

File tree

agent/src/main/java/com/cloud/agent/properties/AgentProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,20 @@ public Property<Integer> getWorkers() {
914914
* */
915915
public static final Property<Integer> INCREMENTAL_SNAPSHOT_RETRY_REBASE_WAIT = new Property<>("incremental.snapshot.retry.rebase.wait", 60);
916916

917+
/**
918+
* Absolute path to a script that is executed on VM NIC plug (VM start) and unplug (VM stop)
919+
* to manage static ARP/NDP entries and host routes for VM interfaces.<br>
920+
* The script is invoked with:<br>
921+
* &nbsp;&nbsp;add: <code>-o add -b &lt;bridge&gt; -m &lt;mac&gt; [-4 &lt;ipv4&gt;] [-6 &lt;ipv6&gt;]</code><br>
922+
* &nbsp;&nbsp;delete: <code>-o delete -b &lt;bridge&gt; -m &lt;mac&gt;</code><br>
923+
* A bundled reference implementation is available at
924+
* <code>scripts/vm/network/vnet/modifymacip.sh</code>.<br>
925+
* Leave empty or unset to disable this feature.<br>
926+
* Data type: String.<br>
927+
* Default value: <code>null</code>
928+
*/
929+
public static final Property<String> VM_NETWORK_MACIP_SCRIPT = new Property<>("vm.network.macip.script", null, String.class);
930+
917931

918932
public static class Property <T>{
919933
private String name;

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class BridgeVifDriver extends VifDriverBase {
4848
private final Object _vnetBridgeMonitor = new Object();
4949
private String _modifyVlanPath;
5050
private String _modifyVxlanPath;
51+
private String _macIpScriptPath;
5152
private String _controlCidr = NetUtils.getLinkLocalCIDR();
5253
private Long libvirtVersion;
5354

@@ -81,6 +82,12 @@ public void configure(Map<String, Object> params) throws ConfigurationException
8182
throw new ConfigurationException("Unable to find modifyvxlan.sh");
8283
}
8384

85+
String macIpScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_NETWORK_MACIP_SCRIPT);
86+
if (macIpScript != null && !macIpScript.isEmpty()) {
87+
_macIpScriptPath = macIpScript;
88+
logger.info("VM network MAC/IP script configured: {}", _macIpScriptPath);
89+
}
90+
8491
libvirtVersion = (Long) params.get("libvirtVersion");
8592
if (libvirtVersion == null) {
8693
libvirtVersion = 0L;
@@ -276,11 +283,14 @@ public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicA
276283
intf.setPxeDisable(true);
277284
}
278285

286+
executeMacIpScript("add", intf.getBrName(), nic.getMac(), nic.getIp(), nic.getIp6Address());
287+
279288
return intf;
280289
}
281290

282291
@Override
283292
public void unplug(LibvirtVMDef.InterfaceDef iface, boolean deleteBr) {
293+
executeMacIpScript("delete", iface.getBrName(), iface.getMacAddress(), null, null);
284294
deleteVnetBr(iface.getBrName(), deleteBr);
285295
}
286296

@@ -400,6 +410,26 @@ private void deleteVnetBr(String brName, boolean deleteBr) {
400410
}
401411
}
402412

413+
private void executeMacIpScript(String op, String brName, String mac, String ipv4, String ipv6) {
414+
if (_macIpScriptPath == null || mac == null || brName == null) {
415+
return;
416+
}
417+
final Script command = new Script(_macIpScriptPath, _timeout, logger);
418+
command.add("-o", op);
419+
command.add("-b", brName);
420+
command.add("-m", mac);
421+
if (ipv4 != null && !ipv4.isEmpty()) {
422+
command.add("-4", ipv4);
423+
}
424+
if (ipv6 != null && !ipv6.isEmpty()) {
425+
command.add("-6", ipv6);
426+
}
427+
final String result = command.execute();
428+
if (result != null) {
429+
logger.warn("MAC/IP script returned error for {} on {}: {}", op, mac, result);
430+
}
431+
}
432+
403433
private void deleteExistingLinkLocalRouteTable(String linkLocalBr) {
404434
Script command = new Script("/bin/bash", _timeout);
405435
command.add("-c");
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env bash
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
19+
# modifymacip.sh -- Manage static ARP/NDP entries and host routes for VM NICs
20+
#
21+
# Usage:
22+
# add: modifymacip.sh -o add -b <bridge> -m <mac> [-4 <ipv4>] [-6 <ipv6>]
23+
# delete: modifymacip.sh -o delete -b <bridge> -m <mac>
24+
#
25+
# On add the script persists the NIC's address info so that delete can
26+
# recover the IPs without them being passed explicitly.
27+
28+
STATE_DIR=/var/run/cloud/macip
29+
30+
usage() {
31+
echo "Usage: $0 -o <add|delete> -b <bridge> -m <mac> [-4 <ipv4>] [-6 <ipv6>]"
32+
}
33+
34+
OP=
35+
BRIDGE=
36+
MAC=
37+
IPV4=
38+
IPV6=
39+
40+
while getopts 'o:b:m:4:6:' OPTION; do
41+
case $OPTION in
42+
o) OP="$OPTARG" ;;
43+
b) BRIDGE="$OPTARG" ;;
44+
m) MAC="$OPTARG" ;;
45+
4) IPV4="$OPTARG" ;;
46+
6) IPV6="$OPTARG" ;;
47+
?) usage; exit 2 ;;
48+
esac
49+
done
50+
51+
if [[ -z "$OP" || -z "$BRIDGE" || -z "$MAC" ]]; then
52+
usage
53+
exit 2
54+
fi
55+
56+
# Normalise MAC address for use as a filename (replace colons with underscores)
57+
MAC_NORM="${MAC//:/_}"
58+
STATE_FILE="${STATE_DIR}/${MAC_NORM}.conf"
59+
60+
add_entries() {
61+
mkdir -p "${STATE_DIR}"
62+
63+
# Persist state so that delete can recover IPs without them being passed in
64+
printf 'IPV4=%s\nIPV6=%s\nBRIDGE=%s\n' "${IPV4}" "${IPV6}" "${BRIDGE}" > "${STATE_FILE}"
65+
66+
if [[ -n "$IPV4" ]]; then
67+
ip neigh replace "${IPV4}" lladdr "${MAC}" dev "${BRIDGE}" nud permanent
68+
ip route replace "${IPV4}/32" dev "${BRIDGE}"
69+
fi
70+
71+
if [[ -n "$IPV6" ]]; then
72+
ip -6 neigh replace "${IPV6}" lladdr "${MAC}" dev "${BRIDGE}" nud permanent
73+
ip -6 route replace "${IPV6}/128" dev "${BRIDGE}"
74+
fi
75+
}
76+
77+
delete_entries() {
78+
local del_ipv4="${IPV4}"
79+
local del_ipv6="${IPV6}"
80+
local del_bridge="${BRIDGE}"
81+
82+
# Recover IPs and bridge from the state file written at add time
83+
if [[ -f "${STATE_FILE}" ]]; then
84+
while IFS='=' read -r key value; do
85+
case "$key" in
86+
IPV4) del_ipv4="${del_ipv4:-$value}" ;;
87+
IPV6) del_ipv6="${del_ipv6:-$value}" ;;
88+
BRIDGE) del_bridge="${del_bridge:-$value}" ;;
89+
esac
90+
done < "${STATE_FILE}"
91+
fi
92+
93+
if [[ -n "$del_ipv4" ]]; then
94+
ip neigh del "${del_ipv4}" dev "${del_bridge}" 2>/dev/null || true
95+
ip route del "${del_ipv4}/32" dev "${del_bridge}" 2>/dev/null || true
96+
fi
97+
98+
if [[ -n "$del_ipv6" ]]; then
99+
ip -6 neigh del "${del_ipv6}" dev "${del_bridge}" 2>/dev/null || true
100+
ip -6 route del "${del_ipv6}/128" dev "${del_bridge}" 2>/dev/null || true
101+
fi
102+
103+
rm -f "${STATE_FILE}"
104+
}
105+
106+
case "$OP" in
107+
add) add_entries ;;
108+
delete) delete_entries ;;
109+
*) usage; exit 2 ;;
110+
esac

0 commit comments

Comments
 (0)