From acf67a6c819e9665c9b9970462229ae70cbc5089 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 12 Jul 2019 17:04:14 -0400 Subject: [PATCH 01/13] Implement node search for confluent This is a viable client to find and get ones node identity. Node credentials are a separate concern, to be handled later. --- confluent_server/confluent/discovery/core.py | 7 +- .../confluent/discovery/protocols/ssdp.py | 110 +++++++++++------- misc/copernicus.c | 34 +++++- 3 files changed, 102 insertions(+), 49 deletions(-) diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 10db839b..5b90dcd4 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -65,7 +65,7 @@ import base64 import confluent.config.configmanager as cfm import confluent.collective.manager as collective import confluent.discovery.protocols.pxe as pxe -#import confluent.discovery.protocols.ssdp as ssdp +import confluent.discovery.protocols.ssdp as ssdp import confluent.discovery.protocols.slp as slp import confluent.discovery.handlers.imm as imm import confluent.discovery.handlers.pxe as pxeh @@ -857,6 +857,8 @@ def get_nodename_from_chained_smms(cfg, handler, info): nodename = newnodename return nodename +def get_node_by_uuid(uuid): + return nodes_by_uuid.get(uuid, None) def get_nodename_from_enclosures(cfg, info): nodename = None @@ -1213,8 +1215,7 @@ def start_detection(): if rechecker is None: rechecktime = util.monotonic_time() + 900 rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg) - - # eventlet.spawn_n(ssdp.snoop, safe_detected) + eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid) def stop_autosense(): for watcher in list(autosensors): diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index 3ad43111..fdb355bd 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -30,9 +30,11 @@ import confluent.neighutil as neighutil import confluent.util as util +import confluent.log as log import eventlet.green.select as select import eventlet.green.socket as socket import struct +import traceback mcastv4addr = '239.255.255.250' mcastv6addr = 'ff02::c' @@ -51,7 +53,7 @@ def scan(services, target=None): yield rply -def snoop(handler, byehandler=None): +def snoop(handler, byehandler=None, protocol=None, uuidlookup=None): """Watch for SSDP notify messages The handler shall be called on any service coming online. @@ -67,6 +69,7 @@ def snoop(handler, byehandler=None): # Normally, I like using v6/v4 agnostic socket. However, since we are # dabbling in multicast wizardry here, such sockets can cause big problems, # so we will have two distinct sockets + tracelog = log.Logger('trace') known_peers = set([]) net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) @@ -85,50 +88,73 @@ def snoop(handler, byehandler=None): net6.bind(('', 1900)) peerbymacaddress = {} while True: - newmacs = set([]) - machandlers = {} - r, _, _ = select.select((net4, net6), (), (), 60) - neighutil.update_neigh() - while r: - for s in r: - (rsp, peer) = s.recvfrom(9000) - rsp = rsp.split('\r\n') - method, _, _ = rsp[0].split(' ', 2) - if method == 'NOTIFY': - ip = peer[0].partition('%')[0] - if ip not in neighutil.neightable: - continue - if peer in known_peers: - continue - mac = neighutil.neightable[ip] - known_peers.add(peer) - newmacs.add(mac) - if mac in peerbymacaddress: - peerbymacaddress[mac]['peers'].append(peer) - else: - peerbymacaddress[mac] = { - 'hwaddr': mac, - 'peers': [peer], - } - peerdata = peerbymacaddress[mac] + try: + newmacs = set([]) + machandlers = {} + r, _, _ = select.select((net4, net6), (), (), 60) + neighutil.update_neigh() + while r: + for s in r: + (rsp, peer) = s.recvfrom(9000) + rsp = rsp.split('\r\n') + method, _, _ = rsp[0].split(' ', 2) + if method == 'NOTIFY': + ip = peer[0].partition('%')[0] + if ip not in neighutil.neightable: + continue + if peer in known_peers: + continue + mac = neighutil.neightable[ip] + known_peers.add(peer) + newmacs.add(mac) + if mac in peerbymacaddress: + peerbymacaddress[mac]['peers'].append(peer) + else: + peerbymacaddress[mac] = { + 'hwaddr': mac, + 'peers': [peer], + } + peerdata = peerbymacaddress[mac] + for headline in rsp[1:]: + if not headline: + continue + header, _, value = headline.partition(':') + header = header.strip() + value = value.strip() + if header == 'NT': + peerdata['service'] = value + elif header == 'NTS': + if value == 'ssdp:byebye': + machandlers[mac] = byehandler + elif value == 'ssdp:alive': + machandlers[mac] = None # handler + elif method == 'M-SEARCH': + if not uuidlookup: + continue + #ip = peer[0].partition('%')[0] for headline in rsp[1:]: if not headline: continue - header, _, value = headline.partition(':') - header = header.strip() - value = value.strip() - if header == 'NT': - peerdata['service'] = value - elif header == 'NTS': - if value == 'ssdp:byebye': - machandlers[mac] = byehandler - elif value == 'ssdp:alive': - machandlers[mac] = handler - r, _, _ = select.select((net4, net6), (), (), 0.1) - for mac in newmacs: - thehandler = machandlers.get(mac, None) - if thehandler: - thehandler(peerbymacaddress[mac]) + headline = headline.partition(':') + if len(headline) < 3: + continue + if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'): + for query in headline[-1].split('/'): + if query.startswith('uuid='): + curruuid = query.split('=', 1)[1].lower() + node = uuidlookup(curruuid) + if not node: + break + reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}'.format(node) + s.sendto(reply, peer) + r, _, _ = select.select((net4, net6), (), (), 0.2) + for mac in newmacs: + thehandler = machandlers.get(mac, None) + if thehandler: + thehandler(peerbymacaddress[mac]) + except Exception: + tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, + event=log.Events.stacktrace) def _find_service(service, target): diff --git a/misc/copernicus.c b/misc/copernicus.c index a9943f11..0a1a1bc0 100644 --- a/misc/copernicus.c +++ b/misc/copernicus.c @@ -68,6 +68,8 @@ int main(int argc, char* argv[]) { struct sockaddr_in6 addr, dst; struct sockaddr_in addr4, dst4; char msg[1024]; + char *nodenameidx; + char nodename[1024]; char lastmsg[1024]; int ifidx, offset; fd_set rfds; @@ -141,19 +143,43 @@ int main(int argc, char* argv[]) { if (ifidx == -1) perror("Unable to select"); if (ifidx) { if (FD_ISSET(n4, &rfds)) { - recvfrom(n4, msg, 1024, 0, (struct sockaddr *)&dst4, &dst4size); + memset(msg, 0, 1024); + /* Deny packet access to the last 24 bytes to assure null */ + recvfrom(n4, msg, 1000, 0, (struct sockaddr *)&dst4, &dst4size); + if (nodenameidx = strstr(msg, "NODENAME: ")) { + nodenameidx += 10; + strncpy(nodename, nodenameidx, 1024); + nodenameidx = strstr(nodenameidx, "\r"); + if (nodenameidx) { nodenameidx[0] = 0; } + printf("NODENAME: %s\n", nodename); + } + memset(msg, 0, 1024); inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size); /* Take measure from printing out the same ip twice in a row */ if (strncmp(lastmsg, msg, 1024) != 0) { - printf("%s\n", msg); + printf("MANAGER: %s\n", msg); strncpy(lastmsg, msg, 1024); } } if (FD_ISSET(ns, &rfds)) { - recvfrom(ns, msg, 1024, 0, (struct sockaddr *)&dst, &dstsize); + memset(msg, 0, 1024); + /* Deny packet access to the last 24 bytes to assure null */ + recvfrom(ns, msg, 1000, 0, (struct sockaddr *)&dst, &dstsize); + if (nodenameidx = strstr(msg, "NODENAME: ")) { + nodenameidx += 10; + strncpy(nodename, nodenameidx, 1024); + nodenameidx = strstr(nodenameidx, "\r"); + if (nodenameidx) { nodenameidx[0] = 0; } + printf("NODENAME: %s\n", nodename); + } + memset(msg, 0, 1024); inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize); if (strncmp(lastmsg, msg, 1024) != 0) { - printf("%s\n", msg); + printf("MANAGER: %s", msg); + if (strncmp(msg, "fe80::", 6) == 0) { + printf("%%%u", dst.sin6_scope_id); + } + printf("\n"); strncpy(lastmsg, msg, 1024); } } From 54f36e259fefc252efa51da6df200105999ad722 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 15 Jul 2019 10:30:36 -0400 Subject: [PATCH 02/13] Avoid copernicus printout of more duplicate data Separate v4 and v6 results for better chance of success in dropping duplicate packets. --- misc/copernicus.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/misc/copernicus.c b/misc/copernicus.c index 0a1a1bc0..f60ff3b8 100644 --- a/misc/copernicus.c +++ b/misc/copernicus.c @@ -70,7 +70,9 @@ int main(int argc, char* argv[]) { char msg[1024]; char *nodenameidx; char nodename[1024]; + char lastnodename[1024]; char lastmsg[1024]; + char last6msg[1024]; int ifidx, offset; fd_set rfds; struct timeval tv; @@ -82,6 +84,10 @@ int main(int argc, char* argv[]) { memset(&addr, 0, sizeof(addr)); memset(&dst, 0, sizeof(dst)); memset(&dst4, 0, sizeof(dst4)); + memset(nodename, 0, 1024); + memset(lastnodename, 0, 1024); + memset(lastmsg, 0, 1024); + memset(last6msg, 0, 1024); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons(190); @@ -151,7 +157,10 @@ int main(int argc, char* argv[]) { strncpy(nodename, nodenameidx, 1024); nodenameidx = strstr(nodenameidx, "\r"); if (nodenameidx) { nodenameidx[0] = 0; } - printf("NODENAME: %s\n", nodename); + if (strncmp(lastnodename, nodename, 1024) != 0) { + printf("NODENAME: %s\n", nodename); + strncpy(lastnodename, nodename, 1024); + } } memset(msg, 0, 1024); inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size); @@ -170,17 +179,20 @@ int main(int argc, char* argv[]) { strncpy(nodename, nodenameidx, 1024); nodenameidx = strstr(nodenameidx, "\r"); if (nodenameidx) { nodenameidx[0] = 0; } - printf("NODENAME: %s\n", nodename); + if (strncmp(lastnodename, nodename, 1024) != 0) { + printf("NODENAME: %s\n", nodename); + strncpy(lastnodename, nodename, 1024); + } } memset(msg, 0, 1024); inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize); - if (strncmp(lastmsg, msg, 1024) != 0) { + if (strncmp(last6msg, msg, 1024) != 0) { printf("MANAGER: %s", msg); if (strncmp(msg, "fe80::", 6) == 0) { printf("%%%u", dst.sin6_scope_id); } printf("\n"); - strncpy(lastmsg, msg, 1024); + strncpy(last6msg, msg, 1024); } } } From 79f5dce6dcdfcae88235cb5d1ca2af341558e95b Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 12 Jul 2019 17:04:14 -0400 Subject: [PATCH 03/13] Implement node search for confluent This is a viable client to find and get ones node identity. Node credentials are a separate concern, to be handled later. --- confluent_server/confluent/discovery/core.py | 7 +- .../confluent/discovery/protocols/ssdp.py | 110 +++++++++++------- misc/copernicus.c | 34 +++++- 3 files changed, 102 insertions(+), 49 deletions(-) diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 10db839b..5b90dcd4 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -65,7 +65,7 @@ import base64 import confluent.config.configmanager as cfm import confluent.collective.manager as collective import confluent.discovery.protocols.pxe as pxe -#import confluent.discovery.protocols.ssdp as ssdp +import confluent.discovery.protocols.ssdp as ssdp import confluent.discovery.protocols.slp as slp import confluent.discovery.handlers.imm as imm import confluent.discovery.handlers.pxe as pxeh @@ -857,6 +857,8 @@ def get_nodename_from_chained_smms(cfg, handler, info): nodename = newnodename return nodename +def get_node_by_uuid(uuid): + return nodes_by_uuid.get(uuid, None) def get_nodename_from_enclosures(cfg, info): nodename = None @@ -1213,8 +1215,7 @@ def start_detection(): if rechecker is None: rechecktime = util.monotonic_time() + 900 rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg) - - # eventlet.spawn_n(ssdp.snoop, safe_detected) + eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid) def stop_autosense(): for watcher in list(autosensors): diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index 3ad43111..fdb355bd 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -30,9 +30,11 @@ import confluent.neighutil as neighutil import confluent.util as util +import confluent.log as log import eventlet.green.select as select import eventlet.green.socket as socket import struct +import traceback mcastv4addr = '239.255.255.250' mcastv6addr = 'ff02::c' @@ -51,7 +53,7 @@ def scan(services, target=None): yield rply -def snoop(handler, byehandler=None): +def snoop(handler, byehandler=None, protocol=None, uuidlookup=None): """Watch for SSDP notify messages The handler shall be called on any service coming online. @@ -67,6 +69,7 @@ def snoop(handler, byehandler=None): # Normally, I like using v6/v4 agnostic socket. However, since we are # dabbling in multicast wizardry here, such sockets can cause big problems, # so we will have two distinct sockets + tracelog = log.Logger('trace') known_peers = set([]) net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) @@ -85,50 +88,73 @@ def snoop(handler, byehandler=None): net6.bind(('', 1900)) peerbymacaddress = {} while True: - newmacs = set([]) - machandlers = {} - r, _, _ = select.select((net4, net6), (), (), 60) - neighutil.update_neigh() - while r: - for s in r: - (rsp, peer) = s.recvfrom(9000) - rsp = rsp.split('\r\n') - method, _, _ = rsp[0].split(' ', 2) - if method == 'NOTIFY': - ip = peer[0].partition('%')[0] - if ip not in neighutil.neightable: - continue - if peer in known_peers: - continue - mac = neighutil.neightable[ip] - known_peers.add(peer) - newmacs.add(mac) - if mac in peerbymacaddress: - peerbymacaddress[mac]['peers'].append(peer) - else: - peerbymacaddress[mac] = { - 'hwaddr': mac, - 'peers': [peer], - } - peerdata = peerbymacaddress[mac] + try: + newmacs = set([]) + machandlers = {} + r, _, _ = select.select((net4, net6), (), (), 60) + neighutil.update_neigh() + while r: + for s in r: + (rsp, peer) = s.recvfrom(9000) + rsp = rsp.split('\r\n') + method, _, _ = rsp[0].split(' ', 2) + if method == 'NOTIFY': + ip = peer[0].partition('%')[0] + if ip not in neighutil.neightable: + continue + if peer in known_peers: + continue + mac = neighutil.neightable[ip] + known_peers.add(peer) + newmacs.add(mac) + if mac in peerbymacaddress: + peerbymacaddress[mac]['peers'].append(peer) + else: + peerbymacaddress[mac] = { + 'hwaddr': mac, + 'peers': [peer], + } + peerdata = peerbymacaddress[mac] + for headline in rsp[1:]: + if not headline: + continue + header, _, value = headline.partition(':') + header = header.strip() + value = value.strip() + if header == 'NT': + peerdata['service'] = value + elif header == 'NTS': + if value == 'ssdp:byebye': + machandlers[mac] = byehandler + elif value == 'ssdp:alive': + machandlers[mac] = None # handler + elif method == 'M-SEARCH': + if not uuidlookup: + continue + #ip = peer[0].partition('%')[0] for headline in rsp[1:]: if not headline: continue - header, _, value = headline.partition(':') - header = header.strip() - value = value.strip() - if header == 'NT': - peerdata['service'] = value - elif header == 'NTS': - if value == 'ssdp:byebye': - machandlers[mac] = byehandler - elif value == 'ssdp:alive': - machandlers[mac] = handler - r, _, _ = select.select((net4, net6), (), (), 0.1) - for mac in newmacs: - thehandler = machandlers.get(mac, None) - if thehandler: - thehandler(peerbymacaddress[mac]) + headline = headline.partition(':') + if len(headline) < 3: + continue + if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'): + for query in headline[-1].split('/'): + if query.startswith('uuid='): + curruuid = query.split('=', 1)[1].lower() + node = uuidlookup(curruuid) + if not node: + break + reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}'.format(node) + s.sendto(reply, peer) + r, _, _ = select.select((net4, net6), (), (), 0.2) + for mac in newmacs: + thehandler = machandlers.get(mac, None) + if thehandler: + thehandler(peerbymacaddress[mac]) + except Exception: + tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, + event=log.Events.stacktrace) def _find_service(service, target): diff --git a/misc/copernicus.c b/misc/copernicus.c index a9943f11..0a1a1bc0 100644 --- a/misc/copernicus.c +++ b/misc/copernicus.c @@ -68,6 +68,8 @@ int main(int argc, char* argv[]) { struct sockaddr_in6 addr, dst; struct sockaddr_in addr4, dst4; char msg[1024]; + char *nodenameidx; + char nodename[1024]; char lastmsg[1024]; int ifidx, offset; fd_set rfds; @@ -141,19 +143,43 @@ int main(int argc, char* argv[]) { if (ifidx == -1) perror("Unable to select"); if (ifidx) { if (FD_ISSET(n4, &rfds)) { - recvfrom(n4, msg, 1024, 0, (struct sockaddr *)&dst4, &dst4size); + memset(msg, 0, 1024); + /* Deny packet access to the last 24 bytes to assure null */ + recvfrom(n4, msg, 1000, 0, (struct sockaddr *)&dst4, &dst4size); + if (nodenameidx = strstr(msg, "NODENAME: ")) { + nodenameidx += 10; + strncpy(nodename, nodenameidx, 1024); + nodenameidx = strstr(nodenameidx, "\r"); + if (nodenameidx) { nodenameidx[0] = 0; } + printf("NODENAME: %s\n", nodename); + } + memset(msg, 0, 1024); inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size); /* Take measure from printing out the same ip twice in a row */ if (strncmp(lastmsg, msg, 1024) != 0) { - printf("%s\n", msg); + printf("MANAGER: %s\n", msg); strncpy(lastmsg, msg, 1024); } } if (FD_ISSET(ns, &rfds)) { - recvfrom(ns, msg, 1024, 0, (struct sockaddr *)&dst, &dstsize); + memset(msg, 0, 1024); + /* Deny packet access to the last 24 bytes to assure null */ + recvfrom(ns, msg, 1000, 0, (struct sockaddr *)&dst, &dstsize); + if (nodenameidx = strstr(msg, "NODENAME: ")) { + nodenameidx += 10; + strncpy(nodename, nodenameidx, 1024); + nodenameidx = strstr(nodenameidx, "\r"); + if (nodenameidx) { nodenameidx[0] = 0; } + printf("NODENAME: %s\n", nodename); + } + memset(msg, 0, 1024); inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize); if (strncmp(lastmsg, msg, 1024) != 0) { - printf("%s\n", msg); + printf("MANAGER: %s", msg); + if (strncmp(msg, "fe80::", 6) == 0) { + printf("%%%u", dst.sin6_scope_id); + } + printf("\n"); strncpy(lastmsg, msg, 1024); } } From c8d0009dac5856964349251e2acc490c7dc0055b Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 15 Jul 2019 10:30:36 -0400 Subject: [PATCH 04/13] Avoid copernicus printout of more duplicate data Separate v4 and v6 results for better chance of success in dropping duplicate packets. --- misc/copernicus.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/misc/copernicus.c b/misc/copernicus.c index 0a1a1bc0..f60ff3b8 100644 --- a/misc/copernicus.c +++ b/misc/copernicus.c @@ -70,7 +70,9 @@ int main(int argc, char* argv[]) { char msg[1024]; char *nodenameidx; char nodename[1024]; + char lastnodename[1024]; char lastmsg[1024]; + char last6msg[1024]; int ifidx, offset; fd_set rfds; struct timeval tv; @@ -82,6 +84,10 @@ int main(int argc, char* argv[]) { memset(&addr, 0, sizeof(addr)); memset(&dst, 0, sizeof(dst)); memset(&dst4, 0, sizeof(dst4)); + memset(nodename, 0, 1024); + memset(lastnodename, 0, 1024); + memset(lastmsg, 0, 1024); + memset(last6msg, 0, 1024); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons(190); @@ -151,7 +157,10 @@ int main(int argc, char* argv[]) { strncpy(nodename, nodenameidx, 1024); nodenameidx = strstr(nodenameidx, "\r"); if (nodenameidx) { nodenameidx[0] = 0; } - printf("NODENAME: %s\n", nodename); + if (strncmp(lastnodename, nodename, 1024) != 0) { + printf("NODENAME: %s\n", nodename); + strncpy(lastnodename, nodename, 1024); + } } memset(msg, 0, 1024); inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size); @@ -170,17 +179,20 @@ int main(int argc, char* argv[]) { strncpy(nodename, nodenameidx, 1024); nodenameidx = strstr(nodenameidx, "\r"); if (nodenameidx) { nodenameidx[0] = 0; } - printf("NODENAME: %s\n", nodename); + if (strncmp(lastnodename, nodename, 1024) != 0) { + printf("NODENAME: %s\n", nodename); + strncpy(lastnodename, nodename, 1024); + } } memset(msg, 0, 1024); inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize); - if (strncmp(lastmsg, msg, 1024) != 0) { + if (strncmp(last6msg, msg, 1024) != 0) { printf("MANAGER: %s", msg); if (strncmp(msg, "fe80::", 6) == 0) { printf("%%%u", dst.sin6_scope_id); } printf("\n"); - strncpy(lastmsg, msg, 1024); + strncpy(last6msg, msg, 1024); } } } From b7b7fd82eb606ed51a81a5210631aa1d6f33f060 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 16 Jul 2019 07:51:24 -0400 Subject: [PATCH 05/13] ECHO a packet back to manager In a later phase, we will want assurances that the neighbor table was populated. Since here we have the sockaddr handy, it makes a lot of sense to take the opportunity to blind fire a packet back. No reply is expected, just enough to trigger arp/neighbor solicitation. --- misc/copernicus.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/misc/copernicus.c b/misc/copernicus.c index f60ff3b8..b727de2a 100644 --- a/misc/copernicus.c +++ b/misc/copernicus.c @@ -162,10 +162,11 @@ int main(int argc, char* argv[]) { strncpy(lastnodename, nodename, 1024); } } - memset(msg, 0, 1024); + memset(msg, 0, 1024); inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size); /* Take measure from printing out the same ip twice in a row */ if (strncmp(lastmsg, msg, 1024) != 0) { + sendto(n4, "PING", 4, 0, (const struct sockaddr *)&dst4, dst4size); printf("MANAGER: %s\n", msg); strncpy(lastmsg, msg, 1024); } @@ -187,6 +188,7 @@ int main(int argc, char* argv[]) { memset(msg, 0, 1024); inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize); if (strncmp(last6msg, msg, 1024) != 0) { + sendto(ns, "PING", 4, 0, (const struct sockaddr *)&dst, dstsize); printf("MANAGER: %s", msg); if (strncmp(msg, "fe80::", 6) == 0) { printf("%%%u", dst.sin6_scope_id); From 43480c2e3b6767a2d0c4e16b2bb58f13721e23ce Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 19 Jul 2019 14:09:51 -0400 Subject: [PATCH 06/13] Fix nodesensors -n with csv --- confluent_client/bin/nodesensors | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index c86e1256..ccf8b917 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -158,7 +158,7 @@ def sensorpass(showout=True, appendtime=False): def format_csv(csvwriter, orderedsensors, resdata, showtime=True): for nodekey in resdata: if showtime: - if showtime.is_integer(): + if isinstance(showtime, int): rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S'), nodekey] else: rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S.') + @@ -199,7 +199,7 @@ def main(): orderedsensors.append(name) orderedsensors.sort() for name in orderedsensors: - headernames.append(sensorheaders[name]) + headernames.append(sensorheaders[name].encode('utf-8')) if options.csv: linebyline = False csvwriter = csv.writer(sys.stdout) From c1abeaff044b3abe63defa7ed8eff348cc26aefb Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 9 Aug 2019 16:43:48 -0400 Subject: [PATCH 07/13] Convert concept to IP based This leaves the door open for routing if supported. The server shall restrict IP_TTL to denote acceptable distance from the manager to accept. --- misc/clortho.c | 127 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/misc/clortho.c b/misc/clortho.c index bfe48d00..69fdaa65 100644 --- a/misc/clortho.c +++ b/misc/clortho.c @@ -2,84 +2,123 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include -#define OUI_ETHERTYPE 0x88b7 #define MAXPACKET 1024 -#define CHDR "\xa4\x8c\xdb\x30\x01" -int get_interface_index(int sock, char *interface) { - struct ifreq req; - memset(&req, 0, sizeof(req)); - strncpy(req.ifr_name, interface, IFNAMSIZ); - if (ioctl(sock, SIOCGIFINDEX, &req) < 0) { - return -1; - } - return req.ifr_ifindex; -} +static const char cryptalpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"; -unsigned char* genpasswd() { +unsigned char* genpasswd(int len) { unsigned char * passwd; int urandom; - passwd = calloc(33, sizeof(char)); + passwd = calloc(len + 1, sizeof(char)); urandom = open("/dev/urandom", O_RDONLY); - read(urandom, passwd, 32); + read(urandom, passwd, len); close(urandom); - for (urandom = 0; urandom < 32; urandom++) { - passwd[urandom] = 0x30 + (passwd[urandom] >> 2); + for (urandom = 0; urandom < len; urandom++) { + passwd[urandom] = cryptalpha[passwd[urandom] >> 2]; } return passwd; } -int parse_macaddr(char* macaddr) { - unsigned char *curr; - unsigned char idx; - curr = strtok(macaddr, ":-"); - idx = 0; - - while (curr != NULL) { - macaddr[idx++] = strtoul(curr, NULL, 16); - curr = strtok(NULL, ":-"); - } - -} int main(int argc, char* argv[]) { int sock; - int iface; + unsigned char currlen, currtype; unsigned char* passwd; + unsigned char* cryptedpass; unsigned char* macaddr; - + struct timeval timeout; + struct addrinfo hints; + struct addrinfo *addrs; + struct addrinfo *curr; + struct sockaddr_in net4bind; + struct sockaddr_in net6bind; unsigned char buffer[MAXPACKET]; + memset(&hints, 0, sizeof(struct addrinfo)); + memset(&net4bind, 0, sizeof(struct sockaddr_in)); + memset(&net6bind, 0, sizeof(struct sockaddr_in)); + memset(&buffer, 0, MAXPACKET); + memset(&timeout, 0, sizeof(struct timeval)); + timeout.tv_sec = 10; + net4bind.sin_port = htons(302); + net6bind.sin_port = htons(302); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; - passwd = genpasswd(); + passwd = genpasswd(32); + memset(buffer, 0, MAXPACKET); + strncpy(buffer, "$5$", 3); + cryptedpass = genpasswd(8); + strncpy(buffer + 3, cryptedpass, 8); + free(cryptedpass); + cryptedpass = crypt(passwd, buffer); if (argc < 3) { - fprintf(stderr, "Missing interface name and target MAC\n"); + fprintf(stderr, "Missing node name and manager\n"); exit(1); } - printf("%s\n", argv[2]); - parse_macaddr(argv[2]); - printf("%s\n", argv[2]); - sock = socket(AF_PACKET, SOCK_DGRAM, htons(OUI_ETHERTYPE)); - if (sock < 0) { - fprintf(stderr, "Unable to open socket (run as root?)\n"); + sock = getaddrinfo(argv[2], "301", &hints, &addrs); + if (sock != 0) { + fprintf(stderr, "Error trying to resolve %s\n", argv[2]); exit(1); } - iface = get_interface_index(sock, argv[1]); - if (iface < 0) { - fprintf(stderr, "Unable to find specified interface '%s'\n", argv[1]); + for (curr = addrs; curr != NULL; curr = curr->ai_next) { + sock = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol); + if (sock < 0) continue; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + if (curr->ai_family == AF_INET) { + bind(sock, (struct sockaddr*)&net4bind, sizeof(struct sockaddr_in)); + } else if (curr->ai_family == AF_INET6) { + bind(sock, (struct sockaddr*)&net6bind, sizeof(struct sockaddr_in6)); + } else { + continue; + } + if (connect(sock, curr->ai_addr, curr->ai_addrlen) == 0) break; + } + if (curr == NULL) { + fprintf(stderr, "Unable to reach %s\n", argv[2]); exit(1); } - - + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + freeaddrinfo(addrs); + read(sock, buffer, 8); + if (memcmp(buffer, "\xc2\xd1-\xa8\x80\xd8j\xba", 8) != 0) { + fprintf(stderr, "Unrecognized server\n"); + exit(1); + } + dprintf(sock, "\x01%c%s", strlen(argv[1]), argv[1]); + write(sock, "\x00\x00", 2); + memset(buffer, 0, MAXPACKET); + read(sock, buffer, 2); + while (buffer[0] != 255) { + currtype = buffer[0]; + currlen = buffer[1]; + memset(buffer, 0, MAXPACKET); + if (currlen) { + read(sock, buffer, currlen); // Max is 255, well under MAX_PACKET + } + if (currtype == 2) { + dprintf(sock, "\x03%c%s\x04%c%s", strlen(buffer), buffer, strlen(cryptedpass), cryptedpass); + write(sock, "\x00\x00", 2); + } else if (currtype == 5) { + printf(passwd); + printf("\n"); + exit(0); + } + buffer[0] = 255; + read(sock, buffer, 2); + } + fprintf(stderr, "Password was not accepted\n"); + exit(1); } - From aaf5aebff799f43537876f65a9370cfa54eb1c5d Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 12 Aug 2019 15:24:51 -0400 Subject: [PATCH 08/13] Fix for tokens with null bytes Since the server may employ the full range of byte values in the echo token, use that length and the buffer to avoid nulls truncating the token. --- misc/clortho.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/misc/clortho.c b/misc/clortho.c index 69fdaa65..73753fd2 100644 --- a/misc/clortho.c +++ b/misc/clortho.c @@ -109,7 +109,9 @@ int main(int argc, char* argv[]) { read(sock, buffer, currlen); // Max is 255, well under MAX_PACKET } if (currtype == 2) { - dprintf(sock, "\x03%c%s\x04%c%s", strlen(buffer), buffer, strlen(cryptedpass), cryptedpass); + dprintf(sock, "\x03%c", currlen); + write(sock, buffer, currlen); + dprintf(sock, "\x04%c%s", strlen(cryptedpass), cryptedpass); write(sock, "\x00\x00", 2); } else if (currtype == 5) { printf(passwd); From 55a0aab5488da779ee238f34bd4fb6a557592f7a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 12 Aug 2019 15:28:00 -0400 Subject: [PATCH 09/13] Add node api key and arming This is the groundwork for having node authentication. The intent is for calling code to modify api.armed if the administrator wants to opt into a one-time set of credential. This design as is currently does not fit a stateless deploy model. That may suggest an additional manual step for a fully stateless model. Alternatively adding support for credential persistence through sealing to a node's TPM, which would allow more freely retrievable node credentials. --- confluent_server/confluent/config/attributes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index a1d23b6c..73e37e31 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -97,6 +97,15 @@ node = { 'description': ('Classification of node as server or switch'), 'validvalues': ('switch', 'server'), }, + 'api.key': { + 'description': ('Crypt of api key for self api requests by node'), + }, + 'api.armed': { + 'description': ('Indicates whether an insecure api key request is allowed. ' + 'The format is an expiration time in ISO8601 format. When ' + 'the indicated time passes or the first time a node claims ' + 'the key, key grants will not be allowed.'), + } #'id': { # 'description': ('Numeric identifier for node') #}, From 2e03b662ea3eb1186c9a37c1160eb03d33021c8f Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 12 Aug 2019 15:31:24 -0400 Subject: [PATCH 10/13] Add a credential server implelmentation This implements the api.armed logic and storing key when using the clortho credential agent. --- confluent_server/confluent/credserver.py | 91 ++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 confluent_server/confluent/credserver.py diff --git a/confluent_server/confluent/credserver.py b/confluent_server/confluent/credserver.py new file mode 100644 index 00000000..bc6364d2 --- /dev/null +++ b/confluent_server/confluent/credserver.py @@ -0,0 +1,91 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2019 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import confluent.config.configmanager as cfm +import datetime +import eventlet +import eventlet.green.socket as socket +import eventlet.greenpool +import os + +class CredServer(object): + def __init__(self, bindhost='::', bindport=301, ttl=1): + self.srv = socket.socket(socket.AF_INET6) + self.srv.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl) + self.srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.srv.bind((bindhost, bindport)) + self.srv.listen(32) + self.gpool = eventlet.greenpool.GreenPool(256) + self.cfm = cfm.ConfigManager(None) + self.runtime = eventlet.spawn(self.listen) + + def listen(self): + while True: + client, info = self.srv.accept() + if info[1] > 1023: + client.close() + continue + self.gpool.spawn_n(self.handle_client, client) + + def handle_client(self, client): + client.send('\xc2\xd1-\xa8\x80\xd8j\xba') + tlv = bytearray(client.recv(2)) + if tlv[0] != 1: + client.close() + return + nodename = client.recv(tlv[1]) + tlv = bytearray(client.recv(2)) + apiarmed = self.cfm.get_node_attributes(nodename, 'api.armed') + apiarmed = apiarmed.get(nodename, {}).get('api.armed', {}).get('value', None) + if not apiarmed: + client.close() + return + now = datetime.datetime.utcnow() + expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ") + if now > expiry: + self.cfm.set_node_attributes({nodename: {'api.armed': ''}}) + client.close() + return + client.send(b'\x02\x20') + rttoken = os.urandom(32) + client.send(rttoken) + client.send('\x00\x00') + tlv = bytearray(client.recv(2)) + if tlv[0] != 3: + client.close() + return + echotoken = client.recv(tlv[1]) + if echotoken != rttoken: + client.close() + return + tlv = bytearray(client.recv(2)) + if tlv[0] != 4: + client.close() + return + echotoken = client.recv(tlv[1]) + self.cfm.set_node_attributes({nodename: {'api.key': echotoken, 'api.armed': ''}}) + client.recv(2) # drain end of message + client.send('\x05\x00') # report success + client.close() + +if __name__ == '__main__': + a = CredServer() + while True: + eventlet.sleep(86400) + + + + From aa059c6a4df598f9325bc96fa9051ef5b22f990a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 12 Aug 2019 16:19:35 -0400 Subject: [PATCH 11/13] Amend some formatting --- confluent_server/confluent/credserver.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/confluent_server/confluent/credserver.py b/confluent_server/confluent/credserver.py index bc6364d2..9f7d5f14 100644 --- a/confluent_server/confluent/credserver.py +++ b/confluent_server/confluent/credserver.py @@ -78,14 +78,10 @@ class CredServer(object): echotoken = client.recv(tlv[1]) self.cfm.set_node_attributes({nodename: {'api.key': echotoken, 'api.armed': ''}}) client.recv(2) # drain end of message - client.send('\x05\x00') # report success + client.send('\x05\x00') # report success client.close() if __name__ == '__main__': a = CredServer() while True: eventlet.sleep(86400) - - - - From 17a8ab32118deca9653da81f3b646f63c75ba2e0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 16 Oct 2019 13:25:21 -0400 Subject: [PATCH 12/13] Use python3 explicitly for building source The source distrobution on CentOS7 does not work well with python2. Using python3 to build the dist for python2 and python3 fixes issues with extra data being missed in packaging. --- confluent_server/buildrpm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/buildrpm b/confluent_server/buildrpm index 14071382..c4f347e8 100755 --- a/confluent_server/buildrpm +++ b/confluent_server/buildrpm @@ -6,7 +6,7 @@ fi ./makesetup VERSION=`cat VERSION` PKGNAME=$(basename $(pwd)) -python setup.py sdist > /dev/null 2>&1 +python3 setup.py sdist > /dev/null 2>&1 cp dist/*.tar.gz ~/rpmbuild/SOURCES sed -e 's/#VERSION#/'$VERSION/ $PKGNAME.spec.tmpl > ~/rpmbuild/SPECS/$PKGNAME.spec rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec 2> /dev/null |grep ^Wrote: From f46939b7ec807d1709b93ec9e4d227cea859d2fd Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 16 Oct 2019 13:50:23 -0400 Subject: [PATCH 13/13] Add missing comma to api attributes --- confluent_server/confluent/config/attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index 73e37e31..c2298ce2 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -105,7 +105,7 @@ node = { 'The format is an expiration time in ISO8601 format. When ' 'the indicated time passes or the first time a node claims ' 'the key, key grants will not be allowed.'), - } + }, #'id': { # 'description': ('Numeric identifier for node') #},