add support for writing the ECN control message (Linux, macOS)

This commit is contained in:
Marten Seemann 2023-07-30 13:42:15 -04:00
parent a7f807856c
commit f919473598
8 changed files with 91 additions and 3 deletions

View file

@ -3,6 +3,7 @@ package quic
import (
"net"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
@ -42,10 +43,16 @@ func newSendConn(c rawConn, remote net.Addr, info packetInfo, logger utils.Logge
}
oob := info.OOB()
// add 32 bytes, so we can add the UDP_SEGMENT msg
if remoteUDPAddr, ok := remote.(*net.UDPAddr); ok {
if remoteUDPAddr.IP.To4() != nil {
oob = appendIPv4ECNMsg(oob, protocol.ECT1)
} else {
oob = appendIPv6ECNMsg(oob, protocol.ECT1)
}
}
// increase oob slice capacity, so we can add the UDP_SEGMENT and ECN control messages without allocating
l := len(oob)
oob = append(oob, make([]byte, 32)...)
oob = oob[:l]
oob = append(oob, make([]byte, 64)...)[:l]
return &sconn{
rawConn: c,
localAddr: localAddr,

View file

@ -8,6 +8,7 @@ import (
"golang.org/x/sys/windows"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
@ -52,3 +53,6 @@ func isRecvMsgSizeErr(err error) bool {
// https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
return errors.Is(err, windows.WSAEMSGSIZE)
}
func appendIPv4ECNMsg([]byte, protocol.ECN) []byte { return nil }
func appendIPv6ECNMsg([]byte, protocol.ECN) []byte { return nil }

View file

@ -15,6 +15,8 @@ const (
ipv4PKTINFO = unix.IP_RECVPKTINFO
)
const ecnIPv4DataLen = 4
// ReadBatch only returns a single packet on OSX,
// see https://godoc.org/golang.org/x/net/ipv4#PacketConn.ReadBatch.
const batchSize = 1

View file

@ -14,6 +14,8 @@ const (
ipv4PKTINFO = 0x7
)
const ecnIPv4DataLen = 4
const batchSize = 8
func parseIPv4PktInfo(body []byte) (ip netip.Addr, _ uint32, ok bool) {

View file

@ -19,6 +19,8 @@ const (
ipv4PKTINFO = unix.IP_PKTINFO
)
const ecnIPv4DataLen = 4
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {

View file

@ -5,6 +5,8 @@ package quic
import (
"net"
"net/netip"
"github.com/quic-go/quic-go/internal/protocol"
)
func newConn(c net.PacketConn, supportsDF bool) (*basicConn, error) {
@ -14,6 +16,9 @@ func newConn(c net.PacketConn, supportsDF bool) (*basicConn, error) {
func inspectReadBuffer(any) (int, error) { return 0, nil }
func inspectWriteBuffer(any) (int, error) { return 0, nil }
func appendIPv4ECNMsg([]byte, protocol.ECN) []byte { return nil }
func appendIPv6ECNMsg([]byte, protocol.ECN) []byte { return nil }
type packetInfo struct {
addr netip.Addr
}

View file

@ -11,6 +11,7 @@ import (
"sync"
"syscall"
"time"
"unsafe"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
@ -279,3 +280,32 @@ func (info *packetInfo) OOB() []byte {
}
return nil
}
func appendIPv4ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
b = append(b, make([]byte, unix.CmsgSpace(ecnIPv4DataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IP
h.Type = unix.IP_TOS
h.SetLen(unix.CmsgLen(ecnIPv4DataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
b[offset] = uint8(val)
return b
}
func appendIPv6ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
const dataLen = 4
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IPV6
h.Type = unix.IPV6_TCLASS
h.SetLen(unix.CmsgLen(dataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
b[offset] = uint8(val)
return b
}

View file

@ -139,6 +139,42 @@ var _ = Describe("OOB Conn Test", func() {
Expect(utils.IsIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeFalse())
Expect(p.ecn).To(Equal(protocol.ECT1))
})
It("sends packets with ECN on IPv4", func() {
conn, packetChan := runServer("udp4", "localhost:0")
defer conn.Close()
c, err := net.ListenUDP("udp4", nil)
Expect(err).ToNot(HaveOccurred())
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv4ECNMsg([]byte{}, val), conn.LocalAddr().(*net.UDPAddr))
Expect(err).ToNot(HaveOccurred())
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.ecn).To(Equal(val))
}
})
It("sends packets with ECN on IPv6", func() {
conn, packetChan := runServer("udp6", "[::1]:0")
defer conn.Close()
c, err := net.ListenUDP("udp6", nil)
Expect(err).ToNot(HaveOccurred())
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv6ECNMsg([]byte{}, val), conn.LocalAddr().(*net.UDPAddr))
Expect(err).ToNot(HaveOccurred())
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.ecn).To(Equal(val))
}
})
})
Context("Packet Info conn", func() {