Question about bridging code

kw3wong at engmail.uwaterloo.ca kw3wong at engmail.uwaterloo.ca
Wed Jul 9 12:23:53 PDT 2003


Hi guys,

My first attempts at hacking FreeBSD kernel code has not been very fruitful, so 
I'm hoping someone with more experience and knowhow might be able to point out 
the mistakes that I'm making.

Firstly, let me explain what I'm trying to do. I'm currently working on a 
University project that performs some type of transformation (compression, 
security, string replacement, etc) on packets as they pass through the system. 
The current setup has the FreeBSD machine configured as a router, and the 
transformation is performed on packets that are routed. This is done via divert 
sockets and everything is fine and dandy, we're getting great results from this 
setup.

However, what we want to do next is to have the machine setup as a ethernet 
bridge instead, and the transformation is to be performed on the bridged 
packets. Unfortunately, as most of you probably know, divert sockets do not 
work with bridges as of yet.

So I've been trying to add a somewhat hack-ish support for divert sockets over 
bridges. The concession that I'm making is that instead of diverting ip 
packets, I'll be diverting ethernet frames. In userspace my program will 
reattach the ethernet headers back onto the packet before passing it back to 
the divert socket. A second concession is that when I sendto the divert socket, 
the sin_zero in the sockaddr must contain the source network adaptor name. All 
these concessions are necessary (I think) as I would otherwise not know how to 
output the data in a ip-less bridge. 

So here is what my code changes involved so far. BTW, I'm using FreeBSD 4.8

1) Removed the check in ipfw_chk (ip_fw2.c) for whether it is layer2 or not. 
This allows briged packets to still match the ipfw2 divert rules 

2) In bridge.c at function bdg_forward, after the ip_fw_chk_ptr (and after the 
check for dummynet, around line 974), the following code fragment is added

    if (i != 0 && (i & IP_FW_PORT_DYNT_FLAG) == 0) {
        struct mbuf *m;

        /* Need to determine whether this is an IP. If not just forward
        */
        if (ntohs(eh->ether_type) != ETHERTYPE_IP)
            goto forward;

        if ( shared ) {
            int j = min(m0->m_pkthdr.len + ETHER_HDR_LEN, max_protohdr) ;

            m0 = m_pullup(m0, j) ;
            if (m0 == NULL)
                return NULL;
        }

        if (shared == 0 && once ) { /* no need to copy */
            m = m0 ;
            m0 = NULL ; /* original is gone */
        } else {
            m = m_copypacket(m0, M_DONTWAIT);
            if (m == NULL) {
                printf("bdg_forward: sorry, m_copypacket failed!\n");
                return m0 ; /* the original is still there... */
            }
        }

        if ( (void *)(eh + 1) == (void *)m->m_data) {
            m->m_data -= ETHER_HDR_LEN ;
            m->m_len += ETHER_HDR_LEN ;
            m->m_pkthdr.len += ETHER_HDR_LEN ;
            bdg_predict++;
        } else {
            M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT);
            if (m == NULL)
            {
                printf("M_PREPEND failed\n");
                /* Should probably return original instead of NULL */
                /* return NULL; */
                return m0;
            }
            bcopy(&save_eh, mtod(m, struct ether_header *), ETHER_HDR_LEN);
        }

        divert_packet(m, 1, i & 0xffff, args.divert_rule);
        return NULL;
    }

This allows me to divert the ethernet frames to userspace.


3) To allow me to inject ethernet frames back into the system via divert 
sockets, I've modified div_output so that it will call ether_output_frame. The 
following are my changes to div_output, which is added before ip_output is 
called:

    /*  rcvif is copied from sin_zero, and is required to be valid
        for the current system to work
    */
    if (m->m_pkthdr.rcvif != NULL && BDG_USED(m->m_pkthdr.rcvif))
    {
        if (m->m_len < sizeof(struct ether_header)) {
            /* XXX error in the caller. */
            error = EINVAL;
            goto cantsend;
        }
        
        return ether_output_frame(m->m_pkthdr.rcvif, m);
    }

4) In userspace for testing purposes, I have a program that simply reads from 
the divert socket, and writes back out to it - here's the core snippet of the 
code.

    while (true)
    {
        sstBytes = ::recvfrom(nFD, kpucInPacket, sizeof(kpucInPacket), 0,
            (struct sockaddr *) &SockAddr, &AddrLen);

        if (sstBytes == -1)
            ::err(errno, "recvfrom");

        ::bcopy(SockAddr.sin_zero, 
            SockAddrSend.sin_zero, 
            sizeof(SockAddr.sin_zero));

        int nSendBytes = ::sendto(nSendFD, (void*)kpucInPacket, sstBytes, 0,
            (struct sockaddr *) &SockAddrSend, sizeof(SockAddrSend));

        if (nSendBytes != sstBytes)
            ::err(errno, "sendto");
    }


Now I understand I'm breaking lots of abstractions/layers, but I do plan to 
clean that up a bit later. And I also understand that perhaps no one else in 
the world needs this functionality - although I can see a couple of other 
possible applications for it. 

The changes does seem to work, I'm able to receive the ethernet frame and also 
reinject it via the divert sockets - ping, ftp, etc. all work over the bridge 
when my test program is running. However, I'm finding that I'm losing/leaking 
mbufs. sbdrop will complain and panic that the sb_cc doesn't match up with what 
the mbuf chains says - usually the sb_cc will be larger by a couple of hundred 
bytes. Furthermore, a netstat -m will show that I have mbufs allocated to 
socket names and address even after the termination of the diverting program. 
This only seem to happen when I transfer over ftp a really large file (>100M) 
at high speed (full line speed of a 100Mbps network). Ping and ftping small 
files do not seem to cause the mbuf leakage.

So my question is, does anyone see where I might be losing the mbufs - is there 
some mbufs that must be freed or not freed that I'm not aware of? I've never 
worked on the FreeBSD kernel before, so I'm not sure 100% sure how to correctly 
manage the mbufs. Any advise, tips, discussion, anything will be highly 
appreciated! =) If anyone needs any more clarification/information, just ask 
and I'll try my best to explain myself better.

Thanks!!
Bernie

----------------------------------------
This mail sent through www.mywaterloo.ca


More information about the freebsd-net mailing list