Hi, This message is part of an off-list discussion I've been having with Toerless, but I hope it's of general interest. The topic is how GRASP will "see" the network when the ACP is running. I've been concerned that the ACP draft isn't specific enough about this: what interfaces, sockets and socket options will GRASP use to communicate over the ACP.
Partly this is due to my own ignorance about practical aspects of VRFs. So, the rest of this message is about exactly how the current GRASP prototype handles interfaces and sockets. Hopefully with this information, the ACP team can explain how this needs to change in the presence of the ACP. I hope it's only the "WHO AM I" section that needs to change. The prototype code is documented at https://www.cs.auckland.ac.nz/~brian/graspy/graspy.pdf and the Python3 code itself is at https://www.cs.auckland.ac.nz/~brian/graspy/grasp.py (under Simplified BSD license). PART 1: WHO AM I? When GRASP starts up, the first thing it does is discover what network interfaces and link-local IPv6 addresses it has, and what global-scope IPv6 address it will use. The way this is done is different between Windows (Winsock) systems and POSIX systems. In this message I focus on POSIX (tested on Linux). (This part is actually a lot easier in Winsock, but completely different.) This is done by a function _get_my_address(). The guts of it, skipping failure cases: for _iid, _ in socket.if_nameindex(): with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as _s: try: _s.connect(('fe80::100', 4096, 0, _iid)) _addr = _s.getsockname()[0] if '%' in _addr: _addr, _zid = _addr.split('%') #strip any Zone ID _loc = ipaddress.IPv6Address(_addr) if _loc.is_link_local: _ll_zone_ids.append([_zid, _loc]) except: pass #Convert interface (Zone) IDs to indexes _ll_zone_ids = [[socket.if_nametoindex(zid), loc] for zid, loc in _ll_zone_ids] #Now _ll_zone_ids[] contains [interface-index, LL-address] for each interface #Get own IPv6 address somewhat portably... _s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) try: #This is a hack. We send a bogon to a site-local multicast #address (reserved by IANA for 'any private experiment'). #Then we can find the sending address from the socket. #Note that this used to use a bogus global unicast address #('2001:db8:f000:baaa:f000:baaa:f000:baaa') but that fails in #case of a ULA prefix with no default IPv6 route. # #To find a non-hack solution, google for 'getnifs.py' _s.connect(('ff05::114', GRASP_LISTEN_PORT)) _s.send(b'0',0) except: pass _sn = _s.getsockname()[0] _s.close() if (not '%' in _sn) and _sn != '::': _new_locator = ipaddress.IPv6Address(_sn) #_new_locator is the IPv6 address we will use PART 2: MULTICASTING GRASP needs to send link-local multicasts separately to each interface. So it needs to create a socket for each interface for this purpose. for _x in _ll_zone_ids: _make_mcssock(_x[0]) where the function _make_mcssock(ifi) does this: mcssock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) mcssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) mcssock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, struct.pack('@I', ifi)) if not listen_self: #don't listen to yourself talking mcssock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, 0) mcssock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1) _mcssocks.append([ifi, mcssock]) so now whenever we need to send a multicast packet on interface ifi, we do something like: _mcssocks[ifi][1].sendto(msgbytes,0,(str(ALL_GRASP_NEIGHBOR_6), GRASP_LISTEN_PORT)) PART 3: LISTENING FOR MULTICASTS GRASP also needs to listen for incoming LL multicasts. That's done by a separate thread called _mclisten(). Simplifying the code a bit: mcrsock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) mcrsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) mcrsock.bind(('',GRASP_LISTEN_PORT)) #join LL multicast group on all interfaces for x in _ll_zone_ids: mreq = ALL_GRASP_NEIGHBOR_6.packed + struct.pack('@I', x[0]) mcrsock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) mcrsock.settimeout(120) while True: try: rawmsg, send_addr = mcrsock.recvfrom(GRASP_DEF_MAX_SIZE) if "%" in send_addr[0]: a,b = send_addr[0].split('%') saddr = ipaddress.IPv6Address(a) else: saddr = ipaddress.IPv6Address(send_addr[0]) sport = send_addr[1] ifn = send_addr[3] At this point we have the raw message in rawmsg, the interface number in ifn, the source address in saddr and the source port in sport. PART 4: LISTENING FOR TCP GRASP needs to listen for incoming discovery responses via TCP. This is fairly straightforward use of a normal socket so I will miss out the details -- see function _init_drsocks(i) and the class of threads _drlisten(threading.Thread) socket.socket(socket.AF_INET6, socket.SOCK_STREAM) setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) bind(('',_port)) listen(5) accept() recvfrom(GRASP_DEF_MAX_SIZE) There is one tricky bit - we bind the TCP socket to the port number used for *sending* discovery multicasts on the same interface. That's the point Michael Richardson and I discussed a few weeks back, so I won't repeat it here. The other TCP sockets used during GRASP synchronization and negotiation are completely straightforward. Regards Brian Carpenter _______________________________________________ Anima mailing list [email protected] https://www.ietf.org/mailman/listinfo/anima
