Checksum calculation changes in DPDK v.19.11? - dpdk

Since upgrading from DPDK 19.08.2 to 19.11.8, UDP Rx packets are failing the IPv4 checksum calculation. We offload Tx checksum calculation to hardware, but on the Rx side we calculate checksum in software by calling rte_ipv4_cksum().
The NIC is a Intel X722 device.
If both Tx and Rx use DPDK 19.08.2, all is ok and rte_ipv4_cksum() returns 0xFFFF (as I expect).
If Tx uses DPDK 19.08.2 but Rx uses 19.11.8, rte_ipv4_cksum() returns 0 (which we count as a failure).
Could this be a bug or am I misunderstanding the checksum calculation?
I notice there is a difference in the return statement of rte_ipv4_cksum() for the two versions:
In 19.0.8:
static inline uint16_t
rte_ipv4_cksum(const struct rte_ipv4_hdr *ipv4_hdr)
{
uint16_t cksum;
cksum = rte_raw_cksum(ipv4_hdr, sizeof(struct rte_ipv4_hdr));
return (cksum == 0xffff) ? cksum : (uint16_t)~cksum;
}
In 19.11.8:
static inline uint16_t
rte_ipv4_cksum(const struct rte_ipv4_hdr *ipv4_hdr)
{
uint16_t cksum;
cksum = rte_raw_cksum(ipv4_hdr, sizeof(struct rte_ipv4_hdr));
return (uint16_t)~cksum;
}

The reason is that the return value of rte_ipv4_cksum(), for a valid checksum, has changed in DPDK 19.11.8.

Related

rte flow for arp packet with dpdk

