NKE socket filter problem

Hello,


I'm having a problem when using a kernel extension that should filter network traffic.

- The kext (through the sftl_filter) starts to filter all packets, and sends them to a process in userspace

- The client performs some operations (in this case, it simply sends the packet data back to the driver unmodified)

- The driver re-injects the modified packet back into the network stream


Incoming data works great. However, uploading data larger than 500kb results in an abruptly closed connection. Smaller files can be uploaded without issue. This is via any protocol, tested on ftp, http, https, etc.


Simplified driver code:

errno_t tl_data_fn(void *cookie, socket_t so, const struct sockaddr *addr, mbuf_t *data, mbuf_t *control, sflt_data_flag_t flags, FilterSocketDataDirection direction) {   
    if (check_tag(data, gidtag, FILTER_TAG_TYPE, direction == FilterSocketDataDirectionIn ? IN_DONE : OUT_DONE)) {
        return 0;
    }
   
    if (!cookie) return result;
   
    filter_cookie *f_cookie = get_filter_cookie(cookie);

    FilterNotification notification; 
    if (direction == FilterSocketDataDirectionIn) {
        notification.event = FilterEventDataIn;
    } else {
        notification.event = FilterEventDataOut;
    }
    notification.socketId = (uint64_t)so;
    notification.inputoutput.dataSize = (uint32_t)mbuf_pkthdr_len(*data);
       
    mbuf_copydata(*data, offset, notification.inputoutput.dataSize, notification.inputoutput.data);
      
    ctl_enqueuedata(f_cookie->ctl_ref, f_cookie->ctl_unit, &notification, sizeof(FilterNotification), CTL_DATA_EOR);
   
    mbuf_freem(*data);
       
    if (control != NULL && *control != NULL)
        mbuf_freem(*control);
   
    return EJUSTRETURN;
}

errno_t tl_data_in_fn(void *cookie, socket_t so, const struct sockaddr *from, mbuf_t *data, mbuf_t *control, sflt_data_flag_t flags) {
    return tl_data_fn(cookie, so, from, data, control, flags, FilterSocketDataDirectionIn);
}

errno_t tl_data_out_fn(void *cookie, socket_t so, const struct sockaddr *to, mbuf_t *data, mbuf_t *control, sflt_data_flag_t flags) {
    return tl_data_fn(cookie, so, to, data, control, flags, FilterSocketDataDirectionOut);
}



errno_t ctl_send(kern_ctl_ref ctl_ref, u_int32_t unit, void *unitinfo, mbuf_t m, int flags) {    
    FilterClientResponse response;
    mbuf_copydata(m, 0, sizeof(response), &response);
    
    mbuf_t data;
    mbuf_allocpacket(MBUF_WAITOK, response.dataSize, NULL, &data);
    mbuf_copyback(data, 0, response.dataSize, response.data, MBUF_WAITOK);
    set_tag(&data, gidtag, FILTER_TAG_TYPE, response.direction == FilterSocketDataDirectionIn ? IN_DONE : OUT_DONE);
    
    if (response.direction == FilterSocketDataDirectionIn) {
        sock_inject_data_in((socket_t)response.socketId, NULL, data, NULL, 0);
    } else {
        sock_inject_data_out((socket_t)response.socketId, NULL, data, NULL, 0);
    }
    
    mbuf_freem(m);
    return 0;
}


tl_data_in_fn and tl_data_out_fn functions are used in sftl_filter. ctl_send is used in kern_ctl_reg as ctl_send_func.



Simplified userspace process code:

int s = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);

//connect to driver

FilterNotification notification;
while (recv(s, &notification, sizeof(FilterNotification), 0) == sizeof(FilterNotification)) {
    FilterClientResponse response;
    response.socketId = notification.socketId;
    response.direction = (notification.event == FilterEventDataIn) ? FilterSocketDataDirectionIn : FilterSocketDataDirectionOut;
    response.dataSize = notification.inputoutput.dataSize;
    memcpy(response.data, notification.inputoutput.data, notification.inputoutput.dataSize);
    send(s, &response, sizeof(response), 0);
}


Any help / advice would be appreciated. Kext and userspace process repository.


Thank you

I don’t see any attempt to handle send-side flow control here. Without that a file upload can easily eat up all of the available mbufs, and things will go badly from there.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Can you explain more about send-side control? NKE example doesn't provide this =(

It is a strict rule that the kernel must not accept arbitrary amounts of data. If it does then the generator of that data (in the networking case this is a user process on the send side and the network on the receive side) can cause the kernel to consume arbitrary amounts of memory, and that would be bad. Thus, as a socket filter NKE you must implement some sort of strategy for limiting your memory use.

If you were working with packets, which the network is allowed to drop, that’s easy: You can just drop these packets. However, if you’re creating a socket filter then you can’t just drop data. You must implement both send- and receive-side flow control.

Having said that, I’m not entirely sure how you go about doing this in a socket filter. For the moment, let’s just consider the send side. In that case flow control is asserted by the socket buffer. You can see the code for doing this in

xnu/bsd/kern/uipc_syscalls.c
(look for the code around the
retry_space
label). Once the kernel has decided there’s enough space, it calls the socket filters (look for the call to
sflt_data_out
) and then puts the data into the socket. The problem is that your socket filter has no access to the socket buffer, and thus has no way of artificially ‘consuming’ space in the buffer to account for the data thats in transit to and from user space.

And that is, alas, about as far as I can take things in the context of DevForums. My recommendation is that you open a DTS tech support incident, which will allow me to spend time doing further research.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
NKE socket filter problem
 
 
Q