java实现基于snmp的网络拓扑发现

背景

目前手上只有二层交换机且支持snmp协议,故先实现子网内的网络拓扑发现,等有了三层交换机后再补充全网络的拓扑发现,至于发现的方式也是我看了一些资料以后用自己觉得可行且较方便的方式实现了出来,如果有什么不对的或者有更好的方法希望大家能够指正分享。

实现

需要了解的一些知识和工具:有snmp(Simple Network Manage Protocol)协议的概念,snmp4j框架,jpcap框架
下面是我实际测试的一个网络拓扑示意图

获取主机的IP和子网掩码
这里用的jpcap里的方法,我们可以获取一个带ipv4且不是回环地址(就是我们通常的127.0.0.1)的IP地址和这块网卡上的子网掩码,如果主机上有多块网卡也没关系(多块网卡在同一子网无需重复,在不同子网的话超出了本文范畴,以后会更新)
// 获取本机上的网络接口对象数组  
            final NetworkInterface[] devices = JpcapCaptor.getDeviceList();  
//获取本机第一个有ipv4网址且不是回环地址的网络接口
            for (NetworkInterface nc : devices) {   
                // 一块卡上可能有多个地址:  
                if(nc.addresses.length > 0 && !nc.loopback){
                    for (int t = 0; t < nc.addresses.length; t++) {  
                    	if(nc.addresses[t].address instanceof Inet4Address){
                    		currentDevice = nc;  //这个就是需要获取的网络接口对象,在这里可以通过它获取ip和子网掩码如nc.addresses[t].address.getHostAddress()  nc.addresses[t].subnet.getAddress()
                    		return nc.addresses[t];
                    	}
                    }  
                }
            }  

根据主机IP和子网掩码算出子网的IP范围
这里就不贴源码了,需要对IP地址和子网掩码有所了解,可以通过刚刚获取的IP和子网掩码做按位与,获得IP地址里的网络号,剩下的位就代表的主机号,主机号的范围就是子网IP的范围。

通过arp请求获取所有子网在线设备
根据上一步获取的子网IP范围,挨个发送arp请求,如果有回应的话则存储起来,获得一个IP,MAC地址的map,下面贴下jpcap发送arp请求的官方例子,在jpcap下载下来的sample文件夹下面都有,我稍微修改加了点注释。
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Arrays;

import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;
import jpcap.packet.ARPPacket;
import jpcap.packet.EthernetPacket;

