hartkopp/can-isotp

Idea/Enhancement: Anonym/discarding bind addresse

Closed this issue · 18 comments

Hi once again :)

I got the following Use-Case:

  • 8 ISO-TP sockets bound to the common OBD/UDS diagnostic addresses, TX: 0x7E0 to 0x7E7 and RX: 0x7E8 to 0x7EF.
  • I want to send a functional request using 0x7DF as TX address.

This is problematic as it forces me to open another socket to send those functional requests. That socket needs an RX address for which I have to pick some, hopefully not otherwise used, address/ID, while it actually should never receive something.

Is there/could there be a way to bind to a "I-don't-care" address, have an "only-sending" socket mode or anything along those lines?

There is generally a difference between establishing a point-to-point communication which needs exactly two CAN IDs, and sending a functional request. The isotp socket is intended for the point-to-point communication and for the functional addressing the CAN_RAW socket is recommended.

Is there no problem with mixing a few ISO-TP and RAW CAN sockets? I was kinda worried a "general" RAW socket could interfere with the ISO-TP communication.

You can open as much CAN sockets as you want from different CAN protocol types. In all cases you need to check whether you make use of the right CAN-IDs resp. CAN-ID pairs so that they don't interfere.
When you write content on a CAN-ID that is received by an isotp socket (due to its configuration at bind time) this will have an impact there.
But e.g. you can use isotpsniffer and isotpdump simultaneously on your host, where isotpsniffer opens two CAN_ISOTP sockets and isotpdump opens a CAN_RAW socket.

But if I got a few ISO-TP sockets sending and receiving while also having a RAW socket that just writes, I will have to flush its buffer occasionally or set it up in a way that filters all the ISO-TP traffic? Sorry for the questions, I am struggling a little to wrap my head around what socket gets what message (possibly duplicates for all sockets that are interested?!).
Nonetheless, this isn't an issue of the module, so I am closing it, thanks!

NP. We can continue the discussion here - unless we want to shift to the Linux-CAN mailing list ...
You would only use the CAN_RAW socket to send the functional request - and you only need to filter for CAN-IDs where you expect a reply to a functional request. AFAIK functional addressing is something that is working on a single CAN frame PDU without a transport protocol. So it is something which happens on the bus 'independently' to CAN_ISOTP sockets - as long as you don't mix up the CAN-IDs ;-)

All functional requests via OBD I have seen so far are <7B, so they fit into a single PDU, but still use the ISO-TP style SF-syntax. UDS requests are another story. Some requests there can be quite long, but I am not yet sure whether a functional addressing is allowed for those.

That is why I came up with using the ISO-TP module (that works awesome so far and safes me a lot of work :)) in the first place. Thanks to it, I don't have to deal with generating padding bytes and the ISO-TP style header with the SF flag and payload-length myself in userspace. Other than the requests, I have seen responses longer than 7B in OBD, e.g. Vehicle Identification Number, which gets sent using ISO-TP, but on physical addresses.

It feels kind of mixed up to have to build a ISO-TP SF manually for functional requests via RAW socket, while having to deal with the possibly multi-frame responses the ISO-TP way, with an ISO TP socket that resembles the payload. I guess the functional requests could be seen as a form of multicast and are hence something different than the 1-to-1 communication of ISO-TP, but they still use the ISO-TP header, which is somewhat confusing.

I guess the functional requests could be seen as a form of multicast and are hence something different than the 1-to-1 communication of ISO-TP, but they still use the ISO-TP header, which is somewhat confusing.

Yes functional requests are 1-to-N, and therefore you can not have a segmented transfer with flow control frames, which always implies a point-to-point (1-to-1) communication.

Btw. a single frame can be received (on ISO-TP sockets) without sending out a flow control frame. I'm not familiar with the CAN-IDs that are used for functional replies: Are these the same CAN-IDs that are also used for normal addressing?

Yes, both functional and physical addressing use the CAN 11bit or 29bit IDs. I could send out the functional request via ISO-TP socket, as it will be short enough to be transmitted as a single CAN frame without flow control, like you said with regard to receiving.

But I am forced to configure a specific rx ID for the socket used for the broadcast, although the actual receiving of any responses that functional broadcast triggered is handled by specific 1-on-1 ISO-TP sockets.

