Line data Source code
1 : /*
2 : * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
3 : */
4 :
5 : #include <boost/uuid/uuid_io.hpp>
6 :
7 : #include <cmn/agent_cmn.h>
8 : #include <route/route.h>
9 :
10 : #include <oper/route_common.h>
11 : #include <oper/vrf.h>
12 : #include <oper/tunnel_nh.h>
13 : #include <oper/mpls.h>
14 : #include <oper/mirror_table.h>
15 : #include <controller/controller_export.h>
16 : #include <controller/controller_peer.h>
17 : #include <controller/controller_route_path.h>
18 : #include <oper/agent_sandesh.h>
19 :
20 : using namespace std;
21 : using namespace boost::asio;
22 :
23 : /////////////////////////////////////////////////////////////////////////////
24 : // Utility functions
25 : /////////////////////////////////////////////////////////////////////////////
26 0 : static void BridgeTableEnqueue(Agent *agent, DBRequest *req) {
27 0 : AgentRouteTable *table = agent->fabric_l2_unicast_table();
28 0 : if (table) {
29 0 : table->Enqueue(req);
30 : }
31 0 : }
32 :
33 0 : static void BridgeTableProcess(Agent *agent, const string &vrf_name,
34 : DBRequest &req) {
35 : AgentRouteTable *table =
36 0 : agent->vrf_table()->GetBridgeRouteTable(vrf_name);
37 0 : if (table) {
38 0 : table->Process(req);
39 : }
40 0 : }
41 :
42 : /////////////////////////////////////////////////////////////////////////////
43 : // BridgeRouteKey methods
44 : /////////////////////////////////////////////////////////////////////////////
45 0 : string BridgeRouteKey::ToString() const {
46 0 : return dmac_.ToString();
47 : }
48 :
49 0 : BridgeRouteKey *BridgeRouteKey::Clone() const {
50 0 : return new BridgeRouteKey(peer(), vrf_name_, dmac_);
51 : }
52 :
53 : AgentRoute *
54 0 : BridgeRouteKey::AllocRouteEntry(VrfEntry *vrf, bool is_multicast) const
55 : {
56 0 : BridgeRouteEntry *entry = new BridgeRouteEntry(vrf, dmac_,
57 0 : peer()->GetType(),
58 0 : is_multicast);
59 0 : return static_cast<AgentRoute *>(entry);
60 : }
61 :
62 : /////////////////////////////////////////////////////////////////////////////
63 : // BridgeAgentRouteTable methods
64 : /////////////////////////////////////////////////////////////////////////////
65 0 : DBTableBase *BridgeAgentRouteTable::CreateTable(DB *db,
66 : const std::string &name) {
67 0 : AgentRouteTable *table = new BridgeAgentRouteTable(db, name);
68 0 : table->Init();
69 0 : return table;
70 : }
71 :
72 0 : BridgeRouteEntry *BridgeAgentRouteTable::FindRoute(const MacAddress &mac) {
73 0 : BridgeRouteEntry entry(vrf_entry(), mac, Peer::LOCAL_PEER, false);
74 0 : return static_cast<BridgeRouteEntry *>(FindActiveEntry(&entry));
75 0 : }
76 :
77 0 : BridgeRouteEntry *BridgeAgentRouteTable::FindRouteNoLock(const MacAddress &mac){
78 0 : BridgeRouteEntry entry(vrf_entry(), mac, Peer::LOCAL_PEER, false);
79 0 : return static_cast<BridgeRouteEntry *>(FindActiveEntryNoLock(&entry));
80 0 : }
81 :
82 0 : BridgeRouteEntry *BridgeAgentRouteTable::FindRoute(const MacAddress &mac,
83 : Peer::Type peer) {
84 0 : BridgeRouteEntry entry(vrf_entry(), mac, peer, false);
85 0 : return static_cast<BridgeRouteEntry *>(FindActiveEntry(&entry));
86 0 : }
87 :
88 : /////////////////////////////////////////////////////////////////////////////
89 : // BridgeAgentRouteTable utility methods to add/delete routes
90 : /////////////////////////////////////////////////////////////////////////////
91 0 : void BridgeAgentRouteTable::AddBridgeReceiveRoute(const Peer *peer,
92 : const string &vrf_name,
93 : const MacAddress &mac,
94 : const string &vn_name,
95 : const string &interface,
96 : bool policy) {
97 0 : Agent *agent = Agent::GetInstance();
98 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
99 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
100 :
101 0 : PacketInterfaceKey intf_key(boost::uuids::nil_uuid(),
102 0 : agent->pkt_interface_name());
103 0 : req.data.reset(new HostRoute(intf_key, vn_name));
104 :
105 0 : BridgeTableEnqueue(agent, &req);
106 0 : }
107 :
108 0 : void BridgeAgentRouteTable::AddBridgeReceiveRoute(const Peer *peer,
109 : const string &vrf_name,
110 : uint32_t vxlan_id,
111 : const MacAddress &mac,
112 : const string &vn_name) {
113 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
114 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, vxlan_id));
115 0 : req.data.reset(new L2ReceiveRoute(vn_name, vxlan_id, 0, PathPreference(),
116 0 : peer->sequence_number()));
117 0 : Process(req);
118 0 : }
119 :
120 0 : void BridgeAgentRouteTable::AddBridgeRoute(const AgentRoute *rt) {
121 0 : const EvpnRouteEntry *evpn_rt =
122 : static_cast<const EvpnRouteEntry *>(rt);
123 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
124 0 : req.key.reset(new BridgeRouteKey(agent()->evpn_peer(),
125 0 : evpn_rt->vrf()->GetName(),
126 0 : evpn_rt->mac(), 0));
127 0 : req.data.reset(new EvpnDerivedPathData(evpn_rt));
128 0 : BridgeTableProcess(agent(), vrf_name(), req);
129 0 : }
130 :
131 :
132 0 : void BridgeAgentRouteTable::AddMacVmBindingRoute(const Peer *peer,
133 : const std::string &vrf_name,
134 : const MacAddress &mac,
135 : const VmInterface *vm_intf,
136 : bool flood_dhcp) {
137 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
138 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
139 0 : req.data.reset(new MacVmBindingPathData(vm_intf, flood_dhcp));
140 0 : BridgeTableProcess(agent(), vrf_name, req);
141 0 : }
142 :
143 0 : void BridgeAgentRouteTable::DeleteMacVmBindingRoute(const Peer *peer,
144 : const std::string &vrf_name,
145 : const MacAddress &mac,
146 : const VmInterface *vm_intf) {
147 0 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
148 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
149 0 : req.data.reset(new MacVmBindingPathData(vm_intf, false));
150 0 : BridgeTableProcess(agent(), vrf_name, req);
151 0 : }
152 :
153 0 : void BridgeAgentRouteTable::DeleteBridgeRoute(const AgentRoute *rt) {
154 0 : const EvpnRouteEntry *evpn_rt =
155 : static_cast<const EvpnRouteEntry *>(rt);
156 0 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
157 0 : req.key.reset(new BridgeRouteKey(agent()->evpn_peer(),
158 0 : evpn_rt->vrf()->GetName(),
159 0 : evpn_rt->mac(), 0));
160 0 : req.data.reset(new EvpnDerivedPathData(evpn_rt));
161 0 : BridgeTableProcess(Agent::GetInstance(), evpn_rt->vrf()->GetName(), req);
162 0 : }
163 :
164 0 : void BridgeAgentRouteTable::Delete(const Peer *peer, const string &vrf_name,
165 : const MacAddress &mac,
166 : uint32_t ethernet_tag) {
167 0 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
168 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, ethernet_tag));
169 0 : req.data.reset(NULL);
170 0 : BridgeTableProcess(Agent::GetInstance(), vrf_name, req);
171 0 : }
172 :
173 0 : AgentRouteData *BridgeAgentRouteTable::BuildNonBgpPeerData(const string &vrf_name,
174 : const std::string &vn_name,
175 : uint32_t label,
176 : int vxlan_id,
177 : uint32_t tunnel_type,
178 : Composite::Type type,
179 : ComponentNHKeyList
180 : &component_nh_key_list,
181 : bool pbb_nh,
182 : bool learning_enabled) {
183 0 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
184 0 : nh_req.key.reset(new CompositeNHKey(type, false, component_nh_key_list,
185 0 : vrf_name));
186 0 : nh_req.data.reset(new CompositeNHData(pbb_nh, learning_enabled, false));
187 : return (new MulticastRoute(vn_name, label,
188 : vxlan_id, tunnel_type,
189 0 : nh_req, type, 0));
190 0 : }
191 :
192 0 : AgentRouteData *BridgeAgentRouteTable::BuildBgpPeerData(const Peer *peer,
193 : const string &vrf_name,
194 : const std::string &vn_name,
195 : uint32_t label,
196 : int vxlan_id,
197 : uint32_t ethernet_tag,
198 : uint32_t tunnel_type,
199 : Composite::Type type,
200 : ComponentNHKeyList
201 : &component_nh_key_list,
202 : bool pbb_nh,
203 : bool learning_enabled) {
204 0 : const BgpPeer *bgp_peer = dynamic_cast<const BgpPeer *>(peer);
205 0 : assert(bgp_peer != NULL);
206 0 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
207 0 : nh_req.key.reset(new CompositeNHKey(type, false, component_nh_key_list,
208 0 : vrf_name));
209 0 : nh_req.data.reset(new CompositeNHData(pbb_nh, learning_enabled, false));
210 : return (new MulticastRoute(vn_name, label, ethernet_tag, tunnel_type,
211 0 : nh_req, type, bgp_peer->sequence_number()));
212 0 : }
213 :
214 0 : void BridgeAgentRouteTable::AddBridgeRoute(const Peer *peer,
215 : const string &vrf_name,
216 : const MacAddress &mac,
217 : uint32_t ethernet_tag,
218 : AgentRouteData *data) {
219 :
220 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
221 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, ethernet_tag));
222 0 : req.data.reset(data);
223 0 : BridgeTableEnqueue(Agent::GetInstance(), &req);
224 0 : }
225 :
226 0 : void BridgeAgentRouteTable::DeleteBridgeRoute(const Peer *peer,
227 : const string &vrf_name,
228 : const MacAddress &mac,
229 : uint32_t ethernet_tag,
230 : COMPOSITETYPE type) {
231 :
232 0 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
233 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, ethernet_tag));
234 0 : DBRequest nh_req;
235 :
236 : //For same BGP peer type comp type helps in identifying if its a delete
237 : //for TOR or EVPN path.
238 : //Only ethernet tag is required, rest are dummy.
239 0 : const BgpPeer *bgp_peer = dynamic_cast<const BgpPeer *>(peer);
240 0 : if (bgp_peer) {
241 0 : req.data.reset(new MulticastRoute("", 0,
242 0 : ethernet_tag, TunnelType::AllType(),
243 : nh_req, type,
244 0 : bgp_peer->sequence_number()));
245 : } else {
246 0 : req.data.reset(new MulticastRoute("", 0, ethernet_tag,
247 0 : TunnelType::AllType(),
248 0 : nh_req, type, 0));
249 : }
250 :
251 0 : BridgeTableEnqueue(Agent::GetInstance(), &req);
252 0 : }
253 :
254 0 : void BridgeAgentRouteTable::AddBridgeBroadcastRoute(const Peer *peer,
255 : const string &vrf_name,
256 : uint32_t ethernet_tag,
257 : AgentRouteData *data) {
258 :
259 0 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
260 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name,
261 0 : MacAddress::BroadcastMac(), ethernet_tag));
262 0 : req.data.reset(data);
263 0 : BridgeTableEnqueue(Agent::GetInstance(), &req);
264 0 : }
265 :
266 0 : void BridgeAgentRouteTable::DeleteBroadcastReq(const Peer *peer,
267 : const string &vrf_name,
268 : uint32_t ethernet_tag,
269 : COMPOSITETYPE type) {
270 0 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
271 0 : req.key.reset(new BridgeRouteKey(peer, vrf_name,
272 0 : MacAddress::BroadcastMac(), ethernet_tag));
273 0 : DBRequest nh_req;
274 : //For same BGP peer type comp type helps in identifying if its a delete
275 : //for TOR or EVPN path.
276 : //Only ethernet tag is required, rest are dummy.
277 0 : const BgpPeer *bgp_peer = dynamic_cast<const BgpPeer *>(peer);
278 0 : if (bgp_peer) {
279 0 : req.data.reset(new MulticastRoute("", 0,
280 0 : ethernet_tag, TunnelType::AllType(),
281 : nh_req, type,
282 0 : bgp_peer->sequence_number()));
283 : } else {
284 0 : req.data.reset(new MulticastRoute("", 0, ethernet_tag,
285 0 : TunnelType::AllType(),
286 0 : nh_req, type, 0));
287 : }
288 :
289 0 : BridgeTableEnqueue(Agent::GetInstance(), &req);
290 0 : }
291 :
292 0 : const VmInterface *BridgeAgentRouteTable::FindVmFromDhcpBinding
293 : (const MacAddress &mac) {
294 0 : const BridgeRouteEntry *l2_rt = FindRoute(mac);
295 0 : if (l2_rt == NULL)
296 0 : return NULL;
297 :
298 0 : const MacVmBindingPath *dhcp_path = l2_rt->FindMacVmBindingPath();
299 0 : if (dhcp_path == NULL)
300 0 : return NULL;
301 0 : return dhcp_path->vm_interface();
302 : }
303 :
304 : /////////////////////////////////////////////////////////////////////////////
305 : // BridgeRouteEntry methods
306 : /////////////////////////////////////////////////////////////////////////////
307 0 : const std::string BridgeRouteEntry::GetAddressString() const {
308 : //For broadcast, xmpp message is sent with address as 255.255.255.255
309 0 : if (prefix_address_ == MacAddress::BroadcastMac()) {
310 0 : return "255.255.255.255";
311 : }
312 0 : return ToString();
313 : }
314 :
315 0 : const std::string BridgeRouteEntry::GetSourceAddressString() const {
316 0 : if (prefix_address_ == MacAddress::BroadcastMac()) {
317 0 : return "0.0.0.0";
318 : }
319 0 : return (MacAddress::kZeroMac).ToString();
320 : }
321 :
322 0 : string BridgeRouteEntry::ToString() const {
323 0 : return prefix_address_.ToString();
324 : }
325 :
326 0 : int BridgeRouteEntry::CompareTo(const Route &rhs) const {
327 0 : const BridgeRouteEntry &a = static_cast<const BridgeRouteEntry &>(rhs);
328 :
329 0 : return prefix_address_.CompareTo(a.prefix_address_);
330 : }
331 :
332 0 : DBEntryBase::KeyPtr BridgeRouteEntry::GetDBRequestKey() const {
333 : BridgeRouteKey *key =
334 0 : new BridgeRouteKey(Agent::GetInstance()->local_vm_peer(),
335 0 : vrf()->GetName(), prefix_address_);
336 0 : return DBEntryBase::KeyPtr(key);
337 : }
338 :
339 0 : void BridgeRouteEntry::SetKey(const DBRequestKey *key) {
340 0 : const BridgeRouteKey *k = static_cast<const BridgeRouteKey *>(key);
341 0 : SetVrf(Agent::GetInstance()->vrf_table()->FindVrfFromName(k->vrf_name()));
342 0 : prefix_address_ = k->GetMac();
343 0 : }
344 :
345 0 : uint32_t BridgeRouteEntry::GetActiveLabel() const {
346 0 : uint32_t label = 0;
347 :
348 0 : if (is_multicast()) {
349 0 : if (TunnelType::ComputeType(TunnelType::AllType()) ==
350 : (1 << TunnelType::VXLAN)) {
351 0 : label = GetActivePath()->vxlan_id();
352 : } else {
353 0 : label = GetActivePath()->label();
354 : }
355 : } else {
356 0 : label = GetActivePath()->GetActiveLabel();
357 : }
358 0 : return label;
359 : }
360 :
361 0 : AgentPath *BridgeRouteEntry::FindPathUsingKeyData
362 : (const AgentRouteKey *key, const AgentRouteData *data) const {
363 0 : const Peer *peer = key->peer();
364 0 : if (is_multicast())
365 0 : return FindMulticastPathUsingKeyData(key, data);
366 0 : const EvpnPeer *evpn_peer = dynamic_cast<const EvpnPeer*>(peer);
367 0 : if (evpn_peer != NULL)
368 0 : return FindEvpnPathUsingKeyData(key, data);
369 :
370 0 : return FindPath(peer);
371 : }
372 :
373 0 : AgentPath *BridgeRouteEntry::FindMulticastPathUsingKeyData
374 : (const AgentRouteKey *key, const AgentRouteData *data) const {
375 0 : assert(is_multicast());
376 :
377 : Route::PathList::const_iterator it;
378 0 : for (it = GetPathList().begin(); it != GetPathList().end();
379 : it++) {
380 0 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
381 0 : if (path->peer() != key->peer())
382 0 : continue;
383 :
384 : //Handle multicast peer matching,
385 : //In case of BGP peer also match VXLAN id.
386 0 : if (path->peer()->GetType() != Peer::BGP_PEER)
387 0 : return const_cast<AgentPath *>(path);
388 :
389 : const MulticastRoute *multicast_data =
390 0 : dynamic_cast<const MulticastRoute *>(data);
391 0 : assert(multicast_data != NULL);
392 0 : if (multicast_data->vxlan_id() != path->vxlan_id())
393 0 : continue;
394 :
395 : //In multicast from same peer, TOR and EVPN comp can
396 : //come. These should not overlap and be installed as
397 : //different path.
398 0 : const CompositeNH *cnh = dynamic_cast<CompositeNH *>(path->nexthop());
399 0 : if ((cnh != NULL) &&
400 0 : (multicast_data->comp_nh_type() != cnh->composite_nh_type()))
401 0 : continue;
402 :
403 0 : return const_cast<AgentPath *>(path);
404 : }
405 0 : return NULL;
406 : }
407 :
408 0 : AgentPath *BridgeRouteEntry::FindEvpnPathUsingKeyData
409 : (const AgentRouteKey *key, const AgentRouteData *data) const {
410 0 : const Peer *peer = key->peer();
411 0 : const EvpnPeer *evpn_peer = dynamic_cast<const EvpnPeer*>(peer);
412 0 : assert(evpn_peer != NULL);
413 :
414 : Route::PathList::const_iterator it;
415 0 : for (it = GetPathList().begin(); it != GetPathList().end(); it++) {
416 0 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
417 0 : if (path->peer() != key->peer())
418 0 : continue;
419 :
420 : //Handle mac route added via evpn route.
421 : const EvpnDerivedPath *evpn_path =
422 0 : dynamic_cast<const EvpnDerivedPath *>(path);
423 : const EvpnDerivedPathData *evpn_data =
424 0 : dynamic_cast<const EvpnDerivedPathData *>(data);
425 0 : assert(evpn_path != NULL);
426 0 : assert(evpn_data != NULL);
427 0 : if (evpn_path->ethernet_tag() != evpn_data->ethernet_tag())
428 0 : continue;
429 0 : if (evpn_path->ip_addr() != evpn_data->ip_addr())
430 0 : continue;
431 0 : return const_cast<AgentPath *>(path);
432 : }
433 0 : return NULL;
434 : }
435 :
436 0 : const MacVmBindingPath *BridgeRouteEntry::FindMacVmBindingPath() const {
437 0 : Agent *agent = (static_cast<AgentRouteTable *> (get_table()))->agent();
438 0 : return dynamic_cast<MacVmBindingPath*>(FindPath(agent->mac_vm_binding_peer()));
439 : }
440 :
441 0 : bool BridgeRouteEntry::ReComputePathAdd(AgentPath *path) {
442 0 : if (is_multicast()) {
443 : //evaluate add of path
444 0 : return ReComputeMulticastPaths(path, false);
445 : }
446 0 : return false;
447 : }
448 :
449 0 : bool BridgeRouteEntry::ReComputePathDeletion(AgentPath *path) {
450 0 : if (is_multicast()) {
451 : //evaluate delete of path
452 0 : return ReComputeMulticastPaths(path, true);
453 : }
454 0 : return false;
455 : }
456 :
457 : /////////////////////////////////////////////////////////////////////////////
458 : // Sandesh related methods
459 : /////////////////////////////////////////////////////////////////////////////
460 0 : void BridgeRouteReq::HandleRequest() const {
461 : VrfEntry *vrf =
462 0 : Agent::GetInstance()->vrf_table()->FindVrfFromId(get_vrf_index());
463 0 : if (!vrf) {
464 0 : ErrorResp *resp = new ErrorResp();
465 0 : resp->set_context(context());
466 0 : resp->Response();
467 0 : return;
468 : }
469 :
470 0 : AgentSandeshPtr sand(new AgentBridgeRtSandesh(vrf, context(), "",
471 0 : get_stale(), get_mac()));
472 0 : sand->DoSandesh(sand);
473 0 : }
474 :
475 0 : AgentSandeshPtr BridgeAgentRouteTable::GetAgentSandesh
476 : (const AgentSandeshArguments *args, const std::string &context) {
477 0 : return AgentSandeshPtr(new AgentBridgeRtSandesh(vrf_entry(), context, "",
478 0 : false, args->GetString("mac")));
479 : }
480 :
481 0 : bool BridgeRouteEntry::DBEntrySandesh(Sandesh *sresp, bool stale) const {
482 0 : BridgeRouteResp *resp = static_cast<BridgeRouteResp *>(sresp);
483 0 : RouteL2SandeshData data;
484 0 : data.set_mac(ToString());
485 0 : data.set_src_vrf(vrf()->GetName());
486 :
487 0 : for (Route::PathList::const_iterator it = GetPathList().begin();
488 0 : it != GetPathList().end(); it++) {
489 0 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
490 0 : if (path) {
491 0 : PathSandeshData pdata;
492 0 : path->SetSandeshData(pdata);
493 0 : if (is_multicast()) {
494 0 : pdata.set_vxlan_id(path->vxlan_id());
495 : }
496 0 : const EvpnDerivedPath *evpn_path = dynamic_cast<const EvpnDerivedPath *>(path);
497 0 : if (evpn_path) {
498 0 : pdata.set_info(evpn_path->parent());
499 : }
500 0 : data.path_list.push_back(pdata);
501 0 : }
502 : }
503 : std::vector<RouteL2SandeshData> &list =
504 0 : const_cast<std::vector<RouteL2SandeshData>&>(resp->get_route_list());
505 0 : list.push_back(data);
506 0 : return true;
507 0 : }
508 :
509 : //Supporting deprecated layer2 requests
510 0 : void Layer2RouteReq::HandleRequest() const {
511 : VrfEntry *vrf =
512 0 : Agent::GetInstance()->vrf_table()->FindVrfFromId(get_vrf_index());
513 0 : if (!vrf) {
514 0 : ErrorResp *resp = new ErrorResp();
515 0 : resp->set_context(context());
516 0 : resp->Response();
517 0 : return;
518 : }
519 :
520 0 : AgentSandeshPtr sand(new AgentLayer2RtSandesh(vrf, context(), "",
521 0 : get_stale()));
522 0 : sand->DoSandesh(sand);
523 0 : }
|