socketcan_第3部分可以使用socketcan进行逆向工程

socketcan

介绍 (Introduction)

The objective of this article is to introduce some practical examples and approaches for reverse engineering a vehicle CAN bus network, using low cost tools and open source software.

本文的目的是介绍一些使用低成本工具和开源软件对汽车CAN总线网络进行反向工程的实用示例和方法。

In Part 1 we created a Virtual CAN interface (vcan0) in our Linux enviornment and used that interface to send and receive a CAN frame, using candump and cansend.

第1部分中,我们在Linux环境中创建了虚拟CAN接口( vcan0 ),并使用该接口通过candump和cansend发送和接收CAN帧。

In Part 2 we developed a very small C application which provides minimum code for using SocketCAN to send a CAN frame.

第2部分中,我们开发了一个非常小的C应用程序 ,它为使用SocketCAN发送CAN帧提供了最少的代码。

In this part (Part 3) we will extend the application in Part 2 to send a CAN frame at a specific time interval, along with some data in the CAN frame.

在这一部分(第3部分)中,我们将扩展第2部分中的应用程序,以在特定的时间间隔发送CAN帧以及CAN帧中的一些数据。

simple_can_tx (simple_can_tx)

I’ve created a github project called simple_can_tx which is a bit more structured than super_easy_can_tx.

我创建了一个名为simple_can_tx的github项目,该项目比super_easy_can_tx更加结构

Choose a favourite location for your development and download the zip, or alternatively clone the repository.

为您的开发选择一个喜欢的位置并下载zip,或者克隆存储库。

$ cd ~/MyDevlopmentFolder
$ git clone https://github.com/davidevansg/simple_can_tx.git

Let’s look at the contents of simple_can_tx using the tree commend

让我们用推荐的看一下simple_can_tx的内容

$ cd simple_can_tx
$ tree
Image for post

Comparing to super_easy_can_tx we have three more files (can.c, can.h and main.h).

super_easy_can_tx相比,我们还有三个文件(can.c,can.h和main.h)。

可以 (can.c)

It’s worth looking at can.c during this bit.

这段时间值得看一下can.c。

We have covered setting up the CAN interface in Part 2 in super_easy_can_tx, what I want to focus on is using a thread in our application. If you are not familiar with Threads, I would start here.

我们已经在super_easy_can_tx的 第2部分中介绍了如何设置CAN接口,我要重点关注的是在我们的应用程序中使用线程。 如果您不熟悉Threads,我将从这里开始

I have made a function (CANThrInit) which is responsibe for creating a thread. A pthread variable (thr_tick) is created along with a pointer to a start routine (function) ‘Thr_Tick’ which will execute this functionality under a new process. A timer is created (line 14 below) and configured to run at 100,000,000 nanoseconds (100 milliseconds) (line 19 below). This will execute the functionality ‘tick_tx’ (line 34 below) every 100 milliseconds up until there is either an issue with the timer, or if there is a ‘reason_to_quit’ (line 25 below), which will then stop and quit the application.

我做了一个函数( CANThrInit ),它负责创建线程。 将创建一个pthread变量( thr_tick )以及一个指向启动例程(函数)' Thr_Tick '的指针,该例程将在新进程下执行此功能。 创建了一个计时器( 下面的第14行 ),并将其配置为以100,000,000纳秒(100毫秒)( 下面的第19行 )运行。 这将每隔100毫秒执行一次功能' tick_tx '( 下面的第34行 ),直到计时器出现问题,或者如果有' reason_to_quit '( 下面的第25行 ),然后它将停止并退出应用程序。

static uint8_t CANThrInit()
{
    if (pthread_create(&thr_tick, NULL, Thr_Tick, NULL))
    {
        perror("Error creating thread\n");
        return FAILURE;
    }


    return SUCCESS;
}


void *Thr_Tick(void *ptr)
{
    int timer_fd = timerfd_create(CLOCK_REALTIME, 0);
    unsigned long long missed;
    struct itimerspec itval;


    itval.it_interval.tv_sec = 0;
    itval.it_interval.tv_nsec = (100 * (NS_TO_MS)); // 100 milliseconds
    itval.it_value.tv_sec = itval.it_interval.tv_sec;
    itval.it_value.tv_nsec = itval.it_interval.tv_nsec;
    int timer_status = timerfd_settime(timer_fd, 0, &itval, NULL);


    /* While the timer is OK and we have no reason to  */
    while ((timer_status != -1) && (reason_to_quit != 1) )
    {
        timer_status = read(timer_fd, &missed, sizeof(missed));
        if (timer_status < 0)
        {
            printf("breaking\n"); 
            break;
        }


        tick_tx(); // call functionality per ~100ms
    }


    printf("out of loop\n");
    CANClose();


    return NULL;
}

