blob: a310a96bc769bd2e88efc28a59d88d347da669dd [file] [log] [blame]
Yi Tseng7a38f9a2017-06-09 14:36:40 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Yi Tseng7a38f9a2017-06-09 14:36:40 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.dhcprelay;
17
18import java.nio.ByteBuffer;
19import java.util.Collection;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070020import java.util.Dictionary;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070021import java.util.Objects;
22import java.util.Optional;
23import java.util.Set;
24import java.util.stream.Stream;
25
Yi Tseng7a38f9a2017-06-09 14:36:40 -070026import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
29import org.apache.felix.scr.annotations.Modified;
30import org.apache.felix.scr.annotations.Property;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.apache.felix.scr.annotations.Service;
34import org.onlab.packet.ARP;
35import org.onlab.packet.DHCP;
36import org.onlab.packet.DHCP6;
37import org.onlab.packet.IPacket;
38import org.onlab.packet.IPv6;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070039import org.onlab.packet.Ethernet;
40import org.onlab.packet.IPv4;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070041import org.onlab.packet.IpAddress;
42import org.onlab.packet.MacAddress;
43import org.onlab.packet.TpPort;
44import org.onlab.packet.UDP;
45import org.onlab.packet.VlanId;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070046import org.onlab.util.Tools;
47import org.onosproject.cfg.ComponentConfigService;
48import org.onosproject.core.ApplicationId;
49import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070050import org.onosproject.dhcprelay.api.DhcpHandler;
51import org.onosproject.dhcprelay.api.DhcpRelayService;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070052import org.onosproject.dhcprelay.store.DhcpRecord;
53import org.onosproject.dhcprelay.store.DhcpRelayStore;
54import org.onosproject.incubator.net.intf.Interface;
55import org.onosproject.incubator.net.intf.InterfaceService;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070056import org.onosproject.net.ConnectPoint;
57import org.onosproject.net.Host;
58import org.onosproject.net.HostId;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070059import org.onosproject.net.config.ConfigFactory;
60import org.onosproject.net.config.NetworkConfigEvent;
61import org.onosproject.net.config.NetworkConfigListener;
62import org.onosproject.net.config.NetworkConfigRegistry;
63import org.onosproject.net.flow.DefaultTrafficSelector;
64import org.onosproject.net.flow.DefaultTrafficTreatment;
65import org.onosproject.net.flow.TrafficSelector;
66import org.onosproject.net.flow.TrafficTreatment;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070067import org.onosproject.net.host.HostEvent;
68import org.onosproject.net.host.HostListener;
69import org.onosproject.net.host.HostService;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070070import org.onosproject.net.packet.DefaultOutboundPacket;
71import org.onosproject.net.packet.OutboundPacket;
72import org.onosproject.net.packet.PacketContext;
73import org.onosproject.net.packet.PacketPriority;
74import org.onosproject.net.packet.PacketProcessor;
75import org.onosproject.net.packet.PacketService;
76import org.onosproject.net.provider.ProviderId;
77import org.osgi.service.component.ComponentContext;
78import org.slf4j.Logger;
79import org.slf4j.LoggerFactory;
80
81import com.google.common.collect.ImmutableSet;
82
Yi Tseng7a38f9a2017-06-09 14:36:40 -070083import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
Yi Tseng7a38f9a2017-06-09 14:36:40 -070084/**
85 * DHCP Relay Agent Application Component.
86 */
87@Component(immediate = true)
88@Service
89public class DhcpRelayManager implements DhcpRelayService {
90 public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
Yi Tseng51301292017-07-28 13:02:59 -070091 public static final ProviderId PROVIDER_ID = new ProviderId("host", DHCP_RELAY_APP);
Yi Tseng7a38f9a2017-06-09 14:36:40 -070092 public static final String HOST_LOCATION_PROVIDER =
93 "org.onosproject.provider.host.impl.HostLocationProvider";
94 private final Logger log = LoggerFactory.getLogger(getClass());
95 private final InternalConfigListener cfgListener = new InternalConfigListener();
96
97 private final Set<ConfigFactory> factories = ImmutableSet.of(
98 new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
99 DhcpRelayConfig.class,
100 "dhcprelay") {
101 @Override
102 public DhcpRelayConfig createConfig() {
103 return new DhcpRelayConfig();
104 }
105 }
106 );
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected NetworkConfigRegistry cfgService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected CoreService coreService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 protected PacketService packetService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected HostService hostService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700121 protected InterfaceService interfaceService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 protected DhcpRelayStore dhcpRelayStore;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected ComponentConfigService compCfgService;
128
Yi Tseng51301292017-07-28 13:02:59 -0700129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
130 target = "(version=4)")
131 protected DhcpHandler v4Handler;
132
133 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
134 target = "(version=6)")
135 protected DhcpHandler v6Handler;
136
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700137 @Property(name = "arpEnabled", boolValue = true,
Yi Tseng51301292017-07-28 13:02:59 -0700138 label = "Enable Address resolution protocol")
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700139 protected boolean arpEnabled = true;
140
141 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
142 private InternalHostListener hostListener = new InternalHostListener();
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700143 private ApplicationId appId;
144
145 @Activate
146 protected void activate(ComponentContext context) {
147 //start the dhcp relay agent
148 appId = coreService.registerApplication(DHCP_RELAY_APP);
149
150 cfgService.addListener(cfgListener);
151 factories.forEach(cfgService::registerConfigFactory);
152 //update the dhcp server configuration.
153 updateConfig();
Yi Tseng51301292017-07-28 13:02:59 -0700154
155 //add the packet processor
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700156 packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
Yi Tseng51301292017-07-28 13:02:59 -0700157
158 // listen host event for dhcp server or the gateway
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700159 hostService.addListener(hostListener);
160 requestDhcpPackets();
161 modified(context);
162
163 // disable dhcp from host location provider
164 compCfgService.preSetProperty(HOST_LOCATION_PROVIDER,
165 "useDhcp", Boolean.FALSE.toString());
166 compCfgService.registerProperties(getClass());
167 log.info("DHCP-RELAY Started");
168 }
169
170 @Deactivate
171 protected void deactivate() {
172 cfgService.removeListener(cfgListener);
173 factories.forEach(cfgService::unregisterConfigFactory);
174 packetService.removeProcessor(dhcpRelayPacketProcessor);
175 hostService.removeListener(hostListener);
176 cancelDhcpPackets();
177 cancelArpPackets();
Yi Tseng51301292017-07-28 13:02:59 -0700178 v4Handler.getDhcpGatewayIp().ifPresent(hostService::stopMonitoringIp);
179 v4Handler.getDhcpServerIp().ifPresent(hostService::stopMonitoringIp);
180 // TODO: DHCPv6 Handler
181
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700182 compCfgService.unregisterProperties(getClass(), true);
183 log.info("DHCP-RELAY Stopped");
184 }
185
186 @Modified
187 protected void modified(ComponentContext context) {
188 Dictionary<?, ?> properties = context.getProperties();
189 Boolean flag;
190
191 flag = Tools.isPropertyEnabled(properties, "arpEnabled");
192 if (flag != null) {
193 arpEnabled = flag;
194 log.info("Address resolution protocol is {}",
195 arpEnabled ? "enabled" : "disabled");
196 }
197
198 if (arpEnabled) {
199 requestArpPackets();
200 } else {
201 cancelArpPackets();
202 }
203 }
204
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700205 private void updateConfig() {
206 DhcpRelayConfig cfg = cfgService.getConfig(appId, DhcpRelayConfig.class);
207 if (cfg == null) {
208 log.warn("Dhcp Server info not available");
209 return;
210 }
Yi Tseng51301292017-07-28 13:02:59 -0700211 Optional<IpAddress> oldDhcpServerIp = v4Handler.getDhcpServerIp();
212 Optional<IpAddress> oldDhcpGatewayIp = v4Handler.getDhcpGatewayIp();
213 v4Handler.setDhcpServerConnectPoint(cfg.getDhcpServerConnectPoint());
214 v4Handler.setDhcpServerIp(cfg.getDhcpServerIp());
215 v4Handler.setDhcpGatewayIp(cfg.getDhcpGatewayIp());
216 v4Handler.setDhcpConnectMac(null);
217 v4Handler.setDhcpConnectVlan(null);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700218
Yi Tseng51301292017-07-28 13:02:59 -0700219 log.info("DHCP server connect point: " + cfg.getDhcpServerConnectPoint());
220 log.info("DHCP server ipaddress " + cfg.getDhcpServerIp());
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700221
Yi Tseng51301292017-07-28 13:02:59 -0700222 IpAddress ipToProbe = v4Handler.getDhcpGatewayIp().isPresent() ? cfg.getDhcpGatewayIp() :
223 cfg.getDhcpServerIp();
224 String hostToProbe = v4Handler.getDhcpGatewayIp().isPresent() ? "gateway" : "DHCP server";
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700225
Yi Tseng51301292017-07-28 13:02:59 -0700226 // TODO: DHCPv6 server config
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700227 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
228 if (hosts.isEmpty()) {
229 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng51301292017-07-28 13:02:59 -0700230 oldDhcpGatewayIp.ifPresent(hostService::stopMonitoringIp);
231 oldDhcpServerIp.ifPresent(hostService::stopMonitoringIp);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700232 hostService.startMonitoringIp(ipToProbe);
233 } else {
234 // Probe target is known; There should be only 1 host with this ip
235 hostUpdated(hosts.iterator().next());
236 }
237 }
238
239 private void hostRemoved(Host host) {
Yi Tseng51301292017-07-28 13:02:59 -0700240 v4Handler.getDhcpServerIp().ifPresent(ip -> {
241 if (host.ipAddresses().contains(ip)) {
242 log.warn("DHCP server {} removed", ip);
243 v4Handler.setDhcpConnectMac(null);
244 v4Handler.setDhcpConnectVlan(null);
245 }
246 });
247 v4Handler.getDhcpGatewayIp().ifPresent(ip -> {
248 if (host.ipAddresses().contains(ip)) {
249 log.warn("DHCP gateway {} removed", ip);
250 v4Handler.setDhcpConnectMac(null);
251 v4Handler.setDhcpConnectVlan(null);
252 }
253 });
254 // TODO: v6 handler
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700255 }
256
257 private void hostUpdated(Host host) {
Yi Tseng51301292017-07-28 13:02:59 -0700258 v4Handler.getDhcpGatewayIp().ifPresent(ip -> {
259 if (host.ipAddresses().contains(ip)) {
260 log.warn("DHCP gateway {} removed", ip);
261 v4Handler.setDhcpConnectMac(host.mac());
262 v4Handler.setDhcpConnectVlan(host.vlan());
263 }
264 });
265 v4Handler.getDhcpServerIp().ifPresent(ip -> {
266 if (host.ipAddresses().contains(ip)) {
267 log.warn("DHCP server {} removed", ip);
268 v4Handler.setDhcpConnectMac(host.mac());
269 v4Handler.setDhcpConnectVlan(host.vlan());
270 }
271 });
272 // TODO: v6 handler
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700273 }
274
275 /**
276 * Request DHCP packet in via PacketService.
277 */
278 private void requestDhcpPackets() {
279 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
280 .matchEthType(Ethernet.TYPE_IPV4)
281 .matchIPProtocol(IPv4.PROTOCOL_UDP)
282 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
283 packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
284
285 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
286 .matchEthType(Ethernet.TYPE_IPV4)
287 .matchIPProtocol(IPv4.PROTOCOL_UDP)
288 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
289 packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
290 }
291
292 /**
293 * Cancel requested DHCP packets in via packet service.
294 */
295 private void cancelDhcpPackets() {
296 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
297 .matchEthType(Ethernet.TYPE_IPV4)
298 .matchIPProtocol(IPv4.PROTOCOL_UDP)
299 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
300 packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
301
302 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
303 .matchEthType(Ethernet.TYPE_IPV4)
304 .matchIPProtocol(IPv4.PROTOCOL_UDP)
305 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
306 packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
307 }
308
309 /**
310 * Request ARP packet in via PacketService.
311 */
312 private void requestArpPackets() {
313 TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
314 .matchEthType(Ethernet.TYPE_ARP);
315 packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
316 }
317
318 /**
319 * Cancel requested ARP packets in via packet service.
320 */
321 private void cancelArpPackets() {
322 TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
323 .matchEthType(Ethernet.TYPE_ARP);
324 packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
325 }
326
327 @Override
328 public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
329 return dhcpRelayStore.getDhcpRecord(hostId);
330 }
331
332 @Override
333 public Collection<DhcpRecord> getDhcpRecords() {
334 return dhcpRelayStore.getDhcpRecords();
335 }
336
Yi Tseng13a41a12017-07-26 13:45:01 -0700337 @Override
338 public Optional<MacAddress> getDhcpServerMacAddress() {
Yi Tseng51301292017-07-28 13:02:59 -0700339 return v4Handler.getDhcpConnectMac();
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700340 }
341
342 /**
343 * Gets DHCP data from a packet.
344 *
345 * @param packet the packet
346 * @return the DHCP data; empty if it is not a DHCP packet
347 */
348 private Optional<DHCP> findDhcp(Ethernet packet) {
349 return Stream.of(packet)
350 .filter(Objects::nonNull)
351 .map(Ethernet::getPayload)
352 .filter(p -> p instanceof IPv4)
353 .map(IPacket::getPayload)
354 .filter(Objects::nonNull)
355 .filter(p -> p instanceof UDP)
356 .map(IPacket::getPayload)
357 .filter(Objects::nonNull)
358 .filter(p -> p instanceof DHCP)
359 .map(p -> (DHCP) p)
360 .findFirst();
361 }
362
363 /**
364 * Gets DHCPv6 data from a packet.
365 *
366 * @param packet the packet
367 * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
368 */
369 private Optional<DHCP6> findDhcp6(Ethernet packet) {
370 return Stream.of(packet)
371 .filter(Objects::nonNull)
372 .map(Ethernet::getPayload)
373 .filter(p -> p instanceof IPv6)
374 .map(IPacket::getPayload)
375 .filter(Objects::nonNull)
376 .filter(p -> p instanceof UDP)
377 .map(IPacket::getPayload)
378 .filter(Objects::nonNull)
379 .filter(p -> p instanceof DHCP6)
380 .map(p -> (DHCP6) p)
381 .findFirst();
382 }
383
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700384
385 private class DhcpRelayPacketProcessor implements PacketProcessor {
386
387 @Override
388 public void process(PacketContext context) {
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700389 // process the packet and get the payload
390 Ethernet packet = context.inPacket().parsed();
391 if (packet == null) {
392 return;
393 }
394
395 findDhcp(packet).ifPresent(dhcpPayload -> {
Yi Tseng51301292017-07-28 13:02:59 -0700396 v4Handler.processDhcpPacket(context, dhcpPayload);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700397 });
398
399 findDhcp6(packet).ifPresent(dhcp6Payload -> {
Yi Tseng51301292017-07-28 13:02:59 -0700400 v6Handler.processDhcpPacket(context, dhcp6Payload);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700401 });
402
403 if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
404 ARP arpPacket = (ARP) packet.getPayload();
405 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
406 Set<Interface> interfaces = interfaceService.
407 getInterfacesByPort(context.inPacket().receivedFrom());
408 //ignore the packets if dhcp server interface is not configured on onos.
409 if (interfaces.isEmpty()) {
410 log.warn("server virtual interface not configured");
411 return;
412 }
413 if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
414 // handle request only
415 return;
416 }
417 MacAddress interfaceMac = interfaces.stream()
418 .filter(iface -> iface.vlan().equals(vlanId))
419 .map(Interface::mac)
420 .filter(mac -> !mac.equals(MacAddress.NONE))
421 .findFirst()
Yi Tseng51301292017-07-28 13:02:59 -0700422 .orElse(MacAddress.ONOS);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700423 if (interfaceMac == null) {
424 // can't find interface mac address
425 return;
426 }
427 processArpPacket(context, packet, interfaceMac);
428 }
429 }
430
431 /**
432 * Processes the ARP Payload and initiates a reply to the client.
433 *
434 * @param context the packet context
435 * @param packet the ethernet payload
436 * @param replyMac mac address to be replied
437 */
438 private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
439 ARP arpPacket = (ARP) packet.getPayload();
440 ARP arpReply = (ARP) arpPacket.clone();
441 arpReply.setOpCode(ARP.OP_REPLY);
442
443 arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
444 arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
445 arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
446 arpReply.setSenderHardwareAddress(replyMac.toBytes());
447
448 // Ethernet Frame.
449 Ethernet ethReply = new Ethernet();
450 ethReply.setSourceMACAddress(replyMac.toBytes());
451 ethReply.setDestinationMACAddress(packet.getSourceMAC());
452 ethReply.setEtherType(Ethernet.TYPE_ARP);
453 ethReply.setVlanID(packet.getVlanID());
454 ethReply.setPayload(arpReply);
455
456 ConnectPoint targetPort = context.inPacket().receivedFrom();
457 TrafficTreatment t = DefaultTrafficTreatment.builder()
458 .setOutput(targetPort.port()).build();
459 OutboundPacket o = new DefaultOutboundPacket(
460 targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
461 if (log.isTraceEnabled()) {
462 log.trace("Relaying ARP packet {} to {}", packet, targetPort);
463 }
464 packetService.emit(o);
465 }
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700466 }
467
468 /**
469 * Listener for network config events.
470 */
471 private class InternalConfigListener implements NetworkConfigListener {
472 @Override
473 public void event(NetworkConfigEvent event) {
474 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
475 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
476 event.configClass().equals(DhcpRelayConfig.class)) {
477 updateConfig();
478 log.info("Reconfigured");
479 }
480 }
481 }
482
483 /**
484 * Internal listener for host events.
485 */
486 private class InternalHostListener implements HostListener {
487 @Override
488 public void event(HostEvent event) {
489 switch (event.type()) {
490 case HOST_ADDED:
491 case HOST_UPDATED:
492 hostUpdated(event.subject());
493 break;
494 case HOST_REMOVED:
495 hostRemoved(event.subject());
496 break;
497 case HOST_MOVED:
498 // XXX todo -- moving dhcp server
499 break;
500 default:
501 break;
502 }
503 }
504 }
505}