public class ARP {
	public static byte[] arp(InetAddress ip) throws java.io.IOException{
		//find network interface
		//获取网络接口列表
		NetworkInterface[] devices=JpcapCaptor.getDeviceList();
		NetworkInterface device=null;

loop:	for(NetworkInterface d:devices){
			for(NetworkInterfaceAddress addr:d.addresses){
				if(!(addr.address instanceof Inet4Address)) continue;
				byte[] bip=ip.getAddress();
				byte[] subnet=addr.subnet.getAddress();
				byte[] bif=addr.address.getAddress();
				for(int i=0;i<4;i++){
					bip[i]=(byte)(bip[i]&subnet[i]);
					bif[i]=(byte)(bif[i]&subnet[i]);
				}
				if(Arrays.equals(bip,bif)){
					device=d;
					break loop;
				}
			}
		}
		
//		device.addresses.
//		System.out.println("");
		
		if(device==null)
			throw new IllegalArgumentException(ip+" is not a local address");
		
		/**
		 * 打开指定的网络接口,并返回实例
		 * 2000:一次抓取的最大bytes
		 * false: If true, the inferface becomes promiscuous mode
		 * 3000:超时时间,并不是所有的平台都支持这个参数,如果不支持这个参数被忽略,如果支持的话只要不超时,Jpcap就会一直等待足够的数据到达
		 */
		//JpcapCaptor:是用来抓包、读取包数据,通过libpcap的抓包文件
		JpcapCaptor captor=JpcapCaptor.openDevice(device,2000,false,100);
		//"arp":condition - a string representation of the filter   true:optimize - If true, the filter is optimized
		captor.setFilter("arp",true);
		//根据名字应该是发送包的对象
		JpcapSender sender=captor.getJpcapSenderInstance();
		
		InetAddress srcip=null;
		//一个网络接口可能有多个地址,我们选取一个ipv4地址进行发送
		for(NetworkInterfaceAddress addr:device.addresses)
			if(addr.address instanceof Inet4Address){
				srcip=addr.address;
				break;
			}
		//广播地址
		byte[] broadcast=new byte[]{(byte)255,(byte)255,(byte)255,(byte)255,(byte)255,(byte)255};
		//是在jpcap.packet里面的一个类,里面还有其他的一些类,从这里也能了解到jpcap支持哪些协议的发送,这里的是用来处理arp和rarp的类
		ARPPacket arp=new ARPPacket();
		//硬件类型 从字面上看是以太网类型
		arp.hardtype=ARPPacket.HARDTYPE_ETHER;
		//协议类型:ip
		arp.prototype=ARPPacket.PROTOTYPE_IP;
		//arp请求
		arp.operation=ARPPacket.ARP_REQUEST;
		//mac地址长度
		arp.hlen=6;
		//ip长度
		arp.plen=4;
		
		//源和目的的mac地址、ip地址
		arp.sender_hardaddr=device.mac_address;
		arp.sender_protoaddr=srcip.getAddress();
		arp.target_hardaddr=broadcast;
		arp.target_protoaddr=ip.getAddress();
		
		//以太网数据包,可以看到这里只需要源和目的mac地址,是处在数据链路层的,后面也指定了arp的数据链路层
		EthernetPacket ether=new EthernetPacket();
		ether.frametype=EthernetPacket.ETHERTYPE_ARP;
		ether.src_mac=device.mac_address;
		ether.dst_mac=broadcast;
		arp.datalink=ether;
		
		//下面就是发送arp和接收了
		sender.sendPacket(arp);
//		System.out.println(System.currentTimeMillis());
		while(true){
			ARPPacket p=(ARPPacket)captor.getPacket();
//			System.out.println(System.currentTimeMillis());
			if(p==null){
				throw new IllegalArgumentException(ip+" is not a local address");
			}
			if(Arrays.equals(p.target_protoaddr,srcip.getAddress())){
				return p.sender_hardaddr;
			}
		}
	}
		
	public static String getMacAddress(byte[] mac){
	    if (mac == null)
	        return null;

	    StringBuilder sb = new StringBuilder(18);
	    for (byte b : mac) {
	        if (sb.length() > 0)
	            sb.append(':');
	        sb.append(String.format("%02x", b));
	    }
	    return sb.toString();
	}
	
	public static void main(String[] args) throws Exception{
		
		byte[] mac=ARP.arp(InetAddress.getByName("192.168.10.196"));
		String strMac = getMacAddress(mac);
		System.out.println(strMac);
		System.exit(0);
		
	}
}

获取子网内交换机列表
这里需要说一下,因为我获取的是某个指定型号的交换机,只需要在上面获取的在线设备的mac地址中判断mac地址的前三位即可,更通用的方法可以用snmp4j测试刚刚获取的在线设备是否支持snmp(有些非交换机设备可能也支持snmp,如某些安装了snmp的计算机,这个可以进一步获取交换机特有的snmp中的mib信息加以区分)

获取主机默认网关的ip和mac地址,以默认网关作为子网的根节点
这里查过一些,java获取的一般方法无非就是在windows下通过执行ipconfig,linux下读配置文件,总感觉不太通用(windows不同的操作系统截取默认网关的地方好像还不同),总想找一个各个平台通用的方法,不过貌似java对这个的支持还不够,后来在看jpcap的sample的时候发现了一个可行方法,前提是需要能够访问外网
//通过访问外网(此处为百度)获取默认网关的mac地址
	public String getGatewayMac(){
		try {
			JpcapCaptor captor = JpcapCaptor.openDevice(currentDevice, 2000, false, 1000);
			// obtain MAC address of the default gateway
			InetAddress pingAddr = InetAddress.getByName("www.baidu.com");
			captor.setFilter("tcp and dst host " + pingAddr.getHostAddress(), true);
			for(int i = 0;i<5;i++){
				new URL("http://www.baidu.com").openStream().close();
				Packet ping = captor.getPacket();
				if (ping == null) {
					System.out.println("[CommonTool_getGatewayMac] getGatewayMac cannot obtain MAC address of default gateway.");
					continue;
				} else if (Arrays.equals(((EthernetPacket) ping.datalink).dst_mac,currentDevice.mac_address))
					continue;
				return ARPTool.getMacAddress(((EthernetPacket) ping.datalink).dst_mac);
			}
		} catch (IOException e) {
			System.out.println("[CommonTool_getGatewayMac] getGatewayMac openDevice failed");
		}
		return null;
	}
