Skip to content

Commit

Permalink
Add probe functionality and sending current LACPDU packet functionality
Browse files Browse the repository at this point in the history
Signed-off-by: Saikrishna Arcot <[email protected]>
  • Loading branch information
saiarcot895 committed Apr 26, 2023
1 parent bd40c1b commit 7fc5ebd
Showing 1 changed file with 81 additions and 36 deletions.
117 changes: 81 additions & 36 deletions scripts/teamd_increase_retry_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import os
import re
import sys
from threading import Thread
import time
import argparse

from swsscommon.swsscommon import DBConnector, Table

Expand Down Expand Up @@ -40,19 +43,21 @@ class LACPRetryCount(Packet):
ByteField("collector_length", 16),
ShortField("collector_max_delay", 0),
XStrFixedLenField("collector_reserved", "", 12),
ByteField("actor_retry_count_type", 0x80),
ByteField("actor_retry_count_length", 4),
ByteField("actor_retry_count", 0),
XStrFixedLenField("actor_retry_count_reserved", "", 1),
ByteField("partner_retry_count_type", 0x81),
ByteField("partner_retry_count_length", 4),
ByteField("partner_retry_count", 0),
XStrFixedLenField("partner_retry_count_reserved", "", 1),
ConditionalField(ByteField("actor_retry_count_type", 0x80), lambda pkt:pkt.version == 0xf1),
ConditionalField(ByteField("actor_retry_count_length", 4), lambda pkt:pkt.version == 0xf1),
ConditionalField(ByteField("actor_retry_count", 0), lambda pkt:pkt.version == 0xf1),
ConditionalField(XStrFixedLenField("actor_retry_count_reserved", "", 1), lambda pkt:pkt.version == 0xf1),
ConditionalField(ByteField("partner_retry_count_type", 0x81), lambda pkt:pkt.version == 0xf1),
ConditionalField(ByteField("partner_retry_count_length", 4), lambda pkt:pkt.version == 0xf1),
ConditionalField(ByteField("partner_retry_count", 0), lambda pkt:pkt.version == 0xf1),
ConditionalField(XStrFixedLenField("partner_retry_count_reserved", "", 1), lambda pkt:pkt.version == 0xf1),
ByteField("terminator_type", 0),
ByteField("terminator_length", 0),
XStrFixedLenField("reserved", "", 42),
ConditionalField(XStrFixedLenField("reserved", "", 42), lambda pkt:pkt.version == 0xf1),
ConditionalField(XStrFixedLenField("reserved", "", 50), lambda pkt:pkt.version != 0xf1),
]

split_layers(scapy.contrib.lacp.SlowProtocol, scapy.contrib.lacp.LACP, subtype=1)
bind_layers(scapy.contrib.lacp.SlowProtocol, LACPRetryCount, subtype=1)

def getPortChannelConfig(portChannelName):
Expand All @@ -63,13 +68,17 @@ def getLldpNeighbors():
process = subprocess.run(["lldpctl", "-f", "json"], capture_output=True)
return json.loads(process.stdout)

def craftLacpPacket(portChannelConfig, portName):
def craftLacpPacket(portChannelConfig, portName, isProbePacket=False, newVersion=True):
portConfig = portChannelConfig["ports"][portName]
actorConfig = portConfig["runner"]["actor_lacpdu_info"]
partnerConfig = portConfig["runner"]["partner_lacpdu_info"]
l2 = Ether(dst="01:80:c2:00:00:02", src=portConfig["ifinfo"]["dev_addr"], type=0x8809)
l3 = scapy.contrib.lacp.SlowProtocol(subtype=0x01)
l4 = LACPRetryCount()
if newVersion:
l4.version = 0xf1
else:
l4.version = 0x1
l4.actor_system_priority = actorConfig["system_priority"]
l4.actor_system = actorConfig["system"]
l4.actor_key = actorConfig["key"]
Expand All @@ -82,8 +91,9 @@ def craftLacpPacket(portChannelConfig, portName):
l4.partner_port_priority = partnerConfig["port_priority"]
l4.partner_port_number = partnerConfig["port"]
l4.partner_state = partnerConfig["state"]
l4.actor_retry_count = 5
l4.partner_retry_count = 3
if newVersion:
l4.actor_retry_count = 5 if not isProbePacket else 3
l4.partner_retry_count = 3
packet = l2 / l3 / l4
return packet