每100毫秒会发生什么? (What happens every 100 milliseconds?)

Every 100 milliseconds the function tick_tx is called (line 1 below), which declares can_frame variable called ‘frame’ (line 4 below), this variable will be used both by the function ‘AssembleFrame’ (line 7 below) to populate the contents of the can_frame (can_id, can_dlc and data) which was introduced in Part 2 and the function ‘SendFrame’ (line 11 below) sends the frame to the socket, and to the Virtual CAN (vcan0) interface.

每隔100毫秒,函数tick_tx被调用( 下面的第1行 ),它声明can_frame变量称为“ frame” ( 下面的第4行 ),函数“ AssembleFrame ”( 下面的第7行 )将同时使用此变量来填充其中的内容。在第2部分中介绍的can_frame(can_id,can_dlc和数据)和函数“ SendFrame”( 下面的第11行 )将帧发送到套接字, 并发送到虚拟CAN( vcan0)接口。

static void tick_tx()
{
    // 100ms tick
    struct can_frame frame;
    uint8_t status;
    uint8_t ret;
    if(AssembleFrame(&frame) != SUCCESS)
    {
        return;
    }
    if(SendFrame (&frame) != SUCCESS)
    {
        return;
    }
    PrintFrame(&frame);
}

Assemble Frame

组装框架

In this application, we are going to put a sequence counter value in one of the data bytes of the CAN frame.

在此应用中,我们将在CAN帧的数据字节之一中放入一个序列计数器值。

A sequence counter can used as a means of ensuring data integrity. As an example, if you are expecting to receive this value every 100 milliseconds (0.1 seconds) and the previous value was 1, you can expect that in another 100 milliseconds time you can expect to receive a value or 2.

序列计数器可以用作确保数据完整性的一种手段。 例如,如果您希望每100毫秒(0.1秒)接收一次该值,而先前的值为1,则可以预期在另外100毫秒时间内可以接收到该值或2。

A sequence counter variable (seq_ctr) is declared as static (see line 3 below), this means that the value for seq_ctr is retained after this function has finished executing. We expect to call this function many-many times and by declaring seq_ctr as static we can retain the value within the function AssembleFrame and will not be accessible from other functions, an alternative would be to declare this as a global variable, but as seq_ctr is only intended to be used within this function (AssembleFrame), this would be considered good practice.

序列计数器变量( seq_ctr )被声明为静态的( 请参见下面的第3行 ),这意味着seq_ctr的值在该函数完成执行后保留。 我们希望多次调用此函数,并且通过将seq_ctr声明为静态,我们可以将值保留在函数AssembleFrame中,并且无法从其他函数访问,另一种方法是将其声明为全局变量,但由于seq_ctr为仅打算在此函数( AssembleFrame )中使用,这将被视为良好做法。

The AssembleFrame function receives a pointer to can_frame variable (line 4 above) and checks if the value is a null pointer (read more here). This is very unlikely to happen, but is good practice to check.

AssembleFrame函数接收一个指向can_frame变量的指针( 上面的第4行 ),并检查该值是否为空指针( 在此处了解更多信息 )。 这种情况极不可能发生,但是要检查是一种很好的做法。

As we are dealing with a pointer to a value, instead of the value (like we were in super_easy_can_tx), we must de-reference the fra pointer to access the values within the structure, this involves using the (*) operator (see line 7 and 8 below).

在处理指向值的指针而不是值时(就像在super_easy_can_tx中一样 ),我们必须取消引用fra指针以访问结构中的值,这涉及到使用(*)运算符( 请参见下方的7和8 )。

We are still setting the can_id and can_dlc in a similar way shown in Part 2, the only difference is that we are dealing with a pointer in this version.

我们仍然按照第2部分中所示的类似方式设置can_id和can_dlc,唯一的区别是我们正在处理此版本中的指针。

I also mentioned in Part 2 that there was some undefined/uninitialized values in the data array, an example can be seen in the image below. In the interests of always knowing what is in our data array, it’s best to set some default values or ensure that the data bytes are set.

我还在第2部分中提到过,数据数组中有一些未定义/未初始化的值,可以在下图中看到一个示例。 为了始终了解数据数组中的内容,最好设置一些默认值或确保设置了数据字节。

Image for post
Undefined / uninitialised data values
未定义/未初始化的数据值