这里是获取网关mac地址的方法,再通过mac地址在在线设备中找到对应的ip。这里应该是通过访问不在同一网段的网址,计算机会把数据直接丢到默认网关,通过解析默认网关的回应回去mac地址。
 
通过snmp获取每个交换机记忆的需要转发的mac地址表
因为是二层交换机,即工作在数据链路层的交换机,我们知道三层是IP层,二层不需要ip直接通过设备的mac地址进行通讯,所以二层交换机需要记下每个端口下设备对应的mac地址,这样才能将数据帧准确送到目的mac地址。有兴趣的话可以google一下交换机的工作原理。而我们通过snmp就可以获得交换机每个端口下记忆的mac地址。这里需要注意一下,交换机接入局域网的那个端口会记忆除了交换机其他端口记忆设备以外的所有子网设备mac地址,我们可以通过是否包含默认网关mac地址来确认哪个端口是接入子网的端口,因为我们发现拓扑只需要知道交换机下面挂的所有设备即可,故可以直接抛弃接入子网那个端口记忆的mac地址。下面贴一下通过snmp4j获取我们需要数据的方法
/**
	 * 获取snmp的mib数据库的 dot1qTpFdbTable 的数据 通过 bulk方式
	 *  "1.3.6.1.2.1.17.7.1.2.2" 这个是获取节点的起始路径 
	 * if arrive dot1qTpFdbStatus return detail see snmp mib
	 * @param targetIp target snmp agent server ip
	 * @param oid 
	 * @return last result's oid
	 */
	public String snmpBulkFdbTable(String targetIp,String oid) {
		
		String lastOid = null;
		Snmp snmp = null;

		try {
			snmp = new Snmp(new DefaultUdpTransportMapping());
			snmp.listen();

			CommunityTarget target = new CommunityTarget();
			target.setCommunity(new OctetString("public"));
			target.setVersion(SnmpConstants.version2c);
			target.setAddress(new UdpAddress(targetIp + "/161"));
			target.setTimeout(3000); // 3s
			target.setRetries(2);

			PDU pdu = new PDU();
			pdu.setType(PDU.GETBULK);
			pdu.setMaxRepetitions(200);
			pdu.setNonRepeaters(0);

			pdu.add(new VariableBinding(new OID(oid)));
			ResponseEvent responseEvent = snmp.send(pdu, target);
			PDU response = responseEvent.getResponse();
			
			if (response == null) {
				System.out.println("[SNMPTool_snmpBulkFdbTable] TimeOut... targetIp:"+targetIp);
			} else {
				if (response.getErrorStatus() == PDU.noError) {
					Vector<? extends VariableBinding> vbs = response.getVariableBindings();
					for (VariableBinding vb : vbs) {
						int dataType = vb.getOid().get(12);
						//dot1qTpFdbAddress
						if(dataType == 1){
							switchLearnedMac.add(vb.getVariable().toString());
						}
						//dot1qTpFdbPort
						else if(vb.getOid().get(12) == 2){
							switchMacPort.add(vb.getVariable().toInt());
						}
						//arrive dot1qTpFdbStatus return
						else{
							return "finish";
						}
//						System.out.println("oid:"+vb.getOid()+"  varilable:"+vb.getVariable());
					}
					lastOid = vbs.get(vbs.size()-1).getOid().toString();
				} else {
					System.out.println("[SNMPTool_snmpBulkFdbTable] targetIp"+targetIp+" Error:" + response.getErrorStatusText());
				}
			}
			snmp.close();
		} catch (IOException e) {
			System.out.println("[SNMPTool_snmpBulkFdbTable] snmpBulkFdbTable targetIp "+targetIp+" IOException "+e.getMessage());
		}
		return lastOid;
	}