Is there a way using rte_flow to send arp and ndp packet to specific rx queue with dpdk
In the rte_flow_item_type I don’t see an entry for arp or ndp
For ipv4 I did the following way
pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
pattern[0].spec = NULL;
pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
pattern[1].spec = NULL;
What do i ned to do for arp and ndp? There is no RTE_FLOW_ITEM_TYPE_ARP
Dpdk version: 19.11
NIC: mlx5, 100G mellanox card,
Since v18.05-rc1, there has been item type RTE_FLOW_ITEM_TYPE_ARP_ETH_IPV4. That being said, it might be unsupported by the PMD in question.
Consider matching on the EtherType field instead:
#include <rte_byteorder.h>
#include <rte_ether.h>
#include <rte_flow.h>
struct rte_flow_item_eth item_eth_mask = {};
struct rte_flow_item_eth item_eth_spec = {};
item_eth_spec.hdr.ether_type = RTE_BE16(RTE_ETHER_TYPE_ARP);
item_eth_mask.hdr.ether_type = RTE_BE16(0xFFFF);
pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
pattern[0].mask = &item_eth_mask;
pattern[0].spec = &item_eth_spec;
In what comes to NDP, perhaps it pays to check out RTE_FLOW_ITEM_TYPE_ICMP6_ND_*. Again, these might be unsupported by the PMD in question. If that is the case, consider the use of RTE_FLOW_ITEM_TYPE_ICMP6 to redirect all of ICMPv6 to the dedicated queue.
The default queue (with RSS disabled) for no match packet for all vendor PMD are queue 0. Expect for TAP PMD where multiple RX queue is supported by OS.
Since you have mentioned the DPDK version is 19.11, the best option is to make use rte_flow_item_eth to filter desired ether type. That is in DPDK 19.11
struct rte_flow_item_eth {
struct rte_ether_addr dst; /**< Destination MAC. */
struct rte_ether_addr src; /**< Source MAC. */
rte_be16_t type; /**< EtherType or TPID. */
};
so using the following code snippet you can achieve packet type steering to desired queue
struct rte_flow_attr attr = { .ingress = 1 };
struct rte_flow_item pattern[10];
struct rte_flow_action actions[10];
struct rte_flow_action_queue actionqueue = { .index = 1 };
struct rte_flow_item_eth eth;
struct rte_flow_item_vlan vlan;
struct rte_flow_item_ipv4 ipv4;
struct rte_flow *flow;
struct rte_flow_error error;
struct rte_flow_item_eth item_eth_mask;
struct rte_flow_item_eth item_eth_spec;
/* memset item_eth_mask and item_eth_spec to 0 */
item_eth_spec.hdr.ether_type = RTE_BE16(RTE_ETHER_TYPE_ARP);
item_eth_mask.hdr.ether_type = 0xFFFF;
/* setting the eth to pass all packets */
pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
pattern[0].spec = ð
pattern[0].last = &item_eth_mask;
pattern[0].mask = &item_eth_spec;
/* end the pattern array */
pattern[1].type = RTE_FLOW_ITEM_TYPE_END;
actions[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
actions[0].conf = &actionqueue;
actions[1].type = RTE_FLOW_ACTION_TYPE_END;
/* validate and create the flow rule */
if (!rte_flow_validate(port, &attr, pattern, actions, &error))
flow = rte_flow_create(port, &attr, pattern, actions, &error);
else
printf("rte_flow err %s\n", error.message);
note: this fails on Intel X710 and E810 NIC as it support VLAn+IP+UDP|TCP|SCTP+Inner.

Arduino SPI hangs during transfers

I'm in the middle of a project, and at a loss. I managed to get 2 Arduinos to talk to each other over SPI, but the slave stops during a series of transfers, seemingly without reason.
And what's really disturbing is that if I send more transfers from the master it continues the series, as if it was waiting to continue.
I'm thinking there's some acknowledgement or flag that stops code execution, but I have no idea.
#include <SPI.h>
boolean ack = 0;
#define ACK 2
byte buffer = 0;
byte rx = 0;
bool SSlast = HIGH;
byte clr = 0;
void stat_upd(byte dat, byte ric) {
Serial.println("---------------------------------------------");
Serial.println("Sent:");
Serial.println(dat, HEX);
Serial.println("Received:"); // 0x81 in teoria
Serial.println(ric, HEX);
return;
}
// Initialize SPI slave.
void SlaveInit(void) {
// Initialize SPI pins.
pinMode(SCK, INPUT);
pinMode(MOSI, INPUT);
pinMode(MISO, INPUT);
pinMode(SS, INPUT);
pinMode(ACK, OUTPUT);
// Enable SPI as slave.
SPCR = 0x6F;
clr = SPSR;
clr = SPDR;
SPI.begin();
}
// SPI Transfer.
byte SPItransfer(byte value) {
byte temp = 0;
SPDR = value;
// temp =SPI.transfer(value);
while (!(SPSR & (1 << SPIF)));
digitalWrite(ACK, LOW);
delay(1);
digitalWrite(ACK, HIGH);
delay(10);
return SPDR;
}
// The setup() function runs after reset.
void setup() { ///////////////// setup
Serial.begin(9600);
SlaveInit();
Serial.println("MC Initialized");
}
void loop() { ////////////// loop
// Slave Enabled?
if (!digitalRead(SS)) {
rx = SPItransfer(0x00);
stat_upd(0x00, rx);
rx = SPItransfer(0x08);
stat_upd(0x08, rx);
rx = SPItransfer(0x5a);
stat_upd(0x5a, rx);
rx = SPItransfer(0x5d);
stat_upd(0x5d, rx);
rx = SPItransfer(0x5c);
stat_upd(0x5c, rx);
rx = SPItransfer(0x5d);
stat_upd(0x5d, rx);
rx = SPItransfer(0x04);
stat_upd(0x04, rx);
rx = SPItransfer(0x00);
stat_upd(0x00, rx);
rx = SPItransfer(0x00);
stat_upd(0x00, rx);
rx = SPItransfer(0x80);
stat_upd(0x80, rx);
}
}
This is a bit of a guess, but my guesses wrt Arduino code are correct more often then not.
Check how you are sending the data via the Master.
As per this code, the slave waits for the SS pin to go low, then talks to the SPI and then clears the ack pin for 1 millisecond and then sends the communicaiton data via the serial port to the PC.
The problem I see here is that 1 millisecond is too small of a time frame for the master to detect it correctly.
So my guess is that, you have written the master code in such a manner, that it does not look at the ACK pin. Which means that the master will clear the SS pin, do the communication and then clear the SS pin immediately.
This is an issue because the slave talks to the serial port once this communication is done. Which means that when the master sets the SS pin low again, the slave might be sending data to the serial monitor, and may miss out on the communication altogether.
To fix this you need to change the slave code to continuously talk to the master while the SS pin is low, and only send the data back to the serial monitor when the SS pin becomes high.

Serial communication not working when class is instantiated in Arduino Environment

I'm trying to write code for my Arduino Mega to communicate with ADXL345 accelerometer using c++ style library.
This is my Accelerometer.h file:
#include <Wire.h>
#define ADXL345 (0x53) // I2C Address of ADXL345 accelerometer
struct Acceleration
{
float x;
float y;
float z;
};
class Accelerometer
{
public:
Accelerometer(void); // Constructor
Acceleration readData(void); // Read sensor data
private:
char buffer[6]; // Buffer to store data (x, y, z: LSB and MSB of each)
char DATA_FORMAT; // Address of DATA_FORMAT Register
char POWER_CTL; // Address of POWER_CTL Register
char DATAX0; // Address of X-Axis LSB Data
char DATAX1; // Address of X-Axis MSB Data
char DATAY0; // Address of Y-Axis LSB Data
char DATAY1; // Address of Y-Axis MSB Data
char DATAZ0; // Address of Z-Axis LSB Data
char DATAZ1; // Address of Z-Axis MSB Data
void writeToAccelerometer(char address, char value);
void readFromAccelerometer(char address, int numOfBytes);
};
And this is my Accelerometer.cpp file:
#include "Accelerometer.h"
#include <Wire.h>
Accelerometer::Accelerometer()
{
Wire.begin(); // Initialize I2C bus
writeToAccelerometer(DATA_FORMAT, 0x01); // +/- 4g range
writeToAccelerometer(POWER_CTL, 0x08); // Measurement Mode
DATA_FORMAT = 0x31; // Address of DATA_FORMAT Register
POWER_CTL = 0x2D; // Address of POWER_CTL Register
DATAX0 = 0x32; // Address of X-Axis LSB Data
DATAX1 = 0x33; // Address of X-Axis MSB Data
DATAY0 = 0x34; // Address of Y-Axis LSB Data
DATAY1 = 0x35; // Address of Y-Axis MSB Data
DATAZ0 = 0x36; // Address of Z-Axis LSB Data
DATAZ1 = 0x37; // Address of Z-Axis MSB Data
}
void Accelerometer::writeToAccelerometer(char address, char value)
{
Wire.beginTransmission(ADXL345); // start transmission to ADXL345
Wire.write(address); // send register address
Wire.write(value); // send value to write
Wire.endTransmission(); // end transmission
}
void Accelerometer::readFromAccelerometer(char address, int numOfBytes)
{
Wire.beginTransmission(ADXL345); // start transmission to ADXL345
Wire.write(address); // send register address
Wire.endTransmission(); // end transmission
Wire.beginTransmission(ADXL345); // start transmission to ADXL345
Wire.requestFrom(ADXL345, numOfBytes); // request some bytes from device
int i = 0;
while(Wire.available()) // while there is data
{
buffer[i] = Wire.read(); // receive a byte and save it in the buffer
i++;
}
Wire.endTransmission(); // end transmission
}
Acceleration Accelerometer::readData()
{
Acceleration R;
readFromAccelerometer(DATAX0, 6); // Read data from sensor
// Merge of data and conversion to int format
int x_acceleration = (((int)buffer[1]) << 8) | buffer[0];
int y_acceleration = (((int)buffer[3]) << 8) | buffer[2];
int z_acceleration = (((int)buffer[5]) << 8) | buffer[4];
R.x = x_acceleration*0.0078;
R.y = x_acceleration*0.0078;
R.z = x_acceleration*0.0078;
return R;
}
I'm using this library im my Arduino sketch with this code:
#include <Wire.h>
#include "Accelerometer.h"
Accelerometer accel;
Acceleration Racc;
void setup()
{
Serial.begin(9600); // Start serial for outbut at 9600 bps
}
void loop()
{
Racc = accel.readData();
// Print data
Serial.print("ACCELERATION - X: ");
Serial.print( Racc.x );
Serial.print(" G / Y: ");
Serial.print( Racc.y );
Serial.print(" G / Z: ");
Serial.print( Racc.z );
Serial.print(" G\n");
delay(100);
}
This code is compilated without erros. However, because of the instantiation of Accelerometer class, Serial communication is not working (I can't see any text in Serial Monitor). When I remove the class instance of the code (letting just Serial communication in code), I can see what is print in Serial Monitor.
Does anybody have any idea about what is going on? I would appreciate any help.
As the ADXL345 can operate either SPI or I2C it needs to be configured, as such. In your case of trying to use it as I2C, the CS of the ADXL345 need to be tied to 3.3V
I2C mode is enabled if the CS pinis tied high to VDD I/O. The CS pin
should always be tied high to VDD I/O or be driven by an external
controller because there is nodefault mode if the CS pin is left
unconnected. Therefore, not taking these precautions may result in an
inability to ommunicatewith the part.
Where in your above code I do not see any pins configured as output to drive the CS.
It has been my experience that when the I2C (wire.h) library is not properly connected to a device it can be blocking. Which is similar to your described symptom here.
You may want to reference your code against others, such as https://github.com/jenschr/Arduino-libraries/tree/master/ADXL345. which appears to fully support the many features of the ADXL345 over I2C

Why doesn´t my program send ARP requests (c++)?

I am learning low level sockets with c++. I have done a simple program that shall send an ARP request. The socket seems to send the packet but I cannot catch it with Wireshark. I have another small program that also sends ARP packets and those packets are captured by Wireshark (my program below is inspired by that program).
Have I done something wrong?
Removed code
EDIT
Removed code
EDIT 2
It seems that I also need to include ethernet header data in the packet, so I now make a packet containing ethernet header and ARP header data. Now the packet goes away and is captured by Wireshark. But Wireshark says it is gratuitous. As you can see, nor IP or MAC address of sender and receiver seem to have been set properly.
36 13.318179 Cimsys_33:44:55 Broadcast ARP 42 Gratuitous ARP for <No address> (Request)
EDIT 3
/*Fill arp header data*/
p.arp.ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
p.arp.ea_hdr.ar_pro = htons(ETH_P_IP);
p.arp.ea_hdr.ar_hln = ETH_ALEN; // Must be pure INTEGER, not called with htons(), as I did
p.arp.ea_hdr.ar_pln = 4; // Must be pure INTEGER, not called with htons(), as I did
p.arp.ea_hdr.ar_op = htons(ETH_P_ARP);
This code does not look quite right:
struct in_addr *s_in_addr = (in_addr*)malloc(sizeof(struct in_addr));
struct in_addr *t_in_addr = (in_addr*)malloc(sizeof(struct in_addr));
s_in_addr->s_addr = inet_addr("192.168.1.5"); // source ip
t_in_addr->s_addr = inet_addr("192.168.1.6"); // target ip
memcpy(arp->arp_spa, &s_in_addr, 6);
memcpy(arp->arp_tpa, &t_in_addr, 6);
In the memcpy You care copying 6 bytes out. However, you are taking an address of a pointer type, which makes it a pointer to a pointer type. I think you meant to just pass in s_in_addr and t_in_addr.
Edit: Alan Curry notes that you are copying 6 bytes from and to objects that are only 4 bytes long.
However, it doesn't seem like the dynamic allocation is doing your code any good, you should just create the the s_in_addr and t_in_addr variables off the stack. Than, you would not need to change your memcpy code.
struct in_addr s_in_addr;
struct in_addr t_in_addr;
s_in_addr.s_addr = inet_addr("192.168.1.5"); // source ip
t_in_addr.s_addr = inet_addr("192.168.1.6"); // target ip
memcpy(arp->arp_spa, &s_in_addr, sizeof(arg->arp_spa));
memcpy(arp->arp_tpa, &t_in_addr, sizeof(arg->arg_tpa));
There is a similar problem with your arp packet itself. So you should allocate it off the stack. To prevent myself from making a lot of code changes, I'll illustrate it slightly differently:
struct ether_arp arp_packet;
struct ether_arp *arp = &arp_packet;
//...
for(int i = 0; i < 10; i++) {
if (sendto(sock, arp, sizeof(arp_packet), 0,
(struct sockaddr *)&sending_socket,
sizeof(sending_socket)) < 0) {
std::cout << "Could not send!" << std::endl;
}
}
#user315052 say that you should use memcpy(arp->arp_spa, &s_in_addr, sizeof(arg->arp_spa)); but this code just copy first 4 bytes of s_in_addr to arp->arp_spa that absolutely do nothing!
so just try this:
* (int32_t*) arp->arp_spa = inet_addr("192.168.1.1")
* (int32_t*) arp->arp_tpa = inet_addr("192.168.1.2")

Why do I not see MSG_EOR for SOCK_SEQPACKET on linux?

I have two processes which are communicating over a pair of sockets created with socketpair() and SOCK_SEQPACKET. Like this:
int ipc_sockets[2];
socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, ipc_sockets);
As I understand it, I should see MSG_EOR in the msg_flags member of "struct msghdr" when receiving a SOCK_SEQPACKET record. I am setting MSG_EOR in sendmsg() to be certain that the record is marked MSG_EOR, but I do not see it when receiving in recvmsg(). I've even tried to set MSG_EOR in the msg_flags field before sending the record, but that made no difference at all.
I think I should see MSG_EOR unless the record was cut short by, e.g. a signal, but I do not. Why is that?
I've pasted my sending and receiving code in below.
Thanks,
jules
int
send_fd(int fd,
void *data,
const uint32_t len,
int fd_to_send,
uint32_t * const bytes_sent)
{
ssize_t n;
struct msghdr msg;
struct iovec iov;
memset(&msg, 0, sizeof(struct msghdr));
memset(&iov, 0, sizeof(struct iovec));
#ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE_SIZEOF_INT];
} control_un;
struct cmsghdr *cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
memset(msg.msg_control, 0, sizeof(control_un.control));
cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cmptr)) = fd_to_send;
#else
msg.msg_accrights = (caddr_t) &fd_to_send;
msg.msg_accrightslen = sizeof(int);
#endif
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov.iov_base = data;
iov.iov_len = len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
#ifdef __linux__
msg.msg_flags = MSG_EOR;
n = sendmsg(fd, &msg, MSG_EOR);
#elif defined __APPLE__
n = sendmsg(fd, &msg, 0); /* MSG_EOR is not supported on Mac
* OS X due to lack of
* SOCK_SEQPACKET support on
* socketpair() */
#endif
switch (n) {
case EMSGSIZE:
return EMSGSIZE;
case -1:
return 1;
default:
*bytes_sent = n;
}
return 0;
}
int
recv_fd(int fd,
void *buf,
const uint32_t len,
int *recvfd,
uint32_t * const bytes_recv)
{
struct msghdr msg;
struct iovec iov;
ssize_t n = 0;
#ifndef HAVE_MSGHDR_MSG_CONTROL
int newfd;
#endif
memset(&msg, 0, sizeof(struct msghdr));
memset(&iov, 0, sizeof(struct iovec));
#ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE_SIZEOF_INT];
} control_un;
struct cmsghdr *cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
memset(msg.msg_control, 0, sizeof(control_un.control));
#else
msg.msg_accrights = (caddr_t) &newfd;
msg.msg_accrightslen = sizeof(int);
#endif
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov.iov_base = buf;
iov.iov_len = len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (recvfd)
*recvfd = -1;
n = recvmsg(fd, &msg, 0);
if (msg.msg_flags) { // <== I should see MSG_EOR here if the entire record was received
return 1;
}
if (bytes_recv)
*bytes_recv = n;
switch (n) {
case 0:
*bytes_recv = 0;
return 0;
case -1:
return 1;
default:
break;
}
#ifdef HAVE_MSGHDR_MSG_CONTROL
if ((NULL != (cmptr = CMSG_FIRSTHDR(&msg)))
&& cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
if (SOL_SOCKET != cmptr->cmsg_level) {
return 0;
}
if (SCM_RIGHTS != cmptr->cmsg_type) {
return 0;
}
if (recvfd)
*recvfd = *((int *) CMSG_DATA(cmptr));
}
#else
if (recvfd && (sizeof(int) == msg.msg_accrightslen))
*recvfd = newfd;
#endif
return 0;
}
With SOCK_SEQPACKET unix domain sockets the only way for the message to be cut short is if the buffer you give to recvmsg() isn't big enough (and in that case you'll get MSG_TRUNC).
POSIX says that SOCK_SEQPACKET sockets must set MSG_EOR at the end of a record, but Linux unix domain sockets don't.
(Refs: POSIX 2008 2.10.10 says SOCK_SEQPACKET must support records, and 2.10.6 says record boundaries are visible to the receiver via the MSG_EOR flag.)
What a 'record' means for a given protocol is up to the implementation to define.
If Linux did implement MSG_EOR for unix domain sockets, I think the only sensible way would be to say that each packet was a record in itself, and so always set MSG_EOR (or maybe always set it when not setting MSG_TRUNC), so it wouldn't be informative anyway.
That's not what MSG_EOR is for.
Remember that the sockets API is an abstraction over a number of different protocols, including UNIX filesystem sockets, socketpairs, TCP, UDP, and many many different network protocols, including X.25 and some entirely forgotten ones.
MSG_EOR is to signal end of record where that makes sense for the underlying protocol. I.e. it is to pass a message to the next layer down that "this completes a record". This may affect for example, buffering, causing the flushing of a buffer. But if the protocol itself doesn't have a concept of a "record" there is no reason to expect the flag to be propagated.
Secondly, if using SEQPACKET you must read the entire message at once. If you do not the remainder will be discarded. That's documented. In particular, MSG_EOR is not a flag to tell you that this is the last part of the packet.
Advice: You are obviously writing a non-SEQPACKET version for use on MacOS. I suggest you dump the SEQPACKET version as it is only going to double the maintenance and coding burden. SOCK_STREAM is fine for all platforms.
When you read the docs, SOCK_SEQPACKET differs from SOCK_STREAM in two distinct ways. Firstly -
Sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.
-- socket(2) from Linux manpages project
aka
For message-based sockets, such as SOCK_DGRAM and SOCK_SEQPACKET, the entire message shall be read in a single operation. If a message is too long to fit in the supplied buffers, and MSG_PEEK is not set in the flags argument, the excess bytes shall be discarded, and MSG_TRUNC shall be set in the msg_flags member of the msghdr structure.
-- recvmsg() in POSIX standard.
In this sense it is similar to SOCK_DGRAM.
Secondly each "datagram" (Linux) / "message" (POSIX) carries a flag called MSG_EOR.
However Linux SOCK_SEQPACKET for AF_UNIX does not implement MSG_EOR. The current docs do not match reality :-)
Allegedly some SOCK_SEQPACKET implementations do the other one. And some implement both. So that covers all the possible different combinations :-)
[1] Packet oriented protocols generally use packet level reads with
truncation / discard semantics and no MSG_EOR. X.25, Bluetooth, IRDA,
and Unix domain sockets use SOCK_SEQPACKET this way.
[2] Record oriented protocols generally use byte stream reads and MSG_EOR
no packet level visibility, no truncation / discard. DECNet and ISO TP use SOCK_SEQPACKET that way.
[3] Packet / record hybrids generally use SOCK_SEQPACKET with truncation /
discard semantics on the packet level, and record terminating packets
marked with MSG_EOR. SPX and XNS SPP use SOCK_SEQPACKET this way.
https://mailarchive.ietf.org/arch/msg/tsvwg/9pDzBOG1KQDzQ2wAul5vnAjrRkA
You've shown an example of paragraph 1.
Paragraph 2 also applies to SOCK_SEQPACKET as defined for SCTP. Although by default it sets MSG_EOR on every sendmsg(). The option to disable this is called SCTP_EXPLICIT_EOR.
Paragraph 3, the one most consistent with the docs, seems to be the most obscure case.
And even the docs are not properly consistent with themselves.
The SOCK_SEQPACKET socket type is similar to the SOCK_STREAM type, and is also connection-oriented. The only difference between these types is that record boundaries are maintained using the SOCK_SEQPACKET type. A record can be sent using one or more output operations and received using one or more input operations, but a single operation never transfers parts of more than one record. Record boundaries are visible to the receiver via the MSG_EOR flag in the received message flags returned by the recvmsg() function. -- POSIX standard