def _listen(self): #Wait for connection self.recv_sock, self.remote_addr = self.sock_fd.accept() print "Accepted connection!" #Update observers while running while not self.disconnected: first_byte = self.recv_sock.recv(1) #Test whether client has dropped if not first_byte: self.disconnected = True #Inform observers for callback in self.dc_listeners: callback() else: msg_len = ord(first_byte) #Receive until we have an entire message msg_body = self.recv_sock.recv(msg_len) while len(msg_body) < msg_len: msg_body = msg_body + self.recv_sock.recv(msg_len - len(msg_body)) msg_parsed = msg.Msg(msg_body) #Give message to all registered observers for callback in self.data_listeners: callback(msg_parsed)
def deserialize(byte_data): """ Converts byte data into message object :param byte_data: bytes to convert to message :returns: Message object """ # Validate byte_data if not is_valid_msg(byte_data): raise ValueError('Attempt to deserialize invalid message') # Calculate the size of the payload and construct unpacking format payload_sz = len(byte_data) - g_HEADER_SZ_BYTES dynamic_fmt = unpack_fmt % (payload_sz) # Unpack the binary data msg_len, raw_msg_type, raw_timestamp, raw_payload \ = struct.unpack(dynamic_fmt, byte_data) msg_type = msg.MsgType(raw_msg_type) payload = msg.decode_payload(msg_type, raw_payload) timestamp = raw_timestamp.decode(c.Config.get(c.ConfigEnum.BYTE_ENCODING)) return msg.Msg(msg_type, payload, timestamp)
def execute(port, src_guid, net_id): ''' Executes message broadcasts to all valid network adapters. Blocks until all broadcasts have been sent out. :param port: Destination port to broadcast to :param src_guid: Host GUID :param net_id: Host's NetID ''' broadcast_ips = netutils.get_broadcast_ips() # Check adapter info _g_logger.debug(f'Broadcast IPs: {broadcast_ips}') # Sanity check that network adapters are available for broadcasting num_broadcast_ips = len(broadcast_ips) if num_broadcast_ips == 0: raise RuntimeError('Unable to find valid network adapter') _g_logger.info('Broadcasting IPs retrieved') # Construct broadcasting message broadcast_msg = msg.Msg(msg.MsgType.ENDPOINT_CONNECTION_BROADCAST, net_id) _g_logger.debug(f'Constructed broadcast message: {broadcast_msg}') # Send out broadcasts for ip in broadcast_ips: __broadcast(broadcast_msg, src_guid, (ip, port))
def test(): # Construct message mt = msg.MsgType.ENDPOINT_TEXT_COMMUNICATION data = 'this is a payload' message = msg.Msg(mt, data) print('Message Pre-serialization') print(message) print() # Serialize message serialized_msg = serialize(message) print('Message Post-serialization') print(serialized_msg) print() # Check validation methods if not is_valid_msg(serialized_msg): raise ValueError('Unable to validate message during testing') msg_type = decode_msgtype(serialized_msg) print('Decoded MsgType: %s' % (msg_type)) print() # Deserialize message deserialized_msg = deserialize(serialized_msg) print('Message Post-deserialization') print(deserialized_msg)
def main(): # actually don't know if script could work on Python earlier # than 3.5.x, but that's the earliest I tested with assert sys.version_info >= (3, 5) path = config_file if not path.startswith('/'): path = os.path.join(SCRIPT_DIR, path) config = read_config(path) if not config: sys.exit("Please fill in the 'config.txt' file.") for section in config.sections(): # don't iterate over config, that will process the DEFAULT section sec = config[section] apply_config_defaults(sec) m = msg.Msg() m.setlevel(sec['verbosity']) for protocol in sec['protocols']: m.put( msg.INFO, '%s - section %s - IPv%s' % (str(datetime.now()), section, protocol)) # deduce DNS record type sec['recordtype'] = {'4': 'A', '6': 'AAAA'}[protocol] # deduce DNS record name sec['recordname'] = { '4': sec['a_name'], '6': sec['aaaa_name'] }[protocol] # Set URL. It ends up looking like: # https://dns.api.gandi.net/api/v5/domains/example.com/raspian/A sec['url'] = '%sdomains/%s/records/%s/%s' % ( sec['api'], sec['domain'], sec['recordname'], sec['recordtype']) m.put(msg.INFO, 'Request API URL is: %s' % sec['url']) # Recently I started getting occasional 502 ("Bad Gateway") # errors. Interestingly, this only happens when I run # the script from a cron job, and only at the run # at the top of the hour (hh:00), The runs at # hh:20 and hh:40 never fail and another system whic runs # hourly at hh:07 never fails. My guess is that there are # a lot of people running some form of dns update from # cron, with a run at the top of the hour, resulting in # server overload (kind of a unintentional DDOS.) # To combat this, if a 502 error is received, wait a little # and retry, but give up if the error persists. I'm also # going to change my cron job to move off the top of the hour. # Check current record max_retries = 5 retry_delay = 23 for attempt in range(max_retries): record = get_record(m, sec) if record.status_code != 502: break m.put( msg.ERROR, 'Got error %d fetching %s record. Retry %d.' % (record.status_code, sec['recordtype'], attempt)) time.sleep(retry_delay) if record.status_code == 502: m.put( msg.ERROR, 'Got error %d repeatedly fetching %s record. Giving up.' % (record.status_code, sec['recordtype'])) sys.exit(2) if record.status_code == 404: # no old record, add it # Discover External IP external_ip = get_ip(m, sec['ipservice'], protocol) m.put( msg.ACTION, 'No old %s record, adding as %s' % (sec['recordtype'], external_ip)) update_record(m, sec, external_ip) elif record.status_code == 200: # old record exists, check and replace if needed old_ip = json.loads(record.text)['rrset_values'][0] m.put( msg.INFO, 'Current %s record value is: %s' % (sec['recordtype'], old_ip)) # this is a very particular quirk for my own home config. # if there is a nonroutable IP address in an A record, # I have put it there for convenience in using one host on # my home network from another, do not update it with # the external IP. This path should never be executed # if I've configured the script right on my own systems, # since the systems where <hostname>.my.domain/A is set to # an unroutable IP should be configured to only update # the AAAA records, and I doubt anybody else is crazy # enough to put private IPs in DNS (I actually don't # even do it any more since I now depend on mDNS # for local name resolution, which is much saner.) if (protocol == '4' and ipaddress.IPv4Address(old_ip).is_private): m.put( msg.ERROR, 'Not updating %s record for %s, check config' % (names[protocol], old_ip)) continue # next protlocol # Discover External IP external_ip = get_ip(m, sec['ipservice'], protocol) m.put(msg.INFO, 'External IP is: %s' % external_ip) if old_ip == external_ip: m.put( msg.NOACTION, 'No change in IPv%s address, nothing done.' % (protocol)) else: m.put( msg.ACTION, 'Updating %s/%s from %s to %s' % (sec['recordname'], sec['recordtype'], old_ip, external_ip)) update_record(m, sec, external_ip) else: # some unexpected condition fetching old record. # perhaps config is bad or api has changed? m.put( msg.ERROR, 'Got error %d fetching %s record. Stop.' % (record.status_code, sec['recordtype'])) sys.exit(2) continue # next protocol continue # next config secion