Skip to content

Commit 3f8193c

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.static` (false by default) that makes BridgeVifDriver invoke on modifymacip.sh 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. Setting 'neigh_suppress on' requires a manual change in the modifyvxlan.sh script as this is not the default behavior.
1 parent 348ce95 commit 3f8193c

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,21 @@ 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+
* When set to <code>true</code>, executes <code>modifymacip.sh</code> (resolved via the
919+
* network scripts directory) on VM NIC plug (VM start) and unplug (VM stop) to manage static
920+
* ARP/NDP entries and host routes for VM interfaces.<br>
921+
* The script is invoked with:<br>
922+
* &nbsp;&nbsp;add: <code>-o add -b &lt;bridge&gt; -m &lt;mac&gt; [-4 &lt;ipv4&gt;] [-6 &lt;ipv6&gt;]</code><br>
923+
* &nbsp;&nbsp;delete: <code>-o delete -b &lt;bridge&gt; -m &lt;mac&gt;</code><br>
924+
* A bundled reference implementation is available at
925+
* <code>scripts/vm/network/vnet/modifymacip.sh</code>.<br>
926+
* Set to <code>false</code> or leave unset to disable this feature.<br>
927+
* Data type: Boolean.<br>
928+
* Default value: <code>false</code>
929+
*/
930+
public static final Property<Boolean> VM_NETWORK_MACIP_STATIC = new Property<>("vm.network.macip.static", false, Boolean.class);
931+
917932

918933
public static class Property <T>{
919934
private String name;

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

Lines changed: 56 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,14 @@ public void configure(Map<String, Object> params) throws ConfigurationException
8182
throw new ConfigurationException("Unable to find modifyvxlan.sh");
8283
}
8384

85+
if (Boolean.TRUE.equals(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_NETWORK_MACIP_STATIC))) {
86+
_macIpScriptPath = Script.findScript(networkScriptsDir, "modifymacip.sh");
87+
if (_macIpScriptPath == null) {
88+
throw new ConfigurationException("Unable to find modifymacip.sh");
89+
}
90+
logger.info("VM network MAC/IP static script configured: {}", _macIpScriptPath);
91+
}
92+
8493
libvirtVersion = (Long) params.get("libvirtVersion");
8594
if (libvirtVersion == null) {
8695
libvirtVersion = 0L;
@@ -276,11 +285,14 @@ public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicA
276285
intf.setPxeDisable(true);
277286
}
278287

288+
executeMacIpScript(intf.getBrName(), nic.getMac(), nic.getIp(), nic.getIp6Address(), nic.getNicSecIps());
289+
279290
return intf;
280291
}
281292

282293
@Override
283294
public void unplug(LibvirtVMDef.InterfaceDef iface, boolean deleteBr) {
295+
executeMacIpScript(iface.getBrName(), iface.getMacAddress());
284296
deleteVnetBr(iface.getBrName(), deleteBr);
285297
}
286298

@@ -400,6 +412,50 @@ private void deleteVnetBr(String brName, boolean deleteBr) {
400412
}
401413
}
402414

415+
private void executeMacIpScript(String brName, String mac) {
416+
if (_macIpScriptPath == null || mac == null || brName == null) {
417+
return;
418+
}
419+
final Script command = new Script(_macIpScriptPath, _timeout, logger);
420+
command.add("-o", "delete");
421+
command.add("-b", brName);
422+
command.add("-m", mac);
423+
final String result = command.execute();
424+
if (result != null) {
425+
logger.warn("MAC/IP script returned error for delete on {}: {}", mac, result);
426+
}
427+
}
428+
429+
private void executeMacIpScript(String brName, String mac, String ipv4, String ipv6, List<String> secondaryIps) {
430+
if (_macIpScriptPath == null || mac == null || brName == null) {
431+
return;
432+
}
433+
final Script command = new Script(_macIpScriptPath, _timeout, logger);
434+
command.add("-o", "add");
435+
command.add("-b", brName);
436+
command.add("-m", mac);
437+
if (ipv4 != null && !ipv4.isEmpty()) {
438+
command.add("-4", ipv4);
439+
}
440+
command.add("-6", NetUtils.ipv6LinkLocal(mac).toString());
441+
if (ipv6 != null && !ipv6.isEmpty()) {
442+
command.add("-6", ipv6);
443+
}
444+
if (secondaryIps != null) {
445+
for (String secIp : secondaryIps) {
446+
if (NetUtils.isValidIp6(secIp)) {
447+
command.add("-6", secIp);
448+
} else {
449+
command.add("-4", secIp);
450+
}
451+
}
452+
}
453+
final String result = command.execute();
454+
if (result != null) {
455+
logger.warn("MAC/IP script returned error for add on {}: {}", mac, result);
456+
}
457+
}
458+
403459
private void deleteExistingLinkLocalRouteTable(String linkLocalBr) {
404460
Script command = new Script("/bin/bash", _timeout);
405461
command.add("-c");
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
# Both -4 and -6 may be specified multiple times to cover primary and secondary
26+
# addresses (e.g. link-local + global unicast for IPv6).
27+
# On delete the bridge neighbour table is queried for all entries matching the
28+
# MAC address; no separate state file is required.
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_LIST=()
38+
IPV6_LIST=()
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_LIST+=("$OPTARG") ;;
46+
6) IPV6_LIST+=("$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+
add_entries() {
57+
for addr in "${IPV4_LIST[@]}"; do
58+
ip neigh replace "${addr}" lladdr "${MAC}" dev "${BRIDGE}" nud permanent
59+
ip route replace "${addr}/32" dev "${BRIDGE}"
60+
done
61+
62+
if [[ "${#IPV6_LIST[@]}" -gt 0 ]]; then
63+
# Ensure IPv6 is enabled on the bridge before installing NDP entries
64+
sysctl -qw "net.ipv6.conf.${BRIDGE}.disable_ipv6=0"
65+
for addr in "${IPV6_LIST[@]}"; do
66+
ip -6 neigh replace "${addr}" lladdr "${MAC}" dev "${BRIDGE}" nud permanent
67+
ip -6 route replace "${addr}/128" dev "${BRIDGE}"
68+
done
69+
fi
70+
}
71+
72+
delete_entries() {
73+
# Find all IPv4 neighbour entries on the bridge matching this MAC and remove them
74+
while read -r addr; do
75+
ip neigh del "${addr}" dev "${BRIDGE}" 2>/dev/null || true
76+
ip route del "${addr}/32" dev "${BRIDGE}" 2>/dev/null || true
77+
done < <(ip neigh show dev "${BRIDGE}" | awk -v mac="${MAC}" 'tolower($3) == tolower(mac) {print $1}')
78+
79+
# Find all IPv6 neighbour entries on the bridge matching this MAC and remove them
80+
while read -r addr; do
81+
ip -6 neigh del "${addr}" dev "${BRIDGE}" 2>/dev/null || true
82+
ip -6 route del "${addr}/128" dev "${BRIDGE}" 2>/dev/null || true
83+
done < <(ip -6 neigh show dev "${BRIDGE}" | awk -v mac="${MAC}" 'tolower($3) == tolower(mac) {print $1}')
84+
}
85+
86+
case "$OP" in
87+
add) add_entries ;;
88+
delete) delete_entries ;;
89+
*) usage; exit 2 ;;
90+
esac

0 commit comments

Comments
 (0)