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 413 : static void BridgeTableEnqueue(Agent *agent, DBRequest *req) {
27 413 : AgentRouteTable *table = agent->fabric_l2_unicast_table();
28 413 : if (table) {
29 405 : table->Enqueue(req);
30 : }
31 413 : }
32 :
33 611 : static void BridgeTableProcess(Agent *agent, const string &vrf_name,
34 : DBRequest &req) {
35 : AgentRouteTable *table =
36 611 : agent->vrf_table()->GetBridgeRouteTable(vrf_name);
37 611 : if (table) {
38 611 : table->Process(req);
39 : }
40 611 : }
41 :
42 : /////////////////////////////////////////////////////////////////////////////
43 : // BridgeRouteKey methods
44 : /////////////////////////////////////////////////////////////////////////////
45 0 : string BridgeRouteKey::ToString() const {
46 0 : return dmac_.ToString();
47 : }
48 :
49 80 : BridgeRouteKey *BridgeRouteKey::Clone() const {
50 80 : return new BridgeRouteKey(peer(), vrf_name_, dmac_);
51 : }
52 :
53 : AgentRoute *
54 1883 : BridgeRouteKey::AllocRouteEntry(VrfEntry *vrf, bool is_multicast) const
55 : {
56 1883 : BridgeRouteEntry *entry = new BridgeRouteEntry(vrf, dmac_,
57 1883 : peer()->GetType(),
58 1883 : is_multicast);
59 1883 : return static_cast<AgentRoute *>(entry);
60 : }
61 :
62 : /////////////////////////////////////////////////////////////////////////////
63 : // BridgeAgentRouteTable methods
64 : /////////////////////////////////////////////////////////////////////////////
65 38 : DBTableBase *BridgeAgentRouteTable::CreateTable(DB *db,
66 : const std::string &name) {
67 38 : AgentRouteTable *table = new BridgeAgentRouteTable(db, name);
68 38 : table->Init();
69 38 : return table;
70 : }
71 :
72 153 : BridgeRouteEntry *BridgeAgentRouteTable::FindRoute(const MacAddress &mac) {
73 153 : BridgeRouteEntry entry(vrf_entry(), mac, Peer::LOCAL_PEER, false);
74 306 : return static_cast<BridgeRouteEntry *>(FindActiveEntry(&entry));
75 153 : }
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 76 : 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 76 : Agent *agent = Agent::GetInstance();
98 76 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
99 76 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
100 :
101 76 : PacketInterfaceKey intf_key(boost::uuids::nil_uuid(),
102 152 : agent->pkt_interface_name());
103 76 : req.data.reset(new HostRoute(intf_key, vn_name));
104 :
105 76 : BridgeTableEnqueue(agent, &req);
106 76 : }
107 :
108 76 : 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 76 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
114 76 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, vxlan_id));
115 152 : req.data.reset(new L2ReceiveRoute(vn_name, vxlan_id, 0, PathPreference(),
116 76 : peer->sequence_number()));
117 76 : Process(req);
118 76 : }
119 :
120 221 : void BridgeAgentRouteTable::AddBridgeRoute(const AgentRoute *rt) {
121 221 : const EvpnRouteEntry *evpn_rt =
122 : static_cast<const EvpnRouteEntry *>(rt);
123 221 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
124 442 : req.key.reset(new BridgeRouteKey(agent()->evpn_peer(),
125 221 : evpn_rt->vrf()->GetName(),
126 221 : evpn_rt->mac(), 0));
127 221 : req.data.reset(new EvpnDerivedPathData(evpn_rt));
128 221 : BridgeTableProcess(agent(), vrf_name(), req);
129 221 : }
130 :
131 :
132 146 : 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 146 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
138 146 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
139 146 : req.data.reset(new MacVmBindingPathData(vm_intf, flood_dhcp));
140 146 : BridgeTableProcess(agent(), vrf_name, req);
141 146 : }
142 :
143 42 : void BridgeAgentRouteTable::DeleteMacVmBindingRoute(const Peer *peer,
144 : const std::string &vrf_name,
145 : const MacAddress &mac,
146 : const VmInterface *vm_intf) {
147 42 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
148 42 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, 0));
149 42 : req.data.reset(new MacVmBindingPathData(vm_intf, false));
150 42 : BridgeTableProcess(agent(), vrf_name, req);
151 42 : }
152 :
153 52 : void BridgeAgentRouteTable::DeleteBridgeRoute(const AgentRoute *rt) {
154 52 : const EvpnRouteEntry *evpn_rt =
155 : static_cast<const EvpnRouteEntry *>(rt);
156 52 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
157 104 : req.key.reset(new BridgeRouteKey(agent()->evpn_peer(),
158 52 : evpn_rt->vrf()->GetName(),
159 52 : evpn_rt->mac(), 0));
160 52 : req.data.reset(new EvpnDerivedPathData(evpn_rt));
161 52 : BridgeTableProcess(Agent::GetInstance(), evpn_rt->vrf()->GetName(), req);
162 52 : }
163 :
164 150 : void BridgeAgentRouteTable::Delete(const Peer *peer, const string &vrf_name,
165 : const MacAddress &mac,
166 : uint32_t ethernet_tag) {
167 150 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
168 150 : req.key.reset(new BridgeRouteKey(peer, vrf_name, mac, ethernet_tag));
169 150 : req.data.reset(NULL);
170 150 : BridgeTableProcess(Agent::GetInstance(), vrf_name, req);
171 150 : }
172 :
173 254 : 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 254 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
184 254 : nh_req.key.reset(new CompositeNHKey(type, false, component_nh_key_list,
185 254 : vrf_name));
186 254 : nh_req.data.reset(new CompositeNHData(pbb_nh, learning_enabled, false));
187 : return (new MulticastRoute(vn_name, label,
188 : vxlan_id, tunnel_type,
189 508 : nh_req, type, 0));
190 254 : }
191 :
192 4 : 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 4 : const BgpPeer *bgp_peer = dynamic_cast<const BgpPeer *>(peer);
205 4 : assert(bgp_peer != NULL);
206 4 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
207 4 : nh_req.key.reset(new CompositeNHKey(type, false, component_nh_key_list,
208 4 : vrf_name));
209 4 : nh_req.data.reset(new CompositeNHData(pbb_nh, learning_enabled, false));
210 : return (new MulticastRoute(vn_name, label, ethernet_tag, tunnel_type,
211 8 : nh_req, type, bgp_peer->sequence_number()));
212 4 : }
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 258 : void BridgeAgentRouteTable::AddBridgeBroadcastRoute(const Peer *peer,
255 : const string &vrf_name,
256 : uint32_t ethernet_tag,
257 : AgentRouteData *data) {
258 :
259 258 : DBRequest req(DBRequest::DB_ENTRY_ADD_CHANGE);
260 258 : req.key.reset(new BridgeRouteKey(peer, vrf_name,
261 258 : MacAddress::BroadcastMac(), ethernet_tag));
262 258 : req.data.reset(data);
263 258 : BridgeTableEnqueue(Agent::GetInstance(), &req);
264 258 : }
265 :
266 79 : void BridgeAgentRouteTable::DeleteBroadcastReq(const Peer *peer,
267 : const string &vrf_name,
268 : uint32_t ethernet_tag,
269 : COMPOSITETYPE type) {
270 79 : DBRequest req(DBRequest::DB_ENTRY_DELETE);
271 79 : req.key.reset(new BridgeRouteKey(peer, vrf_name,
272 79 : MacAddress::BroadcastMac(), ethernet_tag));
273 79 : 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 79 : const BgpPeer *bgp_peer = dynamic_cast<const BgpPeer *>(peer);
278 79 : if (bgp_peer) {
279 4 : req.data.reset(new MulticastRoute("", 0,
280 2 : ethernet_tag, TunnelType::AllType(),
281 : nh_req, type,
282 2 : bgp_peer->sequence_number()));
283 : } else {
284 154 : req.data.reset(new MulticastRoute("", 0, ethernet_tag,
285 77 : TunnelType::AllType(),
286 77 : nh_req, type, 0));
287 : }
288 :
289 79 : BridgeTableEnqueue(Agent::GetInstance(), &req);
290 79 : }
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 217 : const std::string BridgeRouteEntry::GetAddressString() const {
308 : //For broadcast, xmpp message is sent with address as 255.255.255.255
309 217 : if (prefix_address_ == MacAddress::BroadcastMac()) {
310 42 : return "255.255.255.255";
311 : }
312 175 : return ToString();
313 : }
314 :
315 2 : const std::string BridgeRouteEntry::GetSourceAddressString() const {
316 2 : if (prefix_address_ == MacAddress::BroadcastMac()) {
317 2 : return "0.0.0.0";
318 : }
319 0 : return (MacAddress::kZeroMac).ToString();
320 : }
321 :
322 1929 : string BridgeRouteEntry::ToString() const {
323 1929 : return prefix_address_.ToString();
324 : }
325 :
326 9281 : int BridgeRouteEntry::CompareTo(const Route &rhs) const {
327 9281 : const BridgeRouteEntry &a = static_cast<const BridgeRouteEntry &>(rhs);
328 :
329 9281 : return prefix_address_.CompareTo(a.prefix_address_);
330 : }
331 :
332 569 : DBEntryBase::KeyPtr BridgeRouteEntry::GetDBRequestKey() const {
333 : BridgeRouteKey *key =
334 569 : new BridgeRouteKey(Agent::GetInstance()->local_vm_peer(),
335 569 : vrf()->GetName(), prefix_address_);
336 569 : 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 760 : AgentPath *BridgeRouteEntry::FindPathUsingKeyData
362 : (const AgentRouteKey *key, const AgentRouteData *data) const {
363 760 : const Peer *peer = key->peer();
364 760 : if (is_multicast())
365 252 : return FindMulticastPathUsingKeyData(key, data);
366 508 : const EvpnPeer *evpn_peer = dynamic_cast<const EvpnPeer*>(peer);
367 508 : if (evpn_peer != NULL)
368 218 : return FindEvpnPathUsingKeyData(key, data);
369 :
370 290 : return FindPath(peer);
371 : }
372 :
373 252 : AgentPath *BridgeRouteEntry::FindMulticastPathUsingKeyData
374 : (const AgentRouteKey *key, const AgentRouteData *data) const {
375 252 : assert(is_multicast());
376 :
377 : Route::PathList::const_iterator it;
378 1210 : for (it = GetPathList().begin(); it != GetPathList().end();
379 : it++) {
380 418 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
381 418 : if (path->peer() != key->peer())
382 227 : continue;
383 :
384 : //Handle multicast peer matching,
385 : //In case of BGP peer also match VXLAN id.
386 191 : if (path->peer()->GetType() != Peer::BGP_PEER)
387 191 : 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 61 : return NULL;
406 : }
407 :
408 218 : AgentPath *BridgeRouteEntry::FindEvpnPathUsingKeyData
409 : (const AgentRouteKey *key, const AgentRouteData *data) const {
410 218 : const Peer *peer = key->peer();
411 218 : const EvpnPeer *evpn_peer = dynamic_cast<const EvpnPeer*>(peer);
412 218 : assert(evpn_peer != NULL);
413 :
414 : Route::PathList::const_iterator it;
415 916 : for (it = GetPathList().begin(); it != GetPathList().end(); it++) {
416 304 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
417 304 : if (path->peer() != key->peer())
418 45 : continue;
419 :
420 : //Handle mac route added via evpn route.
421 : const EvpnDerivedPath *evpn_path =
422 259 : dynamic_cast<const EvpnDerivedPath *>(path);
423 : const EvpnDerivedPathData *evpn_data =
424 259 : dynamic_cast<const EvpnDerivedPathData *>(data);
425 259 : assert(evpn_path != NULL);
426 259 : assert(evpn_data != NULL);
427 259 : if (evpn_path->ethernet_tag() != evpn_data->ethernet_tag())
428 0 : continue;
429 259 : if (evpn_path->ip_addr() != evpn_data->ip_addr())
430 86 : continue;
431 173 : return const_cast<AgentPath *>(path);
432 : }
433 45 : return NULL;
434 : }
435 :
436 612 : const MacVmBindingPath *BridgeRouteEntry::FindMacVmBindingPath() const {
437 612 : Agent *agent = (static_cast<AgentRouteTable *> (get_table()))->agent();
438 612 : return dynamic_cast<MacVmBindingPath*>(FindPath(agent->mac_vm_binding_peer()));
439 : }
440 :
441 760 : bool BridgeRouteEntry::ReComputePathAdd(AgentPath *path) {
442 760 : if (is_multicast()) {
443 : //evaluate add of path
444 252 : return ReComputeMulticastPaths(path, false);
445 : }
446 508 : return false;
447 : }
448 :
449 281 : bool BridgeRouteEntry::ReComputePathDeletion(AgentPath *path) {
450 281 : if (is_multicast()) {
451 : //evaluate delete of path
452 61 : return ReComputeMulticastPaths(path, true);
453 : }
454 220 : 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 : }
|