The function memset (see line 10 below) is used here to effectively initialise this memory with some value. The man(ual) for memset explains this function will fill the first ’n’ bytes ((*fra).can_dlc) of an area of memory, which is the start of the data array for the frame (&(*fra).data[0]), with a value of 0x00 (0). This is pretty much the same as running the loop (see line 13–15 below). This will result in all of the bytes in the data array being set to 0x00, as seen in the image below.

这里使用函数memset ( 请参阅下面的第10行 )以一些值有效地初始化此内存。 memset的管理员解释了此函数将填充内存区域的前'n'个字节( (* fra).can_dlc ),这是帧( &(* fra)的数据数组的开始。 data [0] ),其值为0x00(0)。 这与运行循环几乎相同( 请参见下面的第13-15行 )。 如下图所示,这将导致数据数组中的所有字节都设置为0x00。

Image for post
Setting data array to an initial / default value
将数据数组设置为初始值/默认值

The final action will be to put the sequence counter value (seq_ctr) into the first byte of the CAN frame data array, and then increment the value (for use next time around).

最后的动作是将序列计数器值( seq_ctr )放入CAN帧数据数组的第一个字节中,然后递增该值(以备下次使用)。

The function finishes and then sends the CAN frame, which we have covered already in Part 2.

函数完成,然后发送CAN帧,这已在第2部分中进行了介绍

static uint8_t AssembleFrame(struct can_frame *fra)
{
    static uint8_t seq_ctr = 0; // sequence counter


    if(fra != NULL)
    {
        (*fra).can_id = 0x100;
        (*fra).can_dlc = 0x08;


        memset(&(*fra).data[0], 0x00, (*fra).can_dlc);
        /* Pretty much the same as this... */
        /*
            for(int i = 0; i < (*fra).can_dlc; i++)
            {
                (*fra).data[i] = 0x00;
            }
        */


        (*fra).data[0] = seq_ctr++;
        return SUCCESS;
    }
    else
    {
        printf("fra null\n");
        return FAILURE;
    }
}

构建simple_can_tx (Building simple_can_tx)

To build this application, navigate to the directory and run the make command

要构建此应用程序,请导航到目录并运行make命令。

cd simple_can_tx
make
Image for post

Running the tree commend again we should now see an additional file name ‘simple_can_tx’ (see red in image below)

再次运行该树,我们现在应该看到一个附加文件名“ simple_can_tx”(请参见下图中的红色)

Image for post

This is our executable created from the source files (.c and .h)

这是我们从源文件(.c和.h)创建的可执行文件

I would recommend starting candump with the following command:

我建议使用以下命令启动candump:

$ (Terminal 1) candump -t d vcan0

Referencing the man(ual) for candump, the ‘-t’ (timestamp) flag with a value ‘d’ (delta) will print the difference in time between the current and previous CAN frame received.

参照手动进行candump ,值“ d ”(delta)的“ -t” (时间戳)标志将打印当前和先前接收到的CAN帧之间的时间差。

// $ (Terminal 2) cd simple_can_tx
$ (Terminal 2) ./simple_can_tx

Running the application for a few seconds, and pressing ‘ctrl+c’ to terminate the application will provide the following.

运行应用程序几秒钟,然后按“ ctrl + c”以终止应用程序,将提供以下内容。

Image for post

If you note the timestamps in the candump output, we are aiming for 100 millisecond (0.1 second) accuracy in our application. We are ‘mostly’ achieving this timing, and depending on your use case may be ‘good enough’, certainly for my activities it was more than suffucient for what I wanted to do. A simpler example of this output can be seen below.

如果您注意到candump输出中的时间戳,我们的目标是在我们的应用程序中达到100毫秒(0.1秒)的精度。 我们“ 主要在达到这个时机,并且取决于您的用例可能“足够好”,当然对于我的活动来说,这足以满足我的需求。 下面是该输出的一个简单示例。

Image for post

You will also note that the first byte of the CAN frame has a value of 0x00, followed by 0x01, 0x02 … 0x0F. This is the sequence counter incremeting each time. Additionally, you will note that all remaining bytes of the data array is set to 0x00 (0).

您还将注意到,CAN帧的第一个字节的值为0x00,后跟0x01、0x02…0x0F。 这是每次递增的序列计数器。 此外,您会注意到数据数组的所有剩余字节都设置为0x00(0)。

In the next part, we will encode a signal (vehicle_speed) into a CAN frame and use another application to decode and interpret that signal.

在下一部分中,我们将信号(vehicle_speed)编码为CAN帧,并使用另一个应用程序对该信号进行解码和解释。

翻译自: https://medium.com/swlh/part-3-can-bus-reverse-engineering-with-socketcan-4a43aca17179

socketcan

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值