Skip to content

gen_udp (socket): recvtos AncData loses raw TOS byte; type mismatch #10968

@voluntas

Description

@voluntas

Describe the bug

  • With gen_udp + inet_backend = socket, received TOS is only {tos, Value} from gen_udp_socket:ctrl2ancdata/2, which drops the CMSG data binary and keeps value only.
  • value is produced by encode_ip_tos() in prim_socket_nif.c: after val band 16#1E, it returns legacy atoms (throughput, …) or an integer. Many different octets map to the same atom (e.g. DSCP 2 → byte 8 and DSCP 34 → byte 136 both → throughput).
  • inet says ancillary TOS is {'tos', byte()} and options use {tos, non_neg_integer()}. Atoms like throughput are not byte() / non_neg_integer() — the documented contract is broken on receive.

To Reproduce

Save as tos_demo.erl, then: erlc tos_demo.erl and

erl -noshell -pa . -eval 'tos_demo:run(), halt(0).'

-module(tos_demo).
-export([run/0]).
run() ->
    {ok, R} = gen_udp:open(0, [{inet_backend, socket}, inet, binary,
                                {active, false}, {recvtos, true}]),
    {ok, Rport} = inet:port(R),
    {ok, S} = gen_udp:open(0, [{inet_backend, socket}, inet, binary,
                               {active, false}]),
    try
        lists:foreach(
          fun(V) ->
              ok = inet:setopts(S, [{tos, V}]),
              ok = gen_udp:send(S, {127, 0, 0, 1}, Rport, <<".">>),
              {ok, {_, _, Anc, _}} = gen_udp:recv(R, 100, 2000),
              io:format("Val=~w 0x~.16B -> Anc ~p~n", [V, V, Anc])
          end,
          [8, 9, 136, 137]),
        lists:foreach(
          fun({Lab, Dscp}) ->
              B = Dscp bsl 2,
              ok = inet:setopts(S, [{tos, B}]),
              ok = gen_udp:send(S, {127, 0, 0, 1}, Rport, <<"x">>),
              {ok, {_, _, Anc, _}} = gen_udp:recv(R, 500, 2000),
              io:format("~s -> Anc ~p~n", [Lab, Anc])
          end,
          [{"DSCP_2", 2}, {"DSCP_34_AF41", 34}])
    after
        gen_udp:close(R), gen_udp:close(S)
    end.

Expected behavior

  • {tos, _} on inet / gen_udp ancillary receive should be {'tos', non_neg_integer()} in 0..255 (the wire octet). ip_tos() atoms must not appear there; those belong to the socket API’s symbolic layer, not a substitute for byte() in inet.
  • Prefer also preserving or exposing raw CMSG data in the gen_udp path (today it is discarded in ctrl2ancdata/2).

Affected versions

Any OTP shipping the socket NIF (practical use OTP 22+; related work from ~2018 in history). Relevant when inet_backend = socket for gen_udp.

Code

  1. TOS → atom/integer: encode_ip_tos (https://github.com/erlang/otp/blob/master/erts/emulator/nifs/common/prim_socket_nif.c#L16245-L16279), mask (https://github.com/erlang/otp/blob/master/erts/emulator/nifs/common/prim_socket_nif.c#L436-L441)
  2. recvmsg / getopt: esock_cmsg_encode_ip_tos (https://github.com/erlang/otp/blob/master/erts/emulator/nifs/common/prim_socket_nif.c#L14204-L14217), esock_getopt_tos (https://github.com/erlang/otp/blob/master/erts/emulator/nifs/common/prim_socket_nif.c#L12580-L12599)
  3. gen_udp drops data: ctrl2ancdata/2 (https://github.com/erlang/otp/blob/master/lib/kernel/src/gen_udp_socket.erl#L2228-L2252)

Additional context

Raw octet is still available via socket:recvmsg (data in ctrl); not via gen_udp AncData as above.
IPv6 tclass CMSG uses integer encoding in the same NIF; IPv4 TOS is special-cased with legacy atom mapping.

Metadata

Metadata

Assignees

Labels

bugIssue is reported as a bugteam:PSAssigned to OTP team PS

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions