358 lines
8.3 KiB
C
358 lines
8.3 KiB
C
/* simplistic Token Ring <-> Ethernet bridge in userspace based on AF_PACKET
|
|
*
|
|
* (C) 2022 by Harald Welte <laforge@gnumonks.org>
|
|
* for Osmocom retronetworking project.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*
|
|
* You need to call this program with two arguments:
|
|
* - first: the token ring device (e.g. 'tr0')
|
|
* - second: the ethernet device (e.g. 'eth0')
|
|
*
|
|
* Optionally, you may psecify with two additional arguments:
|
|
* - third: A file containing MAC addresses on TR side
|
|
* - fourth: A file containing MAC addresses on ETH side
|
|
*
|
|
* The file syntax is one MAC address per line in 01:02:03:04:05:06 format.
|
|
*
|
|
* If MAC address files are specified, the bridge will only pass frames for those
|
|
* addresses and drop all other frames. Please note that you need to specify the
|
|
* bit ordering _native_ to the respective side. So Eth order for Eth; TR order for TR.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/uio.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <linux/if.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_packet.h>
|
|
#include <net/ethernet.h>
|
|
|
|
#include "utils.h"
|
|
#include "mac_table.h"
|
|
|
|
|
|
#define TR_ALEN 6
|
|
#define AC 0x10
|
|
#define LLC_FRAME 0x40
|
|
|
|
struct tr_hdr {
|
|
uint8_t ac;
|
|
uint8_t fc;
|
|
uint8_t daddr[TR_ALEN];
|
|
uint8_t saddr[TR_ALEN];
|
|
} __attribute((packed));
|
|
|
|
|
|
struct bridge_state {
|
|
struct {
|
|
int socket;
|
|
struct mac_table mac_tbl;
|
|
} tr;
|
|
struct {
|
|
int socket;
|
|
struct mac_table mac_tbl;
|
|
} eth;
|
|
};
|
|
|
|
static int devname2ifindex(const char *ifname)
|
|
{
|
|
struct ifreq ifr;
|
|
int sk, rc;
|
|
|
|
sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
|
if (sk < 0)
|
|
return sk;
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
|
|
ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0;
|
|
|
|
rc = ioctl(sk, SIOCGIFINDEX, &ifr);
|
|
close(sk);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return ifr.ifr_ifindex;
|
|
}
|
|
|
|
static int enable_promisc(int sk, int ifindex)
|
|
{
|
|
struct packet_mreq mreq = {0};
|
|
int rc;
|
|
|
|
mreq.mr_ifindex = ifindex;
|
|
mreq.mr_type = PACKET_MR_PROMISC;
|
|
|
|
rc = setsockopt(sk, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Unable to set promiscuous mode: %s\n", strerror(errno));
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int open_packet_socket(int ifindex, int proto)
|
|
{
|
|
struct sockaddr_ll addr;
|
|
int fd, rc;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sll_family = AF_PACKET;
|
|
addr.sll_protocol = htons(proto);
|
|
addr.sll_ifindex = ifindex;
|
|
/* we want only packets for _other_ hosts, not packets sent by us or received for us locally */
|
|
addr.sll_pkttype = PACKET_OTHERHOST;
|
|
|
|
fd = socket(AF_PACKET, SOCK_RAW, htons(proto));
|
|
if (fd < 0) {
|
|
fprintf(stderr, "Can not create AF_PACKET socket. Are you root or have CAP_NET_RAW?\n");
|
|
return fd;
|
|
}
|
|
|
|
/* there's a race condition between the above syscall and the bind() call below,
|
|
* causing other packets to be received in between */
|
|
|
|
rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Can not bind AF_PACKET socket to ifindex %d\n", ifindex);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
rc = enable_promisc(fd, ifindex);
|
|
if (rc < 0) {
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int open_packet_socket_for_netdev(const char *ifname, int proto)
|
|
{
|
|
int rc;
|
|
|
|
rc = devname2ifindex(ifname);
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Cannot find net-device '%s': %s\n", ifname, strerror(errno));
|
|
return rc;
|
|
}
|
|
|
|
return open_packet_socket(rc, proto);
|
|
}
|
|
|
|
|
|
|
|
static int write_tr(int socket, const struct tr_hdr *trh, const uint8_t *payload, size_t payload_len)
|
|
{
|
|
struct iovec iov[2] = {
|
|
{
|
|
.iov_base = (void *) trh,
|
|
.iov_len = sizeof(*trh),
|
|
}, {
|
|
.iov_base = (void *) payload,
|
|
.iov_len = payload_len,
|
|
}
|
|
};
|
|
|
|
return writev(socket, iov, 2);
|
|
}
|
|
|
|
static int eth2tr(struct bridge_state *bst)
|
|
{
|
|
char tr_src[MAC_STR_SIZE], tr_dst[MAC_STR_SIZE], eth_src[MAC_STR_SIZE], eth_dst[MAC_STR_SIZE];
|
|
uint8_t buf[2000];
|
|
const struct ethhdr *ethh = (struct ethhdr *) buf;
|
|
struct tr_hdr trh;
|
|
int rc, ethlen, eth_payload_len;
|
|
|
|
rc = read(bst->eth.socket, buf, sizeof(buf));
|
|
if (rc <= 0) {
|
|
fprintf(stderr, "Error reading from ETH: %s\n", strerror(errno));
|
|
return rc;
|
|
}
|
|
ethlen = rc;
|
|
|
|
if (ethlen < sizeof(*ethh)) {
|
|
fprintf(stderr, "short frame from ETH\n");
|
|
return 0;
|
|
}
|
|
|
|
mac2str_buf(eth_src, ethh->h_source);
|
|
mac2str_buf(eth_dst, ethh->h_dest);
|
|
|
|
/* 802.3 frame: 'proto' is actually the length */
|
|
eth_payload_len = htons(ethh->h_proto);
|
|
if (sizeof(*ethh) + eth_payload_len > ethlen) {
|
|
fprintf(stderr, "TR<-ETH: Not bridging unrealistically long (%zu > %u) frame %s->%s\n",
|
|
sizeof(*ethh) + eth_payload_len, ethlen, eth_src, eth_dst);
|
|
return 0;
|
|
}
|
|
|
|
trh.ac = AC;
|
|
trh.fc = LLC_FRAME;
|
|
memcpy(&trh.daddr, ethh->h_dest, sizeof(trh.daddr));
|
|
osmo_revbytebits_buf(trh.daddr, TR_ALEN);
|
|
memcpy(&trh.saddr, ethh->h_source, sizeof(trh.saddr));
|
|
osmo_revbytebits_buf(trh.saddr, TR_ALEN);
|
|
|
|
mac2str_buf(tr_src, trh.saddr);
|
|
mac2str_buf(tr_dst, trh.daddr);
|
|
|
|
if (mac_table_empty(&bst->tr.mac_tbl) || mac_table_contains(&bst->tr.mac_tbl, trh.daddr)) {
|
|
printf("TR<-ETH: %s/%s <- %s/%s\n", tr_src, eth_src, tr_dst, eth_dst);
|
|
return write_tr(bst->tr.socket, &trh, buf + sizeof(*ethh), eth_payload_len);
|
|
} else {
|
|
printf("TR<-ETH: Ignoring frame to unknown MAC %s\n", mac2str(trh.daddr));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int write_eth(int socket, const struct ethhdr *ethh, const uint8_t *payload, size_t payload_len)
|
|
{
|
|
struct iovec iov[2] = {
|
|
{
|
|
.iov_base = (void *) ethh,
|
|
.iov_len = sizeof(*ethh),
|
|
}, {
|
|
.iov_base = (void *) payload,
|
|
.iov_len = payload_len,
|
|
}
|
|
};
|
|
|
|
return writev(socket, iov, 2);
|
|
}
|
|
|
|
static int tr2eth(struct bridge_state *bst)
|
|
{
|
|
char tr_src[MAC_STR_SIZE], tr_dst[MAC_STR_SIZE], eth_src[MAC_STR_SIZE], eth_dst[MAC_STR_SIZE];
|
|
uint8_t buf[2000];
|
|
const struct tr_hdr *trh = (const struct tr_hdr *) buf;
|
|
struct ethhdr ethh;
|
|
int rc, trlen;
|
|
|
|
rc = read(bst->tr.socket, buf, sizeof(buf));
|
|
if (rc <= 0) {
|
|
fprintf(stderr, "Error reading from TR: %s\n", strerror(errno));
|
|
return rc;
|
|
}
|
|
trlen = rc;
|
|
|
|
if (trlen < sizeof(*trh)) {
|
|
fprintf(stderr, "short frame from TR\n");
|
|
return 0;
|
|
}
|
|
mac2str_buf(tr_src, trh->saddr);
|
|
mac2str_buf(tr_dst, trh->daddr);
|
|
|
|
if (trh->fc != LLC_FRAME) {
|
|
fprintf(stderr, "TR->ETH: Not bridging unexpected non-LLC frame %s->%s FC 0x%02x\n",
|
|
tr_src, tr_dst, trh->fc);
|
|
return 0;
|
|
}
|
|
|
|
memcpy(ðh.h_dest, trh->daddr, sizeof(ethh.h_dest));
|
|
osmo_revbytebits_buf(ethh.h_dest, TR_ALEN);
|
|
memcpy(ðh.h_source, trh->saddr, sizeof(ethh.h_source));
|
|
osmo_revbytebits_buf(ethh.h_source, TR_ALEN);
|
|
/* We're doing 802.3 and not Ethernet II */
|
|
ethh.h_proto = htons(trlen - sizeof(*trh));
|
|
|
|
mac2str_buf(eth_src, ethh.h_source);
|
|
mac2str_buf(eth_dst, ethh.h_dest);
|
|
|
|
if (mac_table_empty(&bst->eth.mac_tbl) || mac_table_contains(&bst->eth.mac_tbl, ethh.h_dest)) {
|
|
printf("TR->ETH: %s/%s -> %s/%s\n", tr_src, eth_src, tr_dst, eth_dst);
|
|
return write_eth(bst->eth.socket, ðh, buf + sizeof(*trh), trlen - sizeof(*trh));
|
|
} else {
|
|
printf("TR->ETH: Ignoring frame to unknown MAC %s\n", mac2str(ethh.h_dest));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static int bridge_main(struct bridge_state *bst)
|
|
{
|
|
int maxfd = 0;
|
|
int rc;
|
|
fd_set read_fds;
|
|
|
|
FD_ZERO(&read_fds);
|
|
|
|
if (bst->tr.socket > maxfd)
|
|
maxfd = bst->tr.socket;
|
|
|
|
if (bst->eth.socket > maxfd)
|
|
maxfd = bst->eth.socket;
|
|
|
|
while (1) {
|
|
FD_SET(bst->tr.socket, &read_fds);
|
|
FD_SET(bst->eth.socket, &read_fds);
|
|
|
|
rc = select(maxfd+1, &read_fds, NULL, NULL, NULL);
|
|
if (rc < 0 && errno != EAGAIN)
|
|
break;
|
|
|
|
if (FD_ISSET(bst->tr.socket, &read_fds))
|
|
tr2eth(bst);
|
|
|
|
if (FD_ISSET(bst->eth.socket, &read_fds))
|
|
eth2tr(bst);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct bridge_state bst;
|
|
const char *tr_name, *eth_name;
|
|
|
|
mac_table_init(&bst.tr.mac_tbl, "TR");
|
|
mac_table_init(&bst.eth.mac_tbl, "ETH");
|
|
|
|
if (argc < 3) {
|
|
fprintf(stderr, "You must specify both TR and ETH device\n");
|
|
exit(2);
|
|
}
|
|
|
|
tr_name = argv[1];
|
|
eth_name = argv[2];
|
|
|
|
if (argc >= 5) {
|
|
mac_table_read(&bst.tr.mac_tbl, argv[3]);
|
|
mac_table_read(&bst.eth.mac_tbl, argv[4]);
|
|
}
|
|
|
|
bst.tr.socket = open_packet_socket_for_netdev(tr_name, ETH_P_ALL);
|
|
if (bst.tr.socket < 0) {
|
|
fprintf(stderr, "Error opening TR\n");
|
|
exit(1);
|
|
}
|
|
|
|
bst.eth.socket = open_packet_socket_for_netdev(eth_name, ETH_P_802_2);
|
|
if (bst.eth.socket < 0) {
|
|
fprintf(stderr, "Error opening ETH\n");
|
|
exit(1);
|
|
}
|
|
|
|
bridge_main(&bst);
|
|
|
|
exit(0);
|
|
}
|