This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: HCI Bluetooth socket bind error on an arm crosscompiled environment
Type: behavior Stage:
Components: Extension Modules Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Thomas.Chiroux
Priority: normal Keywords: patch

Created on 2015-02-11 15:50 by Thomas.Chiroux, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
bluetooth_bind_arm.patch Thomas.Chiroux, 2015-02-11 15:50 patch file
Messages (1)
msg235751 - (view) Author: Thomas Chiroux (Thomas.Chiroux) * Date: 2015-02-11 15:50
This bug bellow occurs only on my crosscompiled environment on arm (marvell armada 166): arm-pxa168-linux-gnueabi
It does not seems to be a cross-compile issue: python compiles without problem and all unittests pass on the target device.

description and first clues
---------------------------

The problem is easyly reproducted using this script:

    #!/usr/bin/python
    import socket
    sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI)
    sock.bind((0,))

which raises the following exception when run on the target device:

    Traceback (most recent call last):
      File "./test_bt.py", line 4, in <module>
         sock.bind((0,))
    OSError: [Errno 22] Invalid argument

This does not give much clues, but strace does (i've filtered to display the
two significant parts of strace)

    socket(PF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, 1) = 3
    bind(3, {sa_family=AF_UNSPEC, sa_data="\0\0\360\35\251\266s\316(U\3\0\0\0"}, 6) = -1 EINVAL (Invalid argument)


(on a working environment, including arm, like a raspberry pi, strace gives the following result (and no traceback of course):

    socket(PF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, 1) = 3
    bind(3, {sa_family=AF_BLUETOOTH, sa_data="\0\0\0\0\0\0X\352\243\266\0\24\265\266"}, 6) = 0

So, on the armada166, between the socket creation and the bind we lost the socket family (AF_UNSPEC instead of AF_BLUETOOTH).

And That's why bind returns invalid argument.

socketmodule and PyArg_ParseTuple
---------------------------------

Now let's look at Modules/socketmodule.c:

After some digging, i've found that the problem is in getsockaddrarg, in the AF_BLUETOOTH / BTPROTO_HCI case
and more precisely on this line:

    https://hg.python.org/cpython/file/ab2c023a9432/Modules/socketmodule.c#l1449

reproducted here:

    if (!PyArg_ParseTuple(args, "i", &_BT_HCI_MEMB(addr, dev))) {

When we execute the PyArg_ParseTuple, the addr->hci_family is crunched (by zeros with my previous python sample).

At this same place, i've done the following test:

    char buffer[8];
    memset(buffer, 0x55, 8);
    if (!PyArg_ParseTuple(args, "i", buffer) {
        PyErr_SetString(PyExc_OSError, "getsockaddrarg: "
                        "wrong format");
        return 0;
    }
    printf("CL: %d %d %d %d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
     
    memset(buffer, 0xAA, 8);
    if (!PyArg_ParseTuple(args, "i", buffer+1) {
        PyErr_SetString(PyExc_OSError, "getsockaddrarg: "
                        "wrong format");
        return 0;
    }
    printf("CL+1: %d %d %d %d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
     
                      
    memset(buffer, 0xBB, 8);
    if (!PyArg_ParseTuple(args, "i", buffer+2) {
        PyErr_SetString(PyExc_OSError, "getsockaddrarg: "
                        "wrong format");
        return 0;
    }
    printf("CL+2: %d %d %d %d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
                       
    memset(buffer, 0xcc, 8);
    if (!PyArg_ParseTuple(args, "i", buffer+3) {
        PyErr_SetString(PyExc_OSError, "getsockaddrarg: "
                        "wrong format");
        return 0;
    }
    printf("CL+3: %d %d %d %d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);

and the result is:

    CL: 0 0 0 0 85 85 85 85
    CL+1: 0 0 0 0 170 170 170 170
    CL+2: 0 0 0 0 187 187 187 187
    CL+3: 0 0 0 0 204 204 204 204

(WTF ??)

in a working environnement (tested on raspberry B+ / python 3.4.2 locally compiled) result is what we should expect:

    CL: 0 0 0 0 85 85 85 85
    CL+1: 170 0 0 0 0 170 170 170
    CL+2: 187 187 0 0 0 0 187 187
    CL+3: 204 204 204 0 0 0 0 204

So on my box if PyArg_ParseTuple write on &addr->hci_dev if write 4 bytes from &addr->hci_family which is 2 bytes BEFORE &addr->hci_dev

At this time I can not understand how it's possible.

Remarks and patch
-----------------

Now I have several remarks and a working patch.

* remark/question 1: why does PyArg_ParseTuple parse an int when addr->hci_dev is an unsigned short ?
even in normal situation, when it works, writing on &addr->hci_dev overflow on the next two bytes which are btw addr->channel (more on that later)
 
* remark/question 2: i've tried to dig more deeply inside PyArg_ParseTuple and found another odd thing, but I did not try to change it without knowing what I do:

in Python/getargs.c, in convertsimple, int parsing result is not casted before returned:

here: https://hg.python.org/cpython/file/ab2c023a9432/Python/getargs.c#l690

(ival is a long). In all other cases (short, unsigned short, char, usigned char), they are casted before return.
[disclosure: i've tested to add the cast and relaunched my bind test, it did not change anything, but it's still strange for me]

* Now a working patch: here below and attached a working patch which results on a good socket bind, but now completely satisfiying:

    --- Python-3.4.2/Modules/socketmodule.c 2014-10-08 10:18:15.000000000 +0200
    +++ CC_PYTHON/Python-3.4.2/Modules/socketmodule.c   2015-02-11 15:42:35.173455634 +0100
    @@ -1446,11 +1446,12 @@ getsockaddrarg(PySocketSockObject *s, Py
                     return 0;
     #else
                 _BT_HCI_MEMB(addr, family) = AF_BLUETOOTH;
    -            if (!PyArg_ParseTuple(args, "i", &_BT_HCI_MEMB(addr, dev))) {
    +            if (!PyArg_ParseTuple(args, "H", &_BT_HCI_MEMB(addr, dev))) {
                     PyErr_SetString(PyExc_OSError, "getsockaddrarg: "
                                     "wrong format");
                     return 0;
                 }
    +            _BT_HCI_MEMB(addr, channel) = HCI_CHANNEL_RAW;
     #endif
                 *len_ret = sizeof *addr;
                 return 1;


in short: I parse now an unsigned short instead of parsing an int which gives me a two bytes long elements which is stored well
on addr->hci_dev without overloading addr->hci_family.

But this modification alone is not enough: addr->hci_channel needed a good value.
that's why i added _BT_HCI_MEMB(addr, channel) = HCI_CHANNEL_RAW; 
which sets addr->hci_channel to zero.
(without this line, any value could be here)

And that led me to another question/problem: how is hci_channel normally handled ?
It does not seems to be a valid parameter of bind; behaviour without the patch will
erase hci_channel while storing int value in hci_dev, so theorically we can assign
a well defined int value in our bind method to both assign the wanted value in
hci_dev and hci_channel, but it does not seems to be a proper way to do it.
History
Date User Action Args
2022-04-11 14:58:12adminsetgithub: 67632
2015-02-11 16:05:05Thomas.Chirouxsettitle: HCI Bluetooth socket bind error on an arm crosscompiler environment -> HCI Bluetooth socket bind error on an arm crosscompiled environment
2015-02-11 15:50:15Thomas.Chirouxcreate