blob: 5e82b7e8904b6d2d42bccee4bbda32bf1bb87f6a [file] [log] [blame]
Pingping Linffa27d32015-04-30 14:41:03 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
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
18import static com.google.common.base.Preconditions.checkNotNull;
19
Pingping Lin53ae34f2015-06-09 10:07:08 -070020import com.fasterxml.jackson.databind.JsonNode;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
Pingping Lindead2052015-06-08 16:07:23 -070023import com.google.common.collect.Maps;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070024
Pingping Lin53ae34f2015-06-09 10:07:08 -070025import java.util.Iterator;
Pingping Linffa27d32015-04-30 14:41:03 -070026import java.util.Map;
Pingping Lindead2052015-06-08 16:07:23 -070027import java.util.Map.Entry;
Pingping Linffa27d32015-04-30 14:41:03 -070028import java.util.concurrent.ConcurrentHashMap;
29
30import org.apache.felix.scr.annotations.Activate;
31import org.apache.felix.scr.annotations.Component;
32import org.apache.felix.scr.annotations.Deactivate;
33import org.apache.felix.scr.annotations.Reference;
34import org.apache.felix.scr.annotations.ReferenceCardinality;
35import org.apache.felix.scr.annotations.Service;
36import org.onlab.packet.Ethernet;
37import org.onlab.packet.IpAddress;
38import org.onlab.packet.IpPrefix;
39import org.onlab.packet.MacAddress;
40import org.onosproject.core.ApplicationId;
41import org.onosproject.core.CoreService;
42import org.onosproject.net.ConnectPoint;
Pingping Lindead2052015-06-08 16:07:23 -070043import org.onosproject.net.DeviceId;
Pingping Linffa27d32015-04-30 14:41:03 -070044import org.onosproject.net.Host;
Pingping Lindead2052015-06-08 16:07:23 -070045import org.onosproject.net.PortNumber;
Pingping Linffa27d32015-04-30 14:41:03 -070046import org.onosproject.net.flow.DefaultTrafficSelector;
47import org.onosproject.net.flow.DefaultTrafficTreatment;
48import org.onosproject.net.flow.TrafficSelector;
49import org.onosproject.net.flow.TrafficTreatment;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070050import org.onosproject.net.host.HostEvent;
51import org.onosproject.net.host.HostListener;
Pingping Linffa27d32015-04-30 14:41:03 -070052import org.onosproject.net.host.HostService;
53import org.onosproject.net.intent.IntentService;
54import org.onosproject.net.intent.Key;
55import org.onosproject.net.intent.PointToPointIntent;
56import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59/**
60 * This is a virtual Broadband Network Gateway (BNG) application. It mainly
61 * has 3 functions:
62 * (1) assigns and replies a public IP address to a REST request with a private
63 * IP address
64 * (2) maintains the mapping from the private IP address to the public IP address
65 * (3) installs point to point intents for the host configured with private IP
66 * address to access Internet
67 */
68@Component(immediate = true)
69@Service
70public class VbngManager implements VbngService {
71
72 private static final String APP_NAME = "org.onosproject.virtualbng";
Pingping Lin53ae34f2015-06-09 10:07:08 -070073 private static final String VBNG_MAP_NAME = "vbng_mapping";
Pingping Linffa27d32015-04-30 14:41:03 -070074
75 private final Logger log = LoggerFactory.getLogger(getClass());
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected CoreService coreService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected HostService hostService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected IntentService intentService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected VbngConfigurationService vbngConfigurationService;
88
89 private ApplicationId appId;
90 private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost;
91 private Map<IpAddress, PointToPointIntent> p2pIntentsToHost;
92
Pingping Lindead2052015-06-08 16:07:23 -070093 // This map stores the mapping from the private IP addresses to VcpeHost.
94 // The IP addresses in this map are all the private IP addresses we failed
95 // to create vBNGs due to the next hop host was not in ONOS.
96 private Map<IpAddress, VcpeHost> privateIpAddressMap;
97
98 // Store the mapping from hostname to connect point
99 private Map<String, ConnectPoint> nodeToPort;
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700100
101 private HostListener hostListener;
102 private IpAddress nextHopIpAddress;
103
Pingping Lindead2052015-06-08 16:07:23 -0700104 private static final DeviceId FABRIC_DEVICE_ID =
105 DeviceId.deviceId("of:8f0e486e73000187");
106
Pingping Linffa27d32015-04-30 14:41:03 -0700107 @Activate
108 public void activate() {
109 appId = coreService.registerApplication(APP_NAME);
110 p2pIntentsFromHost = new ConcurrentHashMap<>();
111 p2pIntentsToHost = new ConcurrentHashMap<>();
Pingping Lindead2052015-06-08 16:07:23 -0700112 privateIpAddressMap = new ConcurrentHashMap<>();
113
114 setupMap();
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700115
116 nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress();
117 hostListener = new InternalHostListener();
118 hostService.addListener(hostListener);
119
Pingping Linffa27d32015-04-30 14:41:03 -0700120 log.info("vBNG Started");
Pingping Lin53ae34f2015-06-09 10:07:08 -0700121
122 // Recover the status before vBNG restarts
123 statusRecovery();
Pingping Linffa27d32015-04-30 14:41:03 -0700124 }
125
126 @Deactivate
127 public void deactivate() {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700128 hostService.removeListener(hostListener);
Pingping Linffa27d32015-04-30 14:41:03 -0700129 log.info("vBNG Stopped");
130 }
131
Pingping Lindead2052015-06-08 16:07:23 -0700132 /**
Pingping Lin53ae34f2015-06-09 10:07:08 -0700133 * Recovers from XOS record. Re-sets up the mapping between private IP
134 * address and public IP address, re-calculates intents and re-installs
135 * those intents.
136 */
137 private void statusRecovery() {
138 log.info("vBNG starts to recover from XOS record......");
Pingping Line10ece02015-06-11 19:26:24 -0700139 RestClient restClient =
140 new RestClient(vbngConfigurationService.getXosIpAddress(),
141 vbngConfigurationService.getXosRestPort());
Pingping Lin53ae34f2015-06-09 10:07:08 -0700142 ObjectNode map = restClient.getRest();
143 if (map == null) {
144 log.info("Stop to recover vBNG status due to the vBNG map "
145 + "is null!");
146 return;
147 }
148
149 log.info("Get record from XOS: {}", map);
150
151 ArrayNode array = (ArrayNode) map.get(VBNG_MAP_NAME);
152 Iterator<JsonNode> entries = array.elements();
153 while (entries.hasNext()) {
154 ObjectNode entry = (ObjectNode) entries.next();
155
156 IpAddress hostIpAdddress =
157 IpAddress.valueOf(entry.get("private_ip").asText());
158 IpAddress publicIpAddress =
159 IpAddress.valueOf(entry.get("routeable_subnet").asText());
160 MacAddress macAddress =
161 MacAddress.valueOf(entry.get("mac").asText());
162 String hostName = entry.get("hostname").asText();
163
164 // Create vBNG
165 createVbng(hostIpAdddress, publicIpAddress, macAddress, hostName);
166
167 }
168 }
169
170 /**
Pingping Lindead2052015-06-08 16:07:23 -0700171 * Sets up mapping from hostname to connect point.
172 */
173 private void setupMap() {
174 nodeToPort = Maps.newHashMap();
175
176 nodeToPort.put("cordcompute01.onlab.us",
177 new ConnectPoint(FABRIC_DEVICE_ID,
178 PortNumber.portNumber(48)));
179
180 nodeToPort.put("cordcompute02.onlab.us",
181 new ConnectPoint(FABRIC_DEVICE_ID,
182 PortNumber.portNumber(47)));
183 }
184
Pingping Lin53ae34f2015-06-09 10:07:08 -0700185 /**
186 * Creates a new vBNG.
187 *
188 * @param privateIpAddress a private IP address
189 * @param publicIpAddress the public IP address for the private IP address
190 * @param hostMacAddress the MAC address for the private IP address
191 * @param hostName the host name for the private IP address
192 */
193 private void createVbng(IpAddress privateIpAddress,
194 IpAddress publicIpAddress,
195 MacAddress hostMacAddress,
196 String hostName) {
197 boolean result = vbngConfigurationService
198 .assignSpecifiedPublicIp(publicIpAddress, privateIpAddress);
199 if (!result) {
200 log.info("Assign public IP address {} for private IP address {} "
201 + "failed!", publicIpAddress, privateIpAddress);
202 log.info("Failed to create vBNG for private IP address {}",
203 privateIpAddress);
204 return;
205 }
206 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
207 privateIpAddress, publicIpAddress);
208
209 // Setup paths between the host configured with private IP and
210 // next hop
211 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
212 hostMacAddress, hostName)) {
213 privateIpAddressMap.put(privateIpAddress,
214 new VcpeHost(hostMacAddress, hostName));
215 }
216 }
217
Pingping Linffa27d32015-04-30 14:41:03 -0700218 @Override
Pingping Lindead2052015-06-08 16:07:23 -0700219 public IpAddress createVbng(IpAddress privateIpAddress,
220 MacAddress hostMacAddress,
221 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700222
Pingping Linffa27d32015-04-30 14:41:03 -0700223 IpAddress publicIpAddress =
224 vbngConfigurationService.getAvailablePublicIpAddress(
225 privateIpAddress);
226 if (publicIpAddress == null) {
227 log.info("Did not find an available public IP address to use.");
228 return null;
229 }
Pingping Linde77ee52015-06-03 17:16:07 -0700230 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
Pingping Linffa27d32015-04-30 14:41:03 -0700231 privateIpAddress, publicIpAddress);
232
233 // Setup paths between the host configured with private IP and
234 // next hop
Pingping Lindead2052015-06-08 16:07:23 -0700235 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
236 hostMacAddress, hostName)) {
237 privateIpAddressMap.put(privateIpAddress,
238 new VcpeHost(hostMacAddress, hostName));
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700239 }
Pingping Linffa27d32015-04-30 14:41:03 -0700240 return publicIpAddress;
241 }
242
Pingping Lind2afaf22015-06-02 10:46:29 -0700243 @Override
244 public IpAddress deleteVbng(IpAddress privateIpAddress) {
245 // Recycle the public IP address assigned to this private IP address.
246 // Recycling will also delete the mapping entry from the private IP
247 // address to public IP address.
248 IpAddress assignedPublicIpAddress = vbngConfigurationService
249 .recycleAssignedPublicIpAddress(privateIpAddress);
250 if (assignedPublicIpAddress == null) {
251 return null;
252 }
253
Pingping Lindead2052015-06-08 16:07:23 -0700254 // Remove the private IP address from privateIpAddressMap
255 privateIpAddressMap.remove(privateIpAddress);
Pingping Lind2afaf22015-06-02 10:46:29 -0700256
257 // Remove intents
258 removeForwardingPaths(privateIpAddress);
259
260 return assignedPublicIpAddress;
261 }
262
263 /**
264 * Removes the forwarding paths in both two directions between host
265 * configured with private IP and next hop.
266 *
267 * @param privateIp the private IP address of a local host
268 */
269 private void removeForwardingPaths(IpAddress privateIp) {
270 PointToPointIntent toNextHopIntent =
271 p2pIntentsFromHost.remove(privateIp);
272 if (toNextHopIntent != null) {
273 intentService.withdraw(toNextHopIntent);
274 //intentService.purge(toNextHopIntent);
275 }
276 PointToPointIntent toLocalHostIntent =
277 p2pIntentsToHost.remove(privateIp);
278 if (toLocalHostIntent != null) {
279 intentService.withdraw(toLocalHostIntent);
280 //intentService.purge(toLocalHostIntent);
281 }
282 }
283
Pingping Linffa27d32015-04-30 14:41:03 -0700284 /**
285 * Sets up forwarding paths in both two directions between host configured
286 * with private IP and next hop.
287 *
288 * @param privateIp the private IP address of a local host
289 * @param publicIp the public IP address assigned for the private IP address
Pingping Lindead2052015-06-08 16:07:23 -0700290 * @param hostMacAddress the MAC address for the IP address
291 * @param hostName the host name for the IP address
Pingping Linffa27d32015-04-30 14:41:03 -0700292 */
Pingping Lindead2052015-06-08 16:07:23 -0700293 private boolean setupForwardingPaths(IpAddress privateIp,
294 IpAddress publicIp,
295 MacAddress hostMacAddress,
296 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700297 checkNotNull(privateIp);
298 checkNotNull(publicIp);
Pingping Lindead2052015-06-08 16:07:23 -0700299 checkNotNull(hostMacAddress);
300 checkNotNull(hostName);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700301
302 if (nextHopIpAddress == null) {
303 log.warn("Did not find next hop IP address");
304 return false;
305 }
Pingping Linffa27d32015-04-30 14:41:03 -0700306
307 // If there are already intents for private IP address in the system,
308 // we will do nothing and directly return.
309 if (p2pIntentsFromHost.containsKey(privateIp)
310 && p2pIntentsToHost.containsKey(privateIp)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700311 return true;
Pingping Linffa27d32015-04-30 14:41:03 -0700312 }
313
Pingping Linffa27d32015-04-30 14:41:03 -0700314 Host nextHopHost = null;
315 if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) {
316 nextHopHost = hostService.getHostsByIp(nextHopIpAddress)
317 .iterator().next();
318 } else {
Pingping Linffa27d32015-04-30 14:41:03 -0700319 hostService.startMonitoringIp(nextHopIpAddress);
320 if (hostService.getHostsByIp(privateIp).isEmpty()) {
321 hostService.startMonitoringIp(privateIp);
322 }
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700323 return false;
Pingping Linffa27d32015-04-30 14:41:03 -0700324 }
325
Pingping Linffa27d32015-04-30 14:41:03 -0700326 ConnectPoint nextHopConnectPoint =
327 new ConnectPoint(nextHopHost.location().elementId(),
328 nextHopHost.location().port());
Pingping Lindead2052015-06-08 16:07:23 -0700329 ConnectPoint localHostConnectPoint = nodeToPort.get(hostName);
Pingping Linffa27d32015-04-30 14:41:03 -0700330
331 // Generate and install intent for traffic from host configured with
332 // private IP
333 if (!p2pIntentsFromHost.containsKey(privateIp)) {
334 PointToPointIntent toNextHopIntent
335 = srcMatchIntentGenerator(privateIp,
336 publicIp,
337 nextHopHost.mac(),
338 nextHopConnectPoint,
339 localHostConnectPoint
340 );
341 p2pIntentsFromHost.put(privateIp, toNextHopIntent);
342 intentService.submit(toNextHopIntent);
343 }
344
345 // Generate and install intent for traffic to host configured with
346 // private IP
347 if (!p2pIntentsToHost.containsKey(privateIp)) {
348 PointToPointIntent toLocalHostIntent
349 = dstMatchIntentGenerator(publicIp,
350 privateIp,
Pingping Lindead2052015-06-08 16:07:23 -0700351 hostMacAddress,
Pingping Linffa27d32015-04-30 14:41:03 -0700352 localHostConnectPoint,
353 nextHopConnectPoint);
Pingping Lind2afaf22015-06-02 10:46:29 -0700354 p2pIntentsToHost.put(privateIp, toLocalHostIntent);
Pingping Linffa27d32015-04-30 14:41:03 -0700355 intentService.submit(toLocalHostIntent);
356 }
357
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700358 return true;
359 }
360
361 /**
362 * Listener for host events.
363 */
364 private class InternalHostListener implements HostListener {
365 @Override
366 public void event(HostEvent event) {
367 log.debug("Received HostEvent {}", event);
368
369 Host host = event.subject();
370 if (event.type() != HostEvent.Type.HOST_ADDED) {
371 return;
372 }
373
374 for (IpAddress ipAddress: host.ipAddresses()) {
Pingping Lindead2052015-06-08 16:07:23 -0700375 // The POST method from XOS gives us MAC and host name, so we
376 // do not need to do anything after receive a vCPE host event
377 // for now.
378 /*if (privateIpAddressSet.contains(ipAddress)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700379 createVbngAgain(ipAddress);
Pingping Lindead2052015-06-08 16:07:23 -0700380 }*/
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700381
382 if (nextHopIpAddress != null &&
383 ipAddress.equals(nextHopIpAddress)) {
Pingping Lindead2052015-06-08 16:07:23 -0700384
385 for (Entry<IpAddress, VcpeHost> entry:
386 privateIpAddressMap.entrySet()) {
387 createVbngAgain(entry.getKey());
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700388 }
Pingping Lindead2052015-06-08 16:07:23 -0700389
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700390 }
391 }
392 }
393 }
394
395 /**
396 * Tries to create vBNG again after receiving a host event if the IP
Pingping Lindead2052015-06-08 16:07:23 -0700397 * address of the host is the next hop IP address.
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700398 *
399 * @param privateIpAddress the private IP address
400 */
401 private void createVbngAgain(IpAddress privateIpAddress) {
402 IpAddress publicIpAddress = vbngConfigurationService
403 .getAssignedPublicIpAddress(privateIpAddress);
404 if (publicIpAddress == null) {
405 // We only need to handle the private IP addresses for which we
406 // already returned the REST replies with assigned public IP
407 // addresses. If a private IP addresses does not have an assigned
408 // public IP address, we should not get it an available public IP
409 // address here, and we should delete it in the unhandled private
Pingping Lindead2052015-06-08 16:07:23 -0700410 // IP address map.
411 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700412 return;
413 }
Pingping Lindead2052015-06-08 16:07:23 -0700414 VcpeHost vcpeHost = privateIpAddressMap.get(privateIpAddress);
415 if (setupForwardingPaths(privateIpAddress, publicIpAddress,
416 vcpeHost.macAddress, vcpeHost.hostName)) {
417 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700418 }
Pingping Linffa27d32015-04-30 14:41:03 -0700419 }
420
421 /**
422 * PointToPointIntent Generator.
423 * <p>
424 * The intent will match the source IP address in packet, rewrite the
425 * source IP address, and rewrite the destination MAC address.
426 * </p>
427 *
428 * @param srcIpAddress the source IP address in packet to match
429 * @param newSrcIpAddress the new source IP address to set
430 * @param dstMacAddress the destination MAC address to set
431 * @param dstConnectPoint the egress point
432 * @param srcConnectPoint the ingress point
433 * @return a PointToPointIntent
434 */
435 private PointToPointIntent srcMatchIntentGenerator(
436 IpAddress srcIpAddress,
437 IpAddress newSrcIpAddress,
438 MacAddress dstMacAddress,
439 ConnectPoint dstConnectPoint,
440 ConnectPoint srcConnectPoint) {
441 checkNotNull(srcIpAddress);
442 checkNotNull(newSrcIpAddress);
443 checkNotNull(dstMacAddress);
444 checkNotNull(dstConnectPoint);
445 checkNotNull(srcConnectPoint);
446
447 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
448 selector.matchEthType(Ethernet.TYPE_IPV4);
449 selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress,
450 IpPrefix.MAX_INET_MASK_LENGTH));
451
452 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
453 treatment.setEthDst(dstMacAddress);
454 treatment.setIpSrc(newSrcIpAddress);
455
456 Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId);
457 PointToPointIntent intent = PointToPointIntent.builder()
458 .appId(appId)
459 .key(key)
460 .selector(selector.build())
461 .treatment(treatment.build())
462 .egressPoint(dstConnectPoint)
463 .ingressPoint(srcConnectPoint)
464 .build();
465
466 log.info("Generated a PointToPointIntent for traffic from local host "
467 + ": {}", intent);
468 return intent;
469 }
470
471 /**
472 * PointToPointIntent Generator.
473 * <p>
474 * The intent will match the destination IP address in packet, rewrite the
475 * destination IP address, and rewrite the destination MAC address.
476 * </p>
477 *
478 * @param dstIpAddress the destination IP address in packet to match
479 * @param newDstIpAddress the new destination IP address to set
480 * @param dstMacAddress the destination MAC address to set
481 * @param dstConnectPoint the egress point
482 * @param srcConnectPoint the ingress point
483 * @return a PointToPointIntent
484 */
485 private PointToPointIntent dstMatchIntentGenerator(
486 IpAddress dstIpAddress,
487 IpAddress newDstIpAddress,
488 MacAddress dstMacAddress,
489 ConnectPoint dstConnectPoint,
490 ConnectPoint srcConnectPoint) {
491 checkNotNull(dstIpAddress);
492 checkNotNull(newDstIpAddress);
493 checkNotNull(dstMacAddress);
494 checkNotNull(dstConnectPoint);
495 checkNotNull(srcConnectPoint);
496
497 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
498 selector.matchEthType(Ethernet.TYPE_IPV4);
499 selector.matchIPDst(IpPrefix.valueOf(dstIpAddress,
500 IpPrefix.MAX_INET_MASK_LENGTH));
501
502 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
503 treatment.setEthDst(dstMacAddress);
504 treatment.setIpDst(newDstIpAddress);
505
506 Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId);
507 PointToPointIntent intent = PointToPointIntent.builder()
508 .appId(appId)
509 .key(key)
510 .selector(selector.build())
511 .treatment(treatment.build())
512 .egressPoint(dstConnectPoint)
513 .ingressPoint(srcConnectPoint)
514 .build();
515 log.info("Generated a PointToPointIntent for traffic to local host "
516 + ": {}", intent);
517
518 return intent;
519 }
Pingping Lindead2052015-06-08 16:07:23 -0700520
521 /**
522 * Constructor to store the a vCPE host info.
523 */
524 private class VcpeHost {
525 MacAddress macAddress;
526 String hostName;
527 public VcpeHost(MacAddress macAddress, String hostName) {
528 this.macAddress = macAddress;
529 this.hostName = hostName;
530 }
531 }
Pingping Linffa27d32015-04-30 14:41:03 -0700532}