|
1
|
|
|
# encoding: utf-8 |
|
2
|
|
|
""" |
|
3
|
|
|
negotiated.py |
|
4
|
|
|
|
|
5
|
|
|
Created by Thomas Mangin on 2012-07-19. |
|
6
|
|
|
Copyright (c) 2009-2017 Exa Networks. All rights reserved. |
|
7
|
|
|
License: 3-clause BSD. (See the COPYRIGHT file) |
|
8
|
|
|
""" |
|
9
|
|
|
|
|
10
|
|
|
from exabgp.protocol.family import AFI |
|
11
|
|
|
from exabgp.bgp.message.open.asn import ASN |
|
12
|
|
|
from exabgp.bgp.message.open.asn import AS_TRANS |
|
13
|
|
|
from exabgp.bgp.message.open.holdtime import HoldTime |
|
14
|
|
|
from exabgp.bgp.message.open.capability.capability import Capability |
|
15
|
|
|
from exabgp.bgp.message.open.capability.refresh import REFRESH |
|
16
|
|
|
from exabgp.bgp.message.open.routerid import RouterID |
|
17
|
|
|
from exabgp.bgp.message.open.capability.extended import ExtendedMessage |
|
18
|
|
|
|
|
19
|
|
|
|
|
20
|
|
|
class Negotiated(object): |
|
21
|
|
|
FREE_SIZE = ExtendedMessage.INITIAL_SIZE - 19 - 2 - 2 |
|
22
|
|
|
|
|
23
|
|
|
def __init__(self, neighbor): |
|
24
|
|
|
self.neighbor = neighbor |
|
25
|
|
|
|
|
26
|
|
|
self.sent_open = None |
|
27
|
|
|
self.received_open = None |
|
28
|
|
|
|
|
29
|
|
|
self.holdtime = HoldTime(0) |
|
30
|
|
|
self.local_as = ASN(0) |
|
31
|
|
|
self.peer_as = ASN(0) |
|
32
|
|
|
self.families = [] |
|
33
|
|
|
self.nexthop = [] |
|
34
|
|
|
self.asn4 = False |
|
35
|
|
|
self.addpath = RequirePath() |
|
36
|
|
|
self.multisession = False |
|
37
|
|
|
self.msg_size = ExtendedMessage.INITIAL_SIZE |
|
38
|
|
|
self.operational = False |
|
39
|
|
|
self.refresh = REFRESH.ABSENT # pylint: disable=E1101 |
|
40
|
|
|
self.aigp = None |
|
41
|
|
|
self.mismatch = [] |
|
42
|
|
|
|
|
43
|
|
|
def sent(self, sent_open): |
|
44
|
|
|
self.sent_open = sent_open |
|
45
|
|
|
if self.received_open: |
|
46
|
|
|
self._negotiate() |
|
47
|
|
|
|
|
48
|
|
|
def received(self, received_open): |
|
49
|
|
|
self.received_open = received_open |
|
50
|
|
|
if self.sent_open: |
|
51
|
|
|
self._negotiate() |
|
52
|
|
|
|
|
53
|
|
|
def _negotiate(self): |
|
54
|
|
|
sent_capa = self.sent_open.capabilities |
|
55
|
|
|
recv_capa = self.received_open.capabilities |
|
56
|
|
|
|
|
57
|
|
|
self.holdtime = HoldTime(min(self.sent_open.hold_time, self.received_open.hold_time)) |
|
58
|
|
|
|
|
59
|
|
|
self.addpath.setup(self.received_open, self.sent_open) |
|
60
|
|
|
self.asn4 = sent_capa.announced(Capability.CODE.FOUR_BYTES_ASN) and recv_capa.announced( |
|
61
|
|
|
Capability.CODE.FOUR_BYTES_ASN |
|
62
|
|
|
) |
|
63
|
|
|
self.operational = sent_capa.announced(Capability.CODE.OPERATIONAL) and recv_capa.announced( |
|
64
|
|
|
Capability.CODE.OPERATIONAL |
|
65
|
|
|
) |
|
66
|
|
|
|
|
67
|
|
|
self.local_as = self.sent_open.asn |
|
68
|
|
|
self.peer_as = self.received_open.asn |
|
69
|
|
|
if self.received_open.asn == AS_TRANS and self.asn4: |
|
70
|
|
|
self.peer_as = recv_capa.get(Capability.CODE.FOUR_BYTES_ASN, self.peer_as) |
|
71
|
|
|
|
|
72
|
|
|
self.families = [] |
|
73
|
|
|
if recv_capa.announced(Capability.CODE.MULTIPROTOCOL) and sent_capa.announced(Capability.CODE.MULTIPROTOCOL): |
|
74
|
|
|
for family in recv_capa[Capability.CODE.MULTIPROTOCOL]: |
|
75
|
|
|
if family in sent_capa[Capability.CODE.MULTIPROTOCOL]: |
|
76
|
|
|
self.families.append(family) |
|
77
|
|
|
|
|
78
|
|
|
self.nexthop = [] |
|
79
|
|
|
if recv_capa.announced(Capability.CODE.NEXTHOP) and sent_capa.announced(Capability.CODE.NEXTHOP): |
|
80
|
|
|
for family in recv_capa[Capability.CODE.NEXTHOP]: |
|
81
|
|
|
if family in sent_capa[Capability.CODE.NEXTHOP]: |
|
82
|
|
|
self.nexthop.append(family) |
|
83
|
|
|
|
|
84
|
|
|
if recv_capa.announced(Capability.CODE.ENHANCED_ROUTE_REFRESH) and sent_capa.announced( |
|
85
|
|
|
Capability.CODE.ENHANCED_ROUTE_REFRESH |
|
86
|
|
|
): |
|
87
|
|
|
self.refresh = REFRESH.ENHANCED # pylint: disable=E1101 |
|
88
|
|
|
elif recv_capa.announced(Capability.CODE.ROUTE_REFRESH) and sent_capa.announced(Capability.CODE.ROUTE_REFRESH): |
|
89
|
|
|
self.refresh = REFRESH.NORMAL # pylint: disable=E1101 |
|
90
|
|
|
|
|
91
|
|
|
if recv_capa.announced(Capability.CODE.EXTENDED_MESSAGE) and sent_capa.announced( |
|
92
|
|
|
Capability.CODE.EXTENDED_MESSAGE |
|
93
|
|
|
): |
|
94
|
|
|
self.msg_size = ExtendedMessage.EXTENDED_SIZE |
|
95
|
|
|
|
|
96
|
|
|
self.multisession = sent_capa.announced(Capability.CODE.MULTISESSION) and recv_capa.announced( |
|
97
|
|
|
Capability.CODE.MULTISESSION |
|
98
|
|
|
) |
|
99
|
|
|
self.multisession |= sent_capa.announced(Capability.CODE.MULTISESSION_CISCO) and recv_capa.announced( |
|
100
|
|
|
Capability.CODE.MULTISESSION_CISCO |
|
101
|
|
|
) |
|
102
|
|
|
|
|
103
|
|
|
if self.multisession: |
|
104
|
|
|
sent_ms_capa = set(sent_capa[Capability.CODE.MULTISESSION]) |
|
105
|
|
|
recv_ms_capa = set(recv_capa[Capability.CODE.MULTISESSION]) |
|
106
|
|
|
|
|
107
|
|
|
if sent_ms_capa == set([]): |
|
108
|
|
|
sent_ms_capa = set([Capability.CODE.MULTIPROTOCOL]) |
|
109
|
|
|
if recv_ms_capa == set([]): |
|
110
|
|
|
recv_ms_capa = set([Capability.CODE.MULTIPROTOCOL]) |
|
111
|
|
|
|
|
112
|
|
|
if sent_ms_capa != recv_ms_capa: |
|
113
|
|
|
self.multisession = (2, 8, 'multisession, our peer did not reply with the same sessionid') |
|
114
|
|
|
|
|
115
|
|
|
# The way we implement MS-BGP, we only send one MP per session |
|
116
|
|
|
# therefore we can not collide due to the way we generate the configuration |
|
117
|
|
|
|
|
118
|
|
|
for capa in sent_ms_capa: |
|
119
|
|
|
# no need to check that the capability exists, we generated it |
|
120
|
|
|
# checked it is what we sent and only send MULTIPROTOCOL |
|
121
|
|
|
if sent_capa[capa] != recv_capa[capa]: |
|
122
|
|
|
self.multisession = (2, 8, 'when checking session id, capability %s did not match' % str(capa)) |
|
123
|
|
|
break |
|
124
|
|
|
|
|
125
|
|
|
elif sent_capa.announced(Capability.CODE.MULTISESSION): |
|
126
|
|
|
self.multisession = (2, 9, 'multisession is mandatory with this peer') |
|
127
|
|
|
|
|
128
|
|
|
# XXX: Does not work as the capa is not yet defined |
|
129
|
|
|
# if received_open.capabilities.announced(Capability.CODE.EXTENDED_MESSAGE) \ |
|
130
|
|
|
# and sent_open.capabilities.announced(Capability.CODE.EXTENDED_MESSAGE): |
|
131
|
|
|
# if self.peer.bgp.received_open_size: |
|
132
|
|
|
# self.received_open_size = self.peer.bgp.received_open_size - 19 |
|
133
|
|
|
|
|
134
|
|
|
def validate(self, neighbor): |
|
135
|
|
|
if neighbor.peer_as is not None and self.peer_as != neighbor.peer_as: |
|
136
|
|
|
return ( |
|
137
|
|
|
2, |
|
138
|
|
|
2, |
|
139
|
|
|
'ASN in OPEN (%d) did not match ASN expected (%d)' % (self.received_open.asn, neighbor.peer_as), |
|
140
|
|
|
) |
|
141
|
|
|
|
|
142
|
|
|
# RFC 6286 : https://tools.ietf.org/html/rfc6286 |
|
143
|
|
|
# XXX: FIXME: check that router id is not self |
|
144
|
|
|
if self.received_open.router_id == RouterID('0.0.0.0'): |
|
145
|
|
|
return (2, 3, '0.0.0.0 is an invalid router_id') |
|
146
|
|
|
|
|
147
|
|
|
if self.received_open.asn == neighbor.local_as: |
|
148
|
|
|
# router-id must be unique within an ASN |
|
149
|
|
|
if self.received_open.router_id == neighbor.router_id: |
|
150
|
|
|
return ( |
|
151
|
|
|
2, |
|
152
|
|
|
3, |
|
153
|
|
|
'BGP Identifier collision, same router-id (%s) on both sides of this IBGP session' |
|
154
|
|
|
% self.received_open.router_id, |
|
155
|
|
|
) |
|
156
|
|
|
|
|
157
|
|
|
if self.received_open.hold_time and self.received_open.hold_time < 3: |
|
158
|
|
|
return (2, 6, 'Hold Time is invalid (%d)' % self.received_open.hold_time) |
|
159
|
|
|
|
|
160
|
|
|
if self.multisession not in (True, False): |
|
161
|
|
|
# XXX: FIXME: should we not use a string and perform a split like we do elswhere ? |
|
162
|
|
|
# XXX: FIXME: or should we use this trick in the other case ? |
|
163
|
|
|
return self.multisession |
|
164
|
|
|
|
|
165
|
|
|
s = set(self.sent_open.capabilities.get(Capability.CODE.MULTIPROTOCOL, [])) |
|
166
|
|
|
r = set(self.received_open.capabilities.get(Capability.CODE.MULTIPROTOCOL, [])) |
|
167
|
|
|
mismatch = s ^ r |
|
168
|
|
|
|
|
169
|
|
|
for family in mismatch: |
|
170
|
|
|
self.mismatch.append(('exabgp' if family in r else 'peer', family)) |
|
171
|
|
|
|
|
172
|
|
|
return None |
|
173
|
|
|
|
|
174
|
|
|
def nexthopself(self, afi): |
|
175
|
|
|
if afi == self.neighbor.local_address.afi: |
|
176
|
|
|
return self.neighbor.local_address |
|
177
|
|
|
|
|
178
|
|
|
# attempting to not barf for next-hop self when the peer is IPv6 |
|
179
|
|
|
if afi == AFI.ipv4: |
|
180
|
|
|
return self.neighbor.router_id |
|
181
|
|
|
|
|
182
|
|
|
raise TypeError( |
|
183
|
|
|
'use of "next-hop self": the route (%s) does not have the same family as the BGP tcp session (%s)' |
|
184
|
|
|
% (afi, self.neighbor.local_address.afi) |
|
185
|
|
|
) |
|
186
|
|
|
|
|
187
|
|
|
|
|
188
|
|
|
# =================================================================== RequirePath |
|
189
|
|
|
|
|
190
|
|
|
|
|
191
|
|
|
class RequirePath(object): |
|
192
|
|
|
CANT = 0b00 |
|
193
|
|
|
RECEIVE = 0b01 |
|
194
|
|
|
SEND = 0b10 |
|
195
|
|
|
BOTH = SEND | RECEIVE |
|
196
|
|
|
|
|
197
|
|
|
def __init__(self): |
|
198
|
|
|
self._send = {} |
|
199
|
|
|
self._receive = {} |
|
200
|
|
|
|
|
201
|
|
|
def setup(self, received_open, sent_open): |
|
202
|
|
|
# A Dict always returning False |
|
203
|
|
|
class FalseDict(dict): |
|
204
|
|
|
def __getitem__(self, key): |
|
205
|
|
|
return False |
|
206
|
|
|
|
|
207
|
|
|
receive = received_open.capabilities.get(Capability.CODE.ADD_PATH, FalseDict()) |
|
208
|
|
|
send = sent_open.capabilities.get(Capability.CODE.ADD_PATH, FalseDict()) |
|
209
|
|
|
|
|
210
|
|
|
# python 2.4 compatibility mean no simple union but using sets.Set |
|
211
|
|
|
union = [] |
|
212
|
|
|
union.extend(send.keys()) |
|
213
|
|
|
union.extend([k for k in receive.keys() if k not in send.keys()]) |
|
214
|
|
|
|
|
215
|
|
|
for k in union: |
|
216
|
|
|
self._send[k] = bool(send.get(k, self.CANT) & self.SEND and receive.get(k, self.CANT) & self.RECEIVE) |
|
217
|
|
|
self._receive[k] = bool(send.get(k, self.CANT) & self.RECEIVE and receive.get(k, self.CANT) & self.SEND) |
|
218
|
|
|
|
|
219
|
|
|
def send(self, afi, safi): |
|
220
|
|
|
return self._send.get((afi, safi), False) |
|
221
|
|
|
|
|
222
|
|
|
def receive(self, afi, safi): |
|
223
|
|
|
return self._receive.get((afi, safi), False) |
|
224
|
|
|
|