diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..363efb6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + cn.x47 + rip + 1.0-SNAPSHOT + + + 8 + 8 + UTF-8 + + + + + io.netty + netty-all + 4.1.68.Final + + + org.projectlombok + lombok + 1.18.34 + + + + diff --git a/src/main/java/cn/x47/RIPService.java b/src/main/java/cn/x47/RIPService.java new file mode 100644 index 0000000..3b51a6a --- /dev/null +++ b/src/main/java/cn/x47/RIPService.java @@ -0,0 +1,70 @@ +package cn.x47; + + +import cn.x47.config.Config; +import cn.x47.model.RIPEntry; +import cn.x47.model.RIPPacket; +import cn.x47.service.RIPClient; +import cn.x47.service.RIPServer; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public class RIPService { + + public static void main(String[] args) throws Exception { + // 启动服务器 + RIPServer server = new RIPServer(); + new Thread(() -> { + try { + server.start(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }).start(); + + // 启动客户端 + RIPClient client = new RIPClient(); + + // 定期发送路由更新 + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(() -> { + RIPPacket packet = createRipResponsePacket(Config.RIP_VERSION); + client.sendRipPacket(packet); + }, 0, 30, TimeUnit.SECONDS); + +// 主线程等待 + Thread.currentThread().join(); + } + + private static RIPPacket createRipResponsePacket(byte version) { + List entries = new ArrayList<>(); + + try { + // 示例:添加本地路由条目 + InetAddress localAddress = InetAddress.getByName("192.168.100.0"); + InetAddress subnetMask = InetAddress.getByName("255.255.255.0"); + InetAddress nextHop = InetAddress.getByName("192.168.123.45"); + + RIPEntry entry = new RIPEntry(); + entry.setAddressFamily((short) 2); // AF_INET + entry.setIpAddress(localAddress); + entry.setMetric(1); + + if (version == 2) { + entry.setRouteTag((short) 0); + entry.setSubnetMask(subnetMask); + entry.setNextHop(nextHop); + } + + entries.add(entry); + + } catch (Exception e) { + e.printStackTrace(); + } + + return new RIPPacket((byte) 2, version, entries); // Command=2 (Response) + } +} diff --git a/src/main/java/cn/x47/config/Config.java b/src/main/java/cn/x47/config/Config.java new file mode 100644 index 0000000..2340d09 --- /dev/null +++ b/src/main/java/cn/x47/config/Config.java @@ -0,0 +1,6 @@ +package cn.x47.config; + +public class Config { + // 设置协议版本,1 表示 RIP v1,2 表示 RIP v2 + public static final byte RIP_VERSION = 2; +} diff --git a/src/main/java/cn/x47/handle/RIPPacketDecoder.java b/src/main/java/cn/x47/handle/RIPPacketDecoder.java new file mode 100644 index 0000000..4184cf4 --- /dev/null +++ b/src/main/java/cn/x47/handle/RIPPacketDecoder.java @@ -0,0 +1,64 @@ +package cn.x47.handle; + + +import cn.x47.model.RIPEntry; +import cn.x47.model.RIPPacket; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.MessageToMessageDecoder; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +/** + * 解码器 + */ +public class RIPPacketDecoder extends MessageToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) throws Exception { + ByteBuf buf = packet.content(); + RIPPacket ripPacket = new RIPPacket(); + ripPacket.setCommand(buf.readByte()); + ripPacket.setVersion(buf.readByte()); + ripPacket.setUnused(buf.readShort()); + + List entries = new ArrayList<>(); + while (buf.readableBytes() >= 20) { + RIPEntry entry = new RIPEntry(); + entry.setAddressFamily((short) buf.readUnsignedShort()); + + if (ripPacket.getVersion() == 2) { + entry.setRouteTag((short) buf.readUnsignedShort()); + } else { + entry.setRouteTag((short) 0); + } + + byte[] ipBytes = new byte[4]; + buf.readBytes(ipBytes); + entry.setIpAddress(InetAddress.getByAddress(ipBytes)); + + if (ripPacket.getVersion() == 2) { + buf.readBytes(ipBytes); + entry.setSubnetMask(InetAddress.getByAddress(ipBytes)); + buf.readBytes(ipBytes); + entry.setNextHop(InetAddress.getByAddress(ipBytes)); + } else { + // 对于 RIP v1,子网掩码和下一跳需要推断或设为默认值 + entry.setSubnetMask(InetAddress.getByName("255.255.255.0")); // 示例,实际应根据 IP 类推断 + entry.setNextHop(packet.sender().getAddress()); + } + + long metric = buf.readUnsignedInt(); + entry.setMetric(metric); + + entries.add(entry); + } + ripPacket.setEntries(entries); + ripPacket.setSenderAddress(packet.sender()); // 设置发送者地址 + out.add(ripPacket); + } + +} diff --git a/src/main/java/cn/x47/handle/RIPPacketEncoder.java b/src/main/java/cn/x47/handle/RIPPacketEncoder.java new file mode 100644 index 0000000..c2a5be4 --- /dev/null +++ b/src/main/java/cn/x47/handle/RIPPacketEncoder.java @@ -0,0 +1,53 @@ +package cn.x47.handle; + + +import cn.x47.model.RIPEntry; +import cn.x47.model.RIPPacket; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.MessageToMessageEncoder; + +import java.net.InetSocketAddress; +import java.util.List; + +/** + * 编码器 + */ +public class RIPPacketEncoder extends MessageToMessageEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, RIPPacket msg, List out) throws Exception { + ByteBuf buf = ctx.alloc().buffer(); + // 编码命令和版本 + buf.writeByte(msg.getCommand()); + buf.writeByte(msg.getVersion()); + buf.writeShort(msg.getUnused()); // unused 字段 + + // 编码路由条目 + for (RIPEntry entry : msg.getEntries()) { + buf.writeShort(entry.getAddressFamily() & 0xFFFF); + if (msg.getVersion() == 2) { + buf.writeShort(entry.getRouteTag() & 0xFFFF); + } + buf.writeBytes(entry.getIpAddress().getAddress()); + + if (msg.getVersion() == 2) { + buf.writeBytes(entry.getSubnetMask().getAddress()); + buf.writeBytes(entry.getNextHop().getAddress()); + } + + buf.writeInt((int) entry.getMetric() & 0xFFFFFFFF); + } + + // 根据版本选择目标地址 + InetSocketAddress recipient; + if (msg.getVersion() == 1) { + recipient = new InetSocketAddress("255.255.255.255", 520); + } else { + recipient = new InetSocketAddress("224.0.0.9", 520); + } + + out.add(new DatagramPacket(buf, recipient)); + } + +} diff --git a/src/main/java/cn/x47/handle/RIPServerHandler.java b/src/main/java/cn/x47/handle/RIPServerHandler.java new file mode 100644 index 0000000..9a8d6b0 --- /dev/null +++ b/src/main/java/cn/x47/handle/RIPServerHandler.java @@ -0,0 +1,186 @@ +package cn.x47.handle; + +import cn.x47.model.RIPEntry; +import cn.x47.model.RIPPacket; +import cn.x47.service.RoutingTable; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +public class RIPServerHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, RIPPacket msg) throws Exception { + InetSocketAddress senderAddress = msg.getSenderAddress(); + byte version = msg.getVersion(); + System.out.println(msg.toString()); + +// if (msg.getCommand() == 2) { // Response +// for (RIPEntry entry : msg.getEntries()) { +// // 更新本地路由表 +// updateRoutingTable(entry, senderAddress, version); +// } +// } else if (msg.getCommand() == 1) { // Request +// // 处理请求,发送响应包 +// handleRequest(ctx, msg); +// } + } + + private void handleRequest(ChannelHandlerContext ctx, RIPPacket requestPacket) throws Exception { + List requestEntries = requestPacket.getEntries(); + byte version = requestPacket.getVersion(); + + boolean isGeneralRequest = false; + + // 检查是否为一般请求 + if (requestEntries.size() == 1) { + RIPEntry entry = requestEntries.get(0); + if (entry.getIpAddress().getHostAddress().equals("0.0.0.0") && entry.getMetric() == 16) { + isGeneralRequest = true; + } + } + + RIPPacket responsePacket = new RIPPacket(); + responsePacket.setCommand((byte) 2); // Response + responsePacket.setVersion(version); // 与请求的版本一致 + responsePacket.setUnused((short) 0); + List responseEntries = new ArrayList<>(); + + if (isGeneralRequest) { + // 一般请求,返回整个路由表 + responseEntries.addAll(getAllRoutingEntries(version)); + } else { + // 特定请求,逐一处理请求的条目 + for (RIPEntry requestEntry : requestEntries) { + RIPEntry route = getRoutingEntry(requestEntry.getIpAddress()); + if (route != null) { + responseEntries.add(route); + } else { + // 如果没有该路由,返回度量值为 16(无限) + RIPEntry unreachableEntry = new RIPEntry(); + unreachableEntry.setAddressFamily(requestEntry.getAddressFamily()); + unreachableEntry.setIpAddress(requestEntry.getIpAddress()); + unreachableEntry.setMetric(16); + + if (version == 2) { + unreachableEntry.setRouteTag((short) 0); + unreachableEntry.setSubnetMask(requestEntry.getSubnetMask()); + unreachableEntry.setNextHop(InetAddress.getByName("0.0.0.0")); + } + + responseEntries.add(unreachableEntry); + } + } + } + + // 构建响应包 + responsePacket.setEntries(responseEntries); + + // 获取发送者的地址 + InetSocketAddress recipient = requestPacket.getSenderAddress(); + + // 发送响应包 + ByteBuf buf = ctx.alloc().buffer().order(ByteOrder.BIG_ENDIAN); // 确保使用大端序 + encodeRIPPacket(responsePacket, buf); + + DatagramPacket responseDatagram = new DatagramPacket(buf, recipient); + ctx.writeAndFlush(responseDatagram); + } + + private void encodeRIPPacket(RIPPacket msg, ByteBuf buf) throws Exception { + // 编码命令和版本 + buf.writeByte(msg.getCommand()); + buf.writeByte(msg.getVersion()); + buf.writeShort(0); // unused 字段 + + // 编码路由条目 + for (RIPEntry entry : msg.getEntries()) { + buf.writeShort(entry.getAddressFamily()); + if (msg.getVersion() == 2) { + buf.writeShort(entry.getRouteTag()); + } + buf.writeBytes(entry.getIpAddress().getAddress()); + + if (msg.getVersion() == 2) { + buf.writeBytes(entry.getSubnetMask().getAddress()); + buf.writeBytes(entry.getNextHop().getAddress()); + } + buf.writeInt((int) (entry.getMetric() & 0xFFFFFFFFL)); + } + } + + private List getAllRoutingEntries(byte version) throws Exception { + List entries = new ArrayList<>(); + // 从路由表中获取所有的路由条目 + for (RIPEntry route : RoutingTable.getInstance().getRoutes().values()) { + RIPEntry entry = new RIPEntry(); + entry.setAddressFamily(route.getAddressFamily()); + entry.setIpAddress(route.getIpAddress()); + entry.setMetric(route.getMetric()); + + if (version == 2) { + entry.setRouteTag(route.getRouteTag()); + entry.setSubnetMask(route.getSubnetMask()); + entry.setNextHop(route.getNextHop()); + } + + entries.add(entry); + } + return entries; + } + + private RIPEntry getRoutingEntry(InetAddress ipAddress) throws Exception { + RIPEntry route = RoutingTable.getInstance().getRoute(ipAddress); + if (route != null) { + RIPEntry entry = new RIPEntry(); + entry.setAddressFamily(route.getAddressFamily()); + entry.setIpAddress(route.getIpAddress()); + entry.setMetric(route.getMetric()); + + entry.setRouteTag(route.getRouteTag()); + entry.setSubnetMask(route.getSubnetMask()); + entry.setNextHop(route.getNextHop()); + + return entry; + } + return null; + } + + private void updateRoutingTable(RIPEntry entry, InetSocketAddress senderAddress, byte version) throws Exception { + if (version == 1) { + // 推断下一跳为发送者的地址 + entry.setNextHop(senderAddress.getAddress()); + // 根据 IP 地址类别推断子网掩码 + InetAddress ipAddress = entry.getIpAddress(); + byte[] addressBytes = ipAddress.getAddress(); + int firstOctet = addressBytes[0] & 0xFF; + + if (firstOctet >= 0 && firstOctet <= 127) { + // A 类地址 + entry.setSubnetMask(InetAddress.getByName("255.0.0.0")); + } else if (firstOctet >= 128 && firstOctet <= 191) { + // B 类地址 + entry.setSubnetMask(InetAddress.getByName("255.255.0.0")); + } else if (firstOctet >= 192 && firstOctet <= 223) { + // C 类地址 + entry.setSubnetMask(InetAddress.getByName("255.255.255.0")); + } else { + // 非法地址,忽略 + System.out.println("Invalid IP address in RIP Entry: " + ipAddress.getHostAddress()); + return; + } + } + // 实现路由表更新逻辑 + RoutingTable.getInstance().updateRoute(entry); + System.out.println("Updated route: " + entry.getIpAddress().getHostAddress() + + " via " + entry.getNextHop().getHostAddress() + + " metric: " + entry.getMetric()); + } +} diff --git a/src/main/java/cn/x47/model/RIPEntry.java b/src/main/java/cn/x47/model/RIPEntry.java new file mode 100644 index 0000000..2636d41 --- /dev/null +++ b/src/main/java/cn/x47/model/RIPEntry.java @@ -0,0 +1,17 @@ +package cn.x47.model; + +import lombok.Data; + +import java.net.InetAddress; + +@Data +public class RIPEntry { + private short addressFamily; + private short routeTag; // RIP v2 特有 + private InetAddress ipAddress; + private InetAddress subnetMask; // RIP v2 特有 + private InetAddress nextHop; // RIP v2 特有 + private long metric; + + +} diff --git a/src/main/java/cn/x47/model/RIPPacket.java b/src/main/java/cn/x47/model/RIPPacket.java new file mode 100644 index 0000000..9c71f2f --- /dev/null +++ b/src/main/java/cn/x47/model/RIPPacket.java @@ -0,0 +1,28 @@ +package cn.x47.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.net.InetSocketAddress; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RIPPacket { + private byte command; // 1=Request, 2=Response + private byte version; // 1=RIP v1, 2=RIP v2 + private short unused = 0; // 未使用,设置为 0 + private List entries; + private InetSocketAddress senderAddress; + + // 添加构造方法和 Getter/Setter + public RIPPacket(byte command, byte version, List entries) { + this.command = command; + this.version = version; + this.unused = 0; + this.entries = entries; + } + +} diff --git a/src/main/java/cn/x47/service/RIPClient.java b/src/main/java/cn/x47/service/RIPClient.java new file mode 100644 index 0000000..69c4f5d --- /dev/null +++ b/src/main/java/cn/x47/service/RIPClient.java @@ -0,0 +1,47 @@ +package cn.x47.service; + + +import cn.x47.handle.RIPPacketEncoder; +import cn.x47.model.RIPPacket; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.DatagramChannel; + +import io.netty.channel.nio.NioEventLoopGroup; + + + +public class RIPClient { + + private final Bootstrap bootstrap; + private final NioEventLoopGroup group; + + public RIPClient() { + group = new NioEventLoopGroup(); + bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioDatagramChannel.class) + .option(ChannelOption.SO_BROADCAST, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(DatagramChannel ch) throws Exception { + ch.pipeline().addLast(new RIPPacketEncoder()); + } + }); + } + + public void sendRipPacket(RIPPacket packet) { + try { + ChannelFuture future = bootstrap.bind(0).sync(); + future.channel().writeAndFlush(packet).sync(); + future.channel().close(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void shutdown() { + group.shutdownGracefully(); + } +} diff --git a/src/main/java/cn/x47/service/RIPServer.java b/src/main/java/cn/x47/service/RIPServer.java new file mode 100644 index 0000000..1feec9d --- /dev/null +++ b/src/main/java/cn/x47/service/RIPServer.java @@ -0,0 +1,34 @@ +package cn.x47.service; + + +import cn.x47.handle.RIPPacketDecoder; +import cn.x47.handle.RIPServerHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; + +public class RIPServer { + + public void start() throws InterruptedException { + NioEventLoopGroup group = new NioEventLoopGroup(); + try { + Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioDatagramChannel.class) + .option(ChannelOption.SO_BROADCAST, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(DatagramChannel ch) throws Exception { + ch.pipeline().addLast(new RIPPacketDecoder(), new RIPServerHandler()); + } + }); + + ChannelFuture future = b.bind(520).sync(); + future.channel().closeFuture().await(); + } finally { + group.shutdownGracefully(); + } + } +} diff --git a/src/main/java/cn/x47/service/RoutingTable.java b/src/main/java/cn/x47/service/RoutingTable.java new file mode 100644 index 0000000..96e1606 --- /dev/null +++ b/src/main/java/cn/x47/service/RoutingTable.java @@ -0,0 +1,38 @@ +package cn.x47.service; + + +import cn.x47.model.RIPEntry; + +import java.net.InetAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class RoutingTable { + private static RoutingTable instance = new RoutingTable(); + private Map routes; + + private RoutingTable() { + routes = new ConcurrentHashMap<>(); + } + + public static RoutingTable getInstance() { + return instance; + } + + public void updateRoute(RIPEntry entry) { + // 简单地将新路由添加或更新到路由表中 + routes.put(entry.getIpAddress(), entry); + } + + public void removeRoute(InetAddress ipAddress) { + routes.remove(ipAddress); + } + + public Map getRoutes() { + return routes; + } + + public RIPEntry getRoute(InetAddress ipAddress) { + return routes.get(ipAddress); + } +}