Extract network datagram endpoint common facilities

...from the UDP endpoint.

Datagram-based transport endpoints (e.g. UDP, RAW IP) can share a lot
of their write path due to the datagram-based nature of these endpoints.
Extract the common facilities from UDP so they can be shared with other
transport endpoints (in a later change).

Test: UDP syscall tests.
PiperOrigin-RevId: 394347774
This commit is contained in:
Ghanan Gowripalan
2021-09-01 17:12:24 -07:00
committed by gVisor bot
parent 5032f4f57d
commit ae3bd32011
13 changed files with 1362 additions and 707 deletions
+1
View File
@@ -48,6 +48,7 @@ go_library(
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport",
"//pkg/tcpip/transport/tcp",
"//pkg/tcpip/transport/udp",
"//pkg/usermem",
+5 -5
View File
@@ -59,8 +59,8 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -2045,7 +2045,7 @@ func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
if isTCPSocket(skType, skProto) && tcp.EndpointState(ep.State()) != tcp.StateInitial {
return syserr.ErrInvalidEndpointState
} else if isUDPSocket(skType, skProto) && udp.EndpointState(ep.State()) != udp.StateInitial {
} else if isUDPSocket(skType, skProto) && transport.DatagramEndpointState(ep.State()) != transport.DatagramEndpointStateInitial {
return syserr.ErrInvalidEndpointState
}
@@ -3331,10 +3331,10 @@ func (s *socketOpsCommon) State() uint32 {
}
case isUDPSocket(s.skType, s.protocol):
// UDP socket.
switch udp.EndpointState(s.Endpoint.State()) {
case udp.StateInitial, udp.StateBound, udp.StateClosed:
switch transport.DatagramEndpointState(s.Endpoint.State()) {
case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateBound, transport.DatagramEndpointStateClosed:
return linux.TCP_CLOSE
case udp.StateConnected:
case transport.DatagramEndpointStateConnected:
return linux.TCP_ESTABLISHED
default:
return 0
+13
View File
@@ -0,0 +1,13 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "transport",
srcs = [
"datagram.go",
"transport.go",
],
visibility = ["//visibility:public"],
deps = ["//pkg/tcpip"],
)
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transport
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip"
)
// DatagramEndpointState is the state of a datagram-based endpoint.
type DatagramEndpointState tcpip.EndpointState
// The states a datagram-based endpoint may be in.
const (
_ DatagramEndpointState = iota
DatagramEndpointStateInitial
DatagramEndpointStateBound
DatagramEndpointStateConnected
DatagramEndpointStateClosed
)
// String implements fmt.Stringer.
func (s DatagramEndpointState) String() string {
switch s {
case DatagramEndpointStateInitial:
return "INITIAL"
case DatagramEndpointStateBound:
return "BOUND"
case DatagramEndpointStateConnected:
return "CONNECTED"
case DatagramEndpointStateClosed:
return "CLOSED"
default:
panic(fmt.Sprintf("unhandled %[1]T variant = %[1]d", s))
}
}
@@ -0,0 +1,43 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
go_library(
name = "network",
srcs = [
"endpoint.go",
"endpoint_state.go",
],
visibility = [
"//pkg/tcpip/transport/udp:__pkg__",
],
deps = [
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport",
],
)
go_test(
name = "network_test",
size = "small",
srcs = ["endpoint_test.go"],
deps = [
":network",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/checker",
"//pkg/tcpip/faketime",
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/tcpip/testutil",
"//pkg/tcpip/transport",
"//pkg/tcpip/transport/udp",
"@com_github_google_go_cmp//cmp:go_default_library",
],
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport"
)
// Resume implements tcpip.ResumableEndpoint.Resume.
func (e *Endpoint) Resume(s *stack.Stack) {
e.mu.Lock()
defer e.mu.Unlock()
e.stack = s
for m := range e.multicastMemberships {
if err := e.stack.JoinGroup(e.netProto, m.nicID, m.multicastAddr); err != nil {
panic(fmt.Sprintf("e.stack.JoinGroup(%d, %d, %s): %s", e.netProto, m.nicID, m.multicastAddr, err))
}
}
switch state := e.State(); state {
case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed:
case transport.DatagramEndpointStateBound:
if len(e.info.ID.LocalAddress) != 0 && !e.isBroadcastOrMulticast(e.info.RegisterNICID, e.effectiveNetProto, e.info.ID.LocalAddress) {
if e.stack.CheckLocalAddress(e.info.RegisterNICID, e.effectiveNetProto, e.info.ID.LocalAddress) == 0 {
panic(fmt.Sprintf("got e.stack.CheckLocalAddress(%d, %d, %s) = 0, want != 0", e.info.RegisterNICID, e.effectiveNetProto, e.info.ID.LocalAddress))
}
}
case transport.DatagramEndpointStateConnected:
var err tcpip.Error
multicastLoop := e.ops.GetMulticastLoop()
e.connectedRoute, err = e.stack.FindRoute(e.info.RegisterNICID, e.info.ID.LocalAddress, e.info.ID.RemoteAddress, e.effectiveNetProto, multicastLoop)
if err != nil {
panic(fmt.Sprintf("e.stack.FindRoute(%d, %s, %s, %d, %t): %s", e.info.RegisterNICID, e.info.ID.LocalAddress, e.info.ID.RemoteAddress, e.effectiveNetProto, multicastLoop, err))
}
default:
panic(fmt.Sprintf("unhandled state = %s", state))
}
}
@@ -0,0 +1,209 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network_test
import (
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/testutil"
"gvisor.dev/gvisor/pkg/tcpip/transport"
"gvisor.dev/gvisor/pkg/tcpip/transport/internal/network"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
func TestEndpointStateTransitions(t *testing.T) {
const (
nicID = 1
)
var (
ipv4NICAddr = testutil.MustParse4("1.2.3.4")
ipv6NICAddr = testutil.MustParse6("a::1")
ipv4RemoteAddr = testutil.MustParse4("6.7.8.9")
ipv6RemoteAddr = testutil.MustParse6("b::1")
)
data := buffer.View([]byte{1, 2, 4, 5})
v4Checker := func(t *testing.T, b buffer.View) {
checker.IPv4(t, b,
checker.SrcAddr(ipv4NICAddr),
checker.DstAddr(ipv4RemoteAddr),
checker.IPPayload(data),
)
}
v6Checker := func(t *testing.T, b buffer.View) {
checker.IPv6(t, b,
checker.SrcAddr(ipv6NICAddr),
checker.DstAddr(ipv6RemoteAddr),
checker.IPPayload(data),
)
}
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
expectedMaxHeaderLength uint16
expectedNetProto tcpip.NetworkProtocolNumber
expectedLocalAddr tcpip.Address
bindAddr tcpip.Address
expectedBoundAddr tcpip.Address
remoteAddr tcpip.Address
expectedRemoteAddr tcpip.Address
checker func(*testing.T, buffer.View)
}{
{
name: "IPv4",
netProto: ipv4.ProtocolNumber,
expectedMaxHeaderLength: header.IPv4MaximumHeaderSize,
expectedNetProto: ipv4.ProtocolNumber,
expectedLocalAddr: ipv4NICAddr,
bindAddr: header.IPv4AllSystems,
expectedBoundAddr: header.IPv4AllSystems,
remoteAddr: ipv4RemoteAddr,
expectedRemoteAddr: ipv4RemoteAddr,
checker: v4Checker,
},
{
name: "IPv6",
netProto: ipv6.ProtocolNumber,
expectedMaxHeaderLength: header.IPv6FixedHeaderSize,
expectedNetProto: ipv6.ProtocolNumber,
expectedLocalAddr: ipv6NICAddr,
bindAddr: header.IPv6AllNodesMulticastAddress,
expectedBoundAddr: header.IPv6AllNodesMulticastAddress,
remoteAddr: ipv6RemoteAddr,
expectedRemoteAddr: ipv6RemoteAddr,
checker: v6Checker,
},
{
name: "IPv4-mapped-IPv6",
netProto: ipv6.ProtocolNumber,
expectedMaxHeaderLength: header.IPv4MaximumHeaderSize,
expectedNetProto: ipv4.ProtocolNumber,
expectedLocalAddr: ipv4NICAddr,
bindAddr: testutil.MustParse6("::ffff:e000:0001"),
expectedBoundAddr: header.IPv4AllSystems,
remoteAddr: testutil.MustParse6("::ffff:0607:0809"),
expectedRemoteAddr: ipv4RemoteAddr,
checker: v4Checker,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
Clock: &faketime.NullClock{},
})
e := channel.New(1, header.IPv6MinimumMTU, "")
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, ipv4NICAddr); err != nil {
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, ipv4NICAddr, err)
}
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, ipv6NICAddr); err != nil {
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, ipv6NICAddr, err)
}
s.SetRouteTable([]tcpip.Route{
{Destination: ipv4RemoteAddr.WithPrefix().Subnet(), NIC: nicID},
{Destination: ipv6RemoteAddr.WithPrefix().Subnet(), NIC: nicID},
})
var ops tcpip.SocketOptions
var ep network.Endpoint
ep.Init(s, test.netProto, udp.ProtocolNumber, &ops)
if state := ep.State(); state != transport.DatagramEndpointStateInitial {
t.Fatalf("got ep.State() = %s, want = %s", state, transport.DatagramEndpointStateInitial)
}
bindAddr := tcpip.FullAddress{Addr: test.bindAddr}
if err := ep.Bind(bindAddr); err != nil {
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
}
if state := ep.State(); state != transport.DatagramEndpointStateBound {
t.Fatalf("got ep.State() = %s, want = %s", state, transport.DatagramEndpointStateBound)
}
if diff := cmp.Diff(ep.GetLocalAddress(), tcpip.FullAddress{Addr: test.expectedBoundAddr}); diff != "" {
t.Errorf("ep.GetLocalAddress() mismatch (-want +got):\n%s", diff)
}
if addr, connected := ep.GetRemoteAddress(); connected {
t.Errorf("got ep.GetRemoteAddress() = (true, %#v), want = (false, _)", addr)
}
connectAddr := tcpip.FullAddress{Addr: test.remoteAddr}
if err := ep.Connect(connectAddr); err != nil {
t.Fatalf("ep.Connect(%#v): %s", connectAddr, err)
}
if state := ep.State(); state != transport.DatagramEndpointStateConnected {
t.Fatalf("got ep.State() = %s, want = %s", state, transport.DatagramEndpointStateConnected)
}
if diff := cmp.Diff(ep.GetLocalAddress(), tcpip.FullAddress{Addr: test.expectedLocalAddr}); diff != "" {
t.Errorf("ep.GetLocalAddress() mismatch (-want +got):\n%s", diff)
}
if addr, connected := ep.GetRemoteAddress(); !connected {
t.Errorf("got ep.GetRemoteAddress() = (false, _), want = (true, %#v)", connectAddr)
} else if diff := cmp.Diff(addr, tcpip.FullAddress{Addr: test.expectedRemoteAddr}); diff != "" {
t.Errorf("remote address mismatch (-want +got):\n%s", diff)
}
ctx, err := ep.AcquireContextForWrite(tcpip.WriteOptions{})
if err != nil {
t.Fatalf("ep.AcquireContexForWrite({}): %s", err)
}
defer ctx.Release()
info := ctx.PacketInfo()
if diff := cmp.Diff(network.WritePacketInfo{
NetProto: test.expectedNetProto,
LocalAddress: test.expectedLocalAddr,
RemoteAddress: test.expectedRemoteAddr,
MaxHeaderLength: test.expectedMaxHeaderLength,
RequiresTXTransportChecksum: true,
}, info); diff != "" {
t.Errorf("write packet info mismatch (-want +got):\n%s", diff)
}
if err := ctx.WritePacket(stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(info.MaxHeaderLength),
Data: data.ToVectorisedView(),
}), false /* headerIncluded */); err != nil {
t.Fatalf("ctx.WritePacket(_, false): %s", err)
}
if pkt, ok := e.Read(); !ok {
t.Fatalf("expected packet to be read from link endpoint")
} else {
test.checker(t, stack.PayloadSince(pkt.Pkt.NetworkHeader()))
}
ep.Close()
if state := ep.State(); state != transport.DatagramEndpointStateClosed {
t.Fatalf("got ep.State() = %s, want = %s", state, transport.DatagramEndpointStateClosed)
}
})
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package transport supports transport protocols.
package transport
+2
View File
@@ -35,6 +35,8 @@ go_library(
"//pkg/tcpip/header/parse",
"//pkg/tcpip/ports",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport",
"//pkg/tcpip/transport/internal/network",
"//pkg/tcpip/transport/raw",
"//pkg/waiter",
],
File diff suppressed because it is too large Load Diff
+19 -40
View File
@@ -15,12 +15,13 @@
package udp
import (
"fmt"
"time"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport"
)
// saveReceivedAt is invoked by stateify.
@@ -66,50 +67,28 @@ func (e *endpoint) Resume(s *stack.Stack) {
e.mu.Lock()
defer e.mu.Unlock()
e.net.Resume(s)
e.stack = s
e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
for m := range e.multicastMemberships {
if err := e.stack.JoinGroup(e.NetProto, m.nicID, m.multicastAddr); err != nil {
panic(err)
}
}
state := e.EndpointState()
if state != StateBound && state != StateConnected {
return
}
netProto := e.effectiveNetProtos[0]
// Connect() and bindLocked() both assert
//
// netProto == header.IPv6ProtocolNumber
//
// before creating a multi-entry effectiveNetProtos.
if len(e.effectiveNetProtos) > 1 {
netProto = header.IPv6ProtocolNumber
}
var err tcpip.Error
if state == StateConnected {
e.route, err = e.stack.FindRoute(e.RegisterNICID, e.ID.LocalAddress, e.ID.RemoteAddress, netProto, e.ops.GetMulticastLoop())
switch state := e.net.State(); state {
case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed:
case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected:
// Our saved state had a port, but we don't actually have a
// reservation. We need to remove the port from our state, but still
// pass it to the reservation machinery.
var err tcpip.Error
id := e.net.Info().ID
id.LocalPort = e.localPort
id.RemotePort = e.remotePort
id, e.boundBindToDevice, err = e.registerWithStack(e.effectiveNetProtos, id)
if err != nil {
panic(err)
}
} else if len(e.ID.LocalAddress) != 0 && !e.isBroadcastOrMulticast(e.RegisterNICID, netProto, e.ID.LocalAddress) { // stateBound
// A local unicast address is specified, verify that it's valid.
if e.stack.CheckLocalAddress(e.RegisterNICID, netProto, e.ID.LocalAddress) == 0 {
panic(&tcpip.ErrBadLocalAddress{})
}
}
// Our saved state had a port, but we don't actually have a
// reservation. We need to remove the port from our state, but still
// pass it to the reservation machinery.
id := e.ID
e.ID.LocalPort = 0
e.ID, e.boundBindToDevice, err = e.registerWithStack(e.effectiveNetProtos, id)
if err != nil {
panic(err)
e.localPort = id.LocalPort
e.remotePort = id.RemotePort
default:
panic(fmt.Sprintf("unhandled state = %s", state))
}
}
+11 -10
View File
@@ -70,28 +70,29 @@ func (r *ForwarderRequest) ID() stack.TransportEndpointID {
// CreateEndpoint creates a connected UDP endpoint for the session request.
func (r *ForwarderRequest) CreateEndpoint(queue *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
ep := newEndpoint(r.stack, r.pkt.NetworkProtocolNumber, queue)
ep.mu.Lock()
defer ep.mu.Unlock()
netHdr := r.pkt.Network()
route, err := r.stack.FindRoute(r.pkt.NICID, netHdr.DestinationAddress(), netHdr.SourceAddress(), r.pkt.NetworkProtocolNumber, false /* multicastLoop */)
if err != nil {
if err := ep.net.Bind(tcpip.FullAddress{NIC: r.pkt.NICID, Addr: netHdr.DestinationAddress(), Port: r.id.LocalPort}); err != nil {
return nil, err
}
if err := ep.net.Connect(tcpip.FullAddress{NIC: r.pkt.NICID, Addr: netHdr.SourceAddress(), Port: r.id.RemotePort}); err != nil {
return nil, err
}
ep := newEndpoint(r.stack, r.pkt.NetworkProtocolNumber, queue)
if err := r.stack.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{r.pkt.NetworkProtocolNumber}, ProtocolNumber, r.id, ep, ep.portFlags, tcpip.NICID(ep.ops.GetBindToDevice())); err != nil {
ep.Close()
route.Release()
return nil, err
}
ep.ID = r.id
ep.route = route
ep.dstPort = r.id.RemotePort
ep.localPort = r.id.LocalPort
ep.remotePort = r.id.RemotePort
ep.effectiveNetProtos = []tcpip.NetworkProtocolNumber{r.pkt.NetworkProtocolNumber}
ep.RegisterNICID = r.pkt.NICID
ep.boundPortFlags = ep.portFlags
ep.state = uint32(StateConnected)
ep.rcvMu.Lock()
ep.rcvReady = true
ep.rcvMu.Unlock()