方法怎么调用可以看下说明(因为想要使用系统1024以下的端口必须要有root权限,所以我为方便测试直接sudo启动的eclipse,导致我的输入法不能用了,就用了蹩脚的英文注释了一下,至于怎么解决输入法问题留着我下一篇写吧)。我们可以通过判断是否返回字符串“finish”来判断是否获取完毕,如果不是“finish”的话我们可以用返回的lashOid继续调用这个方法,直到返回“finish”。还有要注意一下这个用的类型是pdu.setType(PDU.GETBULK),这样一次能获取到尽量多的数据,具体可以看下snmp协议。

根据上面获取到的数据还原出整个子网的拓扑结构
上面的种种步骤无法是为了最后一步做准备,下面是我给出的算法,希望大家能讨论下是否还有更好的算法,或者能有什么地方可以改进
还是以开头的拓扑结构作为说明

由于交换机194,196,198的第5个端口都包含默认网关的mac地址故不需要这个端口的mac地址表,现获取的每个交换机的mac地址转发表如下(为了直观,mac地址用相应的ip地址代替)



可以看出上级交换机包含下级交换机及其下挂设备,所以上级交换机包含的设备一定大于下级交换机,可以得出目前下挂设备最多的交换机一定是跟默认网关直连的,然后我们可以继续对下挂设备最多的交换机进行处理,例子中就是192.168.10.196交换机,以196作为父节点,查看下挂设备是否包含交换机,如果包含的话找出下挂设备最多的交换机,继续上述计算,这里就用到了递归,一旦没有了下挂设备,则达到了递归的出口。每次找出最大交换机后,将其从子网在线交换机IP-MAC地址表中删除,这样下次找最大下挂设备交换机时就不用处理已经处理过的交换机了。还有一个需要注意的是由于上级交换机下挂设备包含下级交换机的下挂设备,所有每处理完一个下挂设备后需要记录,这样处理上级的时候判断是否已处理过,就会避免重复处理同一个下挂设备。表达能力有限,再贴个计算过程的动态图


最后为了大家方便测试讨论最后的算法,我贴个我做测试用的类(前面获取数据的步骤全部省略,直接自己凑的数据,注意一下凑的数据一定要符合上面的规则)

测试类

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.yanxw.object.NetworkDevice;
import com.yanxw.object.TreeNode;

public class SimulationData {
	//子网内设备IP与设备信息的map
		private static Map<String,NetworkDevice> subnetIpDevices = new LinkedHashMap<>();
		//子网内设备mac与设备信息的map
		private static Map<String,NetworkDevice> subnetMacDevices = new LinkedHashMap<>();
		//子网内交换机ip与交换机信息的map
		private static Map<String,NetworkDevice> subnetIpSwitchs = new LinkedHashMap<>();
		//子网内交换机mac与交换机信息的map
		private static Map<String,NetworkDevice> subnetMacSwitchs = new LinkedHashMap<>();
		//需要用来展示拓扑结构树的节点信息
		private static TreeNode[] nodes;
		//全局变量TreeNode数组的索引
		private static int nodeIndex = 1;
		
		//网关IP
		private static String gatewayIp = "192.168.10.1";
		private static float gatewayId;
		
		//Map<IP(交换机的IP地址),Set<Mac>(交换机除了接入网络的其他端口需要转发的mac地址)> 用于全局存储所有子网内交换机对应的mac地址
		private static Map<String,Set<String>> subnetSwitchsNextMacs;
		//已经生成节点信息的计算机(type = 3)节点的set 利用这个来防止重复生成计算机节点
		private static Set<String> hasNodeComputerMac = new HashSet<>();
		
