From 74b281bcbf7ef7a984a2815884812d8aab9deb71 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Wed, 9 Nov 2011 16:08:25 +0100 Subject: working again after complete refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- ldra.c | 305 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 286 insertions(+), 19 deletions(-) (limited to 'ldra.c') 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 *)ð->h_proto + sizeof(struct vlan_tag), ð->h_proto, sizeof(eth->h_proto)); /* copy original protocol */ + vtag = (struct vlan_tag *)ð->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, ð->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 -- cgit v1.2.3