diff --git a/sim_ether.c b/sim_ether.c index 2de2564f..3cc40ace 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -1721,6 +1721,66 @@ char mac_string[32]; eth_mac_fmt(mac, mac_string); sim_debug(dev->dbit, dev->dptr, "Determining Address Conflict for MAC address: %s\n", mac_string); +/* The process of checking address conflicts is used in two ways: + 1) to determine the behavior of the currently running packet + delivery facility regarding whether it may receive copies + of every packet sent (and how many). + 2) to verify if a MAC address which this facility is planning + to use as the source address of packets is already in use + by some other node on the local network + Case #1, doesn't require (and explicitly doesn't want) any + interaction or response from other systems on the LAN so + therefore no considerations regarding switch packet forwarding + are important. Meanwhile, Case #2 does require responses from + other components on the LAN to provide useful functionality. + The original designers of this mechanism did this when essentially + all LANs were single collision domains (i.e. ALL nodes which might + be affected by an address conflict were physically present on a single + Ethernet cable which might have been extended by a couple of repeaters). + Since that time, essentially no networks are single collision domains. + Thick and thinwire Ethernet cables don’t exist and very few networks + even have hubs. Today, essentially all LANs are deployed using one + or more layers of network switches. In a switched LAN environment, the + switches on the LAN ‘learn’ which ports on the LAN source traffic from + which MAC addresses and then forward traffic destined for particular + MAC address to the appropriate ports. If a particular MAC address is + already in use somewhere on the LAN, then the switches ‘know’ where + it is. The host based test using the loopback protocol is poorly + designed to detect this condition. This test is performed by the host + first changing the device’s Physical MAC address to the address which + is to be tested, and then sending a loopback packet FROM AND TO this + MAC address with a loopback reply to be sent by a system which may be + currently using the MAC address. If no reply is received, then the + MAC address is presumed to be unused. The sending of this packet will + result in its delivery to the right system since the switch port/MAC + address tables know where to deliver packets destined to this MAC + address, however the response it generates won’t be delivered to the + system performing the test since the switches on the LAN won’t know + about the local port being the right target for packets with this MAC + address. A better test design to detect these conflicts would be for + the testing system to send a loopback packet FROM the current physical + MAC address (BEFORE changing it) TO the MAC address being tested with + the loopback response coming to the current physical MAC address of + the device. If a response is received, then the address is in use and + the attempt to change the device’s MAC address should fail. Since we + can’t change the software running in these simulators to implement this + better conflict detection approach, we can still ‘do the right thing’ + in the sim_ether layer. We’re already handling the loopback test + packets specially since we always had to avoid receiving the packets + which were being sent, but needed to allow for the incoming loopback + packets to be properly dealt with. We can extend this current special + handling to change outgoing ‘loopback to self’ packets to have source + AND loopback destination addresses in the packets to be the host NIC’s + physical address. The switch network will already know the correct + MAC/port relationship for the host NIC’s physical address, so loopback + response packets will be delivered as needed. + + Code in _eth_write and _eth_callback provide the special handling to + perform the described loopback packet adjustments, and code in + eth_filter_hash makes sure that the loopback response packets are received. + + */ + /* build a loopback forward request packet */ memset (&send, 0, sizeof(ETH_PACK)); send.len = ETH_MIN_PACKET; /* minimum packet size */ @@ -1740,7 +1800,7 @@ if (status != SCPE_OK) { char *msg; msg = "Eth: Error Transmitting packet: %s\r\n" "You may need to run as root, or install a libpcap version\r\n" - "which is at least 0.9 from www.tcpdump.org\r\n"; + "which is at least 0.9 from your OS vendor or www.tcpdump.org\r\n"; printf(msg, strerror(errno)); if (sim_log) fprintf (sim_log, msg, strerror(errno)); return status; @@ -1752,7 +1812,10 @@ sim_os_ms_sleep (300); /* time for a conflicting host to respond */ do { memset (&recv, 0, sizeof(ETH_PACK)); status = eth_read (dev, &recv, NULL); - if (memcmp(send.msg, recv.msg, 14)== 0) + if (((0 == memcmp(send.msg+12, recv.msg+12, 2)) && /* Protocol Match */ + (0 == memcmp(send.msg, recv.msg+6, 6)) && /* Source Match */ + (0 == memcmp(send.msg+6, recv.msg, 6))) || /* Destination Match */ + (0 == memcmp(send.msg, recv.msg, 14))) /* Packet Match (Reflection) */ responses++; } while (recv.len > 0); @@ -1789,12 +1852,16 @@ if (!packet) return SCPE_ARG; /* make sure packet is acceptable length */ if ((packet->len >= ETH_MIN_PACKET) && (packet->len <= ETH_MAX_PACKET)) { - int loopback_self_frame = LOOPBACK_SELF_FRAME(dev->physical_addr, packet->msg); + int loopback_self_frame = LOOPBACK_SELF_FRAME(packet->msg, packet->msg); eth_packet_trace (dev, packet->msg, packet->len, "writing"); /* record sending of loopback packet (done before actual send to avoid race conditions with receiver) */ if (loopback_self_frame) { + if (dev->have_host_nic_phy_addr) { + memcpy(&packet->msg[6], dev->host_nic_phy_hw_addr, sizeof(ETH_MAC)); + memcpy(&packet->msg[18], dev->host_nic_phy_hw_addr, sizeof(ETH_MAC)); + } #ifdef USE_READER_THREAD pthread_mutex_lock (&dev->self_lock); #endif @@ -1803,7 +1870,7 @@ if ((packet->len >= ETH_MIN_PACKET) && (packet->len <= ETH_MAX_PACKET)) { #ifdef USE_READER_THREAD pthread_mutex_unlock (&dev->self_lock); #endif - } + } /* dispatch write request (synchronous; no need to save write info to dev) */ switch (dev->eth_api) { @@ -2343,6 +2410,17 @@ int from_me = 0; int i; int bpf_used; +if ((dev->have_host_nic_phy_addr) && + (LOOPBACK_PHYSICAL_RESPONSE(dev->host_nic_phy_hw_addr, dev->physical_addr, data))) { + u_char *datacopy = malloc(header->len); + + memcpy(datacopy, data, header->len); + memcpy(datacopy, dev->physical_addr, sizeof(ETH_MAC)); + memcpy(datacopy+18, dev->physical_addr, sizeof(ETH_MAC)); + _eth_callback(info, header, datacopy); + free(datacopy); + return; +} switch (dev->eth_api) { case ETH_API_PCAP: #ifdef USE_BPF @@ -2377,7 +2455,9 @@ switch (dev->eth_api) { } /* detect reception of loopback packet to our physical address */ -if (LOOPBACK_SELF_FRAME(dev->physical_addr, data)) { +if ((LOOPBACK_SELF_FRAME(dev->physical_addr, data)) || + (dev->have_host_nic_phy_addr && + LOOPBACK_PHYSICAL_REFLECTION(dev->host_nic_phy_hw_addr, data))) { #ifdef USE_READER_THREAD pthread_mutex_lock (&dev->self_lock); #endif @@ -2394,7 +2474,7 @@ if (LOOPBACK_SELF_FRAME(dev->physical_addr, data)) { #ifdef USE_READER_THREAD pthread_mutex_unlock (&dev->self_lock); #endif - } +} if (bpf_used ? to_me : (to_me && !from_me)) { if (header->len > ETH_MIN_JUMBO_FRAME) { @@ -2686,6 +2766,10 @@ if ((addr_count) && (dev->reflections > 0)) { memcpy(dev->physical_addr, &dev->filter_address[i], sizeof(ETH_MAC)); /* let packets through where dst and src are the same as our physical address */ sprintf (&buf[strlen(buf)], " or ((ether dst %s) and (ether src %s))", mac, mac); + if (dev->have_host_nic_phy_addr) { + eth_mac_fmt(&dev->host_nic_phy_hw_addr, mac); + sprintf(&buf[strlen(buf)], "or ((ether dst %s) and (ether proto 0x9000))", mac); + } break; } } @@ -2694,9 +2778,9 @@ if ((0 == strlen(buf)) && (!dev->promiscuous)) /* Empty filter means match nothi strcpy(buf, "ether host fe:ff:ff:ff:ff:ff"); /* this should be a good match nothing filter */ sim_debug(dev->dbit, dev->dptr, "BPF string is: |%s|\n", buf); -/* get netmask, which is a required argument for compiling, the value, in our - case isn't actually interesting since the filters we generate aren't - referencing IP fields, networks or values */ +/* get netmask, which is a required argument for compiling. The value, + in our case isn't actually interesting since the filters we generate + aren't referencing IP fields, networks or values */ if ((dev->eth_api == ETH_API_PCAP) && (pcap_lookupnet(dev->name, &bpf_subnet, &bpf_netmask, errbuf)<0)) bpf_netmask = 0; diff --git a/sim_ether.h b/sim_ether.h index 8200116e..29c9e7a8 100644 --- a/sim_ether.h +++ b/sim_ether.h @@ -136,13 +136,30 @@ #define ETH_MIN_JUMBO_FRAME ETH_MAX_PACKET /* Threshold size for Jumbo Frame Processing */ #define LOOPBACK_SELF_FRAME(phy_mac, msg) \ - ((memcmp(phy_mac, msg , 6) == 0) && \ - (memcmp(phy_mac, msg+6, 6) == 0) && \ - ((msg)[12] == 0x90) && ((msg)[13] == 0x00) && \ + (((msg)[12] == 0x90) && ((msg)[13] == 0x00) && \ ((msg)[14] == 0x00) && ((msg)[15] == 0x00) && \ ((msg)[16] == 0x02) && ((msg)[17] == 0x00) && \ - (memcmp(phy_mac, msg+18, 6) == 0) && \ - ((msg)[24] == 0x01) && ((msg)[25] == 0x00)) + ((msg)[24] == 0x01) && ((msg)[25] == 0x00) && \ + (memcmp(phy_mac, (msg), 6) == 0) && \ + (memcmp(phy_mac, (msg)+6, 6) == 0) && \ + (memcmp(phy_mac, (msg)+18, 6) == 0)) + +#define LOOPBACK_PHYSICAL_RESPONSE(host_phy, phy_mac, msg) \ + (((msg)[12] == 0x90) && ((msg)[13] == 0x00) && \ + ((msg)[14] == 0x08) && ((msg)[15] == 0x00) && \ + ((msg)[16] == 0x02) && ((msg)[17] == 0x00) && \ + ((msg)[24] == 0x01) && ((msg)[25] == 0x00) && \ + (memcmp(host_phy, (msg)+18, 6) == 0) && \ + (memcmp(host_phy, (msg), 6) == 0) && \ + (memcmp(phy_mac, (msg)+6, 6) == 0)) + +#define LOOPBACK_PHYSICAL_REFLECTION(host_phy, msg) \ + (((msg)[12] == 0x90) && ((msg)[13] == 0x00) && \ + ((msg)[14] == 0x00) && ((msg)[15] == 0x00) && \ + ((msg)[16] == 0x02) && ((msg)[17] == 0x00) && \ + ((msg)[24] == 0x01) && ((msg)[25] == 0x00) && \ + (memcmp(host_phy, (msg)+6, 6) == 0) && \ + (memcmp(host_phy, (msg)+18, 6) == 0)) struct eth_packet { uint8 msg[ETH_FRAME_SIZE]; /* ethernet frame (message) */