		public static void main(String[] args) {
			//--------------------------------------用于测试用的数据的构造 start------------------------------------//
			subnetIpDevices.put("192.168.10.1", new NetworkDevice("00:23:cd:87:b6:18", "192.168.10.1", 1));
			
			subnetIpDevices.put("192.168.10.14", new NetworkDevice("ff:ff:ff:87:b6:14", "192.168.10.14", 3));
			subnetIpDevices.put("192.168.10.15", new NetworkDevice("ff:ff:ff:87:b6:15", "192.168.10.15", 3));
			subnetIpDevices.put("192.168.10.16", new NetworkDevice("ff:ff:ff:87:b6:16", "192.168.10.16", 3));
			subnetIpDevices.put("192.168.10.17", new NetworkDevice("ff:ff:ff:87:b6:17", "192.168.10.17", 3));

			subnetIpDevices.put("192.168.10.89", new NetworkDevice("f0:8a:28:00:03:89", "192.168.10.89", 2));
			subnetIpDevices.put("192.168.10.196", new NetworkDevice("f0:8a:28:00:03:96", "192.168.10.196", 2));
			subnetIpDevices.put("192.168.10.198", new NetworkDevice("f0:8a:28:00:03:98", "192.168.10.198", 2));
			

			
			//init subnetMacDevices
			for(Entry<String, NetworkDevice> entry : subnetIpDevices.entrySet()){
				subnetMacDevices.put(entry.getValue().getMacAddress(), entry.getValue());
			}
			//init subnetIpSwitchs
			for(Entry<String, NetworkDevice> entry : subnetIpDevices.entrySet()){
				if(entry.getValue().getDeviceType() == 2){
					subnetIpSwitchs.put(entry.getKey(), entry.getValue());
				}
			}
			
			//init subnetMacSwitchs
			for(Entry<String, NetworkDevice> entry : subnetMacDevices.entrySet()){
				if(entry.getValue().getDeviceType() == 2){
					subnetMacSwitchs.put(entry.getKey(), entry.getValue());
				}
			}
			
			//
			
//			check();
			//根据子网在线设备数初始化TreeNode数组
			nodes = new TreeNode[subnetIpDevices.size()];
			
			subnetSwitchsNextMacs = new HashMap<>();
			//get subnet switchs port mac set
			for(Entry<String, NetworkDevice> entry : subnetIpSwitchs.entrySet()){
				
				Set<String> set = getSwitchPortMac(entry.getKey());
				subnetSwitchsNextMacs.put(entry.getKey(), set);
				
			}
			
			nodes[0] = new TreeNode(gatewayId, 0, gatewayIp, true, "../../../css/zTreeStyle/img/diy/gateway1.png");
			
			//--------------------------------------用于测试用的数据的构造 end------------------------------------//
			
//			checkSwitchMac(switchsNextMacs);
			
			//循环生成拓扑结构树,每生成一个分支,就把该分支删除掉,直到将网关下所有分支全部生成完毕
			while(subnetSwitchsNextMacs.size() > 0){
				generateNodeTree(gatewayIp,subnetSwitchsNextMacs);
			}
			
			//展示生成的节点,这里的数据就可以用来生成树需要的json数据了,用类似于gson的工具可以直接生成
			for(TreeNode node : nodes){
				try{
					System.out.println("id:"+node.getId()+"  pId:"+node.getpId()+"  isOpen:"+node.isOpen());
				}catch (Exception e){
					break;
				}
			}
			
//			for(Entry<String, Set<String>> entry : subnetSwitchsNextMacs.entrySet()){
//				System.out.println(entry.getKey());
//			}
			
			
		}
		
