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