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
|
|
|
|