Expand All @@ -92,16 +102,37 @@ def getPortChannels():
portchannelTable = Table(configDb, "PORTCHANNEL")
return list(portchannelTable.getKeys())

def main():
class LacpPacketListenThread(Thread):
def __init__(self, port):
Thread.__init__(self)
self.port = port
self.detectedNewVersion = False

def lacpPacketCallback(self, pkt):
if pkt["LACPRetryCount"].version == 0xf1:
self.detectedNewVersion = True
return self.detectedNewVersion

def run(self):
sniff(stop_filter=self.lacpPacketCallback, iface=self.port, filter="ether proto 0x8809", store=0, timeout=30)

def sendLacpPackets(packets):
while True:
for port, packet in packets:
sendp(packet, iface=port)
time.sleep(15)

def main(probeOnly=False):
if os.geteuid() != 0:
print("Root privileges required for this operation")
sys.exit(1)
return
return False
portChannels = getPortChannels()
if not portChannels:
return True
for portChannel in portChannels:
config = getPortChannelConfig(portChannel)
lldpInfo = getLldpNeighbors()
peerSupportsFeature = None
for portName in config["ports"].keys():
interfaceLldpInfo = [k for k in lldpInfo["lldp"]["interface"] if portName in k]
if not interfaceLldpInfo:
Expand All @@ -115,32 +146,46 @@ def main():
continue
if "SONiC" not in peerInfo["descr"]:
print("WARNING: Peer device is not a SONiC device; skipping")
peerSupportsFeature = False
break
sonicVersionMatch = re.search(r"SONiC Software Version: SONiC\.(.*?)(?: - |$)", peerInfo["descr"])
if not sonicVersionMatch:
print("WARNING: Unable to get SONiC version info for peer device; skipping")
continue
sonicVersion = sonicVersionMatch.group(1)
if "teamd-retry-count" in sonicVersion:
print("SUCCESS: Peer device {} is running version of SONiC ({}) with teamd retry count feature".format(peerName, sonicVersion))
peerSupportsFeature = True
break
sonicVersionComponents = sonicVersion.split(".")
if sonicVersionComponents[0] in MIN_TAG_FOR_EACH_VERSION and int(sonicVersionComponents[1]) >= MIN_TAG_FOR_EACH_VERSION[sonicVersionComponents[0]]:
print("SUCCESS: Peer device {} is running version of SONiC ({}) with teamd retry count feature".format(peerName, sonicVersion))
peerSupportsFeature = True

# Start sniffing thread
lacpThread = LacpPacketListenThread(portName)
lacpThread.start()

# Generate and send probe packet
probePacket = craftLacpPacket(config, portName, isProbePacket=True)
sendp(probePacket, iface=portName)

lacpThread.join()

resetProbePacket = craftLacpPacket(config, portName, newVersion=False)
time.sleep(2)
sendp(resetProbePacket, iface=portName, count=2, inter=0.5)

if lacpThread.detectedNewVersion:
print("SUCCESS: Peer device {} is running version of SONiC with teamd retry count feature".format(peerName))
break
else:
print("WARNING: Peer device {} is running version of SONiC ({}) without teamd retry count feature; skipping".format(peerName, sonicVersion))
peerSupportsFeature = False
print("WARNING: Peer device {} is running version of SONiC without teamd retry count feature".format(peerName))
break
if peerSupportsFeature:
retryCountChangeProcess = subprocess.run(["config", "portchannel", "retry-count", "set", portChannel, "5"])
if retryCountChangeProcess.returncode != 0:
if not probeOnly:
retryCountGetProcess = subprocess.run(["config", "portchannel", "retry-count", "get", portChannels[0]])
if retryCountGetProcess.returncode == 0:
# Currently running on SONiC version with teamd retry count feature
for portChannel in portChannels:
subprocess.run(["config", "portchannel", "retry-count", "set", portChannel, "5"])
else:
lacpPackets = []
for portChannel in portChannels:
config = getPortChannelConfig(portChannel)
for portName in config["ports"].keys():
packet = craftLacpPacket(config, portName)
sendp(packet, iface=portName)
lacpPackets.append((portName, packet))
sendLacpPackets(lacpPackets)

if __name__ == "__main__":
main()
parser = argparse.ArgumentParser(description='Teamd retry count changer.')
parser.add_argument('--probe-only', action='store_true',
help='Probe the peer devices only, to verify that they support the teamd retry count feature')
args = parser.parse_args()
main(args.probe_only)

0 comments on commit 7fc5ebd

Please sign in to comment.