tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Lua in-kernel (lbuf library)



Hi Christoph,

On Mon, Oct 14, 2013 at 10:02 AM, Christoph Badura <bad%bsd.de@localhost> wrote:
> First, I find the usage of the "buf" terminology confusing.  In kernel
> context I associate "buf" with the file system buffe cache "buf" structure.
> Packet buffers a called "mbufs".  I would appreciate it if the terminology
> was consistent with the kernel or at least not confusing.

This is due my lack of creativeness =).. I'm quite open for naming suggestions.

> Also, having to switch mentally between zero-based arrays in the kernel C
> code and 1-based arrays in the Lua code make my head ache.

It's something that doesn't bug me so much.. But, if necessary it
could be changed to 0-based in this userdata.

> On Thu, Oct 10, 2013 at 03:15:54PM -0300, Lourival Vieira Neto wrote:
>> C API:
>>
>> lbuf_new(lua_State L, void * buffer, size_t length, lua_Alloc free, bool 
>> net);
>>
>> * creates a new lbuf userdatum and pushes it on the Lua stack. The net
>> flag indicates if it is necessary to perform endianness conversion.
>
> I what is "buffer" and how does it relate to mbufs?  How do I create a new
> "lbuf" from an mbuf? Or from an array of bytes?

Note, non-contiguous buffer still an open problem in lbuf. I don't
know if should use a ptrdiff_t  to pass the distance to 'next' field,
a 'next()' to return 'next' field or something else.

However, you could create a lbuf from a mbuf header as follows:

  lbuf_new(L, mbuf->m_data, mbuf->m_len, NULL, true);

or from an array:

  uint8_t array[ N ];
  lbuf_new(L, (void *) array, N, NULL, false); // 'false' means 'use
the platform endianess'

Then, you could call a Lua function passing this lbuf, for example:

  lua_getglobal(L, "handler");
  lbuf_new(L, mbuf->m_data, mbuf->m_len, NULL, true);
  lua_pcall(L, 1, 0, 0);

> In order to indicate that endianness conversion is necessary I need to
> know the future uses of the buffer.  Clairvoyance excepted, that is kinda
> hard.

It's a generic data structure that could be used to handle bit fields
or nonaligned data.

> If you are going to make the buffers endianness aware, why not record the
> endianness that the packet is encoded in.  And byteswapping can be
> performed automatically depending on the consumers endianness.  I think
> this way a lot of redundant code can be avoided.
>
> And you don't describe under what circumstances endianness convresion is
> performed.

Yes, mea culpa =(. I wasn't clear about that. 'net' flag was the way I
found to 'record' the buffer endianness. What means, true if the
buffer uses BE and false if it uses HE. It has the same semantics of
hton* and ntoh* functions. Don't know if it is better to pass the
endianness itself as a flag (e.g., enum { BIG_ENDIAN, LITTLE_ENDIAN,
HOST_ENDIAN }). What do you think?

So, if you set net flag true when you access a bit field, the
conversion to and from big endian, if needed, is done automatically
taking the smaller aligned set of bits. For example:

  buf:rawget(0, 9) ~> if net flag is *true*: takes 16 bits from
beginning of the buffer (as is); convert these 2 bytes from BE to HE
(if necessary); and returns these 2 bytes masked to preserve only the
most significant 9 bits (zeroing the remaining bits) and shifted to
LSB. If net is *false*: just returns the first 2 bytes masked and
shifted (without conversion). Then these 2 bytes are expanded to
lua_Number type (int64_t in kernel)

That is:

a) If net flag is _true_ and the platform is LE:
1- Takes 16 bits:
[ b0  | b1  | b2  | b3  | b4  | b5  | b6  | b7  ][ b8  | b9  | b10 |
b11 | b12 | b13 | b14 | b15 ]

2- Convert it to LE:
[ b8  | b9  | b10 | b11 | b12 | b13 | b14 | b15 ][ b0  | b1  | b2  |
b3  | b4  | b5  | b6  | b7  ]

3- Returns the first 2 bytes masked and shifted:
[ b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8 ][ 0 | 0 | 0 | 0 | 0 | 0 | 0 | b0 ]

