summaryrefslogtreecommitdiff
path: root/ldra.c
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2011-11-09 16:08:25 +0100
committerBjørn Mork <bjorn@mork.no>2011-11-09 16:20:38 +0100
commit74b281bcbf7ef7a984a2815884812d8aab9deb71 (patch)
tree6f0a099a28f2369f12417e3c2c3eb66a83291ab9 /ldra.c
parent4525ea16327a142113abdfaadd3133b8aa6c9ce2 (diff)
working again after complete refactoring
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'ldra.c')
-rw-r--r--ldra.c305
1 files changed, 286 insertions, 19 deletions
diff --git a/ldra.c b/ldra.c
index d588a02..21dc274 100644
--- a/ldra.c
+++ b/ldra.c
@@ -163,14 +163,126 @@ u_int16_t udp6chksum(struct ip6_hdr *ip6)
}
+/* decodes the relay attributes, returning the interfaceid and the OPTION_RELAY_MSG option */
+int parse_relay_packet(struct dhcpv6_option **relay_msg, size_t len)
+{
+ int interfaceid = -1;
+ int n = 0;
+ struct dhcpv6_option *opt;
+ char *buf = (char *)*relay_msg;
+
+ /* default to not found */
+ *relay_msg = NULL;
+
+ /* parse the RELAY options before copying anything, so that we know e.g. if we need a VLAN tag */
+ while (n < len) {
+ opt = (struct dhcpv6_option *)(buf + n);
+ n += sizeof(struct dhcpv6_option) + ntohs(opt->len);
+
+// fprintf(stderr, "opt=%p, opt->code=%#06hx, opt->len=%hd\n", opt, ntohs(opt->code), ntohs(opt->len));
+
+ switch (ntohs(opt->code)) {
+ case OPTION_INTERFACE_ID:
+ if (interfaceid > 0) { /* sanity: never accept more than one of these! */
+ fprintf(stderr, "multiple OPTION_INTERFACE_ID is not allowed!\n");
+ return -1;
+ }
+ interfaceid = ntohl(*(int *)&opt->data);
+ break;
+ case OPTION_RELAY_MSG:
+ if (*relay_msg) { /* sanity: never accept more than one of these! */
+ fprintf(stderr, "multiple OPTION_RELAY_MSG is not allowed!\n");
+ return -1;
+ }
+ /* save a pointer to this option to preserve both lenth and data for later */
+ *relay_msg = opt;
+ break;
+ default:
+ fprintf(stderr, "ignoring unknown relay option: %hd\n", ntohs(opt->code));
+ }
+ }
+
+ /* verify that we found all the required options */
+ if ((*relay_msg == NULL) || (interfaceid < 0)) {
+ fprintf(stderr, "couldn't find all mandatory options - ignoring packet\n");
+ return -1;
+ }
+
+ /* return the index of the interface where this frame should be sent */
+ return interfaceid;
+}
+
+/* set both IPv6 and UDP length fields and return pointer to UDP header */
+struct udphdr *setlen(struct ip6_hdr *ip6, int datalen)
+{
+ struct udphdr *udp;
+ int len = htons(datalen + sizeof(struct udphdr));
+
+ ip6->ip6_plen = len;
+ udp = (struct udphdr *)((char *)ip6 + sizeof(struct ip6_hdr));
+ udp->len = len;
+ return udp;
+}
+
+/* copy packet headers, possibly inserting vlan tag */
+int copyheader(char *txbuf, char *buf, int tci)
+{
+ struct ethhdr *eth;
+ struct vlan_tag *vtag;
+ int size;
+
+ /* 1. copy ethernet header */
+ eth = memcpy(txbuf, buf, sizeof(struct ethhdr));
+ size = sizeof(struct ethhdr);
+
+ /* 2. insert VLAN tag */
+ if (tci > 0) {
+ memcpy((char *)&eth->h_proto + sizeof(struct vlan_tag), &eth->h_proto, sizeof(eth->h_proto)); /* copy original protocol */
+ vtag = (struct vlan_tag *)&eth->h_proto;
+ vtag->v_tpid = htons(ETH_P_8021Q);
+ vtag->v_tci = htons(tci);
+ size += sizeof(struct vlan_tag);
+ }
+
+ /* 3. copy IPv6 header and update length */
+ memcpy(txbuf + size, buf + sizeof(struct ethhdr), sizeof(struct ip6_hdr));
+ size += sizeof(struct ip6_hdr);
+
+ return size;
+}
+
+int sendit(int s, char *txbuf, int len, int ifidx)
+{
+ struct sockaddr_ll ll;
+ struct ethhdr *eth = (struct ethhdr *)txbuf;
+ int ret;
+
+ memset(&ll, 0, sizeof(ll));
+ ll.sll_family = AF_PACKET;
+ ll.sll_ifindex = ifidx;
+ ll.sll_halen = sizeof(eth->h_dest);
+ memcpy(&ll.sll_addr, &eth->h_dest, ll.sll_halen);
+
+ fprintf(stderr, "sending %d bytes to %s on ifid=%d:\n", len, macstr((char *)ll.sll_addr), ll.sll_ifindex);
+
+ if (len < 1000)
+ print_hex(txbuf, len);
+
+ if (ret = sendto(s, txbuf, len, 0, (struct sockaddr *)&ll, sizeof(ll)) == -1)
+ fprintf(stderr, "sendto() failed: %m\n");
+
+ return ret;
+}
+
int main(int argc, char *argv[]) {
int domain, s, i;
char str[INET6_ADDRSTRLEN];
struct sockaddr_ll ll;
char *buf, *txbuf;
- int val, fromlen, len = 0;
+ int fromlen, len = 0;
int on = 1;
int vlan;
+ int size;
struct sock_fprog fprog;
@@ -327,6 +439,8 @@ Steven McCanne and Van Jacobson of Lawrence Berkeley Laboratory.
if (ll.sll_pkttype == PACKET_OUTGOING)
continue;
+ /* reset before continuing */
+ vlan=0;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
struct tpacket_auxdata *aux;
@@ -337,12 +451,173 @@ Steven McCanne and Van Jacobson of Lawrence Berkeley Laboratory.
continue;
aux = (struct tpacket_auxdata *)CMSG_DATA(cmsg);
- if (aux->tp_status & TP_STATUS_VLAN_VALID) {
- fprintf(stderr, "got tpacket_auxdata with tp_vlan_tci=%hd\n", aux->tp_vlan_tci);
+ if (aux->tp_status & TP_STATUS_VLAN_VALID)
vlan = aux->tp_vlan_tci;
- }
}
+ /* do some common checks before knowing whether this packet goes up or down */
+
+ /* NOTE: Not necessarily a relay message, but we only look at msg_type until we know for sure */
+ dhcpv6 = (struct dhcpv6_relay_msg *)(buf + PKTHDRZ);
+
+ fprintf(stderr, "received %d bytes from %s on interface %d (vlan=%d) with DHCPv6 type=%hhd\n",
+ len, macstr((char *)ll.sll_addr), ll.sll_ifindex, vlan, dhcpv6->msg_type);
+
+
+ /* packets from upstream are simple to discard */
+ if (ll.sll_ifindex == idx_up) {
+ struct dhcpv6_option *opt;
+ int txintf;
+ int datalen;
+
+ /* only DHCPV6_RELAY_REPL messages are to be processed */
+ if (dhcpv6->msg_type != DHCPV6_RELAY_REPL) {
+ fprintf(stderr, "ignoring message type %hhu on interface %d\n", dhcpv6->msg_type, ll.sll_ifindex);
+ continue;
+ }
+
+ /* FIMXE:
+ verify that destination and source address is link local
+ verify that source port is 547
+ */
+
+ /* parse DHCP RELAY message */
+ opt = (struct dhcpv6_option *)&dhcpv6->options;
+ txintf = parse_relay_packet(&opt, buf + len - (char *)&dhcpv6->options);
+
+ /* parse error? */
+ if (txintf < 0)
+ continue;
+
+ /* create tx packet header */
+ size = copyheader(txbuf, buf, txintf & 0xfff);
+
+ /* we change most of the UDP header, so create a new instead of copying */
+ ip6 = (struct ip6_hdr *)(txbuf + size - sizeof(struct ip6_hdr));
+ udp = setlen(ip6, ntohs(opt->len));
+ udp->dest = htons(546);
+ udp->source = htons(547);
+ size += sizeof(struct udphdr);
+
+ /* copy in the DHCP message */
+ memcpy(txbuf + size, &opt->data, ntohs(opt->len));
+ size += ntohs(opt->len);
+
+ /* update the checksum after copying all the data */
+ udp->check = htons(udp6chksum(ip6));
+
+ /* ready to send message */
+ sendit(s, txbuf, size, txintf >> 16);
+
+ } else { /* most likely downstream, but could still be a VLAN tagged upstream on a common port */
+ struct dhcpv6_option *opt;
+ int extra;
+ int datalen;
+ int *val;
+ int hopcount = 0;
+ int trusted = 1; /* FIMXE: allow setting this per interface */
+ int mtu = 1500; /* FIMXE: get this from the upstream interface? */
+
+
+ /* FIMXE: We receive three(!) copies of the same frame in the common case:
+ tagged on port, untagged in VLAN interface, untagged on bridge interface
+ */
+
+ /* require tag */
+ if (!vlan)
+ continue;
+
+ switch (dhcpv6->msg_type) {
+ case DHCPV6_RELAY_REPL:
+ /* silently ignore this, as the reason might be that upstream is a VLAN on a common port */
+ continue;
+
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_REPLY:
+ case DHCPV6_RECONFIGURE:
+ fprintf(stderr, "ignoring message type %hhu on interface %d\n", dhcpv6->msg_type, ll.sll_ifindex);
+ continue;
+
+ case DHCPV6_RELAY_FORW:
+ /* ignore these on untrusted interfaces */
+ if (!trusted) {
+ fprintf(stderr, "ignoring message type %hhu on untrused interface %d\n", dhcpv6->msg_type, ll.sll_ifindex);
+ continue;
+ }
+ hopcount = dhcpv6->hop_count + 1; /* propagate */
+ }
+
+ /* FIMXE:
+ verify that destination address is ff02::1:2
+ verify that source address is link local
+ verify that source port is 546 (unless hopcount > 0)
+ verify that message type is not one of the forbidden ones
+
+ */
+
+ /* create tx packet header */
+ size = copyheader(txbuf, buf, 0); /* no VLAN */
+
+ /* save a pointer to the IPv6 header */
+ ip6 = (struct ip6_hdr *)(txbuf + size - sizeof(struct ip6_hdr));
+ /* abusing the fact that we haven't updated this field yet */
+ datalen = ntohs(ip6->ip6_plen) - sizeof(struct udphdr);
+
+ /* just reserve space for the UDP header for now */
+ size += sizeof(struct udphdr);
+
+ /* 2. fill inn DHCP relay header */
+ dhcpv6 = (struct dhcpv6_relay_msg *)(txbuf + size);
+ dhcpv6->msg_type = DHCPV6_RELAY_FORW;
+ dhcpv6->hop_count = hopcount;
+ memset(&dhcpv6->link_addr, 0, sizeof(dhcpv6->link_addr));
+ memcpy(&dhcpv6->peer_addr, &ip6->ip6_src, sizeof(dhcpv6->peer_addr));
+
+ /* fill in INTERFACE-ID */
+ opt = (struct dhcpv6_option *)&dhcpv6->options;
+ opt->code = htons(OPTION_INTERFACE_ID);
+ opt->len = htons(sizeof(int));
+ val = (int *)&opt->data;
+ *val = htonl((ll.sll_ifindex) << 16 | (vlan & 0xfff));
+
+ /* fill in OPTION_RELAY_MSG */
+ opt = nextopt(opt);
+ opt->code = htons(OPTION_RELAY_MSG);
+ opt->len = htons(datalen);
+
+ /* how many bytes did we insert? */
+ extra = (char *)&opt->data - (char *)dhcpv6;
+
+ /* account for total packet length */
+ size += extra + datalen;
+
+ /* verify that we have enough space lefte for the original DHCP packet */
+ if (size > mtu) {
+ fprintf(stderr, "ERROR: relay packet exceeds MTU (%d > %d)\n", size, mtu);
+ continue;
+ }
+
+ /* copy the original DHCP packet into the OPTION_RELAY_MSG */
+ memcpy(&opt->data, buf + sizeof(struct ethhdr) + sizeof(struct ip6_hdr) + sizeof(struct udphdr), datalen);
+
+ /* update the UDP header now that we have the length and data in place */
+ udp = setlen(ip6, datalen + extra); /* orig + inserted DHCP relay options */
+ udp->dest = htons(547);
+ udp->source = htons(547);
+ udp->check = htons(udp6chksum(ip6));
+
+ /* ready to send message */
+ sendit(s, txbuf, size, idx_up);
+
+
+ }
+ }
+
+ close(s);
+ return(0);
+}
+
+#if 0
if (vlan && (vlan != 602) && (ll.sll_ifindex == idx_down)) {
/* insert RELAY-MSG + INTERFACE-ID + OPTION_RELAY_MSG header */
@@ -435,24 +710,12 @@ Steven McCanne and Van Jacobson of Lawrence Berkeley Laboratory.
udp->len = new;
ip6->ip6_plen = new;
- /* fixup UDP checksum - why is the 2* and +2 correct? */
- new = ~(ntohs(udp->check)) + 2 * extra + 2; /* initialize to old sum + added length */
-
/* maybe correct UDP source port? NOTE: may already by 547 if we received the packet from another relay */
if (ntohs(udp->source) == 546)
udp->source = htons(547);
- csum = (u_int16_t *)dhcpv6;
- for (j = 0; j < extra / 2; j++) /* FIXME: assumes extra % 2 == 0 */
- new += htons(csum[j]);
-
- while (new >> 16)
- new = (new & 0xffff) + (new >> 16);
-
- if (new)
- udp->check = ~(ntohs(new));
- else
- udp->check = 0xffff;
+ /* update UDP checksum - we could have based this on the old sum, but... */
+ udp->check = htons(udp6chksum(ip6));
/* finally: transmit the packet on the upstream interface */
@@ -598,6 +861,7 @@ Steven McCanne and Van Jacobson of Lawrence Berkeley Laboratory.
continue;
}
+
/*
fprintf(stderr, "%s(): received %d bytes from address with len=%d on ifid=%d\n",
__FUNCTION__, len, ll.sll_halen, ll.sll_ifindex );
@@ -655,8 +919,11 @@ Steven McCanne and Van Jacobson of Lawrence Berkeley Laboratory.
*/
- }
+
+}
+
close(s);
return(0);
}
+#endif