internal/capi: zero-capacity UDP read fails with "bad buffer" and drops the datagram

Open Beginner friendly
#163 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
69/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
go
Domain
networking

Research direction

The issue points to the C API in tailcat.h and the UDP read path exercised by internal/capi. Start by reading the read API documentation and the existing UDP read tests; the reproduction can be saved as internal/capi/udp_zero_read_test.go and run with go test -v ./internal/capi -run TestUDPZeroCapacityRead. Done means zero-capacity reads follow one documented behavior and a regression test confirms whether the datagram is consumed.

Written by the indexing model from the issue text.

Description

tc_conn_read(conn, token, NULL, 0, &count, &err) on a UDP flow returns TC_FAILURE with read udp [...]: bad buffer, and the pending datagram is discarded even though the call failed.

tailcat.h does not say what a zero-capacity UDP read does, but it reads as allowed:

For UDP, one call consumes one datagram, truncating it if capacity is too small; an empty datagram returns TC_OK with count zero. buffer may be NULL only when capacity is zero.

The TCP case is spelled out ("A zero-capacity TCP read succeeds with count zero"); the UDP case is ambiguous.

Reproduction

Save as internal/capi/udp_zero_read_test.go (it uses the newE2E helpers from e2e_test.go in the same package) and run go test -v ./internal/capi -run TestUDPZeroCapacityRead:

package capi

import "testing"

func TestUDPZeroCapacityRead(t *testing.T) {
	x := newE2E(t)
	_, dialed, accepted := x.connect(UDP) // sends "hello"
	Write(dialed, x.token(), []byte("second"))

	buf0 := make([]byte, 0)
	n0, err0 := Read(accepted, x.token(), buf0)
	t.Logf("zero-capacity read: n=%d err=%v", n0, err0)

	buf := make([]byte, 64)
	n, err := Read(accepted, x.token(), buf)
	t.Logf("next read: n=%d err=%v data=%q", n, err, buf[:n])
}

Output:

zero-capacity read: n=0 err=read udp [fd7a:...]:39350: bad buffer
next read: n=6 err=<nil> data="second"

"hello" is lost / dropped.

Expected

Either of these, with more clarity in the header:

  • Treat it as truncation to zero bytes: TC_OK with count 0, consuming one datagram. This would match a too-small buffer (a 2-byte read of "hello" returns TC_OK with "he").
  • Reject it: TC_INVALID_ARGUMENT without consuming the datagram.
Dominant language
Go
Stars
8.8k
Forks
404
Avg merge
3h 48m
Merged PRs (30d)
1

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tailscale/tailcat

All issues in tailscale/tailcat

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.