b) If net flag is _false_ and the platform is LE:
1- Takes 16 bits:
[ b0  | b1  | b2  | b3  | b4  | b5  | b6  | b7  ][ b8  | b9  | b10 |
b11 | b12 | b13 | b14 | b15 ]

2- Returns the first 2 bytes masked and shifted:
[ b9  | b10 | b11 | b12 | b13 | b14 | b15 | b0  ][  0  | 0  | 0  | 0
| 0  | 0  | 0  | b8  ]

c) If net flag is _true or false_ and platform is BE:
1- Takes 16 bits:
[ b0  | b1  | b2  | b3  | b4  | b5  | b6  | b7  ][ b8  | b9  | b10 |
b11 | b12 | b13 | b14 | b15 ]

2- Returns the first 2 bytes masked and shifted:
[  0  | 0  | 0  | 0  | 0  | 0  | 0  | b0  ][ b1  | b2  | b3  | b4  |
b5  | b6  | b7  | b8  ]

>> Lua API:
>>
>> - array access (1)
>>
>> lbuf:mask(alignment [, offset, length])
>> buf[ix] ~> accesses 'alignment' bits from 'alignment*(ix -1)+offset' position
>>
>> e.g.:
>> buf:mask(3)
>> buf[3] ~> accesses 3 bits from bit-6 position
>
> What does that mean? Does it return the top-most 2 bits from the first
> byte plus the least significant bit fom the second byte of the buffer?

It means the least-most 2 bits from the first byte and the LSB from the second.

> What is 'length' for?

Offset and length could be used to impose boundaries to the mask. For
example, if you want to analyse a segment of the buffer that is
organized in a logical array of 2 bytes starting from the second byte
and that has 3 elements, you could do: buf:mask(16, 8, 3).

> How does endianness conversion fit in?

Endianness conversion is done using the smaller aligned amount of
bits; in this case, 1 byte, which does not applies to endianness.

>> - array access (2)
>>
>> buf:mask{ length_pos1, length_pos2, ... }
>> buf[ix] ~> accesses 'length_pos(ix)' bits from 'length_pos1 + ...
>> length_pos(ix-1)' position
>>
>> e.g.:
>> buf:mask{ 2, 2, 32, 9 }
>> buf[2] ~> accesses 2 bits from bit-2 position
>
> What exactly would "buf[3]" return.  Please be explicit in whether you are
> counting byte offsets or bit offsets.  I can't figure that out from your
> description.

It would return 32 bits (converted or not, depending on 'net' flag)
from bit-4 (MSB-ordered).
mask{ ... } receives bit offsets and array access receives mask field
index. I'm always counting bit offsets. Bytes are only used to
endianness conversion.

> Personally, the idea of making array access to the buffer depend on
> state stored in the buffer does not look appealing to me.  It prevents
> buffers to be passed around because consumers don't know what they will
> get back on array access.

I think it could be useful to access nonaligned and aligned data
easily without caring about naming fields.

>> buf:mask{ field = { offset, length }, ... }
>> buf.field ~> 'field.length' bits from 'offset' position
>
> This actually makes some sense to me.

=)

>> buf:segment(offset [, length])
>> returns a new lbuf corresponding a 'buf' segment.
>
> What is a a 'segment' actually?

Segment is a sub-buffer. You could use just a portion of a main buffer
with another mask (e.g., to dissect a payload).

>> - mask reusing
>> lbuf.mask{ ... }
>
> This makes sense again...

=)

>> function filter(packet)
>>   packet:mask(ethernet_mask)
>>   if packet.type == 0x88CC then
>>     lldp_pdu = packet.segment(payload_offset):mask(lldp_mask)
>>     if packet.version < 1 return DROP end
>>   end
>>   return PASS
>> end
>
> ... except the code seems to be not runnable. Where does 'payload_offset'
> come from?

It's a variable which could be set by the script itself or loaded by
the C module. I could use the value itself (like 0x88CC), but I just
wanted to save the time of reading the standard.

> And don't you mean lldp_pdu.version?

Yes, sorry about that.

> I find it not helpful when the examples do not actually work.

Well, the library it is not ready yet. This example is just a draft to
discuss a concept. However, this fragment should be runnable when the
lib is implemented (except by packet.version mistake).

> --chris

Regards,
-- 
Lourival Vieira Neto


Home | Main Index | Thread Index | Old Index