I will have to read more about UDS to know whether there is a scenario, where a functional broadcast needs to be done, that contains data that is to be split into multi frames the ISO-TP way... but that would admittedly call for some very weird flow control handling in a 1-to-N scenario.

Yes, both functional and physical addressing use the CAN 11bit or 29bit IDs. I could send out the functional request via ISO-TP socket, as it will be short enough to be transmitted as a single CAN frame without flow control, like you said with regard to receiving.

No. My idea was to only receive the ECU-specific reply if the CAN-ID for answers would be the same for this ECU in either functional and normal addressing.

I think sending a functional request is done by a single CAN-ID (0x7DF ??) - therefore I would recommend a CAN_RAW socket for this.

But I am forced to configure a specific rx ID for the socket used for the broadcast, although the actual receiving of any responses that functional broadcast triggered is handled by specific 1-on-1 ISO-TP sockets.

As written above. If the CAN-IDs for (functional and normal addressing) replies are identical you could read the SF PDUs from the isotp socket too.

I will have to read more about UDS to know whether there is a scenario, where a functional broadcast needs to be done, that contains data that is to be split into multi frames the ISO-TP way... but that would admittedly call for some very weird flow control handling in a 1-to-N scenario.

No need. The functional addressing and the responses do not have flow control. Everything is done with single CAN frames.

But it would be cool, if you could share your findings in the end.

As written above. If the CAN-IDs for (functional and normal addressing) replies are identical you could read the SF PDUs from the isotp socket too.
No need. The functional addressing and the responses do not have flow control. Everything is done with single CAN frames.

The responses are no single frames, not necessarily. My current solution is working, using a mix of RAW and ISO-TP sockets as suggested in your very first response.

Guess we are talking different directions...

I send a functionally addressed request via RAW socket to 0x7DF (11-bit version). OBD/UDS defines at most 8 ECUs can respond. Those can be physically addressed via 0x7E0 to 0x7E7 and they respond back to the tester/my app via 0x7E8 to 0x7EF. I set up 8 ISO-TP sockets with the TX addresses set to 0x7E0 to 0x7E7 and RX addresses to 0x7E8 to 0x7EF. Now if I send a functional request via RAW socket on 0x7DF, any ECU that got something to respond, responds to its corresponding ISO-TP socket (incoming 0x7E8 to 0x7EF, flow control out on 0x7E0 to 0x7E7).
Second version, also working, is to swap the RAW socket with yet another ISO-TP socket and just bind its RX address to something unused. As any 0x7DF addressed functional request I transmit is <7B, the RX address is never used for flow control anyway. The responses to the ECU-specific sockets (physically addressed), triggered by the functionally addressed "broadcast", can very well be >7B and hence need to have flow control on an N-to-N base.

Both these approaches work, but with both I have to make sure the RAW or ISO-TP socket for functional requests never receives anything/is filtered. Using the RAW approach, my current favorite because I don't need to occupy a random ID, still forces me to build the ISO-TP header for a functional request, although it's always an SF-header, manually. Calculating that one byte into a raw frame isn't exactly hard, but I thought there might be a way to do this with the ISO-TP module, without having to make sure, the RX address I bind it to, isn't used by anything.

Thanks for the explanation!

Both these approaches work, but with both I have to make sure the RAW or ISO-TP socket for functional requests never receives anything/is filtered. Using the RAW approach, my current favorite because I don't need to occupy a random ID, still forces me to build the ISO-TP header for a functional request, although it's always an SF-header, manually. Calculating that one byte into a raw frame isn't exactly hard, but I thought there might be a way to do this with the ISO-TP module, without having to make sure, the RX address I bind it to, isn't used by anything.

Ah, ok. I now get the point. It would be something like a new flag, e.g. CAN_ISOTP_FADDR_TX_ONLY where only the tp.tx_id is used at bind() time (no registering of a tp.rx_id) and where you only can send PDUs that are max 7 bytes => SF. Right?

So the only difference would be

  1. You would not need a CAN_RAW socket but only another CAN_ISOTP socket
  2. You only need to write up to 7 bytes to the socket and the CAN-ID and PCI byte with the length information is provided automatically

Exactly. My pros, for an implementation enabling a CAN_ISOTP_FADDR_TX_ONLY flag, are:

  • At the moment I need a RAW socket for something that could be considered ISO-TP as it has an ISO-TP style header/PCI.
  • Using a RAW socket, I need to build the PCI myself and have it filter all other traffic.
  • Using a ISO-TP socket right now, I need to make sure I bind to an RX address that isn't used. A new CAN_ISOTP_FADDR_TX_ONLY flag could change that.
  • Could be considered cleaner on my end/in my application/for OBD Use-Cases :)