		/**
		 * 这个方法采用了递归
		 * 我们是通过snmp获取的交换机下挂设备mac地址,获取的下挂设备的mac地址集合包括所有的直连设备(包括交换机和其他设备)和直连设备下挂设备的mac地址的集合
		 * 给上面的话举个例子交换机1下挂交换机2、3,和设备a,2下挂设备b,则1下挂设备2、3、a、b,2下挂设备b,3则没有下挂设备,如果设备b也是交换机,
		 * 下挂了c、d,则c和d既属于1,也属于2
		 * 这里也可以看出父节点的下挂设备包含了子节点和子节点的下挂设备,所有父节点下挂设备一定比子节点下挂设备多
		 * @param parentIp 父节点的ip(父节点可以是网关或交换机)
		 * @param switchsNextMacs 父节点下的所有支持snmp的交换机及交换机下挂的设备的mac  地址Map<String(交换机IP),Set<String>(交换机下挂设备的mac地址集合)>
		 * 
		 * 基于上面的限定,我们首先要得出一个结论,即拥有最多下挂设备的交换机一定是parentIp的直接子节点,
		 * 这个可以反证一个,如果不是直接子节点的话那么它和parentIp直接一定还有其他交换机,即它的父节点,但是根据上面的限定,子节点的下挂设备不可能大于父节点的下挂设备
		 * 
		 * 首先以默认网关为父节点,找到下挂设备最多的交换机,这样这个交换机就可以确定是跟默认网关直连的了,然后再看这个交换机下的所有下挂设备是否包含交换机,如果包含则
		 * 将这个交换机作为parentIp,将所有下挂的交换机作为switchsNextMacs,再次调用自身,直到没有下挂交换机为止
		 */
		private static void generateNodeTree(String parentIp,Map<String,Set<String>> switchsNextMacs){
			//找出所有交换机中下挂设备最多的一个交换机
			String maxSwitchIp = getNextMaxSwitchIp(switchsNextMacs);
			
			Set<String> parentSet = null;
			if(subnetSwitchsNextMacs.containsKey(parentIp)){
				//获取当前处理的交换机的父设备的所有下挂设备
				parentSet = subnetSwitchsNextMacs.get(parentIp);
				//默认网关不支持snmp,无法获取所有下挂设备
				if(null != parentSet){
					//将当前处理的交换机从父设备的下挂设备中剔除,直到剔除干净,才算处理完了父设备
					//@@标记A
					parentSet.remove(subnetIpDevices.get(maxSwitchIp).getMacAddress());
				}
			}
			//利用上面结论,这个下挂设备最多的交换机是parentIp直连的,生成节点数据
			nodes[nodeIndex] = new TreeNode(getDeviceIp(maxSwitchIp), getDeviceIp(parentIp), maxSwitchIp, true, "../../../css/zTreeStyle/img/diy/switch1.png");
			nodeIndex++;
			
			//获取当前处理的有最多下挂设备的交换机的所有下挂设备
			Set<String> nextMacs = subnetSwitchsNextMacs.get(maxSwitchIp);
			
			//<ip,Set<mac>> 用于存储当前处理的有最多下挂设备的交换机的下挂交换机
			Map<String,Set<String>> nextMap = new HashMap<>();
			
			for(String mac : nextMacs){
				if(subnetMacDevices.containsKey(mac) && subnetMacDevices.get(mac).getDeviceType() == 2){
					String switchIp = subnetMacDevices.get(mac).getIpAddress();
					nextMap.put(switchIp,switchsNextMacs.get(switchIp));
					//把是当前处理的有最多下挂设备的交换机的所有下挂交换机从它的父交换机的下挂设备中清除,
					//这样当回到处理父交换机时就不用再处理这些肯定不是跟她直连的交换机了
					if(null != parentSet){
						parentSet.remove(mac);
					}
				}
			}

			while(nextMap.size() > 0){
//				System.out.println("maxSwitchIp:"+maxSwitchIp+"  nextMap size:"+nextMap.size());
				//当下挂交换机的数量大于0时递归调用自己
				generateNodeTree(maxSwitchIp,nextMap);
				
				//调用完成后重新获取当前处理交换机的下挂交换机,如果还大于0继续
				//交换机的剔除是在@@标记A处调用的
				nextMap.clear();

				for(String mac : nextMacs){
					if(subnetMacDevices.containsKey(mac) && subnetMacDevices.get(mac).getDeviceType() == 2){
						String switchIp = subnetMacDevices.get(mac).getIpAddress();
						nextMap.put(switchIp,switchsNextMacs.get(switchIp));
					}
				}
			}

			//进入到这里代表当前处理的交换机已经没有下挂交换机了,可以把其他的下挂设备生成节点数据了,
			//要注意的是:因为父节点包含子节点和子节点的所有子节点,所有有些子节点的下挂设备已经生成过了,存储在hasNodeComputerMac里面的
			//把子节点的下挂设备剔除掉就是当前交换机自己的下挂设备啦
			for(String mac : nextMacs){
				if(subnetMacDevices.containsKey(mac) && !hasNodeComputerMac.contains(mac)){
					String computerIp = subnetMacDevices.get(mac).getIpAddress();;
					nodes[nodeIndex] = new TreeNode(getDeviceIp(computerIp), getDeviceIp(maxSwitchIp), computerIp, "../../../css/zTreeStyle/img/diy/computer1.png");
					hasNodeComputerMac.add(mac);
					nodeIndex++;
				}
			}
			
			//最后把自己从需要处理的交换机集合中剔除掉就行了
			subnetSwitchsNextMacs.remove(maxSwitchIp);
		}
		
		
		//找出所有交换机中下挂设备最多的一个交换机
		private static String getNextMaxSwitchIp(Map<String,Set<String>> switchsNextMacs){
			int maxCount = -1;
			String maxSwitchIp = null;
			for(Entry<String, Set<String>> entry : switchsNextMacs.entrySet()){
				if(entry.getValue().size() > maxCount){
					maxCount = entry.getValue().size();
					maxSwitchIp = entry.getKey();
				}
			}
			return maxSwitchIp;
		}
		
