mirror of
https://github.com/digarok/gsplus.git
synced 2024-11-24 22:30:55 +00:00
396 lines
12 KiB
Plaintext
396 lines
12 KiB
Plaintext
/*
|
|
GSport - an Apple //gs Emulator
|
|
Copyright (C) 2013-2014 by Peter Neubauer
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/** This module implements the ELAP port of the bridge. **/
|
|
|
|
#include <stdbool.h>
|
|
#include "../defc.h"
|
|
#include "atbridge.h"
|
|
#include "elap.h"
|
|
#include "port.h"
|
|
#include "aarp.h"
|
|
#include "elap_defs.h"
|
|
#include "pcap_delay.h"
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <Windows.h>
|
|
#include <NspAPI.h>
|
|
#endif
|
|
#ifdef WIN32
|
|
#include <winsock.h>
|
|
#include <IPHlpApi.h>
|
|
#endif
|
|
#ifdef __linux__
|
|
#include <netinet/in.h>
|
|
#include <netpacket/packet.h>
|
|
#endif
|
|
|
|
extern int g_ethernet_interface;
|
|
|
|
static pcap_t* pcap_session;
|
|
|
|
static struct packet_port_t elap_port;
|
|
static struct ether_addr_t HW_LOCAL;
|
|
|
|
/*static void dump_device_list(pcap_if_t* devices)
|
|
{
|
|
int i = 0;
|
|
for(pcap_if_t* device = devices; device; device = device->next)
|
|
{
|
|
printf("%d. %s", ++i, device->name);
|
|
if (device->description)
|
|
printf(" (%s)\n", device->description);
|
|
else
|
|
printf(" (No description available)\n");
|
|
}
|
|
}*/
|
|
|
|
static void elap_clone_host_mac(pcap_if_t* device)
|
|
{
|
|
if (!device)
|
|
return;
|
|
|
|
#ifdef WIN32
|
|
////
|
|
// Extract the device GUID, which Windows uses to identify the device.
|
|
char* name = device->name;
|
|
while (name && *name != '{')
|
|
name++;
|
|
size_t guidLen = strlen(name);
|
|
|
|
////
|
|
// Find and copy the device MAC address.
|
|
ULONG size = sizeof(IP_ADAPTER_ADDRESSES) * 15;
|
|
IP_ADAPTER_ADDRESSES* addresses = malloc(size);
|
|
|
|
ULONG result = GetAdaptersAddresses(AF_UNSPEC,
|
|
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, addresses, &size);
|
|
|
|
if (result == ERROR_BUFFER_OVERFLOW)
|
|
{
|
|
// The addresses buffer is too small. Allocate a bigger buffer.
|
|
free(addresses);
|
|
addresses = malloc(size);
|
|
result = GetAdaptersAddresses(AF_UNSPEC,
|
|
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, addresses, &size);
|
|
}
|
|
|
|
if (result == NO_ERROR)
|
|
{
|
|
// Search for the desired adapter address.
|
|
IP_ADAPTER_ADDRESSES* current = addresses;
|
|
while (current)
|
|
{
|
|
if (current->PhysicalAddressLength == ETHER_ADDR_LEN && memcmp(current->AdapterName, name, guidLen) == 0)
|
|
{
|
|
memcpy(&HW_LOCAL.mac, ¤t->PhysicalAddress, sizeof(HW_LOCAL.mac));
|
|
break;
|
|
}
|
|
current = current->Next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
halt_printf("ATBridge: Failed to find host MAC address (%d).", result);
|
|
}
|
|
|
|
free(addresses);
|
|
#else
|
|
struct pcap_addr* address;
|
|
for (address = device->addresses; address != 0; address = address->next)
|
|
if (address->addr->sa_family == AF_PACKET)
|
|
{
|
|
struct sockaddr_ll* ll = (struct sockaddr_ll*)address->addr;
|
|
memcpy(&HW_LOCAL.mac, ll->sll_addr, sizeof(HW_LOCAL.mac));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const struct ether_addr_t* elap_get_mac()
|
|
{
|
|
return &HW_LOCAL;
|
|
}
|
|
|
|
bool elap_init()
|
|
{
|
|
port_init(&elap_port);
|
|
|
|
memcpy(&HW_LOCAL, &HW_LOCAL_DEFAULT, sizeof(HW_LOCAL));
|
|
|
|
pcap_if_t* device;
|
|
pcap_if_t* alldevs;
|
|
int i = 0;
|
|
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
|
|
|
// Load the PCAP library.
|
|
if (!pcapdelay_load())
|
|
{
|
|
halt_printf("ATBridge: PCAP not available.\n");
|
|
return false;
|
|
}
|
|
|
|
// Retrieve the device list.
|
|
if(pcapdelay_findalldevs(&alldevs, errbuf) == -1)
|
|
{
|
|
atbridge_printf("ATBridge: Error enumerating PCAP devices: %s\n", errbuf);
|
|
return false;
|
|
}
|
|
//dump_device_list(alldevs);
|
|
|
|
// Jump to the selected adapter.
|
|
for (device = alldevs, i = 0; i < g_ethernet_interface; device = device->next, i++);
|
|
if (!device)
|
|
{
|
|
halt_printf("ATBridge: PCAP device not found. Check interface number in settings.\n");
|
|
return false;
|
|
}
|
|
|
|
// Clone the MAC address of the underlying interface. In certain configurations (e.g. Windows with an MS Loopback
|
|
// interface), the interface, even in promiscous mode, filters foreign MAC addresses.
|
|
elap_clone_host_mac(device);
|
|
|
|
// Open the adapter,
|
|
if ((pcap_session = pcapdelay_open_live(device->name, // name of the device
|
|
65536, // portion of the packet to capture.
|
|
// 65536 grants that the whole packet will be captured on all the MACs.
|
|
1, // promiscuous mode (nonzero means promiscuous)
|
|
1, // read timeout
|
|
errbuf // error buffer
|
|
)) == NULL)
|
|
{
|
|
halt_printf("ATBridge: Unable to open the adapter. Pcap does not support %s.\n", device->name);
|
|
pcapdelay_freealldevs(alldevs);
|
|
return false;
|
|
}
|
|
|
|
// The device must support Ethernet because the bridge "speaks" EtherTalk.
|
|
if (pcapdelay_datalink(pcap_session) == DLT_EN10MB)
|
|
{
|
|
pcapdelay_setnonblock(pcap_session, 1, errbuf);
|
|
|
|
atbridge_printf("ATBridge: AppleTalk bridging using network device '%s' with Ethernet address %.2X:%.2X:%.2X:%.2X:%.2X:%.2X.\n",
|
|
device->description, HW_LOCAL.mac[0], HW_LOCAL.mac[1], HW_LOCAL.mac[2], HW_LOCAL.mac[3], HW_LOCAL.mac[4], HW_LOCAL.mac[5]);
|
|
|
|
pcapdelay_freealldevs(alldevs);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
pcapdelay_close(pcap_session);
|
|
pcap_session = 0;
|
|
halt_printf("ATBridge: Selected network device %s must support Ethernet.\n", device->description);
|
|
pcapdelay_freealldevs(alldevs);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void elap_shutdown()
|
|
{
|
|
port_shutdown(&elap_port);
|
|
if (pcap_session)
|
|
{
|
|
pcapdelay_close(pcap_session);
|
|
pcap_session = 0;
|
|
}
|
|
pcapdelay_unload();
|
|
}
|
|
|
|
void elap_enqueue_out(struct packet_t* packet)
|
|
{
|
|
enqueue_packet(&elap_port.out, packet);
|
|
}
|
|
|
|
struct packet_t* elap_dequeue_in()
|
|
{
|
|
return dequeue(&elap_port.in);
|
|
}
|
|
|
|
void elap_send(const struct ether_addr_t* dest, const struct snap_discriminator_t* discriminator, size_t size, byte data[])
|
|
{
|
|
if (pcap_session && dest && discriminator)
|
|
{
|
|
// Allocate heap space for the frame.
|
|
const size_t frame_size = sizeof(struct ethernet_header_t) + sizeof(struct snap_header_t) + size;
|
|
u_char* frame_data;
|
|
size_t pad = 0;
|
|
if (frame_size < ETHER_MIN_SIZE)
|
|
pad = ETHER_MIN_SIZE - frame_size;
|
|
frame_data = (u_char*)malloc(frame_size + pad);
|
|
|
|
// Build the 802.3 header.
|
|
struct ethernet_header_t* ether = (struct ethernet_header_t*)frame_data;
|
|
memcpy(ether->dest.mac, dest, sizeof(ether->dest.mac));
|
|
memcpy(ether->source.mac, HW_LOCAL.mac, sizeof(ether->source.mac));
|
|
ether->length = htons(frame_size - sizeof(struct ethernet_header_t));
|
|
|
|
// Build the 802.2 header.
|
|
struct snap_header_t* snap = (struct snap_header_t*)(frame_data + sizeof(struct ethernet_header_t));
|
|
snap->dsap = SNAP_DSAP;
|
|
snap->ssap = SNAP_SSAP;
|
|
snap->control = SNAP_CONTROL;
|
|
memcpy(&snap->discriminator, discriminator, sizeof(snap->discriminator));
|
|
|
|
// Add the data payload.
|
|
struct snap_header_t* payload = (struct snap_header_t*)(frame_data + sizeof(struct ethernet_header_t) + sizeof(struct snap_header_t));
|
|
memcpy(payload, data, size);
|
|
|
|
// Add padding to meet minimum Ethernet frame size.
|
|
if (pad > 0)
|
|
memset(frame_data + frame_size, 0, pad);
|
|
|
|
pcapdelay_sendpacket(pcap_session, frame_data, frame_size + pad);
|
|
}
|
|
}
|
|
|
|
static bool elap_send_one_queued()
|
|
{
|
|
// Attempt to send one packet out the host network interface.
|
|
struct packet_t* packet = queue_peek(&elap_port.out);
|
|
if (packet)
|
|
{
|
|
// Find the MAC address.
|
|
const struct ether_addr_t* dest;
|
|
if (packet->dest.node == at_broadcast_node)
|
|
dest = &HW_APPLETALK_BROADCAST;
|
|
else
|
|
dest = aarp_request_hardware(&packet->dest);
|
|
|
|
// Send it.
|
|
if (dest)
|
|
{
|
|
elap_send(dest, &SNAP_APPLETALK, packet->size, packet->data);
|
|
|
|
dequeue(&elap_port.out);
|
|
free(packet->data);
|
|
free(packet);
|
|
}
|
|
else
|
|
{
|
|
// AARP does not have the needed hardware address. Give AARP time to obtain the address and keep the current packet.
|
|
if (!aarp_retry())
|
|
{
|
|
// However, if AARP has reached the retry limit, the packet is undeliverable. Discard the packet and move on.
|
|
atbridge_printf("ATBridge: AARP failed to find MAC address for network %d node %d. Dropping packet.\n", packet->dest.network, packet->dest.node);
|
|
aarp_retry_reset();
|
|
dequeue(&elap_port.out);
|
|
free(packet->data);
|
|
free(packet);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static void elap_send_all_queued()
|
|
{
|
|
while (elap_send_one_queued());
|
|
}
|
|
|
|
static bool elap_receive_one()
|
|
{
|
|
if (!pcap_session)
|
|
return false;
|
|
|
|
struct pcap_pkthdr header;
|
|
const u_char* packet = pcapdelay_next(pcap_session, &header);
|
|
if (packet)
|
|
{
|
|
// Receive and process one packet from the host network interface.
|
|
////
|
|
|
|
// Check the Ethernet 802.3 header.
|
|
const struct ethernet_header_t* ether = (struct ethernet_header_t*)packet;
|
|
if (header.len > sizeof(struct ethernet_header_t) &&
|
|
ntohs(ether->length) <= ETHER_MAX_SIZE &&
|
|
ntohs(ether->length) > sizeof(struct snap_header_t) &&
|
|
(memcmp(ðer->source, &HW_LOCAL, sizeof(ether->source)) != 0) && /* Ignore packets sent from our node. */
|
|
(memcmp(ðer->dest, &HW_LOCAL, sizeof(ether->dest)) == 0 || /* Accept packets destined for our node ... */
|
|
memcmp(ðer->dest, &HW_APPLETALK_BROADCAST, sizeof(ether->dest)) == 0 /* ... or for broadcast. */)
|
|
)
|
|
{
|
|
// Check the 802.2 SNAP header.
|
|
const struct snap_header_t* snap = (struct snap_header_t*)(packet + sizeof(struct ethernet_header_t));
|
|
if (snap->dsap == SNAP_DSAP &&
|
|
snap->ssap == SNAP_SSAP &&
|
|
snap->control == SNAP_CONTROL)
|
|
{
|
|
if (memcmp(&snap->discriminator, &SNAP_APPLETALK, sizeof(snap->discriminator)) == 0)
|
|
{
|
|
// Handle an AppleTalk packet.
|
|
const size_t payload_size = ntohs(ether->length) - sizeof(struct snap_header_t);
|
|
const u_char* payload = packet + sizeof(struct ethernet_header_t) + sizeof(struct snap_header_t);
|
|
|
|
byte* copy = (byte*)malloc(payload_size);
|
|
memcpy(copy, payload, payload_size);
|
|
|
|
// ELAP does not support short-form DDP, so this must be a long-form DDP packet.
|
|
struct at_addr_t source, dest;
|
|
if (payload_size >= sizeof(struct DDP_LONG))
|
|
{
|
|
// Extract the protocol address from the header.
|
|
//
|
|
// ELAP really shouldn't be looking at the header for the next level of the protocol stack,
|
|
// but this is a convenient place to glean addresses for the AMT, a process that needs both
|
|
// hardware and protocol addresses.
|
|
struct DDP_LONG* header = (struct DDP_LONG*)copy;
|
|
dest.network = (at_network_t)ntohs(header->dest_net);
|
|
source.network = (at_network_t)ntohs(header->source_net);
|
|
dest.node = header->dest_node;
|
|
source.node = header->source_node;
|
|
|
|
enqueue(&elap_port.in, dest, source, LAP_DDP_LONG, payload_size, copy);
|
|
|
|
aarp_glean(&source, ðer->source);
|
|
}
|
|
else
|
|
atbridge_printf("ATBridge: Dropping invalid short ELAP frame.\n");
|
|
}
|
|
else if (memcmp(&snap->discriminator, &SNAP_AARP, sizeof(snap->discriminator)) == 0)
|
|
{
|
|
// Handle an AARP packet.
|
|
struct aarp_header_t* aarp = (struct aarp_header_t*)(packet + sizeof(struct ethernet_header_t) + sizeof(struct snap_header_t));
|
|
aarp->dest_proto_addr.addr.network = ntohs(aarp->dest_proto_addr.addr.network);
|
|
aarp->source_proto_addr.addr.network = ntohs(aarp->source_proto_addr.addr.network);
|
|
aarp->function = ntohs(aarp->function);
|
|
aarp->hardware_type = ntohs(aarp->hardware_type);
|
|
aarp->protocol_type = ntohs(aarp->protocol_type);
|
|
aarp_handle_packet(aarp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static void elap_receive_all()
|
|
{
|
|
while (elap_receive_one());
|
|
}
|
|
|
|
void elap_process()
|
|
{
|
|
elap_receive_all();
|
|
elap_send_all_queued();
|
|
} |