Cons, that might contradict such a flag, are:

  • Technically a functional request in the OBD style isn't ISO-TP, although it has a ISO-TP style header, as it is no 1-to-1 connection.(?!)
  • The only Use-Case I know of so far, is OBD on ISO-TP on CAN. I don't know how commonly ISO-TP sockets on Linux are used for that by others.
  • The current implementation, where I need a RAW socket or an ISO-TP socket with a random RX address, isn't exactly that big of a hassle.
  • Could be considered hacky in the kernel module, as it extends a defined protocol by a probably quite special feature.

Exactly. My pros, for an implementation enabling a CAN_ISOTP_FADDR_TX_ONLY flag, are:

Maybe the naming needs some rework too :-)

* At the moment I need a RAW socket for something that could be considered ISO-TP as it has an ISO-TP style header/PCI.

* Using a RAW socket, I need to build the PCI myself and have it filter all other traffic.

* Using a ISO-TP socket right now, I need to make sure I bind to an RX address that isn't used. A new `CAN_ISOTP_FADDR_TX_ONLY` flag could change that.

* Could be considered cleaner on my end/in my application/for OBD Use-Cases :)

I have one more point for the 'pro' list:
When you set the can_isotp_ll_options with mtu and tx_dl correctly you don't only get the PCI generation but also the defined CAN-ID is ensured every time and we can provide a test whether the content fits into a SF.

Cons, that might contradict such a flag, are:

* Technically a functional request in the OBD style isn't ISO-TP, although it has a ISO-TP style header, as it is no 1-to-1 connection.(?!)

* The only Use-Case I know of so far, is OBD on ISO-TP on CAN. I don't know how commonly ISO-TP sockets on Linux are used for that by others.

* The current implementation, where I need a RAW socket or an ISO-TP socket with a random RX address, isn't exactly that big of a hassle.

* Could be considered hacky in the kernel module, as it extends a defined protocol by a probably quite special feature.

Yes. You are right with all the cons, but we already have some testing and hacking options in the flags, like CAN_ISOTP_FORCE_?XSTMIN. The fact, that we provide a feature for the potential main use-case UDS/OBD has no drawbacks for other users. I just makes it more convenient for UDS/OBD programmers as it also makes clear what functional addressing means: You send a SF on a special CAN-ID and you can not expect any reply/content via this path/socket.

I also implemented a similar OBD ECU discovery mechanism in my OBD library for Scala, where I bind a CAN_RAW socket where I "broadcast" manually constructed SFs to 7DF and then setup CAN_ISOTP sockets for all CAN IDs that I receive a response from within a time frame. I then always use the discovered addresses and never again 7DF. Initially I also found it odd, that I can't just use CAN_ISOTP for functional addressing, but figured it's probably a weird special case not worth the effort.

But I agree, implementing this behind a flag will probably not hurt anyone, while making these things simpler/more robust for others.

Would the resulting change in the module be part of 5.10 or get backported to it?

Thanks for your feedback @pschichtel !
Yes, the main development path is now the mainline Linux kernel and I already wrote a patch for it.
As @Thomseeen wanted to do some tests, I created a branch here https://github.com/hartkopp/can-isotp/tree/sf_broadcast
Maybe this patch then can go into Linux 5.11 as the 5.10 kernel is already in 'rc6' which usually blocks new features.
I can send the patch for mainline development on the Linux-CAN mailing list today for review.

Edit: Discussion on mailing list is here:
https://lore.kernel.org/linux-can/AM9PR06MB7283E62151D53EF677B87517B5F20@AM9PR06MB7283.eurprd06.prod.outlook.com/T/#t

Really happy that @marckleinebudde applied the upstream patch for this enhancement:
https://lore.kernel.org/linux-can/184c943e-513a-4679-692a-bb3b1133ad8f@pengutronix.de/T/#t
Thanks all (incl Marc!) !!

It's not mainline, yet. I'll send a pull request on Friday. 😸

Yes. I know. But it is now in a stage that makes it very probable to show up in mainline in this way some day ;-)
So it can be seen and settled from the API standpoint, which is really nice!
Tnx!