		private static Set<String> getSwitchPortMac(String targetIp){
			
			Set<String> result = new HashSet<>();
			switch (targetIp) {
			case "192.168.10.89":
				result.add("ff:ff:ff:87:b6:14");
				result.add("ff:ff:ff:87:b6:15");
				break;
			case "192.168.10.196":
				result.add("ff:ff:ff:87:b6:16");
				result.add("ff:ff:ff:87:b6:17");
				result.add("f0:8a:28:00:03:98");
				break;
			case "192.168.10.198":
				result.add("ff:ff:ff:87:b6:16");
				break;
			default:
				break;
			}
			
			return result;
			
		}
		
		public static float getDeviceIp(String deviceIp){
			String[] strs = deviceIp.split("\\.");
			float f = Float.parseFloat(strs[3]);
			return f;
		}
}

NetworkDevice类

public class NetworkDevice {

	private String macAddress;
	private String ipAddress;
	//1:default gateway  2:switch  3:computer
	private int deviceType;
	
	public NetworkDevice(){
		
	}
	
	public NetworkDevice(String macAddress, String ipAddress) {
		super();
		this.macAddress = macAddress;
		this.ipAddress = ipAddress;
	}
	
	public NetworkDevice(String macAddress, String ipAddress, int type){
		super();
		this.macAddress = macAddress;
		this.ipAddress = ipAddress;
		this.deviceType = type;
	}
	
	public String getMacAddress() {
		return macAddress;
	}
	public void setMacAddress(String macAddress) {
		this.macAddress = macAddress;
	}
	public String getIpAddress() {
		return ipAddress;
	}
	public void setIpAddress(String ipAddress) {
		this.ipAddress = ipAddress;
	}

	public int getDeviceType() {
		return deviceType;
	}

	public void setDeviceType(int deviceType) {
		this.deviceType = deviceType;
	}
	
	
}

TreeNode类(这个是为了展示用的,我为了方便用的zTree做的展示)

public class TreeNode {
	
	private float id;
	private float pId;
	private String name;
	private boolean open;
	private String icon;
	public TreeNode(){
		
	}
	
	
	public TreeNode(float id, float pId, String name, String icon) {
		super();
		this.id = id;
		this.pId = pId;
		this.name = name;
		this.icon = icon;
	}


	public TreeNode(float id, float pId, String name, boolean open, String icon) {
		super();
		this.id = id;
		this.pId = pId;
		this.name = name;
		this.open = open;
		this.icon = icon;
	}


	public float getId() {
		return id;
	}
	public void setId(float id) {
		this.id = id;
	}
	public float getpId() {
		return pId;
	}
	public void setpId(float pId) {
		this.pId = pId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public boolean isOpen() {
		return open;
	}
	public void setOpen(boolean open) {
		this.open = open;
	}
	public String getIcon() {
		return icon;
	}
	public void setIcon(String icon) {
		this.icon = icon;
	}
	
	
	
}

最后大家可以根据测试类最后打印的结果得到一棵树,以此作为判断是否成功,最后贴一张在测试网络中发现的拓扑图,用的zTree做的展示



结语

到这里就算把子网内的拓扑发现讲完了,在实现的过程中也碰到过一些问题,更发现有更多的网络知识需要掌握,后面我会把遇到的问题整理出来,还有因为这个程序是要给一位用c#的同事调用的,所以后面也会讲把java通过ikvm打包成dll供c#调用(网上查的好多说不能包含第三方jar包或者调用native方法的,实践下来是可以的)。
以上这些过程都是我闭门造车出来的,难免有理解不当或错误的地方,全部写完也发现占用了很大的篇幅,其实内容就提纲的那么几步,希望大家能有耐心看完,最好能够讨论指正。

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值