COURSE CONTENT/ List of Experiments [R23_PEC(A)]:
1. Study of Network devices in detail and connect the computers in Local Area Network.
2. Write a Program to implement the data link layer farming methods such as
i. Character stuffing
ii. Bit stuffing
3. Write a Program to implement data link layer farming method checksum.
4. Write a program for Hamming Code generation for error detection and correction.
5. Write a Program to implement on a data set of characters the three CRC polynomials – CRC 12, CRC 16 and CRC CCIP.
6. Write a Program to implement Sliding window protocol for Goback N.
7. Write a Program to implement Sliding window protocol for Selective repeat.
8. Write a Program to implement Stop and Wait Protocol.
9. Write a program for congestion control using leaky bucket algorithm
10. Write a Program to implement Dijkstra‗s algorithm to compute the Shortest path through a graph.
11. Write a Program to implement Distance vector routing algorithm by obtaining routing table at each node (Take an example subnet graph with weights indicating delay between nodes).
12. Write a Program to implement Broadcast tree by taking subnet of hosts.
13. Wireshark i. Packet Capture Using Wire shark ii. Starting Wire shark iii. Viewing Captured Traffic iv. Analysis and Statistics & Filters.
14. How to run Nmap scan 15. Operating System Detection using Nmap
16. Do the following using NS2 Simulator
i. NS2 Simulator-Introduction
ii. Simulate to Find the Number of Packets Dropped
iii. Simulate to Find the Number of Packets Dropped by TCP/UDP
iv. Simulate to Find the Number of Packets Dropped due to Congestion
v. Simulate to Compare Data Rate& Throughput.
Below experiments/programs written in Python
Usage: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
[-r count] [-s count] [[-j host-list] | [-k host-list]]
[-w timeout] [-R] [-S srcaddr] [-c compartment] [-p]
[-4] [-6] target_name
Options:
-t Ping the specified host until stopped.
To see statistics and continue - type Control-Break;
To stop - type Control-C.
-a Resolve addresses to hostnames.
-n count Number of echo requests to send.
-l size Send buffer size.
-f Set Don't Fragment flag in packet (IPv4-only).
-i TTL Time To Live.
-v TOS Type Of Service (IPv4-only. This setting has been deprecated
and has no effect on the type of service field in the IP
Header).
-r count Record route for count hops (IPv4-only).
-s count Timestamp for count hops (IPv4-only).
-j host-list Loose source route along host-list (IPv4-only).
-k host-list Strict source route along host-list (IPv4-only).
-w timeout Timeout in milliseconds to wait for each reply.
-R Use routing header to test reverse route also (IPv6-only).
Per RFC 5095 the use of this routing header has been
deprecated. Some systems may drop echo requests if
this header is used.
-S srcaddr Source address to use.
-c compartment Routing compartment identifier.
-p Ping a Hyper-V Network Virtualization provider address.
-4 Force using IPv4.
-6 Force using IPv6.
# Experiment2a Character Stuffing with correct escape logic (esc after flag/esc)
def character_stuffing(data, flag='F', esc='E'):
stuffed_data = flag # Start with the flag
for char in data:
if char == flag or char == esc:
stuffed_data += esc # Insert escape BEFORE special char
stuffed_data += char
stuffed_data += flag # End with the flag
return stuffed_data
def character_unstuffing(stuffed_data, flag='F', esc='E'):
unstuffed_data = ''
i = 1 # Skip starting flag
while i < len(stuffed_data) - 1:
if stuffed_data[i] == esc:
i += 1 # Skip the escape character
unstuffed_data += stuffed_data[i]
i += 1
return unstuffed_data
# --- Runtime input ---
data = input("Enter the data to be stuffed: ")
flag = input("Enter the flag character (delimiter): ")
esc = input("Enter the escape character: ")
stuffed = character_stuffing(data, flag, esc)
unstuffed = character_unstuffing(stuffed, flag, esc)
print("\nOriginal Data: ", data)
print("Stuffed Data: ", stuffed)
print("Unstuffed Data: ", unstuffed)
#Experiment2b BitStuffing
def bit_stuffing(data):
stuffed = ''
count = 0
for bit in data:
if bit == '1':
count += 1
stuffed += bit
if count == 5:
stuffed += '0' # Stuff 0 after five consecutive 1s
count = 0
else:
stuffed += bit
count = 0
return stuffed
def bit_unstuffing(stuffed):
unstuffed = ''
count = 0
i = 0
while i < len(stuffed):
if stuffed[i] == '1':
count += 1
unstuffed += '1'
if count == 5:
i += 1 # Skip stuffed 0
count = 0
else:
unstuffed += '0'
count = 0
i += 1
return unstuffed
# Example usage:
#data = "1111110011111001111"
data=input("Enter binary number to be stuffed: ")
stuffed_bits = bit_stuffing(data)
unstuffed_bits = bit_unstuffing(stuffed_bits)
print("\nOriginal Bits: ",data)
print("Stuffed Bits: ", stuffed_bits)
print("Unstuffed Bits: ", unstuffed_bits)
#Experiment3 Checksum
def calculate_checksum(data_list):
"""
Calculates 16-bit checksum by summing all 16-bit blocks and returning 1's complement
"""
checksum = 0
for data in data_list:
checksum += int(data, 2) # binary to int
# Handle overflow
if checksum > 0xFFFF:
checksum = (checksum & 0xFFFF) + 1
# 1's complement
checksum = ~checksum & 0xFFFF
return format(checksum, '016b') # return 16-bit binary string
def verify_checksum(data_list, received_checksum):
total_data = data_list + [received_checksum]
computed = calculate_checksum(total_data)
return computed == '0000000000000000'
# Example usage
if __name__ == "__main__":
print("\n--- Checksum Calculation ---")
n = int(input("Enter number of 16-bit data words: "))
data_words = []
for i in range(n):
word = input(f"Enter data word {i+1} (16-bit binary): ")
if len(word) != 16 or not set(word).issubset({'0', '1'}):
print("Invalid input. Must be 16-bit binary.")
exit(1)
data_words.append(word)
checksum = calculate_checksum(data_words)
print(f"\nCalculated Checksum: {checksum}")
# Simulate receiver side verification
print("\n--- Receiver Side Verification ---")
valid = verify_checksum(data_words, checksum)
if valid:
print("No Error Detected (Checksum Verified)")
else:
print("Error Detected")
#Experiment4 Hamming Code
def calc_parity_bits(data_bits):
n = len(data_bits)
r = 0
while (2 ** r) < (n + r + 1):
r += 1
return r
def insert_parity_bits(data_bits):
r = calc_parity_bits(data_bits)
total_length = len(data_bits) + r
hamming_code = ['0'] * total_length
j = 0 # index for data bits
for i in range(1, total_length + 1):
if (i & (i - 1)) == 0: # power of 2 positions
continue
hamming_code[i - 1] = data_bits[j]
j += 1
# Set parity bits
for i in range(r):
pos = 2 ** i
parity = 0
for j in range(1, total_length + 1):
if j & pos and j != pos:
parity ^= int(hamming_code[j - 1])
hamming_code[pos - 1] = str(parity)
return ''.join(hamming_code)
def detect_and_correct(hamming_code):
n = len(hamming_code)
error_pos = 0
r = calc_parity_bits(hamming_code)
for i in range(r):
pos = 2 ** i
parity = 0
for j in range(1, n + 1):
if j & pos:
parity ^= int(hamming_code[j - 1])
if parity != 0:
error_pos += pos
if error_pos != 0:
print(f"Error found at position: {error_pos}")
hamming_code = list(hamming_code)
hamming_code[error_pos - 1] = '1' if hamming_code[error_pos - 1] == '0' else '0'
print("Corrected Hamming Code: ", ''.join(hamming_code))
else:
print("No Error Detected in Hamming Code")
return ''.join(hamming_code)
# Example usage
if __name__ == "__main__":
print("\n--- Hamming Code Generation and Error Detection ---")
data = input("Enter binary data bits (e.g., 1011): ")
if not set(data).issubset({'0', '1'}):
print("Invalid input. Only binary digits allowed.")
exit(1)
encoded = insert_parity_bits(data)
print("Generated Hamming Code: ", encoded)
# Simulate error detection and correction
print("\n--- Error Detection ---")
received = input(f"Enter received Hamming Code (same length {len(encoded)}): ")
if len(received) != len(encoded):
print("Length mismatch!")
else:
detect_and_correct(received)
#part2 visualization
def visualize_hamming_code(data_bits, hamming_code):
r = calc_parity_bits(data_bits)
total_length = len(hamming_code)
print("\n--- Hamming Code Visualization ---")
print(f"Total Bits (Data + Parity): {total_length}")
print("Position : Bit Type : Value : Covered By Parity")
for i in range(1, total_length + 1):
bit = hamming_code[i - 1]
bit_type = "Parity" if (i & (i - 1)) == 0 else "Data"
covered_by = []
# Check which parity bits cover this position
for j in range(r):
if i & (2 ** j):
covered_by.append(f"P{2 ** j}")
print(f"{i:^8} : {bit_type:^8} : {bit:^5} : {', '.join(covered_by)}")
"""
Experiment5 CRC
#Both Sender and Receiver must agree G(x) i.e., Generator Polynomial; no of binary bits in = N
# Message/ Frame = M(x)
# append (N-1) 0's to right of M(x) (its binary form)
# perform modulo-2 division; remainder has (N-1) bits;
# to M(x) right of it, append remainder bits
# THIS IS CRC
#-------------------------------------------------------------
#Cyclic Redundancy Check (CRC) is an error-detecting code used in digital networks. CRC appends redundant bits derived from binary division by a generator polynomial.
#CRC-12 polynomial: x¹² + x¹¹ + x³ + x² + x + 1 → binary: 1100000001111
#CRC-16 polynomial: x¹⁶ + x¹⁵ + x² + 1 → binary: 11000000000000101
#CRC-CCITT polynomial: x¹⁶ + x¹² + x⁵ + 1 → binary: 10001000000100001
#---------------------------------------------------------------
For standard use, you would input:
CRC-12: G(x) = 1100000001111
CRC-16-IBM: G(x) = 10000000000000101 ; Applications: Bisynch, XMODEM, disk storage
CRC-CCITT: G(x) = 10001000000100001 ; Applications: X.25, V.42, Bluetooth, HDLC
The code doesn't enforce a specific polynomial, so it can work with any agreed-upon G(x), standard or custom, as long as both sender and receiver use the same G(x).
"""
# Block 1: Read input binary G(x), predefined M(x); count number of bits in G(x) and store in 'n'
def read_inputs():
G = input("Enter the CRC-12 generator polynomial G(x) in binary (expected: 1100000001111): ")
M = "101100" # Predefined M(x)
# Validate G(x) is binary and matches CRC-12 standard
if not all(bit in '01' for bit in G):
raise ValueError("G(x) must be a binary string (0s and 1s only).")
if G != "1100000001111":
raise ValueError("G(x) must be the CRC-12 standard polynomial: 1100000001111")
# Validate M(x) is binary
if not all(bit in '01' for bit in M):
raise ValueError("Predefined M(x) must be a binary string (0s and 1s only).")
n = len(G) # Number of bits in G(x)
return G, M, n
# Block 2: Append n-1 zeroes to the right of M(x)
def append_zeroes(M, n):
return M + '0' * (n - 1)
# Block 3: Modulo-2 (XOR) division process to obtain remainder
def modulo2_division(dividend, divisor):
# Convert strings to lists for easier manipulation
dividend = list(dividend)
divisor_length = len(divisor)
# Perform XOR division
for i in range(len(dividend) - divisor_length + 1):
if dividend[i] == '1': # Only divide if the current bit is 1
for j in range(divisor_length):
# XOR operation: 0^0=0, 1^1=0, 0^1=1, 1^0=1
dividend[i + j] = '0' if dividend[i + j] == divisor[j] else '1'
# Extract remainder (last n-1 bits)
remainder = ''.join(dividend[-(divisor_length - 1):])
return remainder
# Block 4: Append remainder to M(x) to form S(x) = CRC
def generate_crc(M, remainder):
return M + remainder
# Block 5: Ask the user to enter CRC R(x)
def get_received_crc():
R = input("Enter the received CRC R(x) in binary: ")
if not all(bit in '01' for bit in R):
raise ValueError("Received CRC must be a binary string (0s and 1s only).")
return R
# Block 6: Check for errors in CRC using G(x) and R(x) to obtain remainder
def check_crc(R, G):
remainder = modulo2_division(R, G)
if all(bit == '0' for bit in remainder):
return "No error in CRC."
else:
return "Error detected in CRC (non-zero remainder: {}).".format(remainder)
# Main program
try:
# Block 1
G, M, n = read_inputs()
print(f"G(x): {G}, M(x): {M}, n (bits in G(x)): {n}")
# Block 2
M_padded = append_zeroes(M, n)
print(f"M(x) with {n-1} zeroes appended: {M_padded}")
# Block 3
remainder = modulo2_division(M_padded, G)
print(f"Remainder after modulo-2 division: {remainder}")
# Block 4
S = generate_crc(M, remainder)
print(f"S(x) (CRC with remainder appended): {S}")
# Block 5
R = get_received_crc()
print(f"Received CRC R(x): {R}")
# Block 6
result = check_crc(R, G)
print(result)
except ValueError as e:
print(f"Error: {e}")
# Block 1: Read input binary G(x), predefined M(x); count number of bits in G(x) and store in 'n'
def read_inputs():
G = input("Enter the CRC-16 generator polynomial G(x) in binary (expected: 10000000000000101): ")
M = "101100" # Predefined M(x)
# Validate G(x) is binary and matches CRC-16-IBM standard
if not all(bit in '01' for bit in G):
raise ValueError("G(x) must be a binary string (0s and 1s only).")
if G != "10000000000000101":
raise ValueError("G(x) must be the CRC-16-IBM standard polynomial: 10000000000000101")
# Validate M(x) is binary
if not all(bit in '01' for bit in M):
raise ValueError("Predefined M(x) must be a binary string (0s and 1s only).")
n = len(G) # Number of bits in G(x)
return G, M, n
# Block 2: Append n-1 zeroes to the right of M(x)
def append_zeroes(M, n):
return M + '0' * (n - 1)
# Block 3: Modulo-2 (XOR) division process to obtain remainder
def modulo2_division(dividend, divisor):
# Convert strings to lists for easier manipulation
dividend = list(dividend)
divisor_length = len(divisor)
# Perform XOR division
for i in range(len(dividend) - divisor_length + 1):
if dividend[i] == '1': # Only divide if the current bit is 1
for j in range(divisor_length):
# XOR operation: 0^0=0, 1^1=0, 0^1=1, 1^0=1
dividend[i + j] = '0' if dividend[i + j] == divisor[j] else '1'
# Extract remainder (last n-1 bits)
remainder = ''.join(dividend[-(divisor_length - 1):])
return remainder
# Block 4: Append remainder to M(x) to form S(x) = CRC
def generate_crc(M, remainder):
return M + remainder
# Block 5: Ask the user to enter CRC R(x)
def get_received_crc():
R = input("Enter the received CRC R(x) in binary: ")
if not all(bit in '01' for bit in R):
raise ValueError("Received CRC must be a binary string (0s and 1s only).")
return R
# Block 6: Check for errors in CRC using G(x) and R(x) to obtain remainder
def check_crc(R, G):
remainder = modulo2_division(R, G)
if all(bit == '0' for bit in remainder):
return "No error in CRC."
else:
return "Error detected in CRC (non-zero remainder: {}).".format(remainder)
# Main program
try:
# Block 1
G, M, n = read_inputs()
print(f"G(x): {G}, M(x): {M}, n (bits in G(x)): {n}")
# Block 2
M_padded = append_zeroes(M, n)
print(f"M(x) with {n-1} zeroes appended: {M_padded}")
# Block 3
remainder = modulo2_division(M_padded, G)
print(f"Remainder after modulo-2 division: {remainder}")
# Block 4
S = generate_crc(M, remainder)
print(f"S(x) (CRC with remainder appended): {S}")
# Block 5
R = get_received_crc()
print(f"Received CRC R(x): {R}")
# Block 6
result = check_crc(R, G)
print(result)
except ValueError as e:
print(f"Error: {e}")
# Block 1: Read input binary G(x), predefined M(x); count number of bits in G(x) and store in 'n'
def read_inputs():
G = input("Enter the CRC-CCITT generator polynomial G(x) in binary (expected: 10001000000100001): ")
M = "101100" # Predefined M(x)
# Validate G(x) is binary and matches CRC-CCITT standard
if not all(bit in '01' for bit in G):
raise ValueError("G(x) must be a binary string (0s and 1s only).")
if G != "10001000000100001":
raise ValueError("G(x) must be the CRC-CCITT standard polynomial: 10001000000100001")
# Validate M(x) is binary
if not all(bit in '01' for bit in M):
raise ValueError("Predefined M(x) must be a binary string (0s and 1s only).")
n = len(G) # Number of bits in G(x)
return G, M, n
# Block 2: Append n-1 zeroes to the right of M(x)
def append_zeroes(M, n):
return M + '0' * (n - 1)
# Block 3: Modulo-2 (XOR) division process to obtain remainder
def modulo2_division(dividend, divisor):
# Convert strings to lists for easier manipulation
dividend = list(dividend)
divisor_length = len(divisor)
# Perform XOR division
for i in range(len(dividend) - divisor_length + 1):
if dividend[i] == '1': # Only divide if the current bit is 1
for j in range(divisor_length):
# XOR operation: 0^0=0, 1^1=0, 0^1=1, 1^0=1
dividend[i + j] = '0' if dividend[i + j] == divisor[j] else '1'
# Extract remainder (last n-1 bits)
remainder = ''.join(dividend[-(divisor_length - 1):])
return remainder
# Block 4: Append remainder to M(x) to form S(x) = CRC
def generate_crc(M, remainder):
return M + remainder
# Block 5: Ask the user to enter CRC R(x)
def get_received_crc():
R = input("Enter the received CRC R(x) in binary: ")
if not all(bit in '01' for bit in R):
raise ValueError("Received CRC must be a binary string (0s and 1s only).")
return R
# Block 6: Check for errors in CRC using G(x) and R(x) to obtain remainder
def check_crc(R, G):
remainder = modulo2_division(R, G)
if all(bit == '0' for bit in remainder):
return "No error in CRC."
else:
return "Error detected in CRC (non-zero remainder: {}).".format(remainder)
# Main program
try:
# Block 1
G, M, n = read_inputs()
print(f"G(x): {G}, M(x): {M}, n (bits in G(x)): {n}")
# Block 2
M_padded = append_zeroes(M, n)
print(f"M(x) with {n-1} zeroes appended: {M_padded}")
# Block 3
remainder = modulo2_division(M_padded, G)
print(f"Remainder after modulo-2 division: {remainder}")
# Block 4
S = generate_crc(M, remainder)
print(f"S(x) (CRC with remainder appended): {S}")
# Block 5
R = get_received_crc()
print(f"Received CRC R(x): {R}")
# Block 6
result = check_crc(R, G)
print(result)
except ValueError as e:
print(f"Error: {e}")
import random
import time
# Block 1: Read input for number of frames and window size
def read_inputs():
try:
total_frames = int(input("Enter the total number of frames to send: "))
window_size = int(input("Enter the window size for Go-Back-N: "))
if total_frames <= 0 or window_size <= 0:
raise ValueError("Total frames and window size must be positive integers.")
if window_size > total_frames:
raise ValueError("Window size cannot be larger than total frames.")
return total_frames, window_size
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize sender state (window, frame sequence, etc.)
def initialize_sender(total_frames, window_size):
sender_window = [] # Current window of frames
next_frame_to_send = 0 # Next frame to send
base = 0 # Base of the window (oldest unacknowledged frame)
return sender_window, next_frame_to_send, base
# Block 3: Simulate sender sending frames within the window
def send_frames(sender_window, next_frame_to_send, base, window_size, total_frames, sent_frames):
frames_sent = []
while next_frame_to_send < total_frames and len(sender_window) < window_size:
print(f"Sender: Sending frame {next_frame_to_send}")
sender_window.append(next_frame_to_send)
frames_sent.append(next_frame_to_send)
sent_frames.add(next_frame_to_send)
next_frame_to_send += 1
return sender_window, next_frame_to_send, frames_sent
# Block 4: Simulate receiver processing frames and sending ACKs
def receive_frames(frames_sent, error_rate=0.2):
received_frames = []
for frame in frames_sent:
# Simulate random frame loss
if random.random() > error_rate: # Frame received successfully
print(f"Receiver: Received frame {frame}")
received_frames.append(frame)
else:
print(f"Receiver: Frame {frame} lost in transmission")
# Send ACK for the last successfully received frame + 1 (cumulative ACK)
if received_frames:
ack = max(received_frames) + 1
print(f"Receiver: Sending ACK for frame {ack}")
return ack
return None # No frames received successfully
# Block 5: Simulate timeout and retransmission for unacknowledged frames
def handle_timeout(sender_window, base, next_frame_to_send, sent_frames):
if sender_window:
print(f"Sender: Timeout! Retransmitting from frame {base}")
# Clear window and retransmit from base
sender_window.clear()
next_frame_to_send = base
return sender_window, next_frame_to_send
# Block 6: Main Go-Back-N protocol simulation
def gobackn_protocol(total_frames, window_size):
sender_window, next_frame_to_send, base = initialize_sender(total_frames, window_size)
sent_frames = set() # Track sent frames to avoid duplicates in output
timeout_duration = 2 # Simulated timeout duration in seconds
error_rate = 0.2 # Probability of frame loss
while base < total_frames:
# Send frames in the window
sender_window, next_frame_to_send, frames_sent = send_frames(
sender_window, next_frame_to_send, base, window_size, total_frames, sent_frames
)
# Simulate channel delay
print("Sender: Waiting for ACK...")
time.sleep(1)
# Receive frames and get ACK
ack = receive_frames(frames_sent, error_rate)
# Process ACK or handle timeout
if ack is not None and ack > base:
print(f"Sender: Received ACK for frame {ack}")
# Move window forward
sender_window = [f for f in sender_window if f >= ack]
base = ack
else:
print("Sender: No valid ACK received or frame lost.")
sender_window, next_frame_to_send = handle_timeout(sender_window, base, next_frame_to_send, sent_frames)
# Simulate timeout check
time.sleep(timeout_duration)
print("Sender: All frames sent and acknowledged successfully!")
# Main program
try:
# Block 1
total_frames, window_size = read_inputs()
print(f"Total frames: {total_frames}, Window size: {window_size}")
# Block 6
gobackn_protocol(total_frames, window_size)
except ValueError as e:
print(f"Error: {e}")
import random
import time
# Block 1: Read input for number of frames and window size
def read_inputs():
try:
total_frames = int(input("Enter the total number of frames to send: "))
window_size = int(input("Enter the window size for Selective Repeat: "))
if total_frames <= 0 or window_size <= 0:
raise ValueError("Total frames and window size must be positive integers.")
if window_size > total_frames:
raise ValueError("Window size cannot be larger than total frames.")
return total_frames, window_size
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize sender and receiver state (window, frame sequence, buffers)
def initialize_protocol(total_frames, window_size):
sender_window = {} # Dictionary to track frames in window: {frame_number: (sent_time, acknowledged)}
receiver_buffer = {} # Dictionary to buffer out-of-order frames: {frame_number: received}
next_frame_to_send = 0 # Next frame to send
base = 0 # Base of the sender window (oldest unacknowledged frame)
expected_frame = 0 # Receiver's next expected frame for in-order delivery
return sender_window, receiver_buffer, next_frame_to_send, base, expected_frame
# Block 3: Simulate sender sending frames within the window
def send_frames(sender_window, next_frame_to_send, base, window_size, total_frames, sent_frames):
frames_sent = []
current_time = time.time()
while next_frame_to_send < total_frames and len(sender_window) < window_size:
if next_frame_to_send not in sender_window:
print(f"Sender: Sending frame {next_frame_to_send}")
sender_window[next_frame_to_send] = (current_time, False) # (sent_time, acknowledged)
frames_sent.append(next_frame_to_send)
sent_frames.add(next_frame_to_send)
next_frame_to_send += 1
return sender_window, next_frame_to_send, frames_sent
# Block 4: Simulate receiver processing frames, buffering, and sending selective ACKs
def receive_frames(frames_sent, receiver_buffer, expected_frame, error_rate=0.2):
acks = []
for frame in frames_sent:
# Simulate random frame loss
if random.random() > error_rate: # Frame received successfully
print(f"Receiver: Received frame {frame}")
receiver_buffer[frame] = True
acks.append(frame) # Send ACK for this frame
print(f"Receiver: Sending ACK for frame {frame}")
else:
print(f"Receiver: Frame {frame} lost in transmission")
# Deliver in-order frames from buffer
delivered = []
while expected_frame in receiver_buffer:
print(f"Receiver: Delivering frame {expected_frame} in order")
delivered.append(expected_frame)
del receiver_buffer[expected_frame]
expected_frame += 1
return acks, receiver_buffer, expected_frame
# Block 5: Process ACKs and handle timeouts for selective retransmission
def process_acks_and_timeouts(sender_window, acks, base, timeout_duration=2):
current_time = time.time()
frames_to_retransmit = []
# Mark received ACKs
for ack in acks:
if ack in sender_window:
sender_window[ack] = (sender_window[ack][0], True) # Mark as acknowledged
print(f"Sender: Received ACK for frame {ack}")
# Advance base to the next unacknowledged frame
while base in sender_window and sender_window[base][1]:
del sender_window[base]
base += 1
# Check for timeouts and queue retransmissions
for frame in list(sender_window.keys()):
sent_time, acknowledged = sender_window[frame]
if not acknowledged and (current_time - sent_time) > timeout_duration:
print(f"Sender: Timeout for frame {frame}. Queuing for retransmission")
frames_to_retransmit.append(frame)
sender_window[frame] = (current_time, False) # Update sent time
return sender_window, base, frames_to_retransmit
# Block 6: Main Selective Repeat protocol simulation
def selective_repeat_protocol(total_frames, window_size):
sender_window, receiver_buffer, next_frame_to_send, base, expected_frame = initialize_protocol(total_frames, window_size)
sent_frames = set() # Track sent frames to avoid duplicates in output
timeout_duration = 2 # Simulated timeout duration in seconds
error_rate = 0.2 # Probability of frame loss
while base < total_frames:
# Send frames in the window
sender_window, next_frame_to_send, frames_sent = send_frames(
sender_window, next_frame_to_send, base, window_size, total_frames, sent_frames
)
# Simulate channel delay
print("Sender: Waiting for ACKs...")
time.sleep(1)
# Receive frames and get selective ACKs
acks, receiver_buffer, expected_frame = receive_frames(
frames_sent, receiver_buffer, expected_frame, error_rate
)
# Process ACKs and check for timeouts
sender_window, base, frames_to_retransmit = process_acks_and_timeouts(
sender_window, acks, base, timeout_duration
)
# Retransmit specific frames that timed out
for frame in frames_to_retransmit:
print(f"Sender: Retransmitting frame {frame}")
sender_window[frame] = (time.time(), False) # Update sent time
sent_frames.add(frame)
frames_sent = [frame] # Send retransmitted frame
acks, receiver_buffer, expected_frame = receive_frames(
frames_sent, receiver_buffer, expected_frame, error_rate
)
sender_window, base, _ = process_acks_and_timeouts(
sender_window, acks, base, timeout_duration
)
# Simulate timeout check
time.sleep(timeout_duration)
print("Sender: All frames sent and acknowledged successfully!")
# Main program
try:
# Block 1
total_frames, window_size = read_inputs()
print(f"Total frames: {total_frames}, Window size: {window_size}")
# Block 6
selective_repeat_protocol(total_frames, window_size)
except ValueError as e:
print(f"Error: {e}")
import random
import time
# Block 1: Read input for number of frames
def read_inputs():
try:
total_frames = int(input("Enter the total number of frames to send: "))
if total_frames <= 0:
raise ValueError("Total frames must be a positive integer.")
return total_frames
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize sender and receiver state
def initialize_protocol():
current_frame = 0 # Current frame to send
seq_num = 0 # Sequence number (0 or 1 for alternating bit)
expected_seq_num = 0 # Receiver's expected sequence number
return current_frame, seq_num, expected_seq_num
# Block 3: Simulate sender sending a frame
def send_frame(current_frame, seq_num, sent_frames):
print(f"Sender: Sending frame {current_frame} with sequence number {seq_num}")
sent_frames.add(current_frame)
return (current_frame, seq_num)
# Block 4: Simulate receiver processing frame and sending ACK
def receive_frame(frame_data, expected_seq_num, error_rate=0.2):
frame_num, seq_num = frame_data
# Simulate random frame loss
if random.random() > error_rate: # Frame received successfully
print(f"Receiver: Received frame {frame_num} with sequence number {seq_num}")
if seq_num == expected_seq_num:
print(f"Receiver: Frame {frame_num} is in order, delivering")
# Send ACK with next expected sequence number
ack_seq_num = 1 - seq_num # Toggle sequence number (0 -> 1, 1 -> 0)
print(f"Receiver: Sending ACK for sequence number {ack_seq_num}")
return ack_seq_num
else:
print(f"Receiver: Frame {frame_num} has incorrect sequence number, discarding")
return None # Discard out-of-order frame
else:
print(f"Receiver: Frame {frame_num} lost in transmission")
return None
# Block 5: Simulate ACK processing and timeout handling
def process_ack_and_timeout(sent_frame, received_ack, expected_ack, timeout_duration=2):
current_time = time.time()
sent_frame_num, sent_seq_num = sent_frame
# Simulate ACK loss
if received_ack is not None and random.random() > 0.2: # ACK received successfully
print(f"Sender: Received ACK for sequence number {received_ack}")
if received_ack == expected_ack:
print(f"Sender: Frame {sent_frame_num} acknowledged successfully")
return True, current_time # ACK matches, move to next frame
else:
print(f"Sender: Incorrect ACK sequence number, waiting...")
return False, current_time
else:
print(f"Sender: ACK for frame {sent_frame_num} lost or not received")
return False, current_time
# Block 6: Main Stop-and-Wait protocol simulation
def stop_and_wait_protocol(total_frames):
current_frame, seq_num, expected_seq_num = initialize_protocol()
sent_frames = set() # Track sent frames to avoid duplicates in output
timeout_duration = 2 # Simulated timeout duration in seconds
error_rate = 0.2 # Probability of frame/ACK loss
last_sent_time = time.time()
while current_frame < total_frames:
# Send frame
sent_frame = send_frame(current_frame, seq_num, sent_frames)
# Simulate channel delay
print("Sender: Waiting for ACK...")
time.sleep(1)
# Receive frame and get ACK
ack = receive_frame(sent_frame, expected_seq_num, error_rate)
# Process ACK or timeout
acknowledged, last_sent_time = process_ack_and_timeout(sent_frame, ack, 1 - seq_num, timeout_duration)
if acknowledged:
# Move to next frame
current_frame += 1
seq_num = 1 - seq_num # Toggle sequence number
expected_seq_num = seq_num
elif (time.time() - last_sent_time) > timeout_duration:
print(f"Sender: Timeout for frame {current_frame}, retransmitting")
last_sent_time = time.time() # Reset timer for retransmission
# Frame is resent in the next iteration
print("Sender: All frames sent and acknowledged successfully!")
# Main program
try:
# Block 1
total_frames = read_inputs()
print(f"Total frames: {total_frames}")
# Block 6
stop_and_wait_protocol(total_frames)
except ValueError as e:
print(f"Error: {e}")
import time
# Block 1: Read input for bucket size, output rate, and packet arrivals
def read_inputs():
try:
bucket_size = int(input("Enter the bucket size (number of packets): "))
output_rate = int(input("Enter the output rate (packets per second): "))
num_arrivals = int(input("Enter the number of packet arrival events: "))
if bucket_size <= 0 or output_rate <= 0 or num_arrivals <= 0:
raise ValueError("Bucket size, output rate, and number of arrivals must be positive integers.")
# Read packet arrivals as (time, packet_count) pairs
packet_arrivals = []
print("Enter each packet arrival as 'time packet_count' (e.g., '1 5' for 5 packets at time 1):")
for _ in range(num_arrivals):
time_val, count = map(int, input().split())
if time_val < 0 or count < 0:
raise ValueError("Time and packet count must be non-negative.")
packet_arrivals.append((time_val, count))
return bucket_size, output_rate, packet_arrivals
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize leaky bucket state
def initialize_bucket(bucket_size):
current_bucket = 0 # Current number of packets in the bucket
last_leak_time = 0 # Time of the last leak event
return current_bucket, last_leak_time
# Block 3: Process incoming packets and update bucket
def process_packets(current_bucket, bucket_size, arrival_time, packet_count):
print(f"Time {arrival_time}: Received {packet_count} packets")
# Check if bucket can accommodate incoming packets
if current_bucket + packet_count <= bucket_size:
current_bucket += packet_count
print(f"Time {arrival_time}: Accepted {packet_count} packets. Bucket now has {current_bucket} packets")
else:
accepted = max(0, bucket_size - current_bucket)
dropped = packet_count - accepted
current_bucket += accepted
print(f"Time {arrival_time}: Accepted {accepted} packets, dropped {dropped} packets due to bucket overflow. Bucket now has {current_bucket} packets")
return current_bucket
# Block 4: Simulate leaking packets at the output rate
def leak_packets(current_bucket, output_rate, current_time, last_leak_time):
# Calculate time elapsed since last leak
time_elapsed = current_time - last_leak_time
packets_leaked = int(time_elapsed * output_rate)
if packets_leaked > 0:
packets_leaked = min(packets_leaked, current_bucket) # Can't leak more than what's in the bucket
current_bucket -= packets_leaked
print(f"Time {current_time}: Leaked {packets_leaked} packets. Bucket now has {current_bucket} packets")
return current_bucket, current_time
# Block 5: Main leaky bucket algorithm simulation
def leaky_bucket_algorithm(bucket_size, output_rate, packet_arrivals):
current_bucket, last_leak_time = initialize_bucket(bucket_size)
# Sort arrivals by time
packet_arrivals.sort(key=lambda x: x[0])
max_time = max(arrival[0] for arrival in packet_arrivals) + 1 # Run until last arrival + 1 second
for current_time in range(max_time + 1):
# Process any packet arrivals at this time
for arrival_time, packet_count in packet_arrivals:
if arrival_time == current_time:
current_bucket = process_packets(current_bucket, bucket_size, arrival_time, packet_count)
# Leak packets at the output rate
current_bucket, last_leak_time = leak_packets(current_bucket, output_rate, current_time, last_leak_time)
# Simulate time step
time.sleep(1)
# Final leak to empty the bucket
while current_bucket > 0:
current_time += 1
current_bucket, last_leak_time = leak_packets(current_bucket, output_rate, current_time, last_leak_time)
time.sleep(1)
print("All packets processed and bucket emptied.")
# Main program
try:
# Block 1
bucket_size, output_rate, packet_arrivals = read_inputs()
print(f"Bucket size: {bucket_size}, Output rate: {output_rate} packets/sec, Packet arrivals: {packet_arrivals}")
# Block 5
leaky_bucket_algorithm(bucket_size, output_rate, packet_arrivals)
except ValueError as e:
print(f"Error: {e}")
import heapq
import math
# Block 1: Read input for graph, source, and destination
def read_inputs():
try:
num_nodes = int(input("Enter the number of nodes in the graph: "))
num_edges = int(input("Enter the number of edges in the graph: "))
if num_nodes <= 0 or num_edges < 0:
raise ValueError("Number of nodes must be positive, and edges must be non-negative.")
# Initialize adjacency list
graph = [[] for _ in range(num_nodes)]
print("Enter each edge as 'source destination weight' (0-based node indices, e.g., '0 1 4' for edge from node 0 to 1 with weight 4):")
for _ in range(num_edges):
src, dest, weight = map(int, input().split())
if src < 0 or src >= num_nodes or dest < 0 or dest >= num_nodes or weight < 0:
raise ValueError("Invalid edge: Source and destination must be within node range, and weight must be non-negative.")
graph[src].append((dest, weight))
# Uncomment the next line for undirected graph
# graph[dest].append((src, weight))
source = int(input("Enter the source node (0-based index): "))
dest = int(input("Enter the destination node (0-based index): "))
if source < 0 or source >= num_nodes or dest < 0 or dest >= num_nodes:
raise ValueError("Source and destination nodes must be within node range.")
return graph, source, dest, num_nodes
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize Dijkstra's algorithm data structures
def initialize_dijkstra(num_nodes, source):
# Initialize distances to infinity
distances = [math.inf] * num_nodes
distances[source] = 0
# Track predecessors for path reconstruction
predecessors = [-1] * num_nodes
# Priority queue: (distance, node)
pq = [(0, source)]
# Track visited nodes
visited = set()
return distances, predecessors, pq, visited
# Block 3: Dijkstra's algorithm core (find shortest paths)
def dijkstra(graph, source, num_nodes, distances, predecessors, pq, visited):
while pq:
current_distance, current_node = heapq.heappop(pq)
# Skip if already visited
if current_node in visited:
continue
# Mark node as visited
visited.add(current_node)
# Explore neighbors
for neighbor, weight in graph[current_node]:
if neighbor not in visited:
distance = current_distance + weight
# Update distance if a shorter path is found
if distance < distances[neighbor]:
distances[neighbor] = distance
predecessors[neighbor] = current_node
heapq.heappush(pq, (distance, neighbor))
return distances, predecessors
# Block 4: Reconstruct and return the shortest path to the destination
def reconstruct_path(distances, predecessors, source, dest):
if distances[dest] == math.inf:
return None, math.inf # No path exists
path = []
current = dest
while current != -1:
path.append(current)
current = predecessors[current]
path.reverse()
# Check if the path starts at the source
if path[0] != source:
return None, math.inf # No valid path
return path, distances[dest]
# Block 5: Main Dijkstra's algorithm execution
def dijkstra_algorithm(graph, source, dest, num_nodes):
distances, predecessors, pq, visited = initialize_dijkstra(num_nodes, source)
distances, predecessors = dijkstra(graph, source, num_nodes, distances, predecessors, pq, visited)
path, distance = reconstruct_path(distances, predecessors, source, dest)
if path is None:
print(f"No path exists from node {source} to node {dest}.")
else:
print(f"Shortest path from node {source} to node {dest}: {' -> '.join(map(str, path))}")
print(f"Shortest path distance: {distance}")
# Main program
try:
# Block 1
graph, source, dest, num_nodes = read_inputs()
print(f"Graph adjacency list: {graph}")
print(f"Source node: {source}, Destination node: {dest}")
# Block 5
dijkstra_algorithm(graph, source, dest, num_nodes)
except ValueError as e:
print(f"Error: {e}")
"""
Example Graph: 4 nodes (0: A, 1: B, 2: C, 3: D) with delays as weights (undirected).
Edges: 0-1:1, 0-2:4, 1-2:1, 1-3:5, 2-3:1.
Algorithm: Simulates Distance Vector Routing using iterative updates (synchronous) until convergence.
Routing Tables: For each node, shows destination, shortest distance (delay), and next hop.
Convergence: Stops when no further updates occur.
"""
import math
num_nodes = 4
# Adjacency matrix (undirected graph with delays as weights)
graph = [[math.inf] * num_nodes for _ in range(num_nodes)]
# Set edges (delays)
graph[0][1] = graph[1][0] = 1 # A-B: 1
graph[0][2] = graph[2][0] = 4 # A-C: 4
graph[1][2] = graph[2][1] = 1 # B-C: 1
graph[1][3] = graph[3][1] = 5 # B-D: 5
graph[2][3] = graph[3][2] = 1 # C-D: 1
# Initialize distances and next_hops
distances = [[math.inf] * num_nodes for _ in range(num_nodes)]
next_hops = [[-1] * num_nodes for _ in range(num_nodes)]
for i in range(num_nodes):
distances[i][i] = 0
next_hops[i][i] = i
for j in range(num_nodes):
if graph[i][j] != math.inf:
distances[i][j] = graph[i][j]
next_hops[i][j] = j
# Iterative update until convergence
iteration = 0
while True:
iteration += 1
old_distances = [row[:] for row in distances]
for i in range(num_nodes):
for j in range(num_nodes):
if i == j:
continue
min_dist = old_distances[i][j]
min_hop = next_hops[i][j]
for k in range(num_nodes):
if graph[i][k] != math.inf and old_distances[k][j] != math.inf:
dist_via_k = graph[i][k] + old_distances[k][j]
if dist_via_k < min_dist:
min_dist = dist_via_k
min_hop = k # Next hop is the neighbor k
distances[i][j] = min_dist
next_hops[i][j] = min_hop
# Check if converged
if all(all(old_distances[i][j] == distances[i][j] for j in range(num_nodes)) for i in range(num_nodes)):
break
# Output routing tables
for i in range(num_nodes):
print(f"Routing table for node {i}:")
for j in range(num_nodes):
dist = distances[i][j]
hop = next_hops[i][j]
print(f" Destination {j}, Distance {dist if dist != math.inf else 'inf'}, Next Hop {hop}")
print()
print("Converged after", iteration, "iterations")
"""
Broadcast Tree:
Purpose: Ensures efficient broadcasting to all hosts in the subnet with minimal total link cost.
Algorithm: Uses Prim's algorithm to compute the Minimum Spanning Tree (MST), which serves as the broadcast tree.
Graph: Undirected, weighted (weights represent delays or costs), and assumed to be connected.
Output: Lists the edges of the broadcast tree (parent -> child, weight) and the total weight.
Block Structure:
Block 1: Reads the number of hosts, number of links, and link details (source, destination, weight), building an adjacency list for an undirected graph.
Block 2: Initializes Prim's algorithm with a priority queue, visited set, edge list, and total weight, starting from host 0.
Block 3: Constructs the broadcast tree (MST) by selecting the minimum-weight edge to an unvisited host, adding it to the tree, and exploring its neighbors.
Block 4: Outputs the edges of the broadcast tree and the total weight.
Block 5: Executes the main algorithm and outputs the results.
Error Handling:
Validates that the number of hosts and links are valid, and edge weights are non-negative.
Ensures source and destination nodes are within the host range.
Checks if the graph is connected (all hosts are included in the MST).
Provides clear error messages for invalid inputs.
Example Subnet:
Consider a subnet with 4 hosts (0, 1, 2, 3) and links:
0-1: 4, 0-2: 8, 1-2: 2, 1-3: 5, 2-3: 1
The broadcast tree (MST) connects all hosts with minimum total weight.
"""
import heapq
import math
# Block 1: Read input for subnet graph (hosts and links)
def read_inputs():
try:
num_hosts = int(input("Enter the number of hosts (nodes) in the subnet: "))
num_links = int(input("Enter the number of links (edges) in the subnet: "))
if num_hosts <= 0 or num_links < 0:
raise ValueError("Number of hosts must be positive, and links must be non-negative.")
# Initialize adjacency list
graph = [[] for _ in range(num_hosts)]
print("Enter each link as 'source destination weight' (0-based host indices, e.g., '0 1 4' for link between host 0 and 1 with weight 4):")
for _ in range(num_links):
src, dest, weight = map(int, input().split())
if src < 0 or src >= num_hosts or dest < 0 or dest >= num_hosts or weight < 0:
raise ValueError("Invalid link: Source and destination must be within host range, and weight must be non-negative.")
graph[src].append((dest, weight))
graph[dest].append((src, weight)) # Undirected graph
return graph, num_hosts
except ValueError as e:
raise ValueError(f"Invalid input: {e}")
# Block 2: Initialize Prim's algorithm for MST
def initialize_prim(num_hosts, start_host=0):
# Track visited hosts
visited = set()
# Priority queue: (weight, node, parent)
pq = [(0, start_host, -1)] # Start with host 0, no parent
# Track edges in the broadcast tree: (parent, node, weight)
tree_edges = []
# Track total weight of the tree
total_weight = 0
return visited, pq, tree_edges, total_weight
# Block 3: Construct broadcast tree using Prim's algorithm
def construct_broadcast_tree(graph, num_hosts, visited, pq, tree_edges, total_weight):
while pq and len(visited) < num_hosts:
weight, current_host, parent = heapq.heappop(pq)
# Skip if already visited
if current_host in visited:
continue
# Add host to visited set
visited.add(current_host)
# Add edge to tree (if not the starting node)
if parent != -1:
tree_edges.append((parent, current_host, weight))
total_weight += weight
# Explore neighbors
for neighbor, weight in graph[current_host]:
if neighbor not in visited:
heapq.heappush(pq, (weight, neighbor, current_host))
# Check if all hosts are included
if len(visited) < num_hosts:
raise ValueError("Graph is not connected; cannot form a broadcast tree.")
return tree_edges, total_weight
# Block 4: Output the broadcast tree
def output_broadcast_tree(tree_edges, total_weight):
print("Broadcast Tree Edges (parent -> child, weight):")
for parent, child, weight in tree_edges:
print(f"Host {parent} -> Host {child}, Weight: {weight}")
print(f"Total weight of the broadcast tree: {total_weight}")
# Block 5: Main broadcast tree construction
def broadcast_tree_algorithm(graph, num_hosts):
visited, pq, tree_edges, total_weight = initialize_prim(num_hosts)
tree_edges, total_weight = construct_broadcast_tree(graph, num_hosts, visited, pq, tree_edges, total_weight)
output_broadcast_tree(tree_edges, total_weight)
# Main program
try:
# Block 1
graph, num_hosts = read_inputs()
print(f"Subnet adjacency list: {graph}")
# Block 5
broadcast_tree_algorithm(graph, num_hosts)
except ValueError as e:
print(f"Error: {e}")
Download Link from official Wireshark server: Link
Know about Wireshark:
It is an open-source network protocol analyzer used for capturing and analyzing network traffic in real-time.
It allows users to inspect packets transmitted over a network, providing detailed insights into protocols, data payloads, and network performance.
Wireshark is widely used for network troubleshooting, security analysis, protocol development, and education.
Packet Capture: Captures packets from wired or wireless networks.
Protocol Analysis: Supports detailed inspection of hundreds of protocols (e.g., TCP, UDP, HTTP, DNS).
Filtering: Allows users to filter packets based on criteria like source/destination IP, port, or protocol.
Graphical Interface: Provides a user-friendly GUI to visualize and analyze packet data.
Open-Source: Free to use, with an active community for updates and support.
Cross-Platform: Runs on Windows, macOS, Linux, and other operating systems.
MythBusters:
Do You Need Two Separate OSes?
No, you don’t need two separate operating systems to run Wireshark on a Windows PC. Wireshark works seamlessly on a single Windows OS, capturing and analyzing packets from the network interfaces available on your PC.
Why you might think two OSes are needed:
If you’re analyzing traffic between two systems (e.g., a client and a server), you can still do this on a single Windows PC running Wireshark, as long as the PC is part of the network or can capture traffic (e.g., via a switch with port mirroring).