forked from sonic-net/sonic-buildimage
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[pcmping]: Add pcmping tool to get portchannel member packet loss sta…
…te (sonic-net#293) * [pcmping]: Add pcmping tool to get portchannel member packet loss state Signed-off-by: Sihui Han <sihan@microsoft.com> * Update as comments Signed-off-by: Sihui Han <sihan@microsoft.com> * Use jumbo frame and enhance the code Signed-off-by: Sihui Han <sihan@microsoft.com> * [pcmping]: use dscp 0 to create packet Signed-off-by: Sihui Han <sihan@microsoft.com>
- Loading branch information
1 parent
3222d7e
commit dfb557c
Showing
2 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
#!/usr/bin/env python | ||
|
||
""" | ||
Portchannel member ping: get portchannel member packet loss state. | ||
""" | ||
|
||
from scapy.all import * | ||
import argparse | ||
import random | ||
import socket | ||
import select | ||
import time | ||
import ipaddr as ipaddress | ||
import sys | ||
import swsssdk | ||
|
||
RCV_SIZE = 20480 | ||
RCV_TIMEOUT = 10000 | ||
SELECT_TIMEOUT = 2 | ||
PROBE_PACKET_LEN = 9000 | ||
DHCP_DEFAULT = 0 | ||
|
||
def receive(my_socket, timeout, exp_pkt, time_sent): | ||
timeLeft = timeout | ||
delay = 0 | ||
while True: | ||
startedSelect = time.time() | ||
whatReady = select.select(my_socket, [], [], timeLeft) | ||
if whatReady[0] == []: # Timeout | ||
return False, delay, None | ||
timeReceived = time.time() | ||
|
||
for sel in whatReady[0]: | ||
recPacket = sel.recv(RCV_SIZE) | ||
e = str(exp_pkt) | ||
# exclude ethernet header | ||
p = str(recPacket[14:]) | ||
if e == p: | ||
delay = (timeReceived - time_sent) * 1000 | ||
return True, delay, sel | ||
timeLeft = timeLeft - (timeReceived - startedSelect) | ||
if timeLeft <= 0: | ||
return False, delay, None | ||
|
||
def find_probe_packet(interface, dst_out, dst_in, sockets, exp_socket, max_iter): | ||
dscp = DHCP_DEFAULT | ||
tos = dscp << 2 | ||
src_out = dst_in | ||
decap_valid = False | ||
for x in range(0, max_iter): | ||
print "---------- Attempting to create probe packet that goes through %s, iteration: %d ---------" % (interface, x) | ||
ip_src = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) | ||
ip_src =ipaddress.IPv4Address(unicode(ip_src,'utf-8')) | ||
while ip_src == ipaddress.IPv4Address(unicode(dst_in,'utf-8')) or \ | ||
ip_src.is_multicast or ip_src.is_private or ip_src.is_reserved: | ||
ip_src = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) | ||
ip_src = ipaddress.IPv4Address(unicode(ip_src,'utf-8')) | ||
inner_pkt = IP(src=ip_src, dst=dst_in, tos=tos, ttl=64, id=1, ihl=None,proto=4)/ICMP() | ||
inner_pkt = inner_pkt/("".join([chr(x%256) for x in xrange(PROBE_PACKET_LEN - len(inner_pkt))])) | ||
pkt = IP(src=src_out, dst=dst_out, tos=tos)/inner_pkt | ||
exp_pkt = inner_pkt | ||
exp_pkt['IP'].ttl = 63 | ||
|
||
time_sent = time.time() | ||
res = send(pkt, iface=interface, verbose=False) | ||
have_received, delay, recv_socket = receive(sockets, SELECT_TIMEOUT, exp_pkt, time_sent) | ||
if have_received and recv_socket == exp_socket: | ||
return pkt, exp_pkt | ||
elif have_received: | ||
decap_valid = True | ||
|
||
if decap_valid: | ||
print ("Decap works properly. link %s is unreachable, Please check!\n" % interface) | ||
sys.exit(0) | ||
else: | ||
print ("Decap using %s doesn't work properly, Please Check!\n" % (dst_out)) | ||
sys.exit(0) | ||
|
||
def get_portchannel(interface): | ||
configdb = swsssdk.ConfigDBConnector() | ||
configdb.connect() | ||
portchannels = configdb.get_table("PORTCHANNEL") | ||
for key, value in portchannels.iteritems(): | ||
if interface in value['members']: | ||
return key, value | ||
sys.stderr.write("Interface %s is not a portchannel member. Please Check!\n" % interface) | ||
sys.exit(1) | ||
|
||
def get_portchannel_ipv4(portchannel_name): | ||
configdb = swsssdk.ConfigDBConnector() | ||
configdb.connect() | ||
config = configdb.get_config() | ||
portchannel_interfaces = config["PORTCHANNEL_INTERFACE"] | ||
for key in portchannel_interfaces.keys(): | ||
pc, ip = key | ||
ip = ip.split("/")[0] | ||
if pc == portchannel_name and ipaddress.IPAddress(ip).version == 4: | ||
return ip | ||
sys.stderr.write("Portchannel %s doesn't have IPV4 address. Please Check!\n" % portchannel) | ||
sys.exit(1) | ||
|
||
def create_socket(interface, portchannel): | ||
# create sockets | ||
try: | ||
sockets = [] | ||
for iface in portchannel['members']: | ||
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_IP)) | ||
s.bind((iface, 0)) | ||
s.settimeout(RCV_TIMEOUT) | ||
if iface == interface: | ||
exp_socket = s | ||
sockets.append(s) | ||
except: | ||
sys.stderr.write("Unable to create socket. Check your permissions\n") | ||
sys.exit(1) | ||
return sockets, exp_socket | ||
|
||
def print_statistics(interface, total_count, recv_count): | ||
print "\n--- %s ping statistics ---" % interface | ||
print ("%d packets transmitted, %d received, %0.2f%% packet loss" % (total_count, recv_count, (total_count-recv_count)*1.0/total_count*100)) | ||
|
||
def main(): | ||
parser = argparse.ArgumentParser(description='Check portchannel member link state', | ||
version='1.0.0', | ||
formatter_class=argparse.RawTextHelpFormatter, | ||
epilog=""" | ||
Example: | ||
pcmping -i Ethernet8 -ip 10.10.10.10 | ||
pcmping -i Ethernet8 -ip 10.10.10.10 -c 5 | ||
pcmping -i Ethernet8 -ip 10.10.10.10 -c 5 -t 20 | ||
""" | ||
) | ||
|
||
parser.add_argument('-i', '--interface', type=str, required=True, help='Portchannel member interface') | ||
parser.add_argument('-ip','--decapip', type=str, required=True, help='Neighbor decap ip (usually same as neighbor loopback ip)') | ||
parser.add_argument('-c', '--count', type=int, help='Number of decap packets to be sent.', default=0) | ||
parser.add_argument('-t', '--trial', type=int, help='Maximum attempt times to create probe packet.\nPlease increase the value as the number of portchannel members increases', default=20) | ||
args = parser.parse_args() | ||
|
||
interface = args.interface | ||
decap_ip = args.decapip | ||
exp_count = args.count | ||
max_trial = args.trial | ||
|
||
portchannel_name, portchannel = get_portchannel(interface) | ||
portchannel_ip = get_portchannel_ipv4(portchannel_name) | ||
sockets, exp_socket = create_socket(interface, portchannel) | ||
pkt, exp_pkt = find_probe_packet(interface, decap_ip, portchannel_ip, sockets, exp_socket, max_trial) | ||
print "Preparation done! Start probing ............" | ||
print "PORTCHANNEL Member PING %d btyes of data. PORTCHANNEL: %s, INTERFACE: %s" % (PROBE_PACKET_LEN, portchannel_name, interface) | ||
recv_count = 0 | ||
total_count = 0 | ||
try: | ||
while True: | ||
time_sent = time.time() | ||
res = send(pkt, iface=interface, verbose=False) | ||
have_received, delay, recv_socket = receive(sockets, SELECT_TIMEOUT, exp_pkt, time_sent) | ||
total_count = total_count + 1 | ||
if have_received: | ||
print "%d bytes from %s: time=%0.4fms" % (PROBE_PACKET_LEN, interface, delay) | ||
recv_count = recv_count + 1 | ||
else: | ||
print "packet not received!" | ||
if total_count == exp_count: | ||
break; | ||
time.sleep(1) | ||
except KeyboardInterrupt: | ||
print_statistics(interface, total_count, recv_count) | ||
return | ||
print_statistics(interface, total_count, recv_count) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters