1from __future__ import print_function 2 3import sys 4import json 5import string 6 7from exabgp.util import character 8from exabgp.util import ordinal 9from exabgp.util import concat_bytes_i 10from exabgp.util import hexstring 11 12from exabgp.bgp.message import Message 13from exabgp.bgp.message import Open 14from exabgp.bgp.message import Notification 15from exabgp.bgp.message.open.asn import ASN 16from exabgp.bgp.message.open.capability import Negotiated 17 18from exabgp.version import json as json_version 19from exabgp.reactor.api.response import Response 20 21from exabgp.protocol.ip import IPv4 22 23 24class _FakeNeighbor(object): 25 def __init__(self, local, remote, asn, peer): 26 self.local_address = IPv4(local) 27 self.peer_address = IPv4(remote) 28 self.peer_as = ASN(asn) 29 self.local_as = ASN(peer) 30 31 32class Transcoder(object): 33 seen_open = { 34 'send': None, 35 'receive': None, 36 } 37 negotiated = None 38 39 json = Response.JSON(json_version) 40 41 def __init__(self, src='json', dst='json'): 42 if src != 'json': 43 raise RuntimeError('left as an exercise to the reader') 44 45 if dst != 'json': 46 raise RuntimeError('left as an exercise to the reader') 47 48 self.convert = self._from_json 49 self.encoder = self.json 50 51 def _state(self): 52 self.seen_open['send'] = None 53 self.seen_open['receive'] = None 54 self.negotiated = None 55 56 def _open(self, direction, message): 57 self.seen_open[direction] = message 58 59 if all(self.seen_open.values()): 60 self.negotiated = Negotiated(None) 61 self.negotiated.sent(self.seen_open['send']) 62 self.negotiated.received(self.seen_open['receive']) 63 64 def _from_json(self, json_string): 65 try: 66 parsed = json.loads(json_string) 67 except ValueError: 68 print('invalid JSON message', file=sys.stderr) 69 sys.exit(1) 70 71 if parsed.get('exabgp', '0.0.0') != json_version: 72 print('invalid json version', json_string, file=sys.stderr) 73 sys.exit(1) 74 75 content = parsed.get('type', '') 76 77 if not content: 78 print('invalid json content', json_string, file=sys.stderr) 79 sys.exit(1) 80 81 neighbor = _FakeNeighbor( 82 parsed['neighbor']['address']['local'], 83 parsed['neighbor']['address']['peer'], 84 parsed['neighbor']['asn']['local'], 85 parsed['neighbor']['asn']['peer'], 86 ) 87 88 if content == 'state': 89 self._state() 90 return json_string 91 92 direction = parsed['neighbor']['direction'] 93 category = parsed['neighbor']['message']['category'] 94 header = parsed['neighbor']['message']['header'] 95 body = parsed['neighbor']['message']['body'] 96 data = concat_bytes_i(character(int(body[_ : _ + 2], 16)) for _ in range(0, len(body), 2)) 97 98 if content == 'open': 99 message = Open.unpack_message(data) 100 self._open(direction, message) 101 return self.encoder.open(neighbor, direction, message, None, header, body) 102 103 if content == 'keepalive': 104 return self.encoder.keepalive(neighbor, direction, None, header, body) 105 106 if content == 'notification': 107 # XXX: Use the code of the Notifcation class here .. 108 message = Notification.unpack_message(data) 109 110 if (message.code, message.subcode) != (6, 2): 111 message.data = data if not len([_ for _ in data if _ not in string.printable]) else hexstring(data) 112 return self.encoder.notification(neighbor, direction, message, None, header, body) 113 114 if len(data) == 0: 115 # shutdown without shutdown communication (the old fashioned way) 116 message.data = '' 117 return self.encoder.notification(neighbor, direction, message, None, header, body) 118 119 # draft-ietf-idr-shutdown or the peer was using 6,2 with data 120 121 shutdown_length = ordinal(data[0]) 122 data = data[1:] 123 124 if shutdown_length == 0: 125 message.data = "empty Shutdown Communication." 126 # move offset past length field 127 return self.encoder.notification(neighbor, direction, message, None, header, body) 128 129 if len(data) < shutdown_length: 130 message.data = "invalid Shutdown Communication (buffer underrun) length : %i [%s]" % ( 131 shutdown_length, 132 hexstring(data), 133 ) 134 return self.encoder.notification(neighbor, direction, message, None, header, body) 135 136 if shutdown_length > 128: 137 message.data = "invalid Shutdown Communication (too large) length : %i [%s]" % ( 138 shutdown_length, 139 hexstring(data), 140 ) 141 return self.encoder.notification(neighbor, direction, message, None, header, body) 142 143 try: 144 message.data = 'Shutdown Communication: "%s"' % data[:shutdown_length].decode('utf-8').replace( 145 '\r', ' ' 146 ).replace('\n', ' ') 147 except UnicodeDecodeError: 148 message.data = "invalid Shutdown Communication (invalid UTF-8) length : %i [%s]" % ( 149 shutdown_length, 150 hexstring(data), 151 ) 152 return self.encoder.notification(neighbor, direction, message, None, header, body) 153 154 trailer = data[shutdown_length:] 155 if trailer: 156 message.data += ", trailing data: " + hexstring(trailer) 157 158 return self.encoder.notification(neighbor, direction, message, None, header, body) 159 160 if not self.negotiated: 161 print('invalid message sequence, open not exchange not complete', json_string, file=sys.stderr) 162 sys.exit(1) 163 164 message = Message.unpack(category, data, self.negotiated) 165 166 if content == 'update': 167 return self.encoder.update(neighbor, direction, message, None, header, body) 168 169 if content == 'eor': # XXX: Should not be required 170 return self.encoder.update(neighbor, direction, message, None, header, body) 171 172 if content == 'refresh': 173 return self.json.refresh(neighbor, direction, message, None, header, body) 174 175 if content == 'operational': 176 return self.json.refresh(neighbor, direction, message, None, header, body) 177 178 raise RuntimeError('the programer is a monkey and forgot a JSON message type') 179