blob: 35ca7f959f1dd041e8242f75eec2c16909cad9e9 [file] [log] [blame]
Pingping Linffa27d32015-04-30 14:41:03 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Pingping Linffa27d32015-04-30 14:41:03 -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.virtualbng;
17
Pingping Lin53ae34f2015-06-09 10:07:08 -070018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Pingping Linffa27d32015-04-30 14:41:03 -070021import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.IpAddress;
29import org.onlab.packet.IpPrefix;
30import org.onlab.packet.MacAddress;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.net.ConnectPoint;
Pingping Lindead2052015-06-08 16:07:23 -070034import org.onosproject.net.DeviceId;
Ray Milkeya2cf3a12018-02-15 16:13:56 -080035import org.onosproject.net.FilteredConnectPoint;
Pingping Linffa27d32015-04-30 14:41:03 -070036import org.onosproject.net.Host;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070041import org.onosproject.net.host.HostEvent;
42import org.onosproject.net.host.HostListener;
Pingping Linffa27d32015-04-30 14:41:03 -070043import org.onosproject.net.host.HostService;
44import org.onosproject.net.intent.IntentService;
45import org.onosproject.net.intent.Key;
46import org.onosproject.net.intent.PointToPointIntent;
47import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
Jonathan Hartbbc352f2015-10-27 19:24:36 -070050import java.util.Iterator;
51import java.util.Map;
52import java.util.Map.Entry;
53import java.util.concurrent.ConcurrentHashMap;
54
55import static com.google.common.base.Preconditions.checkNotNull;
56
Pingping Linffa27d32015-04-30 14:41:03 -070057/**
58 * This is a virtual Broadband Network Gateway (BNG) application. It mainly
59 * has 3 functions:
60 * (1) assigns and replies a public IP address to a REST request with a private
61 * IP address
62 * (2) maintains the mapping from the private IP address to the public IP address
63 * (3) installs point to point intents for the host configured with private IP
64 * address to access Internet
65 */
66@Component(immediate = true)
67@Service
68public class VbngManager implements VbngService {
69
70 private static final String APP_NAME = "org.onosproject.virtualbng";
Pingping Lin53ae34f2015-06-09 10:07:08 -070071 private static final String VBNG_MAP_NAME = "vbng_mapping";
Pingping Linffa27d32015-04-30 14:41:03 -070072
73 private final Logger log = LoggerFactory.getLogger(getClass());
74
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected CoreService coreService;
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected HostService hostService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected IntentService intentService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected VbngConfigurationService vbngConfigurationService;
86
87 private ApplicationId appId;
88 private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost;
89 private Map<IpAddress, PointToPointIntent> p2pIntentsToHost;
90
Pingping Lindead2052015-06-08 16:07:23 -070091 // This map stores the mapping from the private IP addresses to VcpeHost.
92 // The IP addresses in this map are all the private IP addresses we failed
93 // to create vBNGs due to the next hop host was not in ONOS.
94 private Map<IpAddress, VcpeHost> privateIpAddressMap;
95
96 // Store the mapping from hostname to connect point
97 private Map<String, ConnectPoint> nodeToPort;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070098
99 private HostListener hostListener;
100 private IpAddress nextHopIpAddress;
101
Pingping Lindead2052015-06-08 16:07:23 -0700102 private static final DeviceId FABRIC_DEVICE_ID =
103 DeviceId.deviceId("of:8f0e486e73000187");
104
Pingping Linffa27d32015-04-30 14:41:03 -0700105 @Activate
106 public void activate() {
107 appId = coreService.registerApplication(APP_NAME);
108 p2pIntentsFromHost = new ConcurrentHashMap<>();
109 p2pIntentsToHost = new ConcurrentHashMap<>();
Pingping Lindead2052015-06-08 16:07:23 -0700110 privateIpAddressMap = new ConcurrentHashMap<>();
111
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700112 nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress();
Jonathan Hartbbc352f2015-10-27 19:24:36 -0700113 nodeToPort = vbngConfigurationService.getNodeToPort();
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700114 hostListener = new InternalHostListener();
115 hostService.addListener(hostListener);
116
Pingping Linffa27d32015-04-30 14:41:03 -0700117 log.info("vBNG Started");
Pingping Lin53ae34f2015-06-09 10:07:08 -0700118
119 // Recover the status before vBNG restarts
120 statusRecovery();
Pingping Linffa27d32015-04-30 14:41:03 -0700121 }
122
123 @Deactivate
124 public void deactivate() {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700125 hostService.removeListener(hostListener);
Pingping Linffa27d32015-04-30 14:41:03 -0700126 log.info("vBNG Stopped");
127 }
128
Pingping Lindead2052015-06-08 16:07:23 -0700129 /**
Pingping Lin53ae34f2015-06-09 10:07:08 -0700130 * Recovers from XOS record. Re-sets up the mapping between private IP
131 * address and public IP address, re-calculates intents and re-installs
132 * those intents.
133 */
134 private void statusRecovery() {
135 log.info("vBNG starts to recover from XOS record......");
Jonathan Hartbbc352f2015-10-27 19:24:36 -0700136 ObjectNode map;
137 try {
138 RestClient restClient =
139 new RestClient(vbngConfigurationService.getXosIpAddress(),
140 vbngConfigurationService.getXosRestPort());
141 map = restClient.getRest();
142 } catch (Exception e) {
143 log.error("Could not contact XOS", e);
144 return;
145 }
Pingping Lin53ae34f2015-06-09 10:07:08 -0700146 if (map == null) {
147 log.info("Stop to recover vBNG status due to the vBNG map "
148 + "is null!");
149 return;
150 }
151
152 log.info("Get record from XOS: {}", map);
153
154 ArrayNode array = (ArrayNode) map.get(VBNG_MAP_NAME);
155 Iterator<JsonNode> entries = array.elements();
156 while (entries.hasNext()) {
157 ObjectNode entry = (ObjectNode) entries.next();
158
159 IpAddress hostIpAdddress =
160 IpAddress.valueOf(entry.get("private_ip").asText());
161 IpAddress publicIpAddress =
162 IpAddress.valueOf(entry.get("routeable_subnet").asText());
163 MacAddress macAddress =
164 MacAddress.valueOf(entry.get("mac").asText());
165 String hostName = entry.get("hostname").asText();
166
167 // Create vBNG
168 createVbng(hostIpAdddress, publicIpAddress, macAddress, hostName);
169
170 }
171 }
172
173 /**
Pingping Lin53ae34f2015-06-09 10:07:08 -0700174 * Creates a new vBNG.
175 *
176 * @param privateIpAddress a private IP address
177 * @param publicIpAddress the public IP address for the private IP address
178 * @param hostMacAddress the MAC address for the private IP address
179 * @param hostName the host name for the private IP address
180 */
181 private void createVbng(IpAddress privateIpAddress,
182 IpAddress publicIpAddress,
183 MacAddress hostMacAddress,
184 String hostName) {
185 boolean result = vbngConfigurationService
186 .assignSpecifiedPublicIp(publicIpAddress, privateIpAddress);
187 if (!result) {
188 log.info("Assign public IP address {} for private IP address {} "
189 + "failed!", publicIpAddress, privateIpAddress);
190 log.info("Failed to create vBNG for private IP address {}",
191 privateIpAddress);
192 return;
193 }
194 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
195 privateIpAddress, publicIpAddress);
196
197 // Setup paths between the host configured with private IP and
198 // next hop
199 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
200 hostMacAddress, hostName)) {
201 privateIpAddressMap.put(privateIpAddress,
202 new VcpeHost(hostMacAddress, hostName));
203 }
204 }
205
Pingping Linffa27d32015-04-30 14:41:03 -0700206 @Override
Pingping Lindead2052015-06-08 16:07:23 -0700207 public IpAddress createVbng(IpAddress privateIpAddress,
208 MacAddress hostMacAddress,
209 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700210
Pingping Linffa27d32015-04-30 14:41:03 -0700211 IpAddress publicIpAddress =
212 vbngConfigurationService.getAvailablePublicIpAddress(
213 privateIpAddress);
214 if (publicIpAddress == null) {
215 log.info("Did not find an available public IP address to use.");
216 return null;
217 }
Pingping Linde77ee52015-06-03 17:16:07 -0700218 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
Pingping Linffa27d32015-04-30 14:41:03 -0700219 privateIpAddress, publicIpAddress);
220
221 // Setup paths between the host configured with private IP and
222 // next hop
Pingping Lindead2052015-06-08 16:07:23 -0700223 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
224 hostMacAddress, hostName)) {
225 privateIpAddressMap.put(privateIpAddress,
226 new VcpeHost(hostMacAddress, hostName));
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700227 }
Pingping Linffa27d32015-04-30 14:41:03 -0700228 return publicIpAddress;
229 }
230
Pingping Lind2afaf22015-06-02 10:46:29 -0700231 @Override
232 public IpAddress deleteVbng(IpAddress privateIpAddress) {
233 // Recycle the public IP address assigned to this private IP address.
234 // Recycling will also delete the mapping entry from the private IP
235 // address to public IP address.
236 IpAddress assignedPublicIpAddress = vbngConfigurationService
237 .recycleAssignedPublicIpAddress(privateIpAddress);
238 if (assignedPublicIpAddress == null) {
239 return null;
240 }
241
Pingping Lindead2052015-06-08 16:07:23 -0700242 // Remove the private IP address from privateIpAddressMap
243 privateIpAddressMap.remove(privateIpAddress);
Pingping Lind2afaf22015-06-02 10:46:29 -0700244
245 // Remove intents
246 removeForwardingPaths(privateIpAddress);
247
248 return assignedPublicIpAddress;
249 }
250
251 /**
252 * Removes the forwarding paths in both two directions between host
253 * configured with private IP and next hop.
254 *
255 * @param privateIp the private IP address of a local host
256 */
257 private void removeForwardingPaths(IpAddress privateIp) {
258 PointToPointIntent toNextHopIntent =
259 p2pIntentsFromHost.remove(privateIp);
260 if (toNextHopIntent != null) {
261 intentService.withdraw(toNextHopIntent);
262 //intentService.purge(toNextHopIntent);
263 }
264 PointToPointIntent toLocalHostIntent =
265 p2pIntentsToHost.remove(privateIp);
266 if (toLocalHostIntent != null) {
267 intentService.withdraw(toLocalHostIntent);
268 //intentService.purge(toLocalHostIntent);
269 }
270 }
271
Pingping Linffa27d32015-04-30 14:41:03 -0700272 /**
273 * Sets up forwarding paths in both two directions between host configured
274 * with private IP and next hop.
275 *
276 * @param privateIp the private IP address of a local host
277 * @param publicIp the public IP address assigned for the private IP address
Pingping Lindead2052015-06-08 16:07:23 -0700278 * @param hostMacAddress the MAC address for the IP address
279 * @param hostName the host name for the IP address
Pingping Linffa27d32015-04-30 14:41:03 -0700280 */
Pingping Lindead2052015-06-08 16:07:23 -0700281 private boolean setupForwardingPaths(IpAddress privateIp,
282 IpAddress publicIp,
283 MacAddress hostMacAddress,
284 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700285 checkNotNull(privateIp);
286 checkNotNull(publicIp);
Pingping Lindead2052015-06-08 16:07:23 -0700287 checkNotNull(hostMacAddress);
288 checkNotNull(hostName);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700289
290 if (nextHopIpAddress == null) {
291 log.warn("Did not find next hop IP address");
292 return false;
293 }
Pingping Linffa27d32015-04-30 14:41:03 -0700294
295 // If there are already intents for private IP address in the system,
296 // we will do nothing and directly return.
297 if (p2pIntentsFromHost.containsKey(privateIp)
298 && p2pIntentsToHost.containsKey(privateIp)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700299 return true;
Pingping Linffa27d32015-04-30 14:41:03 -0700300 }
301
Pingping Linffa27d32015-04-30 14:41:03 -0700302 Host nextHopHost = null;
303 if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) {
304 nextHopHost = hostService.getHostsByIp(nextHopIpAddress)
305 .iterator().next();
306 } else {
Pingping Linffa27d32015-04-30 14:41:03 -0700307 hostService.startMonitoringIp(nextHopIpAddress);
308 if (hostService.getHostsByIp(privateIp).isEmpty()) {
309 hostService.startMonitoringIp(privateIp);
310 }
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700311 return false;
Pingping Linffa27d32015-04-30 14:41:03 -0700312 }
313
Pingping Linffa27d32015-04-30 14:41:03 -0700314 ConnectPoint nextHopConnectPoint =
315 new ConnectPoint(nextHopHost.location().elementId(),
316 nextHopHost.location().port());
Pingping Lindead2052015-06-08 16:07:23 -0700317 ConnectPoint localHostConnectPoint = nodeToPort.get(hostName);
Pingping Linffa27d32015-04-30 14:41:03 -0700318
319 // Generate and install intent for traffic from host configured with
320 // private IP
321 if (!p2pIntentsFromHost.containsKey(privateIp)) {
322 PointToPointIntent toNextHopIntent
323 = srcMatchIntentGenerator(privateIp,
324 publicIp,
325 nextHopHost.mac(),
326 nextHopConnectPoint,
327 localHostConnectPoint
328 );
329 p2pIntentsFromHost.put(privateIp, toNextHopIntent);
330 intentService.submit(toNextHopIntent);
331 }
332
333 // Generate and install intent for traffic to host configured with
334 // private IP
335 if (!p2pIntentsToHost.containsKey(privateIp)) {
336 PointToPointIntent toLocalHostIntent
337 = dstMatchIntentGenerator(publicIp,
338 privateIp,
Pingping Lindead2052015-06-08 16:07:23 -0700339 hostMacAddress,
Pingping Linffa27d32015-04-30 14:41:03 -0700340 localHostConnectPoint,
341 nextHopConnectPoint);
Pingping Lind2afaf22015-06-02 10:46:29 -0700342 p2pIntentsToHost.put(privateIp, toLocalHostIntent);
Pingping Linffa27d32015-04-30 14:41:03 -0700343 intentService.submit(toLocalHostIntent);
344 }
345
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700346 return true;
347 }
348
349 /**
350 * Listener for host events.
351 */
352 private class InternalHostListener implements HostListener {
353 @Override
354 public void event(HostEvent event) {
355 log.debug("Received HostEvent {}", event);
356
357 Host host = event.subject();
358 if (event.type() != HostEvent.Type.HOST_ADDED) {
359 return;
360 }
361
362 for (IpAddress ipAddress: host.ipAddresses()) {
Pingping Lindead2052015-06-08 16:07:23 -0700363 // The POST method from XOS gives us MAC and host name, so we
364 // do not need to do anything after receive a vCPE host event
365 // for now.
366 /*if (privateIpAddressSet.contains(ipAddress)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700367 createVbngAgain(ipAddress);
Pingping Lindead2052015-06-08 16:07:23 -0700368 }*/
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700369
370 if (nextHopIpAddress != null &&
371 ipAddress.equals(nextHopIpAddress)) {
Pingping Lindead2052015-06-08 16:07:23 -0700372
373 for (Entry<IpAddress, VcpeHost> entry:
374 privateIpAddressMap.entrySet()) {
375 createVbngAgain(entry.getKey());
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700376 }
Pingping Lindead2052015-06-08 16:07:23 -0700377
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700378 }
379 }
380 }
381 }
382
383 /**
384 * Tries to create vBNG again after receiving a host event if the IP
Pingping Lindead2052015-06-08 16:07:23 -0700385 * address of the host is the next hop IP address.
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700386 *
387 * @param privateIpAddress the private IP address
388 */
389 private void createVbngAgain(IpAddress privateIpAddress) {
390 IpAddress publicIpAddress = vbngConfigurationService
391 .getAssignedPublicIpAddress(privateIpAddress);
392 if (publicIpAddress == null) {
393 // We only need to handle the private IP addresses for which we
394 // already returned the REST replies with assigned public IP
395 // addresses. If a private IP addresses does not have an assigned
396 // public IP address, we should not get it an available public IP
397 // address here, and we should delete it in the unhandled private
Pingping Lindead2052015-06-08 16:07:23 -0700398 // IP address map.
399 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700400 return;
401 }
Pingping Lindead2052015-06-08 16:07:23 -0700402 VcpeHost vcpeHost = privateIpAddressMap.get(privateIpAddress);
403 if (setupForwardingPaths(privateIpAddress, publicIpAddress,
404 vcpeHost.macAddress, vcpeHost.hostName)) {
405 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700406 }
Pingping Linffa27d32015-04-30 14:41:03 -0700407 }
408
409 /**
410 * PointToPointIntent Generator.
411 * <p>
412 * The intent will match the source IP address in packet, rewrite the
413 * source IP address, and rewrite the destination MAC address.
414 * </p>
415 *
416 * @param srcIpAddress the source IP address in packet to match
417 * @param newSrcIpAddress the new source IP address to set
418 * @param dstMacAddress the destination MAC address to set
419 * @param dstConnectPoint the egress point
420 * @param srcConnectPoint the ingress point
421 * @return a PointToPointIntent
422 */
423 private PointToPointIntent srcMatchIntentGenerator(
424 IpAddress srcIpAddress,
425 IpAddress newSrcIpAddress,
426 MacAddress dstMacAddress,
427 ConnectPoint dstConnectPoint,
428 ConnectPoint srcConnectPoint) {
429 checkNotNull(srcIpAddress);
430 checkNotNull(newSrcIpAddress);
431 checkNotNull(dstMacAddress);
432 checkNotNull(dstConnectPoint);
433 checkNotNull(srcConnectPoint);
434
435 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
436 selector.matchEthType(Ethernet.TYPE_IPV4);
437 selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress,
438 IpPrefix.MAX_INET_MASK_LENGTH));
439
440 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
441 treatment.setEthDst(dstMacAddress);
442 treatment.setIpSrc(newSrcIpAddress);
443
444 Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId);
445 PointToPointIntent intent = PointToPointIntent.builder()
446 .appId(appId)
447 .key(key)
448 .selector(selector.build())
449 .treatment(treatment.build())
Ray Milkeya2cf3a12018-02-15 16:13:56 -0800450 .filteredEgressPoint(new FilteredConnectPoint(dstConnectPoint))
451 .filteredIngressPoint(new FilteredConnectPoint(srcConnectPoint))
Pingping Linffa27d32015-04-30 14:41:03 -0700452 .build();
453
454 log.info("Generated a PointToPointIntent for traffic from local host "
455 + ": {}", intent);
456 return intent;
457 }
458
459 /**
460 * PointToPointIntent Generator.
461 * <p>
462 * The intent will match the destination IP address in packet, rewrite the
463 * destination IP address, and rewrite the destination MAC address.
464 * </p>
465 *
466 * @param dstIpAddress the destination IP address in packet to match
467 * @param newDstIpAddress the new destination IP address to set
468 * @param dstMacAddress the destination MAC address to set
469 * @param dstConnectPoint the egress point
470 * @param srcConnectPoint the ingress point
471 * @return a PointToPointIntent
472 */
473 private PointToPointIntent dstMatchIntentGenerator(
474 IpAddress dstIpAddress,
475 IpAddress newDstIpAddress,
476 MacAddress dstMacAddress,
477 ConnectPoint dstConnectPoint,
478 ConnectPoint srcConnectPoint) {
479 checkNotNull(dstIpAddress);
480 checkNotNull(newDstIpAddress);
481 checkNotNull(dstMacAddress);
482 checkNotNull(dstConnectPoint);
483 checkNotNull(srcConnectPoint);
484
485 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
486 selector.matchEthType(Ethernet.TYPE_IPV4);
487 selector.matchIPDst(IpPrefix.valueOf(dstIpAddress,
488 IpPrefix.MAX_INET_MASK_LENGTH));
489
490 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
491 treatment.setEthDst(dstMacAddress);
492 treatment.setIpDst(newDstIpAddress);
493
494 Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId);
495 PointToPointIntent intent = PointToPointIntent.builder()
496 .appId(appId)
497 .key(key)
498 .selector(selector.build())
499 .treatment(treatment.build())
Ray Milkeya2cf3a12018-02-15 16:13:56 -0800500 .filteredEgressPoint(new FilteredConnectPoint(dstConnectPoint))
501 .filteredIngressPoint(new FilteredConnectPoint(srcConnectPoint))
Pingping Linffa27d32015-04-30 14:41:03 -0700502 .build();
503 log.info("Generated a PointToPointIntent for traffic to local host "
504 + ": {}", intent);
505
506 return intent;
507 }
Pingping Lindead2052015-06-08 16:07:23 -0700508
509 /**
510 * Constructor to store the a vCPE host info.
511 */
512 private class VcpeHost {
513 MacAddress macAddress;
514 String hostName;
515 public VcpeHost(MacAddress macAddress, String hostName) {
516 this.macAddress = macAddress;
517 this.hostName = hostName;
518 }
519 }
Pingping Linffa27d32015-04-30 14:41:03 -0700520}