Skip to content

[RFC] Client IPv6 weirdness #552

@rg0now

Description

@rg0now

Currently the client makes a modest effort to find out the requested address family if one is not supplied by the caller. This goes by calling inferAddressFamilyFromConn, which looks at the client socket's local address and runs some heuristics to make an educated guess. The problem is that this can go wrong in stupid cornercases (that I seem to be hitting repeatedly with STUNner's test suite).

Consider the below test, which opens a server on an IPv4 socket and tries to make an allocation.

func TestClientInAddrAny(t *testing.T) {
	udpListener, err := net.ListenPacket("udp4", ":3478") // nolint: noctx
	assert.NoError(t, err)

	server, err := NewServer(ServerConfig{
		AuthHandler: func(ra *RequestAttributes) (userID string, key []byte, ok bool) {
			return ra.Username, GenerateAuthKey(ra.Username, ra.Realm, "pass"), true
		},
		PacketConnConfigs: []PacketConnConfig{
			{
				PacketConn: udpListener,
				RelayAddressGenerator: &RelayAddressGeneratorStatic{
					RelayAddress: net.ParseIP("127.0.0.1"),
					Address:      "0.0.0.0",
				},
			},
		},
		Realm: "pion.ly",
	})
	assert.NoError(t, err)

	conn, err := net.ListenPacket("udp", ":0") // THE OFFENDING LINE
	assert.NoError(t, err)

	client, err := NewClient(&ClientConfig{
		Conn:           conn,
		STUNServerAddr: testAddr,
		TURNServerAddr: testAddr,
		Username:       "foo",
		Password:       "pass",
	})
	assert.NoError(t, err)
	assert.NoError(t, client.Listen())

	allocation, err := client.Allocate()
	assert.NoError(t, err)
}

Note that we do not set ClientConfig.RequestedAddressFamily so the client relies on the heuristic to find out the requested address family. If we allocate the client socket with conn, err := net.ListenPacket("udp", ":0") the test fails (note that we set the network to generic udp): the heuristic gets the address family for INADDR_ANY wrong (it seems net.Addr.IP.To4() returns nil for the address string :0), so it sets the requested address family to IPv6 which the server then denies. However, conn, err := net.ListenPacket("udp4", ":0") (with an explicit udp4 network) makes the test pass.

The takeaway I think is not that sloppy people using generic network assignments (e.g., udp instead of udp4/6) should be able to live with the consequences (someone will do this anyway, me included), or that the heuristics in inferAddressFamilyFromConn are wrong. It seems to me that once the caller has created a network socket it becomes very difficult to find out their intent just by looking at the socket descriptors as returned by the OS.

I'm wondering whether we should make the ClientConfig.RequestedAddressFamily explicit: if the caller wants IPv6 then they must set the RequestedAddressFamily to RequestedAddressFamilyIPv6, otherwise the client defaults to RequestedAddressFamilyIPv4. This would allow us to remove the heuristics and return explicit errors when something does not match.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions