#!/usr/bin/env python3 # Multicast client # Adapted from: http://chaos.weblogs.us/archives/164 import socket import os from os import path import struct ANY = "0.0.0.0" MCAST_ADDR = "239.192.76.84" MCAST_PORT = 1954 # Create a UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # IPPROTO_UDP could just be 0 # Allow multiple sockets to use the same PORT number sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1) # Bind to the port that we know will receive multicast data sock.bind((ANY,MCAST_PORT)) # Tell the kernel that we want to add ourselves to a multicast group # The address for the multicast group is the third param status = sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MCAST_ADDR) + socket.inet_aton(ANY)) while 1: data, addr = sock.recvfrom(1024) if len(data) < 4: continue data = data[4:] # Trim down to LLAP packet (not "frame") if len(data) < 3: continue llap_dest_node = data[0] llap_src_node = data[1] llap_proto_type = data[2] data = data[3:] # Trim down to LLAP payload if llap_proto_type == 1: # ddp, short if len(data) < 5: continue ddp_len, ddp_dest_socket, ddp_src_socket, ddp_proto_type = struct.unpack_from('>HBBB', data) data = data[5:ddp_len] elif llap_proto_type == 2: # ddp, long (what should we do with this extra information?) if len(data) < 13: continue ddp_len, ddp_cksum, ddp_dest_net, ddp_src_net, ddp_dest_node, ddp_src_node, ddp_dest_socket, ddp_src_socket, ddp_proto_type = struct.unpack_from('>4H5B', data) ddp_hop_count = (ddp_len >> 10) & 0xF ddp_len &= 0x3FF data = data[13:ddp_len] print(ddp_src_net, ddp_dest_net) else: # llap control packet -- can probably ignore! continue print(f'datagram {llap_proto_type} {llap_src_node}:{ddp_src_socket}->{llap_dest_node}:{ddp_dest_socket}') if ddp_proto_type == 2: # NBP if len(data) < 2: continue nbp_func = data[0] >> 4 nbp_tuple_cnt = data[0] & 0xF nbp_id = data[1] data = data[2:] nbp_tuples = [] while data and len(nbp_tuples) < nbp_tuple_cnt: if len(data) < 5: break this_tuple = list(struct.unpack_from('>HBBB', data)) data = data[5:] for i in range(3): # This should be coded more defensively, perhaps using exceptions this_tuple.append(data[1:1+data[0]].decode('mac_roman')) data = data[1+data[0]:] nbp_tuples.append(tuple(this_tuple)) print(' NBP func', nbp_func, 'id', nbp_id) for t in nbp_tuples: print(f' net:node:sock={t[0]}:{t[1]}:{t[2]} enum={t[3]} object:type@zone={t[4]}:{t[5]}@{t[6]}') elif ddp_proto_type == 10: # ATBOOT if len(data) < 2: print('malformed short packet %r' % data.hex()) continue boot_type, boot_vers = struct.unpack_from('>BB', data) data = data[2:] if boot_type == 1: print(' ATBOOT "syn"', data.hex()) elif boot_type == 2: print(' ATBOOT "ack"', data.hex()) elif boot_type == 3: print(' ATBOOT image request', data.hex()) elif boot_type == 4: print(' ATBOOT image reply', data.hex()) elif boot_type == 128: a, b, c, d = struct.unpack_from('>HLL32p', data) d = d.decode('mac_roman') print(f' ATBOOT Elliot block seq={hex(a)} blkIdx={hex(b)} byteLen={hex(c)} img={repr(d)}') elif boot_type == 129: a, b = struct.unpack_from('>HL', data) print(f' ATBOOT Elliot reply seq={hex(a)} relByteIdx={hex(b)}') else: print(' ATBOOT', boot_type, boot_vers, data.hex()) else: print('Totally unknown DDP type', ddp_proto_type) print(' ' + data.hex())