CORD-60 Support dynamic vSG creation/deletion
We no longer need to configure /32 IP in interfaces. SR will push a per-host route when discovering a host with IP address(es) that does not belong to configured subnet. Also includes: - HostHandler refactoring Change-Id: Ic1ad42d1ccdfee32be85f49e6fc94d9026000ffc
Showing
7 changed files
with
162 additions
and
35 deletions
... | @@ -15,11 +15,13 @@ | ... | @@ -15,11 +15,13 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.segmentrouting; | 16 | package org.onosproject.segmentrouting; |
17 | 17 | ||
18 | +import com.google.common.collect.ImmutableSet; | ||
18 | import com.google.common.collect.Maps; | 19 | import com.google.common.collect.Maps; |
19 | import com.google.common.collect.Sets; | 20 | import com.google.common.collect.Sets; |
20 | import org.onlab.packet.Ip4Address; | 21 | import org.onlab.packet.Ip4Address; |
21 | import org.onlab.packet.Ip4Prefix; | 22 | import org.onlab.packet.Ip4Prefix; |
22 | import org.onlab.packet.IpPrefix; | 23 | import org.onlab.packet.IpPrefix; |
24 | +import org.onosproject.net.ConnectPoint; | ||
23 | import org.onosproject.net.Device; | 25 | import org.onosproject.net.Device; |
24 | import org.onosproject.net.DeviceId; | 26 | import org.onosproject.net.DeviceId; |
25 | import org.onosproject.net.Link; | 27 | import org.onosproject.net.Link; |
... | @@ -45,9 +47,9 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -45,9 +47,9 @@ import static com.google.common.base.Preconditions.checkNotNull; |
45 | * routing rule population. | 47 | * routing rule population. |
46 | */ | 48 | */ |
47 | public class DefaultRoutingHandler { | 49 | public class DefaultRoutingHandler { |
48 | - | 50 | + private static final int MAX_RETRY_ATTEMPTS = 5; |
49 | - private static Logger log = LoggerFactory | 51 | + private static final String ECMPSPG_MISSING = "ECMP shortest path graph not found"; |
50 | - .getLogger(DefaultRoutingHandler.class); | 52 | + private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class); |
51 | 53 | ||
52 | private SegmentRoutingManager srManager; | 54 | private SegmentRoutingManager srManager; |
53 | private RoutingRulePopulator rulePopulator; | 55 | private RoutingRulePopulator rulePopulator; |
... | @@ -56,7 +58,6 @@ public class DefaultRoutingHandler { | ... | @@ -56,7 +58,6 @@ public class DefaultRoutingHandler { |
56 | private DeviceConfiguration config; | 58 | private DeviceConfiguration config; |
57 | private final Lock statusLock = new ReentrantLock(); | 59 | private final Lock statusLock = new ReentrantLock(); |
58 | private volatile Status populationStatus; | 60 | private volatile Status populationStatus; |
59 | - private static final int MAX_RETRY_ATTEMPTS = 5; | ||
60 | private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); | 61 | private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); |
61 | 62 | ||
62 | /** | 63 | /** |
... | @@ -113,7 +114,7 @@ public class DefaultRoutingHandler { | ... | @@ -113,7 +114,7 @@ public class DefaultRoutingHandler { |
113 | } | 114 | } |
114 | 115 | ||
115 | EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(sw.id(), srManager); | 116 | EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(sw.id(), srManager); |
116 | - if (!populateEcmpRoutingRules(sw.id(), ecmpSpg)) { | 117 | + if (!populateEcmpRoutingRules(sw.id(), ecmpSpg, ImmutableSet.of())) { |
117 | log.debug("populateAllRoutingRules: populationStatus is ABORTED"); | 118 | log.debug("populateAllRoutingRules: populationStatus is ABORTED"); |
118 | populationStatus = Status.ABORTED; | 119 | populationStatus = Status.ABORTED; |
119 | log.debug("Abort routing rule population"); | 120 | log.debug("Abort routing rule population"); |
... | @@ -210,7 +211,7 @@ public class DefaultRoutingHandler { | ... | @@ -210,7 +211,7 @@ public class DefaultRoutingHandler { |
210 | if (link.size() == 1) { | 211 | if (link.size() == 1) { |
211 | log.trace("repopulateRoutingRulesForRoutes: running ECMP graph for device {}", link.get(0)); | 212 | log.trace("repopulateRoutingRulesForRoutes: running ECMP graph for device {}", link.get(0)); |
212 | EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(link.get(0), srManager); | 213 | EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(link.get(0), srManager); |
213 | - if (populateEcmpRoutingRules(link.get(0), ecmpSpg)) { | 214 | + if (populateEcmpRoutingRules(link.get(0), ecmpSpg, ImmutableSet.of())) { |
214 | log.debug("Populating flow rules from {} to all is successful", | 215 | log.debug("Populating flow rules from {} to all is successful", |
215 | link.get(0)); | 216 | link.get(0)); |
216 | currentEcmpSpgMap.put(link.get(0), ecmpSpg); | 217 | currentEcmpSpgMap.put(link.get(0), ecmpSpg); |
... | @@ -255,7 +256,8 @@ public class DefaultRoutingHandler { | ... | @@ -255,7 +256,8 @@ public class DefaultRoutingHandler { |
255 | nextHops.add(via.get(0)); | 256 | nextHops.add(via.get(0)); |
256 | } | 257 | } |
257 | } | 258 | } |
258 | - if (!populateEcmpRoutingRulePartial(targetSw, dst, nextHops)) { | 259 | + if (!populateEcmpRoutingRulePartial(targetSw, dst, |
260 | + nextHops, ImmutableSet.of())) { | ||
259 | return false; | 261 | return false; |
260 | } | 262 | } |
261 | log.debug("Populating flow rules from {} to {} is successful", | 263 | log.debug("Populating flow rules from {} to {} is successful", |
... | @@ -422,8 +424,17 @@ public class DefaultRoutingHandler { | ... | @@ -422,8 +424,17 @@ public class DefaultRoutingHandler { |
422 | return subLinks; | 424 | return subLinks; |
423 | } | 425 | } |
424 | 426 | ||
427 | + /** | ||
428 | + * Populate ECMP rules for subnets from all switches to destination. | ||
429 | + * | ||
430 | + * @param destSw Device ID of destination switch | ||
431 | + * @param ecmpSPG ECMP shortest path graph | ||
432 | + * @param subnets Subnets to be populated. If empty, populate all configured subnets. | ||
433 | + * @return true if succeed | ||
434 | + */ | ||
425 | private boolean populateEcmpRoutingRules(DeviceId destSw, | 435 | private boolean populateEcmpRoutingRules(DeviceId destSw, |
426 | - EcmpShortestPathGraph ecmpSPG) { | 436 | + EcmpShortestPathGraph ecmpSPG, |
437 | + Set<Ip4Prefix> subnets) { | ||
427 | 438 | ||
428 | HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = ecmpSPG | 439 | HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = ecmpSPG |
429 | .getAllLearnedSwitchesAndVia(); | 440 | .getAllLearnedSwitchesAndVia(); |
... | @@ -440,7 +451,7 @@ public class DefaultRoutingHandler { | ... | @@ -440,7 +451,7 @@ public class DefaultRoutingHandler { |
440 | nextHops.add(via.get(0)); | 451 | nextHops.add(via.get(0)); |
441 | } | 452 | } |
442 | } | 453 | } |
443 | - if (!populateEcmpRoutingRulePartial(targetSw, destSw, nextHops)) { | 454 | + if (!populateEcmpRoutingRulePartial(targetSw, destSw, nextHops, subnets)) { |
444 | return false; | 455 | return false; |
445 | } | 456 | } |
446 | } | 457 | } |
... | @@ -449,9 +460,19 @@ public class DefaultRoutingHandler { | ... | @@ -449,9 +460,19 @@ public class DefaultRoutingHandler { |
449 | return true; | 460 | return true; |
450 | } | 461 | } |
451 | 462 | ||
463 | + /** | ||
464 | + * Populate ECMP rules for subnets from target to destination via nexthops. | ||
465 | + * | ||
466 | + * @param targetSw Device ID of target switch | ||
467 | + * @param destSw Device ID of destination switch | ||
468 | + * @param nextHops List of next hops | ||
469 | + * @param subnets Subnets to be populated. If empty, populate all configured subnets. | ||
470 | + * @return true if succeed | ||
471 | + */ | ||
452 | private boolean populateEcmpRoutingRulePartial(DeviceId targetSw, | 472 | private boolean populateEcmpRoutingRulePartial(DeviceId targetSw, |
453 | DeviceId destSw, | 473 | DeviceId destSw, |
454 | - Set<DeviceId> nextHops) { | 474 | + Set<DeviceId> nextHops, |
475 | + Set<Ip4Prefix> subnets) { | ||
455 | boolean result; | 476 | boolean result; |
456 | 477 | ||
457 | if (nextHops.isEmpty()) { | 478 | if (nextHops.isEmpty()) { |
... | @@ -473,13 +494,11 @@ public class DefaultRoutingHandler { | ... | @@ -473,13 +494,11 @@ public class DefaultRoutingHandler { |
473 | } | 494 | } |
474 | 495 | ||
475 | if (targetIsEdge && destIsEdge) { | 496 | if (targetIsEdge && destIsEdge) { |
476 | - Set<Ip4Prefix> subnets = config.getSubnets(destSw); | 497 | + subnets = (subnets != null && !subnets.isEmpty()) ? subnets : config.getSubnets(destSw); |
477 | log.debug("* populateEcmpRoutingRulePartial in device {} towards {} for subnets {}", | 498 | log.debug("* populateEcmpRoutingRulePartial in device {} towards {} for subnets {}", |
478 | targetSw, destSw, subnets); | 499 | targetSw, destSw, subnets); |
479 | - result = rulePopulator.populateIpRuleForSubnet(targetSw, | 500 | + result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets, |
480 | - subnets, | 501 | + destSw, nextHops); |
481 | - destSw, | ||
482 | - nextHops); | ||
483 | if (!result) { | 502 | if (!result) { |
484 | return false; | 503 | return false; |
485 | } | 504 | } |
... | @@ -575,18 +594,54 @@ public class DefaultRoutingHandler { | ... | @@ -575,18 +594,54 @@ public class DefaultRoutingHandler { |
575 | } | 594 | } |
576 | } | 595 | } |
577 | 596 | ||
578 | - public void purgeEcmpGraph(DeviceId deviceId) { | 597 | + /** |
598 | + * Populate rules of given subnet at given location. | ||
599 | + * | ||
600 | + * @param cp connect point of the subnet being added | ||
601 | + * @param subnets subnet being added | ||
602 | + * @return true if succeed | ||
603 | + */ | ||
604 | + protected boolean populateSubnet(ConnectPoint cp, Set<Ip4Prefix> subnets) { | ||
605 | + statusLock.lock(); | ||
606 | + try { | ||
607 | + EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(cp.deviceId()); | ||
608 | + if (ecmpSpg == null) { | ||
609 | + log.warn("Fail to populating subnet {}: {}", subnets, ECMPSPG_MISSING); | ||
610 | + return false; | ||
611 | + } | ||
612 | + return populateEcmpRoutingRules(cp.deviceId(), ecmpSpg, subnets); | ||
613 | + } finally { | ||
614 | + statusLock.unlock(); | ||
615 | + } | ||
616 | + } | ||
617 | + | ||
618 | + /** | ||
619 | + * Revoke rules of given subnet at given location. | ||
620 | + * | ||
621 | + * @param subnets subnet being removed | ||
622 | + * @return true if succeed | ||
623 | + */ | ||
624 | + protected boolean revokeSubnet(Set<Ip4Prefix> subnets) { | ||
625 | + statusLock.lock(); | ||
626 | + try { | ||
627 | + return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets); | ||
628 | + } finally { | ||
629 | + statusLock.unlock(); | ||
630 | + } | ||
631 | + } | ||
632 | + | ||
633 | + protected void purgeEcmpGraph(DeviceId deviceId) { | ||
579 | currentEcmpSpgMap.remove(deviceId); | 634 | currentEcmpSpgMap.remove(deviceId); |
580 | if (updatedEcmpSpgMap != null) { | 635 | if (updatedEcmpSpgMap != null) { |
581 | updatedEcmpSpgMap.remove(deviceId); | 636 | updatedEcmpSpgMap.remove(deviceId); |
582 | } | 637 | } |
583 | } | 638 | } |
584 | 639 | ||
585 | - private class RetryFilters implements Runnable { | 640 | + private final class RetryFilters implements Runnable { |
586 | int attempts = MAX_RETRY_ATTEMPTS; | 641 | int attempts = MAX_RETRY_ATTEMPTS; |
587 | DeviceId devId; | 642 | DeviceId devId; |
588 | 643 | ||
589 | - public RetryFilters(DeviceId deviceId) { | 644 | + private RetryFilters(DeviceId deviceId) { |
590 | devId = deviceId; | 645 | devId = deviceId; |
591 | } | 646 | } |
592 | 647 | ... | ... |
This diff is collapsed. Click to expand it.
... | @@ -212,22 +212,33 @@ public class RoutingRulePopulator { | ... | @@ -212,22 +212,33 @@ public class RoutingRulePopulator { |
212 | * Populates IP flow rules for the subnets of the destination router. | 212 | * Populates IP flow rules for the subnets of the destination router. |
213 | * | 213 | * |
214 | * @param deviceId switch ID to set the rules | 214 | * @param deviceId switch ID to set the rules |
215 | - * @param subnets subnet information | 215 | + * @param subnets subnet being added |
216 | * @param destSw destination switch ID | 216 | * @param destSw destination switch ID |
217 | * @param nextHops next hop switch ID list | 217 | * @param nextHops next hop switch ID list |
218 | * @return true if all rules are set successfully, false otherwise | 218 | * @return true if all rules are set successfully, false otherwise |
219 | */ | 219 | */ |
220 | - public boolean populateIpRuleForSubnet(DeviceId deviceId, | 220 | + public boolean populateIpRuleForSubnet(DeviceId deviceId, Set<Ip4Prefix> subnets, |
221 | - Set<Ip4Prefix> subnets, | 221 | + DeviceId destSw, Set<DeviceId> nextHops) { |
222 | - DeviceId destSw, | ||
223 | - Set<DeviceId> nextHops) { | ||
224 | - | ||
225 | for (IpPrefix subnet : subnets) { | 222 | for (IpPrefix subnet : subnets) { |
226 | if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) { | 223 | if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) { |
227 | return false; | 224 | return false; |
228 | } | 225 | } |
229 | } | 226 | } |
227 | + return true; | ||
228 | + } | ||
230 | 229 | ||
230 | + /** | ||
231 | + * Revokes IP flow rules for the subnets. | ||
232 | + * | ||
233 | + * @param subnets subnet being removed | ||
234 | + * @return true if all rules are removed successfully, false otherwise | ||
235 | + */ | ||
236 | + public boolean revokeIpRuleForSubnet(Set<Ip4Prefix> subnets) { | ||
237 | + for (IpPrefix subnet : subnets) { | ||
238 | + if (!revokeIpRuleForRouter(subnet)) { | ||
239 | + return false; | ||
240 | + } | ||
241 | + } | ||
231 | return true; | 242 | return true; |
232 | } | 243 | } |
233 | 244 | ||
... | @@ -310,6 +321,40 @@ public class RoutingRulePopulator { | ... | @@ -310,6 +321,40 @@ public class RoutingRulePopulator { |
310 | } | 321 | } |
311 | 322 | ||
312 | /** | 323 | /** |
324 | + * Revokes IP flow rules for the router IP address. | ||
325 | + * | ||
326 | + * @param ipPrefix the IP address of the destination router | ||
327 | + * @return true if all rules are removed successfully, false otherwise | ||
328 | + */ | ||
329 | + public boolean revokeIpRuleForRouter(IpPrefix ipPrefix) { | ||
330 | + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); | ||
331 | + sbuilder.matchIPDst(ipPrefix); | ||
332 | + sbuilder.matchEthType(Ethernet.TYPE_IPV4); | ||
333 | + TrafficSelector selector = sbuilder.build(); | ||
334 | + TrafficTreatment dummyTreatment = DefaultTrafficTreatment.builder().build(); | ||
335 | + | ||
336 | + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective | ||
337 | + .builder() | ||
338 | + .fromApp(srManager.appId) | ||
339 | + .makePermanent() | ||
340 | + .withSelector(selector) | ||
341 | + .withTreatment(dummyTreatment) | ||
342 | + .withPriority(getPriorityFromPrefix(ipPrefix)) | ||
343 | + .withFlag(ForwardingObjective.Flag.SPECIFIC); | ||
344 | + | ||
345 | + ObjectiveContext context = new DefaultObjectiveContext( | ||
346 | + (objective) -> log.debug("IP rule for router {} revoked", ipPrefix), | ||
347 | + (objective, error) -> | ||
348 | + log.warn("Failed to revoke IP rule for router {}: {}", ipPrefix, error)); | ||
349 | + | ||
350 | + srManager.deviceService.getAvailableDevices().forEach(device -> { | ||
351 | + srManager.flowObjectiveService.forward(device.id(), fwdBuilder.remove(context)); | ||
352 | + }); | ||
353 | + | ||
354 | + return true; | ||
355 | + } | ||
356 | + | ||
357 | + /** | ||
313 | * Populates MPLS flow rules to all routers. | 358 | * Populates MPLS flow rules to all routers. |
314 | * | 359 | * |
315 | * @param deviceId target device ID of the switch to set the rules | 360 | * @param deviceId target device ID of the switch to set the rules |
... | @@ -471,6 +516,7 @@ public class RoutingRulePopulator { | ... | @@ -471,6 +516,7 @@ public class RoutingRulePopulator { |
471 | * that drivers can obtain other information (like Router MAC and IP). | 516 | * that drivers can obtain other information (like Router MAC and IP). |
472 | * | 517 | * |
473 | * @param deviceId the switch dpid for the router | 518 | * @param deviceId the switch dpid for the router |
519 | + * @return true if operation succeeds | ||
474 | */ | 520 | */ |
475 | public boolean populateRouterMacVlanFilters(DeviceId deviceId) { | 521 | public boolean populateRouterMacVlanFilters(DeviceId deviceId) { |
476 | log.debug("Installing per-port filtering objective for untagged " | 522 | log.debug("Installing per-port filtering objective for untagged " | ... | ... |
... | @@ -153,7 +153,7 @@ public class SegmentRoutingManager implements SegmentRoutingService { | ... | @@ -153,7 +153,7 @@ public class SegmentRoutingManager implements SegmentRoutingService { |
153 | protected ApplicationId appId; | 153 | protected ApplicationId appId; |
154 | protected DeviceConfiguration deviceConfiguration = null; | 154 | protected DeviceConfiguration deviceConfiguration = null; |
155 | 155 | ||
156 | - private DefaultRoutingHandler defaultRoutingHandler = null; | 156 | + protected DefaultRoutingHandler defaultRoutingHandler = null; |
157 | private TunnelHandler tunnelHandler = null; | 157 | private TunnelHandler tunnelHandler = null; |
158 | private PolicyHandler policyHandler = null; | 158 | private PolicyHandler policyHandler = null; |
159 | private InternalPacketProcessor processor = null; | 159 | private InternalPacketProcessor processor = null; | ... | ... |
... | @@ -46,6 +46,8 @@ import java.util.Optional; | ... | @@ -46,6 +46,8 @@ import java.util.Optional; |
46 | import java.util.Set; | 46 | import java.util.Set; |
47 | import java.util.concurrent.ConcurrentHashMap; | 47 | import java.util.concurrent.ConcurrentHashMap; |
48 | 48 | ||
49 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
50 | + | ||
49 | /** | 51 | /** |
50 | * Segment Routing configuration component that reads the | 52 | * Segment Routing configuration component that reads the |
51 | * segment routing related configuration from Network Configuration Manager | 53 | * segment routing related configuration from Network Configuration Manager |
... | @@ -167,7 +169,6 @@ public class DeviceConfiguration implements DeviceProperties { | ... | @@ -167,7 +169,6 @@ public class DeviceConfiguration implements DeviceProperties { |
167 | } | 169 | } |
168 | } | 170 | } |
169 | }); | 171 | }); |
170 | - | ||
171 | }); | 172 | }); |
172 | } | 173 | } |
173 | 174 | ||
... | @@ -517,4 +518,38 @@ public class DeviceConfiguration implements DeviceProperties { | ... | @@ -517,4 +518,38 @@ public class DeviceConfiguration implements DeviceProperties { |
517 | cfgService.getConfig(appId, SegmentRoutingAppConfig.class); | 518 | cfgService.getConfig(appId, SegmentRoutingAppConfig.class); |
518 | return (appConfig != null) ? appConfig.suppressHost() : ImmutableSet.of(); | 519 | return (appConfig != null) ? appConfig.suppressHost() : ImmutableSet.of(); |
519 | } | 520 | } |
521 | + | ||
522 | + /** | ||
523 | + * Add subnet to specific connect point. | ||
524 | + * | ||
525 | + * @param cp connect point | ||
526 | + * @param ip4Prefix subnet being added to the device | ||
527 | + */ | ||
528 | + public void addSubnet(ConnectPoint cp, Ip4Prefix ip4Prefix) { | ||
529 | + checkNotNull(cp); | ||
530 | + checkNotNull(ip4Prefix); | ||
531 | + SegmentRouterInfo srinfo = deviceConfigMap.get(cp.deviceId()); | ||
532 | + if (srinfo == null) { | ||
533 | + log.warn("Device {} is not configured. Abort.", cp.deviceId()); | ||
534 | + return; | ||
535 | + } | ||
536 | + srinfo.subnets.put(cp.port(), ip4Prefix); | ||
537 | + } | ||
538 | + | ||
539 | + /** | ||
540 | + * Remove subnet from specific connect point. | ||
541 | + * | ||
542 | + * @param cp connect point | ||
543 | + * @param ip4Prefix subnet being removed to the device | ||
544 | + */ | ||
545 | + public void removeSubnet(ConnectPoint cp, Ip4Prefix ip4Prefix) { | ||
546 | + checkNotNull(cp); | ||
547 | + checkNotNull(ip4Prefix); | ||
548 | + SegmentRouterInfo srinfo = deviceConfigMap.get(cp.deviceId()); | ||
549 | + if (srinfo == null) { | ||
550 | + log.warn("Device {} is not configured. Abort.", cp.deviceId()); | ||
551 | + return; | ||
552 | + } | ||
553 | + srinfo.subnets.remove(cp.port(), ip4Prefix); | ||
554 | + } | ||
520 | } | 555 | } | ... | ... |
This diff is collapsed. Click to expand it.
... | @@ -13,12 +13,6 @@ | ... | @@ -13,12 +13,6 @@ |
13 | }, | 13 | }, |
14 | { | 14 | { |
15 | "vlan" : "222" | 15 | "vlan" : "222" |
16 | - }, | ||
17 | - { | ||
18 | - "ips" : [ | ||
19 | - "A.A.A.146/32", "A.A.A.147/32", "A.A.A.148/32", "A.A.A.149/32", | ||
20 | - "A.A.A.150/32", "A.A.A.151/32", "A.A.A.152/32", "A.A.A.153/32" | ||
21 | - ] | ||
22 | } | 16 | } |
23 | ] | 17 | ] |
24 | }, | 18 | }, |
... | @@ -52,9 +46,6 @@ | ... | @@ -52,9 +46,6 @@ |
52 | "interfaces" : [ | 46 | "interfaces" : [ |
53 | { | 47 | { |
54 | "ips" : [ "10.0.2.254/24" ] | 48 | "ips" : [ "10.0.2.254/24" ] |
55 | - }, | ||
56 | - { | ||
57 | - "ips" : [ "A.A.A.130/32", "A.A.A.131/32" ] | ||
58 | } | 49 | } |
59 | ] | 50 | ] |
60 | }, | 51 | }, | ... | ... |
-
Please register or login to post a comment