diff --git a/src/main/java/li/cil/oc2/common/Config.java b/src/main/java/li/cil/oc2/common/Config.java index 32f178e8..a243ffde 100644 --- a/src/main/java/li/cil/oc2/common/Config.java +++ b/src/main/java/li/cil/oc2/common/Config.java @@ -47,9 +47,9 @@ public final class Config { @Path("vxlan") public static boolean enable = false; @Path("vxlan") public static String remoteHost = "::1"; - @Path("vxlan") public static int remotePort = 4789; + @Path("vxlan") public static short remotePort = 4789; @Path("vxlan") public static String bindHost = "::1"; - @Path("vxlan") public static int bindPort = 4789; + @Path("vxlan") public static short bindPort = 4789; public static boolean computersUseEnergy() { return computerEnergyPerTick > 0 && computerEnergyStorage > 0; diff --git a/src/main/java/li/cil/oc2/common/block/Blocks.java b/src/main/java/li/cil/oc2/common/block/Blocks.java index 0f9c4e87..6957abde 100644 --- a/src/main/java/li/cil/oc2/common/block/Blocks.java +++ b/src/main/java/li/cil/oc2/common/block/Blocks.java @@ -23,6 +23,7 @@ public final class Blocks { public static final RegistryObject NETWORK_HUB = BLOCKS.register("network_hub", NetworkHubBlock::new); public static final RegistryObject PROJECTOR = BLOCKS.register("projector", ProjectorBlock::new); public static final RegistryObject REDSTONE_INTERFACE = BLOCKS.register("redstone_interface", RedstoneInterfaceBlock::new); + public static final RegistryObject VXLAN_HUB = BLOCKS.register("vxlan_hub", VxlanBlock::new); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/block/NetworkPortalBlock.java b/src/main/java/li/cil/oc2/common/block/NetworkPortalBlock.java deleted file mode 100644 index de701756..00000000 --- a/src/main/java/li/cil/oc2/common/block/NetworkPortalBlock.java +++ /dev/null @@ -1,35 +0,0 @@ -package li.cil.oc2.common.block; - -import li.cil.oc2.common.blockentity.BlockEntities; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.item.context.BlockPlaceContext; -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.material.Material; -import org.jetbrains.annotations.Nullable; - -public class NetworkPortalBlock extends HorizontalDirectionalBlock implements EntityBlock { - - public NetworkPortalBlock() { - 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()); - } - - @Nullable - @Override - public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) { - return BlockEntities.NETWORK_HUB.get().create(pos, state); - } -} diff --git a/src/main/java/li/cil/oc2/common/block/VxlanBlock.java b/src/main/java/li/cil/oc2/common/block/VxlanBlock.java new file mode 100644 index 00000000..6a8911f8 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/VxlanBlock.java @@ -0,0 +1,62 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.common.blockentity.BlockEntities; +import li.cil.oc2.common.blockentity.NetworkHubBlockEntity; +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.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); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void createBlockStateDefinition(final StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } +} diff --git a/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java b/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java index 1c885086..460c3475 100644 --- a/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java +++ b/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java @@ -26,6 +26,7 @@ public final class BlockEntities { public static final RegistryObject> NETWORK_HUB = register(Blocks.NETWORK_HUB, NetworkHubBlockEntity::new); public static final RegistryObject> PROJECTOR = register(Blocks.PROJECTOR, ProjectorBlockEntity::new); public static final RegistryObject> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE, RedstoneInterfaceBlockEntity::new); + public static final RegistryObject> VXLAN_HUB = register(Blocks.VXLAN_HUB, VxlanBlockEntity::new); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java new file mode 100644 index 00000000..c2931962 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java @@ -0,0 +1,121 @@ +package li.cil.oc2.common.blockentity; + +import li.cil.oc2.api.capabilities.NetworkInterface; +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 javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; + +public final class VxlanBlockEntity extends ModBlockEntity implements NetworkInterface { + private static final int TTL_COST = 1; + private int vti = ((int) (Math.random() * Integer.MAX_VALUE)) & 0x00ff_ffff; + private boolean initialized = false; + + /////////////////////////////////////////////////////////////////// + + // 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.NETWORK_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) { + getAdjacentInterfaces().forEach(adjacentInterface -> { + if (adjacentInterface != source) { + adjacentInterface.writeEthernetFrame(this, frame, timeToLive - TTL_COST); + } + }); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void load(CompoundTag tag) { + super.load(tag); + vti = tag.getInt("vti"); + } + + @Override + protected void onUnload(final boolean isRemove) { + TunnelManager.instance().unregisterVti(vti); + + super.onUnload(isRemove); + } + + @Override + public void onLoad() { + super.onLoad(); + + TunnelManager.instance().registerVti(vti, this); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) { + collector.offer(Capabilities.NETWORK_INTERFACE, this); + } + + /////////////////////////////////////////////////////////////////// + + private Stream 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 optional = neighborBlockEntity.getCapability(Capabilities.NETWORK_INTERFACE, side.getOpposite()); + optional.ifPresent(adjacentInterface -> { + adjacentBlockInterfaces[side.get3DDataValue() + 1] = adjacentInterface; + LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged()); + }); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java b/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java index 1b641523..3d9b7770 100644 --- a/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java +++ b/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java @@ -2,6 +2,7 @@ package li.cil.oc2.common.vxlan; import li.cil.oc2.api.capabilities.NetworkInterface; import li.cil.oc2.common.Config; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.net.*; @@ -9,11 +10,19 @@ import java.util.HashMap; public class TunnelManager { - private final HashMap tunnels = new HashMap<>(); + private final HashMap tunnels = new HashMap<>(); private final 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; socket = new DatagramSocket(bindPort, bindHost); socket.connect(remoteHost, remotePort); } @@ -29,12 +38,7 @@ public class TunnelManager { e.printStackTrace(); } - new Thread(new Runnable() { - @Override - public void run() { - INSTANCE.listen(); - } - }).start(); + new Thread(() -> INSTANCE.listen()).start(); } } @@ -50,19 +54,21 @@ public class TunnelManager { } byte flags = packet.getData()[0]; - int vni_1 = packet.getData()[4]; + int vni = packet.getData()[6] + + packet.getData()[5] << 8 + + packet.getData()[4] << 16; if ((flags & 0x08) != 0x08) { continue; } - NetworkInterface iface = tunnels.get(vni); + TunnelInterface iface = tunnels.get(vni); if (iface != null) { byte[] inner = new byte[packet.getData().length - 8]; - copyBytes(packet.getData(), inner, 8, 0, packet.getData().length - 8); + System.arraycopy(packet.getData(), 8, inner, 0, packet.getData().length - 8); - iface.writeEthernetFrame(null, inner, 255); + iface.target.writeEthernetFrame(iface, inner, 255); } } } catch (IOException e) { @@ -74,9 +80,44 @@ public class TunnelManager { return INSTANCE; } - 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]; + public void sendToVti(int vti, byte[] payload) { + 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); + } + + public void registerVti(int vti, NetworkInterface iface) { + tunnels.put(vti, new TunnelInterface(vti, iface)); + } + + public void unregisterVti(int vti) { + tunnels.remove(vti); + } + + public class TunnelInterface implements NetworkInterface { + final NetworkInterface target; + private final int vti; + + public TunnelInterface(int vti, NetworkInterface iface) { + this.vti = vti; + this.target = iface; + } + + @Override + public byte[] readEthernetFrame() { + return new byte[0]; + } + + @Override + public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) { + TunnelManager.this.sendToVti(vti, frame); } } }