Skip to content

Commit e4fb675

Browse files
authored
Add the sandbox network protocol, client and Linux network service (#346)
* Add the sandbox network protocol, client and Linux network service internal/sandboxnet defines the Network protocol (one Connect per Link stream, then raw bytes), its strict codec, host grammar and egress check, the Connect client with a net.Conn, and the generic Serve that resolves, checks, dials and splices with orderly half-close and abort kept apart. apps/sandboxio/internal/netservice is the Linux resolver and dialer. sandboxlink.Stream gains the deadline methods yamux streams already have. * Tighten the network egress check, dial effects, forwarding and Conn Never permit an unspecified destination, which a TCP stack connects to the sandbox itself, and dial the checked literal over tcp4 or tcp6. Give an errno-typed dial failure EffectNone only when socket or connect returned it. Hold bytes that arrive after a successful dial until Connected is written. Serialize Conn reads and writes and fail calls after a passed deadline, as net.Conn does. State the Stream deadline and concurrency contract, the two request ID sequences on a service stream and the half-close residual. * Order the network Conn's FIN after writes and type native dial timeouts Take the write lock for CloseWrite and the orderly Close, so no Write lands after the FIN; Close aborts instead when a Write is in progress, which ends the pending call. Update a deadline and its cached copy under one lock. Type a native dial timeout as TimedOut in the Linux service, and read the Connect's deadline from the clock, since a socket deadline can fire before the context reports its end.
1 parent b07d15e commit e4fb675

16 files changed

Lines changed: 1903 additions & 9 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ OpenAgentCore is protocol-first and modular. Core orchestrates operations that p
2525
| Provider–Sandbox I/O startup | `internal/sandboxbootstrap/bootstrap.go` | [Sandbox bootstrap](docs/sandbox-bootstrap.md) |
2626
| Runtime–file service | `internal/sandboxfs/protocol.go` | [File access protocol](docs/file-access-protocol.md) |
2727
| Runtime–process service | `internal/sandboxprocess/protocol.go` | [Process protocol](docs/process-protocol.md) |
28+
| Runtime–network service | `internal/sandboxnet/protocol.go` | [Sandbox network protocol](docs/sandbox-network-protocol.md) |
2829
| Core–Runtime wire | `internal/agentdaemon/proto/` | [Core–Runtime protocol](docs/runtime-protocol.md) |
2930
| Runtime–Harness | `apps/daemon/internal/agent/harness.go` | [Harness onboarding](contracts/agents-api/harness-onboarding.md) |
3031
| Harness–Model provider | `internal/modelprovider/config.go` | [Model execution](contracts/agents-api/model-execution.md) |
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//go:build linux
2+
3+
// Package netservice is the Linux network service of the Sandbox I/O service.
4+
// It resolves names with the sandbox's resolver and dials TCP from the
5+
// sandbox's network namespace. sandboxnet.Serve runs the Network protocol over
6+
// it: the one Connect, the egress check, the answer and the splice.
7+
package netservice
8+
9+
import (
10+
"context"
11+
"errors"
12+
"net"
13+
"net/netip"
14+
"os"
15+
"syscall"
16+
17+
"github.com/MiniMax-AI/OpenAgentCore/internal/sandboxlink"
18+
"github.com/MiniMax-AI/OpenAgentCore/internal/sandboxnet"
19+
"github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire"
20+
)
21+
22+
// Service resolves and dials for sandboxnet.Serve.
23+
type Service struct {
24+
resolver *net.Resolver
25+
dialer net.Dialer
26+
}
27+
28+
var _ sandboxnet.Service = (*Service)(nil)
29+
30+
// New returns a service that resolves with the sandbox's system resolver.
31+
func New() *Service { return &Service{resolver: net.DefaultResolver} }
32+
33+
// Handle serves one Network stream under the egress its Bind carries. It is
34+
// the Serve function of the sandboxlink.ServiceNetwork handler.
35+
func (s *Service) Handle(ctx context.Context, b sandboxlink.Bind, st sandboxlink.Stream) {
36+
sandboxnet.Serve(ctx, st, b.Egress, s)
37+
}
38+
39+
// Resolve returns the IPv4 and IPv6 addresses of host. A name the resolver
40+
// reports as nonexistent, or without addresses, is NameNotResolved.
41+
func (s *Service) Resolve(ctx context.Context, host string) ([]netip.Addr, error) {
42+
addrs, err := s.resolver.LookupNetIP(ctx, "ip", host)
43+
var dnsErr *net.DNSError
44+
if errors.As(err, &dnsErr) && dnsErr.IsNotFound {
45+
return nil, &sandboxnet.Error{Code: sandboxnet.CodeNameNotResolved, Effect: sandboxwire.EffectNone, Cause: err}
46+
}
47+
return addrs, err
48+
}
49+
50+
// Dial connects to addr. The address is a literal and the network names its
51+
// family, so nothing is resolved and no other address is tried.
52+
func (s *Service) Dial(ctx context.Context, addr netip.AddrPort) (*net.TCPConn, error) {
53+
network := "tcp4"
54+
if addr.Addr().Is6() {
55+
network = "tcp6"
56+
}
57+
c, err := s.dialer.DialContext(ctx, network, addr.String())
58+
if err != nil {
59+
return nil, dialError(err)
60+
}
61+
return c.(*net.TCPConn), nil
62+
}
63+
64+
// dialError types a failed dial by its errno. Only an errno that socket or
65+
// connect itself returned proves that no connection was made, so only that
66+
// carries EffectNone. A kernel connect timeout, and any errno from a later
67+
// step, such as registering the socket with the poller after connect was
68+
// issued, carry EffectPossible. Without an errno, the socket deadline Go sets
69+
// from the Connect's timeout is TimedOut: it can fire before the context
70+
// reports its end.
71+
func dialError(err error) error {
72+
var errno syscall.Errno
73+
if !errors.As(err, &errno) {
74+
var ne net.Error
75+
if errors.Is(err, os.ErrDeadlineExceeded) || errors.As(err, &ne) && ne.Timeout() {
76+
return &sandboxnet.Error{Code: sandboxnet.CodeTimedOut, Effect: sandboxwire.EffectPossible, Cause: err}
77+
}
78+
return err
79+
}
80+
effect := sandboxwire.EffectPossible
81+
var sys *os.SyscallError
82+
if errors.As(err, &sys) && (sys.Syscall == "socket" || sys.Syscall == "connect") {
83+
effect = sandboxwire.EffectNone
84+
}
85+
var code sandboxnet.Code
86+
switch errno {
87+
case syscall.ECONNREFUSED:
88+
code = sandboxnet.CodeConnectionRefused
89+
case syscall.ENETUNREACH, syscall.EHOSTUNREACH, syscall.ENETDOWN, syscall.EHOSTDOWN, syscall.EAFNOSUPPORT:
90+
code = sandboxnet.CodeUnreachable
91+
case syscall.EADDRNOTAVAIL, syscall.EMFILE, syscall.ENFILE, syscall.ENOBUFS, syscall.ENOMEM, syscall.EAGAIN:
92+
code = sandboxnet.CodeResourceExhausted
93+
case syscall.EACCES, syscall.EPERM:
94+
code = sandboxnet.CodeDenied
95+
case syscall.ETIMEDOUT:
96+
code, effect = sandboxnet.CodeTimedOut, sandboxwire.EffectPossible
97+
default:
98+
return err
99+
}
100+
return &sandboxnet.Error{Code: code, Effect: effect, Cause: err}
101+
}

0 commit comments

Comments
 (0)