Add VXLAN, Switch and PCI to 1.19.2 version
@@ -77,7 +77,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "net.minecraftforge:forge:1.19.2-43.2.10"
|
||||
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
|
||||
annotationProcessor "org.spongepowered:mixin:0.8.5:processor"
|
||||
|
||||
// Specify the libs embedded in the library mod explicitly for local development, where
|
||||
@@ -90,6 +90,7 @@ dependencies {
|
||||
compileOnly "li.cil.sedna:sedna-buildroot:0.0.8"
|
||||
}
|
||||
implementation "curse.maven:sedna-511276:3885542"
|
||||
minecraftLibrary "org.apache.commons:commons-collections4:4.4"
|
||||
|
||||
implementation fg.deobf("curse.maven:markdownmanual-502485:4306669")
|
||||
implementation fg.deobf("curse.maven:architectury-api-419699:4521273")
|
||||
@@ -186,6 +187,7 @@ jar {
|
||||
"Implementation-Vendor" : "Sangar",
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
"MixinConfigs" : "mixins.oc2.json",
|
||||
"ContainedDeps" : "commons-collections4-4.4.jar"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters;
|
||||
import li.cil.oc2.common.integration.IMC;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import li.cil.oc2.common.util.ServerScheduler;
|
||||
import li.cil.oc2.common.vxlan.TunnelManager;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
|
||||
@@ -16,5 +17,6 @@ public final class CommonSetup {
|
||||
Network.initialize();
|
||||
RPCMethodParameterTypeAdapters.initialize();
|
||||
ServerScheduler.initialize();
|
||||
TunnelManager.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public final class Config {
|
||||
@Path("energy.blocks") public static int chargerEnergyStorage = 10000;
|
||||
@Path("energy.blocks") public static int projectorEnergyPerTick = 20;
|
||||
@Path("energy.blocks") public static int projectorEnergyStorage = 2000;
|
||||
@Path("energy.blocks") public static int cardCageEnergyPerTick = 20;
|
||||
@Path("energy.blocks") public static int cardCageEnergyStorage = 2000;
|
||||
|
||||
@Path("energy.entities") public static int robotEnergyPerTick = 5;
|
||||
@Path("energy.entities") public static int robotEnergyStorage = 750000;
|
||||
@@ -42,6 +44,12 @@ public final class Config {
|
||||
@Path("admin.virtual_network") public static int ethernetFrameTimeToLive = 12;
|
||||
@Path("admin.virtual_network") public static int hubEthernetFramesPerTick = 32;
|
||||
|
||||
@Path("vxlan") public static boolean enable = true;
|
||||
@Path("vxlan") public static String remoteHost = "::1";
|
||||
@Path("vxlan") public static int remotePort = 4789;
|
||||
@Path("vxlan") public static String bindHost = "::1";
|
||||
@Path("vxlan") public static int bindPort = 4789;
|
||||
|
||||
public static boolean computersUseEnergy() {
|
||||
return computerEnergyPerTick > 0 && computerEnergyStorage > 0;
|
||||
}
|
||||
@@ -54,6 +62,10 @@ public final class Config {
|
||||
return projectorEnergyStorage > 0 && projectorEnergyPerTick > 0;
|
||||
}
|
||||
|
||||
public static boolean cardCagesUseEnergy() {
|
||||
return cardCageEnergyStorage > 0 && cardCageEnergyPerTick > 0;
|
||||
}
|
||||
|
||||
public static boolean robotsUseEnergy() {
|
||||
return robotEnergyPerTick > 0 && robotEnergyStorage > 0;
|
||||
}
|
||||
|
||||
@@ -65,8 +65,10 @@ public final class ConfigManager {
|
||||
|
||||
static {
|
||||
PARSERS.put(int.class, ConfigManager::parseIntField);
|
||||
PARSERS.put(short.class, ConfigManager::parseShortField);
|
||||
PARSERS.put(long.class, ConfigManager::parseLongField);
|
||||
PARSERS.put(double.class, ConfigManager::parseDoubleField);
|
||||
PARSERS.put(boolean.class, ConfigManager::parseBooleanField);
|
||||
PARSERS.put(String.class, ConfigManager::parseStringField);
|
||||
PARSERS.put(UUID.class, ConfigManager::parseUUIDField);
|
||||
PARSERS.put(ResourceLocation.class, ConfigManager::parseResourceLocationField);
|
||||
@@ -134,6 +136,16 @@ public final class ConfigManager {
|
||||
return new ConfigFieldPair<>(field, configValue);
|
||||
}
|
||||
|
||||
private static ConfigFieldPair<?> parseShortField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
|
||||
final short defaultValue = field.getShort(instance);
|
||||
final short minValue = (short) Math.max(getMin(field), Short.MIN_VALUE);
|
||||
final short maxValue = (short) Math.min(getMax(field), Short.MAX_VALUE);
|
||||
|
||||
final ForgeConfigSpec.IntValue configValue = builder.defineInRange(path, defaultValue, minValue, maxValue);
|
||||
|
||||
return new ConfigFieldPair<>(field, configValue);
|
||||
}
|
||||
|
||||
private static ConfigFieldPair<?> parseLongField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
|
||||
final long defaultValue = field.getLong(instance);
|
||||
final long minValue = (long) Math.max(getMin(field), Long.MIN_VALUE);
|
||||
@@ -170,6 +182,14 @@ public final class ConfigManager {
|
||||
return new ConfigFieldPair<>(field, configValue, UUID::fromString);
|
||||
}
|
||||
|
||||
private static ConfigFieldPair<?> parseBooleanField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
|
||||
final boolean defaultValue = (boolean) field.get(instance);
|
||||
|
||||
final ForgeConfigSpec.ConfigValue<Boolean> configValue = builder.define(path, defaultValue);
|
||||
|
||||
return new ConfigFieldPair<>(field, configValue);
|
||||
}
|
||||
|
||||
private static ConfigFieldPair<?> parseResourceLocationField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
|
||||
final ResourceLocation defaultValue = (ResourceLocation) field.get(instance);
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ public final class Main {
|
||||
|
||||
ProviderRegistry.initialize();
|
||||
DeviceTypes.initialize();
|
||||
|
||||
BlockDeviceDataRegistry.initialize();
|
||||
FirmwareRegistry.initialize();
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
package li.cil.oc2.common.block;
|
||||
|
||||
import li.cil.oc2.api.API;
|
||||
import li.cil.oc2.common.blockentity.VxlanBlockEntity;
|
||||
import li.cil.oc2.common.util.RegistryUtils;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
@@ -24,8 +26,12 @@ public final class Blocks {
|
||||
public static final RegistryObject<KeyboardBlock> KEYBOARD = BLOCKS.register("keyboard", KeyboardBlock::new);
|
||||
public static final RegistryObject<NetworkConnectorBlock> NETWORK_CONNECTOR = BLOCKS.register("network_connector", NetworkConnectorBlock::new);
|
||||
public static final RegistryObject<NetworkHubBlock> NETWORK_HUB = BLOCKS.register("network_hub", NetworkHubBlock::new);
|
||||
public static final RegistryObject<NetworkSwitchBlock> NETWORK_SWITCH = BLOCKS.register("network_switch", NetworkSwitchBlock::new);
|
||||
public static final RegistryObject<ProjectorBlock> PROJECTOR = BLOCKS.register("projector", ProjectorBlock::new);
|
||||
public static final RegistryObject<RedstoneInterfaceBlock> REDSTONE_INTERFACE = BLOCKS.register("redstone_interface", RedstoneInterfaceBlock::new);
|
||||
public static final RegistryObject<VxlanBlock> VXLAN_HUB = BLOCKS.register("vxlan_hub", VxlanBlock::new);
|
||||
public static final RegistryObject<PciCardCageBlock> PCI_CARD_CAGE = BLOCKS.register("pci_card_cage", PciCardCageBlock::new);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package li.cil.oc2.common.block;
|
||||
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.NetworkHubBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class NetworkSwitchBlock extends HorizontalDirectionalBlock implements EntityBlock {
|
||||
public NetworkSwitchBlock() {
|
||||
super(Properties
|
||||
.of(Material.METAL)
|
||||
.sound(SoundType.METAL)
|
||||
.strength(1.5f, 6.0f));
|
||||
registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(final BlockPlaceContext context) {
|
||||
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) {
|
||||
final BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof final NetworkHubBlockEntity networkHub) {
|
||||
networkHub.handleNeighborChanged();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// EntityBlock
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
return BlockEntities.NETWORK_SWITCH.get().create(pos, state);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
|
||||
super.createBlockStateDefinition(builder);
|
||||
builder.add(FACING);
|
||||
}
|
||||
}
|
||||
100
src/main/java/li/cil/oc2/common/block/PciCardCageBlock.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package li.cil.oc2.common.block;
|
||||
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.TickableBlockEntity;
|
||||
import li.cil.oc2.common.util.VoxelShapeUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class PciCardCageBlock extends HorizontalDirectionalBlock implements EntityBlock, EnergyConsumingBlock {
|
||||
public static final BooleanProperty LIT = BlockStateProperties.LIT;
|
||||
|
||||
// We bake the visual indents on the front and sides into the collision shape, to prevent stuff being
|
||||
// placeable on those sides, such as network connectors, torches, etc.
|
||||
private static final VoxelShape NEG_Z_SHAPE = Shapes.join(Shapes.block(), Shapes.or(
|
||||
Shapes.box(0 / 16f, 2 / 16f, 2 / 16f, 1 / 16f, 6 / 16f, 14 / 16f),
|
||||
Shapes.box(15 / 16f, 2 / 16f, 2 / 16f, 16 / 16f, 6 / 16f, 14 / 16f),
|
||||
Shapes.box(4 / 16f, 4 / 16f, 0 / 16f, 12 / 16f, 12 / 16f, 2 / 16f)
|
||||
), (a, b) -> a && !b);
|
||||
private static final VoxelShape NEG_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_Z_SHAPE);
|
||||
private static final VoxelShape POS_Z_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_X_SHAPE);
|
||||
private static final VoxelShape POS_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(POS_Z_SHAPE);
|
||||
|
||||
public PciCardCageBlock() {
|
||||
super(Properties
|
||||
.of(Material.METAL)
|
||||
.sound(SoundType.METAL)
|
||||
.lightLevel(state -> state.getValue(LIT) ? 8 : 0)
|
||||
.strength(1.5f, 6.0f));
|
||||
registerDefaultState(getStateDefinition().any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(LIT, false));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getEnergyConsumption() {
|
||||
if (Config.cardCagesUseEnergy()) {
|
||||
return Config.cardCageEnergyPerTick;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
return BlockEntities.PCI_CARD_CAGE.get().create(pos, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(final Level level, final BlockState state, final BlockEntityType<T> type) {
|
||||
return TickableBlockEntity.createServerTicker(level, type, BlockEntities.PROJECTOR.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public VoxelShape getShape(final BlockState state, final BlockGetter level, final BlockPos blockPos, final CollisionContext context) {
|
||||
return switch (state.getValue(FACING)) {
|
||||
case NORTH -> NEG_Z_SHAPE;
|
||||
case SOUTH -> POS_Z_SHAPE;
|
||||
case WEST -> NEG_X_SHAPE;
|
||||
default -> POS_X_SHAPE;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(final BlockPlaceContext context) {
|
||||
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING, LIT);
|
||||
}
|
||||
}
|
||||
70
src/main/java/li/cil/oc2/common/block/VxlanBlock.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package li.cil.oc2.common.block;
|
||||
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.TickableBlockEntity;
|
||||
import li.cil.oc2.common.blockentity.VxlanBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class VxlanBlock extends HorizontalDirectionalBlock implements EntityBlock {
|
||||
public VxlanBlock() {
|
||||
super(Properties
|
||||
.of(Material.METAL)
|
||||
.sound(SoundType.METAL)
|
||||
.strength(1.5f, 6.0f));
|
||||
registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(final BlockPlaceContext context) {
|
||||
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) {
|
||||
final BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof final VxlanBlockEntity vxlanEntity) {
|
||||
vxlanEntity.handleNeighborChanged();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// EntityBlock
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
return BlockEntities.VXLAN_HUB.get().create(pos, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(final Level level, final BlockState state, final BlockEntityType<T> type) {
|
||||
return TickableBlockEntity.createServerTicker(level, type, BlockEntities.VXLAN_HUB.get());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
|
||||
super.createBlockStateDefinition(builder);
|
||||
builder.add(FACING);
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,12 @@ public final class BlockEntities {
|
||||
public static final RegistryObject<BlockEntityType<KeyboardBlockEntity>> KEYBOARD = register(Blocks.KEYBOARD, KeyboardBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<NetworkConnectorBlockEntity>> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR, NetworkConnectorBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<NetworkHubBlockEntity>> NETWORK_HUB = register(Blocks.NETWORK_HUB, NetworkHubBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<NetworkSwitchBlockEntity>> NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH, NetworkSwitchBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<ProjectorBlockEntity>> PROJECTOR = register(Blocks.PROJECTOR, ProjectorBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<RedstoneInterfaceBlockEntity>> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE, RedstoneInterfaceBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<VxlanBlockEntity>> VXLAN_HUB = register(Blocks.VXLAN_HUB, VxlanBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<PciCardCageBlockEntity>> PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE, PciCardCageBlockEntity::new);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
package li.cil.oc2.common.blockentity;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.DocumentedDevice;
|
||||
import li.cil.oc2.api.bus.device.object.NamedDevice;
|
||||
import li.cil.oc2.api.capabilities.NetworkInterface;
|
||||
import li.cil.oc2.common.Constants;
|
||||
import li.cil.oc2.common.block.NetworkSwitchBlock;
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.NetworkHubBlockEntity;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.util.LazyOptionalUtils;
|
||||
import li.cil.oc2.common.util.LevelUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
public final class NetworkSwitchBlockEntity extends ModBlockEntity implements NamedDevice, DocumentedDevice, NetworkInterface, TickableBlockEntity {
|
||||
private final String GET_LINK_STATE = "getLinkState";
|
||||
private final String GET_HOST_TABLE = "getHostTable";
|
||||
private final String GET_PORT_CONFIG = "getPortConfig";
|
||||
private final String SET_PORT_CONFIG = "setPortConfig";
|
||||
|
||||
private final long HOST_TTL = 20 * 60 * 2;
|
||||
private final int TTL_COST = 1;
|
||||
private final Map<Long, HostEntry> hostTable = new HashMap<>();
|
||||
private final PortSettings[] portSettings = new PortSettings[Constants.BLOCK_FACE_COUNT];
|
||||
private int tickCount = 0;
|
||||
private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT];
|
||||
private boolean haveAdjacentBlocksChanged = true;
|
||||
|
||||
public NetworkSwitchBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
super(BlockEntities.NETWORK_SWITCH.get(), pos, state);
|
||||
for (int i = 0; i < portSettings.length; i++) {
|
||||
portSettings[i] = new PortSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeEthernetFrame(final NetworkInterface source, byte[] frame, final int timeToLive) {
|
||||
validateAdjacentBlocks();
|
||||
long tickTime = getLevel().getGameTime();
|
||||
long destMac = macToLong(frame, 0);
|
||||
long srcMac = macToLong(frame, 6);
|
||||
short vlan = getVLAN(frame);
|
||||
Optional<Integer> optSide = sideReverseLookup(source);
|
||||
if (!optSide.isPresent()) {
|
||||
return;
|
||||
}
|
||||
int side = optSide.get();
|
||||
if (hostTable.size() <= 256) {
|
||||
hostTable.put(srcMac, new HostEntry(side, tickTime));
|
||||
}
|
||||
PortSettings ingressSettings = portSettings[side];
|
||||
SwitchLog log = new SwitchLog(vlan, side, srcMac, destMac);
|
||||
if (vlan == 0) {
|
||||
// Untagged packet
|
||||
Pair<Short, byte[]> pair = removeVLANTag(frame); // Remove tag in case there is a 0-tag
|
||||
frame = pair.getSecond();
|
||||
if (ingressSettings.untagged != 0) {
|
||||
frame = addVLANTag(frame, ingressSettings.untagged);
|
||||
vlan = ingressSettings.untagged;
|
||||
}
|
||||
} else {
|
||||
if (!(ingressSettings.trunkAll || ingressSettings.tagged.contains(vlan))) {
|
||||
// drop packet with disallowed vlan
|
||||
log.drop("Tag not allowed for ingress");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HostEntry host = hostTable.get(destMac);
|
||||
if (host != null) {
|
||||
if (host.iface == side && !ingressSettings.hairpin) {
|
||||
// if packet is to same port and hairpin is disabled, drop
|
||||
log.drop("hairpin disabled");
|
||||
return;
|
||||
}
|
||||
writeToSide(frame, host.iface, vlan, log, timeToLive);
|
||||
} else {
|
||||
log.flood();
|
||||
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
|
||||
if (i != side) {
|
||||
writeToSide(frame, i, vlan, log, timeToLive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readEthernetFrame() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeToSide(byte[] frame, int side, short vlan, SwitchLog log, int timeToLive) {
|
||||
log.egressPort(side);
|
||||
NetworkInterface iface = adjacentBlockInterfaces[side];
|
||||
if (iface != null) {
|
||||
PortSettings egressSettings = portSettings[side];
|
||||
if (egressSettings.untagged != 0 && vlan == 0) {
|
||||
log.drop("inner tag untagged");
|
||||
return;
|
||||
}
|
||||
|
||||
if (egressSettings.untagged == vlan) {
|
||||
Pair<Short, byte[]> pair = removeVLANTag(frame);
|
||||
frame = pair.getSecond();
|
||||
log.egressVlan = 0;
|
||||
} else if (!(egressSettings.trunkAll || egressSettings.tagged.contains(vlan))) {
|
||||
// Drop packets with wrong tag
|
||||
log.drop("Tag not allowed for egress");
|
||||
return;
|
||||
} else {
|
||||
log.egressVlan = vlan;
|
||||
}
|
||||
log.emit();
|
||||
iface.writeEthernetFrame(this, frame, timeToLive - TTL_COST);
|
||||
}
|
||||
}
|
||||
|
||||
private long macToLong(final byte[] mac, int offset) {
|
||||
long ret = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
ret |= ((((long) mac[i + offset]) & 0xff) << (i * 8));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick() {
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
if (tickCount++ % 20 == 0) {
|
||||
long threshold = getLevel().getGameTime() - HOST_TTL;
|
||||
if (threshold < 0) {
|
||||
return;
|
||||
}
|
||||
hostTable.entrySet().removeIf(e -> e.getValue().timestamp < threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDeviceDocumentation(final DeviceVisitor visitor) {
|
||||
visitor.visitCallback(GET_HOST_TABLE)
|
||||
.description("Returns the MAC address table of the switch")
|
||||
.returnValueDescription("The MAC table. For each host the mac address, the age (in ticks) and the face is returned");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getDeviceTypeNames() {
|
||||
return singletonList("switch");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAdditional(final CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
ListTag hosts = new ListTag();
|
||||
for (Map.Entry<Long, HostEntry> host : hostTable.entrySet()) {
|
||||
CompoundTag thisHost = new CompoundTag();
|
||||
thisHost.put("mac", LongTag.valueOf(host.getKey()));
|
||||
thisHost.put("side", IntTag.valueOf(host.getValue().iface));
|
||||
thisHost.put("timestamp", LongTag.valueOf(host.getValue().timestamp));
|
||||
hosts.add(thisHost);
|
||||
}
|
||||
tag.put("hosts", hosts);
|
||||
|
||||
ListTag ports = new ListTag();
|
||||
for (PortSettings myPort : portSettings) {
|
||||
CompoundTag port = new CompoundTag();
|
||||
myPort.save(port);
|
||||
ports.add(port);
|
||||
}
|
||||
tag.put("ports", ports);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(final CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
Tag hosts = tag.get("hosts");
|
||||
if (hosts != null) {
|
||||
for (Tag host_ : ((ListTag) hosts)) {
|
||||
CompoundTag host = (CompoundTag) host_;
|
||||
hostTable.put(
|
||||
host.getLong("mac"),
|
||||
new HostEntry(
|
||||
tag.getInt("side"),
|
||||
tag.getLong("timestamp")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tag ports = tag.get("ports");
|
||||
if (ports != null) {
|
||||
int i = 0;
|
||||
for (Tag port : ((ListTag) ports)) {
|
||||
portSettings[i++] = PortSettings.load((CompoundTag) port);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Callback(name = GET_HOST_TABLE)
|
||||
public List<LuaHostEntry> getHostTable() {
|
||||
long now = getLevel().getGameTime();
|
||||
return hostTable
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> new LuaHostEntry(macLongToString(e.getKey()), now - e.getValue().timestamp, e.getValue().iface))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Callback(name = GET_PORT_CONFIG, synchronize = false)
|
||||
public PortSettings[] getPortSettings() {
|
||||
return portSettings;
|
||||
}
|
||||
|
||||
@Callback(name = SET_PORT_CONFIG)
|
||||
public void setPortSettings(List<LinkedTreeMap> settings) {
|
||||
int max = Math.min(portSettings.length, settings.size());
|
||||
for (int i = 0; i < max; i++) {
|
||||
portSettings[i].untagged = ((Double) settings.get(i).get("untagged")).shortValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(name = GET_LINK_STATE)
|
||||
public boolean[] getLinkState() {
|
||||
validateAdjacentBlocks();
|
||||
boolean[] sides = new boolean[Constants.BLOCK_FACE_COUNT];
|
||||
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
|
||||
sides[i] = adjacentBlockInterfaces[i] != null;
|
||||
}
|
||||
return sides;
|
||||
}
|
||||
|
||||
private Optional<Integer> sideReverseLookup(NetworkInterface iface) {
|
||||
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
|
||||
if (iface == adjacentBlockInterfaces[i]) {
|
||||
return Optional.of(i);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static String macLongToString(long mac) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (i != 0) {
|
||||
ret.append(":");
|
||||
}
|
||||
ret.append(String.format("%02x", (mac >> (i * 8)) & 0xff));
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
private byte[] addVLANTag(byte[] packet, short tag) {
|
||||
if (tag != 0) {
|
||||
byte[] ret = new byte[packet.length + 4];
|
||||
copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header
|
||||
copyBytes(packet, ret, 12, 16, packet.length - 12);
|
||||
ret[12] = (byte) 0x81;
|
||||
ret[13] = (byte) 0x00;
|
||||
ret[14] = (byte) ((tag >> 8) & 0x0f);
|
||||
ret[15] = (byte) (tag & 0xff);
|
||||
return ret;
|
||||
} else {
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
private short getVLAN(byte[] packet) {
|
||||
if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) {
|
||||
return (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8));
|
||||
} else {
|
||||
return (short) 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Short, byte[]> removeVLANTag(byte[] packet) {
|
||||
if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) {
|
||||
byte[] ret = new byte[packet.length - 4];
|
||||
copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header
|
||||
copyBytes(packet, ret, 16, 12, packet.length - 16); // Copy payload
|
||||
short tag = (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8)); // Extract vlan tag
|
||||
return new Pair<>(tag, ret);
|
||||
} else {
|
||||
return new Pair<>((short) 0, packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyBytes(byte[] input, byte[] output, int inputOffset, int outputOffset, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
output[outputOffset + i] = input[inputOffset + i];
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAdjacentBlocks() {
|
||||
if (isRemoved() || !haveAdjacentBlocksChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Direction side : Constants.DIRECTIONS) {
|
||||
adjacentBlockInterfaces[side.get3DDataValue()] = null;
|
||||
}
|
||||
|
||||
haveAdjacentBlocksChanged = false;
|
||||
|
||||
if (level == null || level.isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BlockPos pos = getBlockPos();
|
||||
for (final Direction side : Constants.DIRECTIONS) {
|
||||
final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side));
|
||||
if (neighborBlockEntity != null) {
|
||||
final LazyOptional<NetworkInterface> optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite());
|
||||
optional.ifPresent(adjacentInterface -> {
|
||||
adjacentBlockInterfaces[side.get3DDataValue()] = adjacentInterface;
|
||||
LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNeighborChanged() {
|
||||
haveAdjacentBlocksChanged = true;
|
||||
}
|
||||
|
||||
private static class HostEntry {
|
||||
public int iface;
|
||||
public long timestamp;
|
||||
public HostEntry(int iface, long timestamp) {
|
||||
this.iface = iface;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LuaHostEntry {
|
||||
public String mac;
|
||||
public long age;
|
||||
public int side;
|
||||
|
||||
public LuaHostEntry(String mac, long age, int iface) {
|
||||
this.mac = mac;
|
||||
this.age = age;
|
||||
this.side = iface;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PortSettings {
|
||||
/**
|
||||
* The VLAN that is both PVID and untagged vlan. It will be removed on egress and added on ingress. If set to 0
|
||||
* this port is put on the global untagged vlan. The global untagged vlan can ever only be used as an untagged vlan
|
||||
*/
|
||||
public short untagged;
|
||||
/**
|
||||
* A list of tagged vlans that will be accepted on both ingress and egress. 0 (the global untagged vlan) is not a valid
|
||||
* value
|
||||
*/
|
||||
public List<Short> tagged;
|
||||
/**
|
||||
* If enabled, packets entering on this port may also leave via this port again
|
||||
*/
|
||||
public boolean hairpin;
|
||||
/**
|
||||
* If this is set, tagged will be ignored. Instead all tagged vlans will be accepted. untagged will still be honored
|
||||
*/
|
||||
public boolean trunkAll;
|
||||
|
||||
public PortSettings(final short untagged, final List<Short> tagged, final boolean hairpin, final boolean trunkAll) {
|
||||
this.untagged = untagged;
|
||||
this.tagged = tagged;
|
||||
this.hairpin = hairpin;
|
||||
this.trunkAll = trunkAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration of an unmanaged switch, which just forwards all tagged vlans as well as the untagged vlan
|
||||
* straight through
|
||||
*/
|
||||
public PortSettings() {
|
||||
this((short) 0, emptyList(), false, true);
|
||||
}
|
||||
|
||||
public void save(final CompoundTag tag) {
|
||||
tag.put("untagged", ShortTag.valueOf(untagged));
|
||||
tag.put("tagged", new IntArrayTag(tagged.stream().map(s -> (int) s).collect(Collectors.toList())));
|
||||
tag.put("hairpin", ByteTag.valueOf(hairpin));
|
||||
tag.put("trunkAll", ByteTag.valueOf(trunkAll));
|
||||
}
|
||||
|
||||
public static PortSettings load(final CompoundTag tag) {
|
||||
short untagged = tag.getShort("untagged");
|
||||
List<Short> tagged = Arrays.stream(tag.getIntArray("tagged"))
|
||||
.mapToObj(i -> (short) i)
|
||||
.collect(Collectors.toList());
|
||||
boolean hairpin = tag.getBoolean("hairpin");
|
||||
boolean trunkAll = tag.getBoolean("trunkAll");
|
||||
|
||||
return new PortSettings(untagged, tagged, hairpin, trunkAll);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SwitchLog {
|
||||
private static final boolean ENABLED = true;
|
||||
private short ingressVlan = 0;
|
||||
private short egressVlan = 0;
|
||||
private int ingressSide = 0;
|
||||
private final long srcMac;
|
||||
private final long destMac;
|
||||
private Integer egressSide = null;
|
||||
|
||||
public SwitchLog(short ingressVlan, int ingressSide, long srcMac, long destMac) {
|
||||
this.ingressVlan = ingressVlan;
|
||||
this.ingressSide = ingressSide;
|
||||
this.srcMac = srcMac;
|
||||
this.destMac = destMac;
|
||||
}
|
||||
|
||||
public void egressPort(int side) {
|
||||
egressSide = side;
|
||||
}
|
||||
|
||||
public void drop(String reason) {
|
||||
if (!ENABLED) return;
|
||||
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
|
||||
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
|
||||
if (egressSide == null) {
|
||||
System.out.printf(
|
||||
"Switch Packet %s (Port %s, VLAN %s) -> %s drop (%s)\n",
|
||||
inMac,
|
||||
ingressSide,
|
||||
ingressVlan,
|
||||
outMac,
|
||||
reason
|
||||
);
|
||||
} else {
|
||||
System.out.printf(
|
||||
"Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s) drop (%s)\n",
|
||||
inMac,
|
||||
ingressSide,
|
||||
ingressVlan,
|
||||
outMac,
|
||||
egressSide,
|
||||
reason
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void emit() {
|
||||
if (!ENABLED) return;
|
||||
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
|
||||
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
|
||||
System.out.printf(
|
||||
"Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s, VLAN %s)\n",
|
||||
inMac,
|
||||
ingressSide,
|
||||
ingressVlan,
|
||||
outMac,
|
||||
egressSide,
|
||||
egressVlan
|
||||
);
|
||||
}
|
||||
|
||||
public void flood() {
|
||||
if (!ENABLED) return;
|
||||
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
|
||||
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
|
||||
System.out.printf(
|
||||
"Switch Packet %s (Port %s, VLAN %s) -> %s flood\n",
|
||||
inMac,
|
||||
ingressSide,
|
||||
ingressVlan,
|
||||
outMac
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package li.cil.oc2.common.blockentity;
|
||||
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.block.PciCardCageBlock;
|
||||
import li.cil.oc2.common.bus.device.vm.block.PciCardCageDevice;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.energy.FixedEnergyStorage;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public final class PciCardCageBlockEntity extends ModBlockEntity implements TickableBlockEntity {
|
||||
|
||||
private static final String ENERGY_TAG_NAME = "energy";
|
||||
private static final String HAS_ENERGY_TAG_NAME = "has_energy";
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final PciCardCageDevice cardCageDevice = new PciCardCageDevice(this, this::handleMountedChanged);
|
||||
private boolean isMounted, hasEnergy;
|
||||
private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.cardCageEnergyStorage);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public PciCardCageBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
super(BlockEntities.PCI_CARD_CAGE.get(), pos, state);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private void handleMountedChanged(final boolean value) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean hasEnergy() {
|
||||
return hasEnergy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick() {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isPowered;
|
||||
if (Config.cardCagesUseEnergy()) {
|
||||
isPowered = energy.extractEnergy(Config.cardCageEnergyPerTick, true) >= Config.cardCageEnergyPerTick;
|
||||
if (isPowered) {
|
||||
energy.extractEnergy(Config.cardCageEnergyPerTick, false);
|
||||
}
|
||||
} else {
|
||||
isPowered = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
final CompoundTag tag = super.getUpdateTag();
|
||||
|
||||
tag.putBoolean(HAS_ENERGY_TAG_NAME, hasEnergy);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(final CompoundTag tag) {
|
||||
super.handleUpdateTag(tag);
|
||||
|
||||
hasEnergy = tag.getBoolean(HAS_ENERGY_TAG_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(final CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
tag.put(ENERGY_TAG_NAME, energy.serializeNBT());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(final CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setBlockState(final BlockState state) {
|
||||
super.setBlockState(state);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) {
|
||||
if (Config.cardCagesUseEnergy()) {
|
||||
collector.offer(Capabilities.energyStorage(), energy);
|
||||
}
|
||||
|
||||
if (direction == getBlockState().getValue(PciCardCageBlock.FACING).getOpposite()) {
|
||||
collector.offer(Capabilities.device(), cardCageDevice);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package li.cil.oc2.common.blockentity;
|
||||
|
||||
import li.cil.oc2.api.capabilities.NetworkInterface;
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.Constants;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.util.LazyOptionalUtils;
|
||||
import li.cil.oc2.common.util.LevelUtils;
|
||||
import li.cil.oc2.common.vxlan.TunnelManager;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.apache.commons.collections4.QueueUtils;
|
||||
import org.apache.commons.collections4.queue.CircularFifoQueue;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class VxlanBlockEntity extends ModBlockEntity implements NetworkInterface, TickableBlockEntity {
|
||||
private static final int TTL_COST = 1;
|
||||
//private int vti = ((int) (Math.random() * Integer.MAX_VALUE)) & 0x00ff_ffff;
|
||||
private int vti = 1000;
|
||||
private int frameCount;
|
||||
private long lastGameTime;
|
||||
|
||||
private final Queue<byte[]> packetQueue = new ArrayBlockingQueue<byte[]>(32);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Each face and the default TunnelInterface connecting to the outernet
|
||||
private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT + 1];
|
||||
private boolean haveAdjacentBlocksChanged = true;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public VxlanBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
super(BlockEntities.VXLAN_HUB.get(), pos, state);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public void handleNeighborChanged() {
|
||||
haveAdjacentBlocksChanged = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readEthernetFrame() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) {
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long gameTime = level.getGameTime();
|
||||
if (gameTime > lastGameTime) {
|
||||
lastGameTime = gameTime;
|
||||
frameCount = 1;
|
||||
} else if (frameCount > Config.hubEthernetFramesPerTick) {
|
||||
return;
|
||||
} else {
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
getAdjacentInterfaces().forEach(adjacentInterface -> {
|
||||
if (adjacentInterface != source) {
|
||||
adjacentInterface.writeEthernetFrame(this, frame, timeToLive - TTL_COST);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick() {
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (adjacentBlockInterfaces[0] != null) {
|
||||
// CircularFifoQueue isn't thread-safe, so we have to synchronize on it.
|
||||
synchronized (packetQueue) {
|
||||
packetQueue.forEach(packet -> writeEthernetFrame(adjacentBlockInterfaces[0], packet, 255));
|
||||
packetQueue.clear();
|
||||
}
|
||||
} else {
|
||||
System.out.printf("VXLAN block is unregistered upstream: VTI=%d\n", vti);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
if (level != null && !level.isClientSide() && tag.contains("vti")) {
|
||||
vti = tag.getInt("vti");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
if (level != null && !level.isClientSide()) {
|
||||
tag.putInt("vti", vti);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnload(final boolean isRemove) {
|
||||
if (level != null && !level.isClientSide()) {
|
||||
adjacentBlockInterfaces[0] = null;
|
||||
TunnelManager.instance().unregisterVti(vti);
|
||||
}
|
||||
|
||||
super.onUnload(isRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadServer() {
|
||||
adjacentBlockInterfaces[0] = TunnelManager.instance().registerVti(vti, this.packetQueue);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) {
|
||||
collector.offer(Capabilities.networkInterface(), this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private Stream<NetworkInterface> getAdjacentInterfaces() {
|
||||
validateAdjacentBlocks();
|
||||
return Arrays.stream(adjacentBlockInterfaces).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
private void validateAdjacentBlocks() {
|
||||
if (isRemoved() || !haveAdjacentBlocksChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Direction side : Constants.DIRECTIONS) {
|
||||
adjacentBlockInterfaces[side.get3DDataValue() + 1] = null;
|
||||
}
|
||||
|
||||
haveAdjacentBlocksChanged = false;
|
||||
|
||||
if (level == null || level.isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BlockPos pos = getBlockPos();
|
||||
for (final Direction side : Constants.DIRECTIONS) {
|
||||
final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side));
|
||||
if (neighborBlockEntity != null) {
|
||||
final LazyOptional<NetworkInterface> optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite());
|
||||
optional.ifPresent(adjacentInterface -> {
|
||||
adjacentBlockInterfaces[side.get3DDataValue() + 1] = adjacentInterface;
|
||||
LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package li.cil.oc2.common.bus.device.vm.block;
|
||||
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
||||
import li.cil.oc2.api.bus.device.vm.VMDevice;
|
||||
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
|
||||
import li.cil.oc2.api.bus.device.vm.context.VMContext;
|
||||
import li.cil.oc2.common.Constants;
|
||||
import li.cil.oc2.common.bus.device.util.IdentityProxy;
|
||||
import li.cil.oc2.common.bus.device.util.OptionalAddress;
|
||||
import li.cil.oc2.common.serialization.BlobStorage;
|
||||
import li.cil.oc2.common.util.NBTTagIds;
|
||||
import li.cil.oc2.common.vm.device.PciRootPortDevice;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PciCardCageDevice extends IdentityProxy<BlockEntity> implements VMDevice {
|
||||
private static final String ADDRESS_TAG_NAME = "address";
|
||||
private static final String BLOB_HANDLE_TAG_NAME = "blob";
|
||||
|
||||
public static final int BUS_COUNT = 16;
|
||||
public static final int WINDOW_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final BooleanConsumer onMountedChanged;
|
||||
|
||||
@Nullable private PciRootPortDevice device;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final OptionalAddress address = new OptionalAddress();
|
||||
@Nullable private UUID blobHandle;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public PciCardCageDevice(final BlockEntity identity, final BooleanConsumer onMountedChanged) {
|
||||
super(identity);
|
||||
this.onMountedChanged = onMountedChanged;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public VMDeviceLoadResult mount(final VMContext context) {
|
||||
if (!allocateDevice(context)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
assert device != null;
|
||||
if (!address.claim(context, device)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
onMountedChanged.accept(true);
|
||||
|
||||
return VMDeviceLoadResult.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() {
|
||||
final PciRootPortDevice pciRootPortDevice = device;
|
||||
device = null;
|
||||
if (pciRootPortDevice != null) {
|
||||
pciRootPortDevice.close();
|
||||
}
|
||||
|
||||
if (blobHandle != null) {
|
||||
BlobStorage.close(blobHandle);
|
||||
}
|
||||
|
||||
onMountedChanged.accept(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (blobHandle != null) {
|
||||
BlobStorage.delete(blobHandle);
|
||||
blobHandle = null;
|
||||
}
|
||||
|
||||
address.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
final CompoundTag tag = new CompoundTag();
|
||||
|
||||
if (blobHandle != null) {
|
||||
tag.putUUID(BLOB_HANDLE_TAG_NAME, blobHandle);
|
||||
}
|
||||
if (address.isPresent()) {
|
||||
tag.putLong(ADDRESS_TAG_NAME, address.getAsLong());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(final CompoundTag tag) {
|
||||
if (tag.hasUUID(BLOB_HANDLE_TAG_NAME)) {
|
||||
blobHandle = tag.getUUID(BLOB_HANDLE_TAG_NAME);
|
||||
}
|
||||
if (tag.contains(ADDRESS_TAG_NAME, NBTTagIds.TAG_LONG)) {
|
||||
address.set(tag.getLong(ADDRESS_TAG_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean allocateDevice(final VMContext context) {
|
||||
if (!context.getMemoryAllocator().claimMemory(Constants.PAGE_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
device = createPciRootPortDevice();
|
||||
} catch (final IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private PciRootPortDevice createPciRootPortDevice() throws IOException {
|
||||
blobHandle = BlobStorage.validateHandle(blobHandle);
|
||||
final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
|
||||
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, WINDOW_SIZE * 2);
|
||||
return new PciRootPortDevice(BUS_COUNT, WINDOW_SIZE, buffer);
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,11 @@ public final class Items {
|
||||
public static final RegistryObject<Item> KEYBOARD = register(Blocks.KEYBOARD);
|
||||
public static final RegistryObject<Item> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR);
|
||||
public static final RegistryObject<Item> NETWORK_HUB = register(Blocks.NETWORK_HUB);
|
||||
public static final RegistryObject<Item> NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH);
|
||||
public static final RegistryObject<Item> PROJECTOR = register(Blocks.PROJECTOR);
|
||||
public static final RegistryObject<Item> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE);
|
||||
public static final RegistryObject<Item> VXLAN_HUB = register(Blocks.VXLAN_HUB);
|
||||
public static final RegistryObject<Item> PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
101
src/main/java/li/cil/oc2/common/vm/device/PciRootPortDevice.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package li.cil.oc2.common.vm.device;
|
||||
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.memory.MemoryAccessException;
|
||||
import li.cil.sedna.utils.DirectByteBufferUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
|
||||
public final class PciRootPortDevice implements MemoryMappedDevice {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
private int length;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public PciRootPortDevice(final int bus_count, final int window_size, final ByteBuffer buffer) {
|
||||
|
||||
length = window_size * 2;
|
||||
if (buffer.capacity() < length) {
|
||||
throw new IllegalArgumentException("Buffer too small.");
|
||||
}
|
||||
|
||||
this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
this.buffer.putInt(0, 0x12345678);
|
||||
this.buffer.putInt(4, 2);
|
||||
this.buffer.putInt(8, 0xFF000000);
|
||||
this.buffer.putInt(12, 0x00000101);
|
||||
this.buffer.putInt(0x10, 0x00000000);
|
||||
this.buffer.putInt(0x2C, 0x12345678);
|
||||
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public void close() {
|
||||
synchronized (buffer) {
|
||||
length = 0;
|
||||
DirectByteBufferUtils.release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean hasChanges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long load(final int offset, final int sizeLog2) throws MemoryAccessException {
|
||||
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
|
||||
System.out.println(String.format("PCI config read: %x %x", offset, sizeLog2));
|
||||
if (offset == 0x10) {
|
||||
long res = buffer.getInt(offset);
|
||||
System.out.println(String.format(" 00:00.0 BAR0 read %x", res));
|
||||
res = res & 0xFFFFF000L;
|
||||
System.out.println(String.format("Clipped 00:00.0 BAR0 read to %x", res));
|
||||
return res;
|
||||
}
|
||||
return switch (sizeLog2) {
|
||||
case 0 -> buffer.get(offset);
|
||||
case 1 -> buffer.getShort(offset);
|
||||
case 2 -> buffer.getInt(offset);
|
||||
case 3 -> buffer.getLong(offset);
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final int offset, final long value, final int sizeLog2) throws MemoryAccessException {
|
||||
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
|
||||
System.out.println(String.format("PCI config write: %x %x %x", offset, value, sizeLog2));
|
||||
switch (sizeLog2) {
|
||||
case 0 -> buffer.put(offset, (byte) value);
|
||||
case 1 -> buffer.putShort(offset, (short) value);
|
||||
case 2 -> buffer.putInt(offset, (int) value);
|
||||
case 3 -> buffer.putLong(offset, value);
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
package li.cil.oc2.common.vm.provider;
|
||||
|
||||
import li.cil.oc2.common.vm.device.PciRootPortDevice;
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import li.cil.sedna.devicetree.DeviceTreeRegistry;
|
||||
|
||||
public final class DeviceTreeProviders {
|
||||
public static void initialize() {
|
||||
DeviceTreeRegistry.putProvider(SimpleFramebufferDevice.class, new SimpleFramebufferDeviceProvider());
|
||||
DeviceTreeRegistry.putProvider(PciRootPortDevice.class, new PciRootPortDeviceProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package li.cil.oc2.common.vm.provider;
|
||||
|
||||
import li.cil.oc2.common.vm.device.PciRootPortDevice;
|
||||
import li.cil.sedna.api.device.Device;
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.devicetree.DeviceNames;
|
||||
import li.cil.sedna.api.devicetree.DevicePropertyNames;
|
||||
import li.cil.sedna.api.devicetree.DeviceTree;
|
||||
import li.cil.sedna.api.devicetree.DeviceTreeProvider;
|
||||
import li.cil.sedna.api.memory.MappedMemoryRange;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PciRootPortDeviceProvider implements DeviceTreeProvider {
|
||||
@Override
|
||||
public Optional<String> getName(final Device device) {
|
||||
return Optional.of("pci");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
|
||||
final Optional<MappedMemoryRange> range = memoryMap.getMemoryRange((MemoryMappedDevice) device);
|
||||
return range.map(r -> {
|
||||
final DeviceTree pci = root.find("/pci");
|
||||
return pci.getChild(deviceName, r.address());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
|
||||
final PciRootPortDevice pr = (PciRootPortDevice) device;
|
||||
final Optional<MappedMemoryRange> range = memoryMap.getMemoryRange((MemoryMappedDevice) device);
|
||||
node
|
||||
.addProp(DevicePropertyNames.COMPATIBLE, "pci-host-cam-generic")
|
||||
.addProp(DevicePropertyNames.DEVICE_TYPE, DeviceNames.PCI)
|
||||
.addProp(DevicePropertyNames.NUM_ADDRESS_CELLS,3)
|
||||
.addProp(DevicePropertyNames.NUM_SIZE_CELLS, 2)
|
||||
.addProp("bus-range", 0, 1)
|
||||
//.addProp("linux,pci-probe-only", 1)
|
||||
.addProp(DevicePropertyNames.RANGES,
|
||||
// type pci.hi pci.lo cpu.hi cpu.lo len.hi len.lo
|
||||
0x02000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x20000000); //
|
||||
}
|
||||
}
|
||||
|
||||
162
src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package li.cil.oc2.common.vxlan;
|
||||
|
||||
import li.cil.oc2.api.capabilities.NetworkInterface;
|
||||
import li.cil.oc2.common.Config;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Queue;
|
||||
|
||||
public class TunnelManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final HashMap<Integer, TunnelInterface> tunnels = new HashMap<>();
|
||||
private DatagramSocket socket;
|
||||
private static TunnelManager INSTANCE;
|
||||
private final InetAddress remoteHost;
|
||||
private final short remotePort;
|
||||
private final InetAddress bindHost;
|
||||
private final short bindPort;
|
||||
|
||||
public TunnelManager(InetAddress bindHost, short bindPort, InetAddress remoteHost, short remotePort) throws SocketException {
|
||||
this.remoteHost = remoteHost;
|
||||
this.remotePort = remotePort;
|
||||
this.bindHost = bindHost;
|
||||
this.bindPort = bindPort;
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
LOGGER.info("Initializing outernet tunnel manager");
|
||||
|
||||
try {
|
||||
INSTANCE = new TunnelManager(
|
||||
InetAddress.getByName(Config.bindHost), (short) Config.bindPort,
|
||||
InetAddress.getByName(Config.remoteHost), (short) Config.remotePort
|
||||
);
|
||||
} catch (SocketException | UnknownHostException e) {
|
||||
LOGGER.error("Failed to bind to configured address: " + e.getMessage());
|
||||
LOGGER.error(e);
|
||||
}
|
||||
|
||||
if (Config.enable) {
|
||||
Thread bgThread = new Thread(() -> {
|
||||
try {
|
||||
INSTANCE.listen();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
});
|
||||
bgThread.setName("VXLAN Background Thread");
|
||||
bgThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void listen() throws IOException {
|
||||
LOGGER.printf(Level.INFO, "Binding %s:%s\n", bindHost, bindPort);
|
||||
|
||||
if (Config.enable) {
|
||||
socket = new DatagramSocket(bindPort, bindHost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
LOGGER.printf(Level.INFO, "Bind successful: connected=%s bound=%s\n", socket.isConnected(), socket.isBound());
|
||||
|
||||
byte[] buffer = new byte[65535];
|
||||
// TODO shut this thread down more cleanly on server shutdown?
|
||||
//noinspection InfiniteLoopStatement
|
||||
while (true) {
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
socket.receive(packet);
|
||||
|
||||
if (packet.getLength() < 8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte flags = packet.getData()[0];
|
||||
int vni = (packet.getData()[6] & 0xFF )
|
||||
| ( ( packet.getData()[5] & 0xFF ) << 8 )
|
||||
| ( ( packet.getData()[4] & 0xFF ) << 16 );
|
||||
|
||||
if ((flags & 0x08) != 0x08) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGGER.debug("recv on vti " + vni);
|
||||
|
||||
TunnelInterface iface = tunnels.get(vni);
|
||||
|
||||
if (iface != null) {
|
||||
byte[] inner = new byte[packet.getLength() - 8];
|
||||
System.arraycopy(packet.getData(), 8, inner, 0, packet.getLength() - 8);
|
||||
|
||||
// CircularFifoQueue isn't thread-safe, so we have to synchronize on it.
|
||||
synchronized (iface.packetQueue) {
|
||||
iface.packetQueue.offer(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TunnelManager instance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void sendToOuternet(int vti, byte[] payload) {
|
||||
if (socket != null) {
|
||||
|
||||
byte[] buffer = new byte[payload.length + 8];
|
||||
|
||||
System.arraycopy(payload, 0, buffer, 8, payload.length);
|
||||
|
||||
buffer[0] = 0x08;
|
||||
buffer[4] = (byte) ((vti >> 16) & 0xff);
|
||||
buffer[5] = (byte) ((vti >> 8) & 0xff);
|
||||
buffer[6] = (byte) (vti & 0xff);
|
||||
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, this.remoteHost, this.remotePort);
|
||||
|
||||
try {
|
||||
socket.send(packet);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("No socket in TunnelManager\n");
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkInterface registerVti(int vti, Queue<byte[]> packetQueue) {
|
||||
TunnelInterface tuniface = new TunnelInterface(vti, packetQueue);
|
||||
tunnels.put(vti, tuniface);
|
||||
return tuniface;
|
||||
}
|
||||
|
||||
public void unregisterVti(int vti) {
|
||||
tunnels.remove(vti);
|
||||
}
|
||||
|
||||
public class TunnelInterface implements NetworkInterface {
|
||||
final Queue<byte[]> packetQueue;
|
||||
private final int vti;
|
||||
|
||||
public TunnelInterface(int vti, Queue<byte[]> packetQueue) {
|
||||
this.vti = vti;
|
||||
this.packetQueue = packetQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readEthernetFrame() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEthernetFrame(final @NotNull NetworkInterface source, final byte @NotNull [] frame, final int timeToLive) {
|
||||
TunnelManager.this.sendToOuternet(vti, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ public final class ModBlockStateProvider extends BlockStateProvider {
|
||||
private static final ResourceLocation NETWORK_HUB_MODEL = new ResourceLocation(API.MOD_ID, "block/network_hub");
|
||||
private static final ResourceLocation PROJECTOR_MODEL = new ResourceLocation(API.MOD_ID, "block/projector");
|
||||
private static final ResourceLocation REDSTONE_INTERFACE_MODEL = new ResourceLocation(API.MOD_ID, "block/redstone_interface");
|
||||
private static final ResourceLocation PCI_CARD_CAGE_MODEL = new ResourceLocation(API.MOD_ID, "block/pci_card_cage");
|
||||
|
||||
|
||||
public ModBlockStateProvider(final DataGenerator generator, final ExistingFileHelper existingFileHelper) {
|
||||
super(generator, API.MOD_ID, existingFileHelper);
|
||||
@@ -55,8 +57,10 @@ public final class ModBlockStateProvider extends BlockStateProvider {
|
||||
.end()
|
||||
.end();
|
||||
horizontalBlock(Blocks.NETWORK_HUB, Items.NETWORK_HUB, NETWORK_HUB_MODEL);
|
||||
horizontalBlock(Blocks.NETWORK_SWITCH, Items.NETWORK_SWITCH, NETWORK_HUB_MODEL);
|
||||
horizontalBlock(Blocks.PROJECTOR, Items.PROJECTOR, PROJECTOR_MODEL);
|
||||
horizontalBlock(Blocks.REDSTONE_INTERFACE, Items.REDSTONE_INTERFACE, REDSTONE_INTERFACE_MODEL);
|
||||
horizontalBlock(Blocks.PCI_CARD_CAGE, Items.PCI_CARD_CAGE, PCI_CARD_CAGE_MODEL);
|
||||
|
||||
registerCableStates();
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Maven-Artifact: org.apache.commons:commons-collections4:4.4
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north": {
|
||||
"model": "oc2:block/network_switch"
|
||||
},
|
||||
"facing=south": {
|
||||
"model": "oc2:block/network_switch",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west": {
|
||||
"model": "oc2:block/network_switch",
|
||||
"y": 270
|
||||
},
|
||||
"facing=east": {
|
||||
"model": "oc2:block/network_switch",
|
||||
"y": 90
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/main/resources/assets/oc2/blockstates/pci_card_cage.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north,lit=false": {
|
||||
"model": "oc2:block/pci_card_cage"
|
||||
},
|
||||
"facing=south,lit=false": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west,lit=false": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 270
|
||||
},
|
||||
"facing=east,lit=false": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 90
|
||||
},
|
||||
"facing=north,lit=true": {
|
||||
"model": "oc2:block/pci_card_cage"
|
||||
},
|
||||
"facing=south,lit=true": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west,lit=true": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 270
|
||||
},
|
||||
"facing=east,lit=true": {
|
||||
"model": "oc2:block/pci_card_cage",
|
||||
"y": 90
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/resources/assets/oc2/blockstates/vxlan_hub.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north": {
|
||||
"model": "oc2:block/vxlan_hub"
|
||||
},
|
||||
"facing=south": {
|
||||
"model": "oc2:block/vxlan_hub",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west": {
|
||||
"model": "oc2:block/vxlan_hub",
|
||||
"y": 270
|
||||
},
|
||||
"facing=east": {
|
||||
"model": "oc2:block/vxlan_hub",
|
||||
"y": 90
|
||||
}
|
||||
}
|
||||
}
|
||||
293
src/main/resources/assets/oc2/models/block/network_switch.json
Normal file
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "oc2:block/network_switch/network_switch_atlas0",
|
||||
"atlas0": "oc2:block/network_switch/network_switch_atlas0",
|
||||
"atlas1": "oc2:block/network_switch/network_switch_atlas1",
|
||||
"atlas2": "oc2:block/network_switch/network_switch_atlas2",
|
||||
"atlas3": "oc2:block/network_switch/network_switch_atlas3"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [0, 0, 10],
|
||||
"to": [16, 1, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"},
|
||||
"east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"},
|
||||
"south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"},
|
||||
"down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 0, 6],
|
||||
"to": [6, 1, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"},
|
||||
"east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"},
|
||||
"south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"},
|
||||
"west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"},
|
||||
"down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 0, 6],
|
||||
"to": [16, 1, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"},
|
||||
"west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 0, 0],
|
||||
"to": [16, 1, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"},
|
||||
"east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"},
|
||||
"west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"},
|
||||
"down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 1, 0],
|
||||
"to": [16, 6, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"},
|
||||
"east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"},
|
||||
"south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"},
|
||||
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"},
|
||||
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 15],
|
||||
"to": [6, 10, 16],
|
||||
"faces": {
|
||||
"east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"},
|
||||
"south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"},
|
||||
"west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"},
|
||||
"up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 6, 15],
|
||||
"to": [16, 10, 16],
|
||||
"faces": {
|
||||
"east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"},
|
||||
"south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"},
|
||||
"west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"},
|
||||
"up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [1, 6, 6],
|
||||
"to": [15, 10, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"},
|
||||
"east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"},
|
||||
"south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"},
|
||||
"west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"},
|
||||
"up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"},
|
||||
"down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 1],
|
||||
"to": [16, 10, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"},
|
||||
"east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"},
|
||||
"west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"},
|
||||
"down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 0],
|
||||
"to": [6, 10, 1],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"},
|
||||
"east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"},
|
||||
"west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"},
|
||||
"up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 6, 0],
|
||||
"to": [16, 10, 1],
|
||||
"faces": {
|
||||
"north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"},
|
||||
"east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"},
|
||||
"west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"},
|
||||
"up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 10, 0],
|
||||
"to": [16, 15, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"},
|
||||
"east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"},
|
||||
"south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"},
|
||||
"west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"},
|
||||
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"},
|
||||
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 13],
|
||||
"to": [16, 16, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"},
|
||||
"east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 12],
|
||||
"to": [6, 16, 13],
|
||||
"faces": {
|
||||
"north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"},
|
||||
"east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"},
|
||||
"south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"},
|
||||
"west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 12],
|
||||
"to": [16, 16, 13],
|
||||
"faces": {
|
||||
"north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"},
|
||||
"up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 10],
|
||||
"to": [16, 16, 12],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"},
|
||||
"east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"},
|
||||
"west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 6],
|
||||
"to": [3, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"},
|
||||
"south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [4, 15, 6],
|
||||
"to": [6, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"},
|
||||
"east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"},
|
||||
"south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 6],
|
||||
"to": [12, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"},
|
||||
"east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"},
|
||||
"south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"},
|
||||
"up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [13, 15, 6],
|
||||
"to": [16, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"},
|
||||
"east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 4],
|
||||
"to": [16, 16, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"},
|
||||
"east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"},
|
||||
"west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 3],
|
||||
"to": [6, 16, 4],
|
||||
"faces": {
|
||||
"north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"},
|
||||
"south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 3],
|
||||
"to": [16, 16, 4],
|
||||
"faces": {
|
||||
"north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"},
|
||||
"west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 0],
|
||||
"to": [16, 16, 3],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"},
|
||||
"east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"},
|
||||
"west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 10],
|
||||
"to": [16, 10, 15],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"},
|
||||
"east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"},
|
||||
"west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"},
|
||||
"down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {}
|
||||
}
|
||||
292
src/main/resources/assets/oc2/models/block/vxlan_hub.json
Normal file
@@ -0,0 +1,292 @@
|
||||
{
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "oc2:block/vxlan_hub/vxlan_hub_atlas0",
|
||||
"atlas0": "oc2:block/vxlan_hub/vxlan_hub_atlas0",
|
||||
"atlas1": "oc2:block/vxlan_hub/vxlan_hub_atlas1",
|
||||
"atlas2": "oc2:block/vxlan_hub/vxlan_hub_atlas2",
|
||||
"atlas3": "oc2:block/vxlan_hub/vxlan_hub_atlas3"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [0, 0, 10],
|
||||
"to": [16, 1, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"},
|
||||
"east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"},
|
||||
"south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"},
|
||||
"down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 0, 6],
|
||||
"to": [6, 1, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"},
|
||||
"east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"},
|
||||
"south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"},
|
||||
"west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"},
|
||||
"down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 0, 6],
|
||||
"to": [16, 1, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"},
|
||||
"west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 0, 0],
|
||||
"to": [16, 1, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"},
|
||||
"east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"},
|
||||
"west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"},
|
||||
"down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 1, 0],
|
||||
"to": [16, 6, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"},
|
||||
"east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"},
|
||||
"south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"},
|
||||
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"},
|
||||
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 15],
|
||||
"to": [6, 10, 16],
|
||||
"faces": {
|
||||
"east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"},
|
||||
"south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"},
|
||||
"west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"},
|
||||
"up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 6, 15],
|
||||
"to": [16, 10, 16],
|
||||
"faces": {
|
||||
"east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"},
|
||||
"south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"},
|
||||
"west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"},
|
||||
"up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 10],
|
||||
"to": [16, 10, 15],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"},
|
||||
"east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"},
|
||||
"west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"},
|
||||
"down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [1, 6, 6],
|
||||
"to": [15, 10, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"},
|
||||
"east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"},
|
||||
"south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"},
|
||||
"west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"},
|
||||
"up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"},
|
||||
"down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 1],
|
||||
"to": [16, 10, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"},
|
||||
"east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"},
|
||||
"west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"},
|
||||
"down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 6, 0],
|
||||
"to": [6, 10, 1],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"},
|
||||
"east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"},
|
||||
"west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"},
|
||||
"up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 6, 0],
|
||||
"to": [16, 10, 1],
|
||||
"faces": {
|
||||
"north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"},
|
||||
"east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"},
|
||||
"west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"},
|
||||
"up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"},
|
||||
"down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 10, 0],
|
||||
"to": [16, 15, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"},
|
||||
"east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"},
|
||||
"south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"},
|
||||
"west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"},
|
||||
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"},
|
||||
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 13],
|
||||
"to": [16, 16, 16],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"},
|
||||
"east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"},
|
||||
"west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 12],
|
||||
"to": [6, 16, 13],
|
||||
"faces": {
|
||||
"north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"},
|
||||
"east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"},
|
||||
"south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"},
|
||||
"west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 12],
|
||||
"to": [16, 16, 13],
|
||||
"faces": {
|
||||
"north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"},
|
||||
"up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 10],
|
||||
"to": [16, 16, 12],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"},
|
||||
"east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"},
|
||||
"west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 6],
|
||||
"to": [3, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"},
|
||||
"south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [4, 15, 6],
|
||||
"to": [6, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"},
|
||||
"east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"},
|
||||
"south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 6],
|
||||
"to": [12, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"},
|
||||
"east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"},
|
||||
"south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"},
|
||||
"up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [13, 15, 6],
|
||||
"to": [16, 16, 10],
|
||||
"faces": {
|
||||
"north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"},
|
||||
"east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"},
|
||||
"west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 4],
|
||||
"to": [16, 16, 6],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"},
|
||||
"east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"},
|
||||
"west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 3],
|
||||
"to": [6, 16, 4],
|
||||
"faces": {
|
||||
"north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"},
|
||||
"south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"},
|
||||
"west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 15, 3],
|
||||
"to": [16, 16, 4],
|
||||
"faces": {
|
||||
"north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"},
|
||||
"east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"},
|
||||
"west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"},
|
||||
"up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 15, 0],
|
||||
"to": [16, 16, 3],
|
||||
"faces": {
|
||||
"north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"},
|
||||
"east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"},
|
||||
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"},
|
||||
"west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"},
|
||||
"up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "oc2:block/network_switch"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "oc2:block/pci_card_cage"
|
||||
}
|
||||
3
src/main/resources/assets/oc2/models/item/vxlan_hub.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "oc2:block/vxlan_hub"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 628 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
59
src/main/scripts/bin/swconfig.lua
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
function usage()
|
||||
print("Usage:")
|
||||
print(" swconfig show_hosts")
|
||||
print(" swconfig show_ports")
|
||||
print(" swconfig set_port <port> untagged <vid>")
|
||||
print(" swconfig set_port <port> trunk_all (on|off)")
|
||||
end
|
||||
|
||||
if not arg[1] then
|
||||
usage()
|
||||
return
|
||||
end
|
||||
|
||||
local cjson = require("cjson").new()
|
||||
|
||||
local devbus = require('devices')
|
||||
local switch = devbus:find("switch")
|
||||
|
||||
if not switch then
|
||||
print("No switch found")
|
||||
return
|
||||
end
|
||||
|
||||
if arg[1] == "show_hosts" then
|
||||
host_table = switch:getHostTable()
|
||||
|
||||
for _, v in ipairs(host_table) do
|
||||
print(v.mac .. " : " .. v.side .. ", Age: " .. v.age)
|
||||
end
|
||||
elseif arg[1] == "show_ports" then
|
||||
local link_state = switch:getLinkState()
|
||||
for i, port in ipairs(switch:getPortConfig()) do
|
||||
print("Port #" .. (i - 1) .. " " .. (link_state[i] and "UP" or "DOWN"))
|
||||
print(" Untagged VLAN: " .. port.untagged)
|
||||
print(" Tagged: " .. table.concat(port.tagged, ", "))
|
||||
print(" Hairpin: " .. (port.hairpin and "on" or "off"))
|
||||
print(" Trunk All: " .. (port.trunk_all and "on" or "off"))
|
||||
end
|
||||
elseif arg[1] == "set_port" then
|
||||
if #arg < 4 then
|
||||
usage()
|
||||
return
|
||||
end
|
||||
local config = switch:getPortConfig()
|
||||
local port = config[tonumber(arg[2]) + 1]
|
||||
if not port then
|
||||
print("Invalid Port Number")
|
||||
return
|
||||
end
|
||||
if arg[3] == "untagged" then
|
||||
port.untagged = tonumber(arg[4])
|
||||
end
|
||||
switch:setPortConfig(config)
|
||||
else
|
||||
usage()
|
||||
return
|
||||
end
|
||||