From 0b42cbc424868bd6cc0c31d2187c0cc871fb1868 Mon Sep 17 00:00:00 2001 From: JordiSubira Date: Mon, 23 Dec 2024 11:32:50 +0100 Subject: [PATCH 1/6] pkg/snet: ignore decoding/parsing reading errors on SCIONPacketConn (#4670) So far, the `snet/SCIONPacketConn` was bubbling up decoding/parsing errors to the calling applications. In principle, these errors are not recoverable by the application. The current solution simulates that the network stack simply discards incorrect/malformated packets. Otherwise,some libraries/applications would misbehave (similar to what would happen if were to receive SCMP errors). If we believe that SCION-aware applications using the low-level `SCIONPacketConn` should receive the error, we can move this up to `Conn`. --- pkg/snet/packet_conn.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/snet/packet_conn.go b/pkg/snet/packet_conn.go index f8fd378929..493a125e2e 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -21,6 +21,7 @@ import ( "time" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/metrics/v2" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" @@ -168,6 +169,14 @@ func (c *SCIONPacketConn) ReadFrom(pkt *Packet, ov *net.UDPAddr) error { if err != nil { return err } + if remoteAddr == nil { + // XXX(JordiSubira): The remote address of the underlay next host + // will not be nil unless there was an error while reading the + // SCION packet. If the err is nil, it means that it was a + // non-recoverable error (e.g., decoding the header) and we + // discard the packet and keep + continue + } *ov = *remoteAddr if scmp, ok := pkt.Payload.(SCMPPayload); ok { if c.SCMPHandler == nil { @@ -206,7 +215,11 @@ func (c *SCIONPacketConn) readFrom(pkt *Packet) (*net.UDPAddr, error) { pkt.Bytes = pkt.Bytes[:n] if err := pkt.Decode(); err != nil { metrics.CounterInc(c.Metrics.ParseErrors) - return nil, serrors.Wrap("decoding packet", err) + // XXX(JordiSubira): We avoid bubbling up parsing errors to the + // caller application to avoid problems with applications + // that don't expect this type of errors. + log.Debug("decoding packet", "error", err) + return nil, nil } udpRemoteAddr := remoteAddr.(*net.UDPAddr) @@ -218,7 +231,11 @@ func (c *SCIONPacketConn) readFrom(pkt *Packet) (*net.UDPAddr, error) { // *loopback:30041* `SCIONPacketConn.lastHop()` should yield the right next hop address. lastHop, err = c.lastHop(pkt) if err != nil { - return nil, serrors.Wrap("extracting last hop based on packet path", err) + // XXX(JordiSubira): We avoid bubbling up parsing errors to the + // caller application to avoid problems with applications + // that don't expect this type of errors. + log.Debug("extracting last hop based on packet path", "error", err) + return nil, nil } } return lastHop, nil From ca52e4f5a1c3c0f8a6dcbb22aa78104bfb645fd9 Mon Sep 17 00:00:00 2001 From: Lukas Vogel Date: Mon, 23 Dec 2024 12:25:16 +0100 Subject: [PATCH 2/6] pkg/snet: fix isShimDispatcher condition (#4672) The condition was missing brackets around the IP. While at it this also simplifies the code a bit. --- pkg/snet/packet_conn.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/snet/packet_conn.go b/pkg/snet/packet_conn.go index 493a125e2e..dc1f07abaf 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -263,12 +263,8 @@ func (c *SCIONPacketConn) LocalAddr() net.Addr { // comes from *loopback:30041*. func (c *SCIONPacketConn) isShimDispatcher(udpAddr *net.UDPAddr) bool { localAddr := c.LocalAddr().(*net.UDPAddr) - if udpAddr.IP.Equal(localAddr.IP) || - udpAddr.IP.IsLoopback() && - udpAddr.Port == underlay.EndhostPort { - return true - } - return false + return udpAddr.Port == underlay.EndhostPort && + (udpAddr.IP.Equal(localAddr.IP) || udpAddr.IP.IsLoopback()) } func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { From efbbd5835f33ab52389976d4b69d68fa7c087230 Mon Sep 17 00:00:00 2001 From: Lukas Vogel Date: Tue, 24 Dec 2024 08:52:07 +0100 Subject: [PATCH 3/6] pkg/daemon: use grpc.NewClient instead of Dial (#4660) Dial and DialContext is deprecated in newer version of gRPC. (See https://pkg.go.dev/google.golang.org/grpc#Dial) --- pkg/daemon/BUILD.bazel | 1 + pkg/daemon/grpc.go | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/daemon/BUILD.bazel b/pkg/daemon/BUILD.bazel index ad0a44f9db..56959d28c5 100644 --- a/pkg/daemon/BUILD.bazel +++ b/pkg/daemon/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//pkg/snet/path:go_default_library", "//private/topology:go_default_library", "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//credentials/insecure:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", ], diff --git a/pkg/daemon/grpc.go b/pkg/daemon/grpc.go index e501a5517d..16bf2e4df1 100644 --- a/pkg/daemon/grpc.go +++ b/pkg/daemon/grpc.go @@ -21,6 +21,7 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" @@ -48,15 +49,14 @@ type Service struct { } func (s Service) Connect(ctx context.Context) (Connector, error) { - a, err := net.ResolveTCPAddr("tcp", s.Address) + conn, err := grpc.NewClient(s.Address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + libgrpc.UnaryClientInterceptor(), + libgrpc.StreamClientInterceptor(), + ) if err != nil { s.Metrics.incConnects(err) - return nil, serrors.Wrap("resolving addr", err) - } - conn, err := libgrpc.SimpleDialer{}.Dial(ctx, a) - if err != nil { - s.Metrics.incConnects(err) - return nil, serrors.Wrap("dialing", err) + return nil, serrors.Wrap("creating client", err) } s.Metrics.incConnects(nil) return grpcConn{conn: conn, metrics: s.Metrics}, nil From 05dfe8158ab4fc87efdebd0ed518b3308f133525 Mon Sep 17 00:00:00 2001 From: Lukas Vogel Date: Tue, 24 Dec 2024 14:47:04 +0100 Subject: [PATCH 4/6] pkg/snet: change snet.Topology to a struct (#4673) Instead of having the snet.Topology as an interface, change it to a struct. This makes the contract clearer, LocalIA and PortRange are only considered when creating a connection. The Interface information however can dynamically change. The daemon API now exposes an adapter to load the topology information once or to load the information periodically. --- control/cmd/control/main.go | 39 ++++------ gateway/gateway.go | 16 ++-- pkg/daemon/BUILD.bazel | 17 ++++- pkg/daemon/topology.go | 135 ++++++++++++++++++++++++++++++++++ pkg/daemon/topology_test.go | 126 +++++++++++++++++++++++++++++++ pkg/snet/conn.go | 15 +--- pkg/snet/packet_conn.go | 30 ++++---- pkg/snet/snet.go | 41 ++++++----- private/app/path/path.go | 10 ++- scion-pki/certs/renew.go | 6 +- scion/cmd/scion/ping.go | 13 ++-- scion/cmd/scion/traceroute.go | 10 +-- scion/showpaths/showpaths.go | 7 +- tools/end2end/main.go | 20 ++++- 14 files changed, 390 insertions(+), 95 deletions(-) create mode 100644 pkg/daemon/topology.go create mode 100644 pkg/daemon/topology_test.go diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index 6f3766517b..91713e8de1 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -222,7 +222,7 @@ func realMain(ctx context.Context) error { SCIONNetworkMetrics: metrics.SCIONNetworkMetrics, SCIONPacketConnMetrics: metrics.SCIONPacketConnMetrics, MTU: topo.MTU(), - Topology: cpInfoProvider{topo: topo}, + Topology: adaptTopology(topo), } quicStack, err := nc.QUICStack() if err != nil { @@ -945,29 +945,22 @@ func (h *healther) GetCAHealth(ctx context.Context) (api.CAHealthStatus, bool) { return api.Unavailable, false } -type cpInfoProvider struct { - topo *topology.Loader -} - -func (c cpInfoProvider) LocalIA(_ context.Context) (addr.IA, error) { - return c.topo.IA(), nil -} - -func (c cpInfoProvider) PortRange(_ context.Context) (uint16, uint16, error) { - start, end := c.topo.PortRange() - return start, end, nil -} - -func (c cpInfoProvider) Interfaces(_ context.Context) (map[uint16]netip.AddrPort, error) { - ifMap := c.topo.InterfaceInfoMap() - ifsToUDP := make(map[uint16]netip.AddrPort, len(ifMap)) - for i, v := range ifMap { - if i > (1<<16)-1 { - return nil, serrors.New("invalid interface id", "id", i) - } - ifsToUDP[uint16(i)] = v.InternalAddr +func adaptTopology(topo *topology.Loader) snet.Topology { + start, end := topo.PortRange() + return snet.Topology{ + LocalIA: topo.IA(), + PortRange: snet.TopologyPortRange{ + Start: start, + End: end, + }, + Interface: func(ifID uint16) (netip.AddrPort, bool) { + a := topo.UnderlayNextHop(ifID) + if a == nil { + return netip.AddrPort{}, false + } + return a.AddrPort(), true + }, } - return ifsToUDP, nil } func getCAHealth( diff --git a/gateway/gateway.go b/gateway/gateway.go index c6395be076..2f2233dfd6 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -240,10 +240,16 @@ func (g *Gateway) Run(ctx context.Context) error { // ********************************************* // Initialize base SCION network information: IA // ********************************************* - localIA, err := g.Daemon.LocalIA(context.Background()) + topoReloader, err := daemon.NewReloadingTopology(ctx, g.Daemon) if err != nil { - return serrors.Wrap("unable to learn local ISD-AS number", err) + return serrors.Wrap("loading topology", err) } + topo := topoReloader.Topology() + go func() { + defer log.HandlePanic() + topoReloader.Run(ctx, 10*time.Second) + }() + localIA := topo.LocalIA logger.Info("Learned local IA from SCION Daemon", "ia", localIA) // ************************************************************************* @@ -299,7 +305,7 @@ func (g *Gateway) Run(ctx context.Context) error { ProbesSendErrors: probesSendErrors, SCMPErrors: g.Metrics.SCMPErrors, SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, - Topology: g.Daemon, + Topology: topo, }, PathUpdateInterval: PathUpdateInterval(ctx), PathFetchTimeout: 0, // using default for now @@ -409,7 +415,7 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetworkNoSCMP is the network for the QUIC server connection. Because SCMP errors // will cause the server's accepts to fail, we ignore SCMP. scionNetworkNoSCMP := &snet.SCIONNetwork{ - Topology: g.Daemon, + Topology: topo, // Discard all SCMP propagation, to avoid accept/read errors on the // QUIC server/client. SCMPHandler: snet.SCMPPropagationStopper{ @@ -472,7 +478,7 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetwork is the network for all SCION connections, with the exception of the QUIC server // and client connection. scionNetwork := &snet.SCIONNetwork{ - Topology: g.Daemon, + Topology: topo, SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: revocationHandler, SCMPErrors: g.Metrics.SCMPErrors, diff --git a/pkg/daemon/BUILD.bazel b/pkg/daemon/BUILD.bazel index 56959d28c5..63b2e6655e 100644 --- a/pkg/daemon/BUILD.bazel +++ b/pkg/daemon/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools/lint:go.bzl", "go_library") +load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -7,6 +7,7 @@ go_library( "daemon.go", "grpc.go", "metrics.go", + "topology.go", ], importpath = "github.com/scionproto/scion/pkg/daemon", visibility = ["//visibility:public"], @@ -15,6 +16,7 @@ go_library( "//pkg/daemon/internal/metrics:go_default_library", "//pkg/drkey:go_default_library", "//pkg/grpc:go_default_library", + "//pkg/log:go_default_library", "//pkg/metrics:go_default_library", "//pkg/private/ctrl/path_mgmt:go_default_library", "//pkg/private/prom:go_default_library", @@ -32,3 +34,16 @@ go_library( "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["topology_test.go"], + deps = [ + ":go_default_library", + "//pkg/addr:go_default_library", + "//pkg/daemon/mock_daemon:go_default_library", + "//pkg/snet:go_default_library", + "@com_github_golang_mock//gomock:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + ], +) diff --git a/pkg/daemon/topology.go b/pkg/daemon/topology.go new file mode 100644 index 0000000000..b1a76b6e96 --- /dev/null +++ b/pkg/daemon/topology.go @@ -0,0 +1,135 @@ +// Copyright 2024 Anapaya Systems +// +// 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 daemon + +import ( + "context" + "net/netip" + "sync/atomic" + "time" + + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/snet" +) + +// LoadTopology loads the local topology from the given connector. The topology +// information is loaded once and does not update automatically. +func LoadTopology(ctx context.Context, conn Connector) (snet.Topology, error) { + ia, err := conn.LocalIA(ctx) + if err != nil { + return snet.Topology{}, serrors.Wrap("loading local ISD-AS", err) + } + start, end, err := conn.PortRange(ctx) + if err != nil { + return snet.Topology{}, serrors.Wrap("loading port range", err) + } + interfaces, err := conn.Interfaces(ctx) + if err != nil { + return snet.Topology{}, serrors.Wrap("loading interfaces", err) + } + + return snet.Topology{ + LocalIA: ia, + PortRange: snet.TopologyPortRange{ + Start: start, + End: end, + }, + Interface: func(ifID uint16) (netip.AddrPort, bool) { + a, ok := interfaces[ifID] + return a, ok + }, + }, nil +} + +// ReloadingTopology is a topology that reloads the interface information +// periodically. It is safe for concurrent use. +type ReloadingTopology struct { + conn Connector + baseTopology snet.Topology + interfaces atomic.Pointer[map[uint16]netip.AddrPort] +} + +// NewReloadingTopology creates a new ReloadingTopology that reloads the +// interface information periodically. The Run method must be called for +// interface information to be populated. +func NewReloadingTopology(ctx context.Context, conn Connector) (*ReloadingTopology, error) { + ia, err := conn.LocalIA(ctx) + if err != nil { + return nil, serrors.Wrap("loading local ISD-AS", err) + } + start, end, err := conn.PortRange(ctx) + if err != nil { + return nil, serrors.Wrap("loading port range", err) + } + t := &ReloadingTopology{ + conn: conn, + baseTopology: snet.Topology{ + LocalIA: ia, + PortRange: snet.TopologyPortRange{Start: start, End: end}, + }, + } + if err := t.loadInterfaces(ctx); err != nil { + return nil, err + } + return t, nil +} + +func (t *ReloadingTopology) Topology() snet.Topology { + base := t.baseTopology + return snet.Topology{ + LocalIA: base.LocalIA, + PortRange: base.PortRange, + Interface: func(ifID uint16) (netip.AddrPort, bool) { + m := t.interfaces.Load() + if m == nil { + return netip.AddrPort{}, false + } + a, ok := (*m)[ifID] + return a, ok + }, + } +} + +func (t *ReloadingTopology) Run(ctx context.Context, period time.Duration) { + ticker := time.NewTicker(period) + defer ticker.Stop() + + reload := func() { + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + if err := t.loadInterfaces(ctx); err != nil { + log.FromCtx(ctx).Error("Failed to reload interfaces", "err", err) + } + } + reload() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + reload() + } + } +} + +func (t *ReloadingTopology) loadInterfaces(ctx context.Context) error { + intfs, err := t.conn.Interfaces(ctx) + if err != nil { + return err + } + t.interfaces.Store(&intfs) + return nil +} diff --git a/pkg/daemon/topology_test.go b/pkg/daemon/topology_test.go new file mode 100644 index 0000000000..9a99212875 --- /dev/null +++ b/pkg/daemon/topology_test.go @@ -0,0 +1,126 @@ +// Copyright 2024 Anapaya Systems +// +// 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 daemon_test + +import ( + "context" + "net/netip" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/daemon/mock_daemon" + "github.com/scionproto/scion/pkg/snet" +) + +func TestLoadTopology(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + conn := mock_daemon.NewMockConnector(ctrl) + wantTopo := testTopology{ + ia: addr.MustParseIA("1-ff00:0:110"), + start: uint16(4096), + end: uint16(8192), + interfaces: map[uint16]netip.AddrPort{ + 1: netip.MustParseAddrPort("10.0.0.1:5153"), + 2: netip.MustParseAddrPort("10.0.0.2:6421"), + }, + } + wantTopo.setupMockResponses(conn) + + topo, err := daemon.LoadTopology(context.Background(), conn) + assert.NoError(t, err) + wantTopo.checkTopology(t, topo) +} + +func TestReloadingTopology(t *testing.T) { + ctrl := gomock.NewController(t) + conn := mock_daemon.NewMockConnector(ctrl) + + wantTopo := testTopology{ + ia: addr.MustParseIA("1-ff00:0:110"), + start: uint16(4096), + end: uint16(8192), + interfaces: map[uint16]netip.AddrPort{ + 1: netip.MustParseAddrPort("10.0.0.1:5153"), + 2: netip.MustParseAddrPort("10.0.0.2:6421"), + }, + } + interfacesLater := map[uint16]netip.AddrPort{ + 2: netip.MustParseAddrPort("10.0.0.2:6421"), + 3: netip.MustParseAddrPort("10.0.0.3:7539"), + } + calls := wantTopo.setupMockResponses(conn) + done := make(chan struct{}) + ctx, cancel := context.WithCancel(context.Background()) + gomock.InOrder( + append(calls, + conn.EXPECT().Interfaces(gomock.Any()).DoAndReturn( + func(context.Context) (map[uint16]netip.AddrPort, error) { + cancel() + return interfacesLater, nil + }, + ).AnyTimes(), + )..., + ) + + loader, err := daemon.NewReloadingTopology(ctx, conn) + assert.NoError(t, err) + topo := loader.Topology() + wantTopo.checkTopology(t, topo) + + go func() { + loader.Run(ctx, 100*time.Millisecond) + close(done) + }() + <-done + wantTopo.interfaces = interfacesLater + wantTopo.checkTopology(t, loader.Topology()) + _, ok := loader.Topology().Interface(1) + assert.False(t, ok) +} + +type testTopology struct { + ia addr.IA + start uint16 + end uint16 + interfaces map[uint16]netip.AddrPort +} + +func (tt testTopology) setupMockResponses(c *mock_daemon.MockConnector) []*gomock.Call { + return []*gomock.Call{ + c.EXPECT().LocalIA(gomock.Any()).Return(tt.ia, nil), + c.EXPECT().PortRange(gomock.Any()).Return(tt.start, tt.end, nil), + c.EXPECT().Interfaces(gomock.Any()).Return(tt.interfaces, nil), + } +} + +func (tt testTopology) checkTopology(t *testing.T, topo snet.Topology) { + t.Helper() + + assert.Equal(t, tt.ia, topo.LocalIA) + assert.Equal(t, tt.start, topo.PortRange.Start) + assert.Equal(t, tt.end, topo.PortRange.End) + for ifID, want := range tt.interfaces { + got, ok := topo.Interface(ifID) + assert.True(t, ok, "interface %d", ifID) + assert.Equal(t, want, got, "interface %d", ifID) + } +} diff --git a/pkg/snet/conn.go b/pkg/snet/conn.go index 3ec657f299..b2635dcbc6 100644 --- a/pkg/snet/conn.go +++ b/pkg/snet/conn.go @@ -16,7 +16,6 @@ package snet import ( - "context" "net" "time" @@ -65,21 +64,13 @@ func NewCookedConn( options ...ConnOption, ) (*Conn, error) { o := apply(options) - localIA, err := topo.LocalIA(context.Background()) - if err != nil { - return nil, err - } local := &UDPAddr{ - IA: localIA, + IA: topo.LocalIA, Host: pconn.LocalAddr().(*net.UDPAddr), } if local.Host == nil || local.Host.IP.IsUnspecified() { return nil, serrors.New("nil or unspecified address is not supported.") } - start, end, err := topo.PortRange(context.Background()) - if err != nil { - return nil, err - } return &Conn{ conn: pconn, local: local, @@ -89,8 +80,8 @@ func NewCookedConn( buffer: make([]byte, common.SupportedMTU), local: local, remote: o.remote, - dispatchedPortStart: start, - dispatchedPortEnd: end, + dispatchedPortStart: topo.PortRange.Start, + dispatchedPortEnd: topo.PortRange.End, }, scionConnReader: scionConnReader{ conn: pconn, diff --git a/pkg/snet/packet_conn.go b/pkg/snet/packet_conn.go index dc1f07abaf..9352a2c734 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -16,7 +16,6 @@ package snet import ( "net" - "net/netip" "syscall" "time" @@ -122,8 +121,9 @@ type SCIONPacketConn struct { // SCMP message is received. SCMPHandler SCMPHandler // Metrics are the metrics exported by the conn. - Metrics SCIONPacketConnMetrics - interfaceMap interfaceMap + Metrics SCIONPacketConnMetrics + // Topology provides interface information for the local AS. + Topology Topology } func (c *SCIONPacketConn) SetReadBuffer(bytes int) error { @@ -299,7 +299,7 @@ func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { if !path.Info.ConsDir { ifID = path.SecondHop.ConsEgress } - return c.interfaceMap.get(ifID) + return c.ifIDToAddr(ifID) case epic.PathType: var path epic.Path if err := path.DecodeFromBytes(rpath.Raw); err != nil { @@ -317,7 +317,7 @@ func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { if !infoField.ConsDir { ifID = hf.ConsEgress } - return c.interfaceMap.get(ifID) + return c.ifIDToAddr(ifID) case scion.PathType: var path scion.Raw if err := path.DecodeFromBytes(rpath.Raw); err != nil { @@ -335,12 +335,20 @@ func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { if !infoField.ConsDir { ifID = hf.ConsEgress } - return c.interfaceMap.get(ifID) + return c.ifIDToAddr(ifID) default: return nil, serrors.New("unknown path type", "type", rpath.PathType.String()) } } +func (c *SCIONPacketConn) ifIDToAddr(ifID uint16) (*net.UDPAddr, error) { + addrPort, ok := c.Topology.Interface(ifID) + if !ok { + return nil, serrors.New("interface number not found", "interface", ifID) + } + return net.UDPAddrFromAddrPort(addrPort), nil +} + type SerializationOptions struct { // If ComputeChecksums is true, the checksums in sent Packets are // recomputed. Otherwise, the checksum value is left intact. @@ -355,13 +363,3 @@ type SerializationOptions struct { // unchanged. InitializePaths bool } - -type interfaceMap map[uint16]netip.AddrPort - -func (m interfaceMap) get(id uint16) (*net.UDPAddr, error) { - addrPort, ok := m[id] - if !ok { - return nil, serrors.New("interface number not found", "interface", id) - } - return net.UDPAddrFromAddrPort(addrPort), nil -} diff --git a/pkg/snet/snet.go b/pkg/snet/snet.go index cc4c13d503..189a8c046f 100644 --- a/pkg/snet/snet.go +++ b/pkg/snet/snet.go @@ -49,11 +49,22 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" ) -// Topology provides local-IA topology information -type Topology interface { - LocalIA(ctx context.Context) (addr.IA, error) - PortRange(ctx context.Context) (uint16, uint16, error) - Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) +// Topology provides information about the topology of the local ISD-AS. +type Topology struct { + // LocalIA is local ISD-AS. + LocalIA addr.IA + // PortRange is the directly dispatched port range. Start and End are + // inclusive. + PortRange TopologyPortRange + // Interface provides information about a local interface. If the interface + // is not present, the second return value must be false. + Interface func(uint16) (netip.AddrPort, bool) +} + +// TopologyPortRange is the range of ports that are directly dispatched to the +// application. The range is inclusive. +type TopologyPortRange struct { + Start, End uint16 } var _ Network = (*SCIONNetwork)(nil) @@ -68,7 +79,8 @@ type SCIONNetworkMetrics struct { // SCIONNetwork is the SCION networking context. type SCIONNetwork struct { // Topology provides local AS information, needed to handle sockets and - // traffic. + // traffic. Note that the Interfaces method might be called once per packet, + // so an efficient implementation is strongly recommended. Topology Topology // ReplyPather is used to create reply paths when reading packets on Conn // (that implements net.Conn). If unset, the default reply pather is used, @@ -91,14 +103,7 @@ func (n *SCIONNetwork) OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketCo if addr == nil || addr.IP.IsUnspecified() { return nil, serrors.New("nil or unspecified address is not supported") } - start, end, err := n.Topology.PortRange(ctx) - if err != nil { - return nil, err - } - ifAddrs, err := n.Topology.Interfaces(ctx) - if err != nil { - return nil, err - } + start, end := n.Topology.PortRange.Start, n.Topology.PortRange.End if addr.Port == 0 { pconn, err = listenUDPRange(addr, start, end) } else { @@ -118,10 +123,10 @@ func (n *SCIONNetwork) OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketCo return nil, err } return &SCIONPacketConn{ - Conn: pconn, - SCMPHandler: n.SCMPHandler, - Metrics: n.PacketConnMetrics, - interfaceMap: ifAddrs, + Conn: pconn, + SCMPHandler: n.SCMPHandler, + Metrics: n.PacketConnMetrics, + Topology: n.Topology, }, nil } diff --git a/private/app/path/path.go b/private/app/path/path.go index 937ca0a580..5a61b8bb64 100644 --- a/private/app/path/path.go +++ b/private/app/path/path.go @@ -83,6 +83,10 @@ func Choose( if err != nil { return nil, serrors.Wrap("fetching paths", err) } + topo, err := daemon.LoadTopology(ctx, conn) + if err != nil { + return nil, serrors.Wrap("loading topology", err) + } if o.epic { // Only use paths that support EPIC and intra-AS (empty) paths. epicPaths := []snet.Path{} @@ -102,7 +106,7 @@ func Choose( paths = epicPaths } if o.probeCfg != nil { - paths, err = filterUnhealthy(ctx, paths, remote, conn, o.probeCfg, o.epic) + paths, err = filterUnhealthy(ctx, paths, remote, topo, o.probeCfg, o.epic) if err != nil { return nil, serrors.Wrap("probing paths", err) } @@ -121,7 +125,7 @@ func filterUnhealthy( ctx context.Context, paths []snet.Path, remote addr.IA, - sd daemon.Connector, + topo snet.Topology, cfg *ProbeConfig, epic bool, ) ([]snet.Path, error) { @@ -144,7 +148,7 @@ func filterUnhealthy( LocalIA: cfg.LocalIA, LocalIP: cfg.LocalIP, SCIONPacketConnMetrics: cfg.SCIONPacketConnMetrics, - Topology: sd, + Topology: topo, }.GetStatuses(subCtx, nonEmptyPaths, pathprobe.WithEPIC(epic)) if err != nil { return nil, serrors.Wrap("probing paths", err) diff --git a/scion-pki/certs/renew.go b/scion-pki/certs/renew.go index 2b28c5de72..a074777fe9 100644 --- a/scion-pki/certs/renew.go +++ b/scion-pki/certs/renew.go @@ -739,9 +739,13 @@ func (r *renewer) requestRemote( IA: r.LocalIA, Host: &net.UDPAddr{IP: localIP}, } + topo, err := daemon.LoadTopology(ctx, r.Daemon) + if err != nil { + return nil, serrors.Wrap("loading topology", err) + } sn := &snet.SCIONNetwork{ - Topology: r.Daemon, + Topology: topo, SCMPHandler: snet.SCMPPropagationStopper{ Handler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, diff --git a/scion/cmd/scion/ping.go b/scion/cmd/scion/ping.go index 85645eccef..1db9204da6 100644 --- a/scion/cmd/scion/ping.go +++ b/scion/cmd/scion/ping.go @@ -151,11 +151,12 @@ On other errors, ping will exit with code 2. } defer sd.Close() - info, err := app.QueryASInfo(traceCtx, sd) + topo, err := daemon.LoadTopology(ctx, sd) if err != nil { - return err + return serrors.Wrap("loading topology", err) } - span.SetTag("src.isd_as", info.IA) + + span.SetTag("src.isd_as", topo.LocalIA) opts := []path.Option{ path.WithInteractive(flags.interactive), @@ -166,7 +167,7 @@ On other errors, ping will exit with code 2. } if flags.healthyOnly { opts = append(opts, path.WithProbing(&path.ProbeConfig{ - LocalIA: info.IA, + LocalIA: topo.LocalIA, LocalIP: localIP, })) } @@ -212,7 +213,7 @@ On other errors, ping will exit with code 2. panic("Invalid Local IP address") } local := addr.Addr{ - IA: info.IA, + IA: topo.LocalIA, Host: addr.HostIP(asNetipAddr), } pldSize := int(flags.size) @@ -266,7 +267,7 @@ On other errors, ping will exit with code 2. } stats, err := ping.Run(ctx, ping.Config{ - Topology: sd, + Topology: topo, Attempts: count, Interval: flags.interval, Timeout: flags.timeout, diff --git a/scion/cmd/scion/traceroute.go b/scion/cmd/scion/traceroute.go index 51777d9c39..ca28d49249 100644 --- a/scion/cmd/scion/traceroute.go +++ b/scion/cmd/scion/traceroute.go @@ -125,11 +125,11 @@ On other errors, traceroute will exit with code 2. return serrors.Wrap("connecting to SCION Daemon", err) } defer sd.Close() - info, err := app.QueryASInfo(traceCtx, sd) + topo, err := daemon.LoadTopology(ctx, sd) if err != nil { - return err + return serrors.Wrap("loading topology", err) } - span.SetTag("src.isd_as", info.IA) + span.SetTag("src.isd_as", topo.LocalIA) path, err := path.Choose(traceCtx, sd, remote.IA, path.WithInteractive(flags.interactive), path.WithRefresh(flags.refresh), @@ -180,14 +180,14 @@ On other errors, traceroute will exit with code 2. panic("Invalid Local IP address") } local := addr.Addr{ - IA: info.IA, + IA: topo.LocalIA, Host: addr.HostIP(asNetipAddr), } ctx = app.WithSignal(traceCtx, os.Interrupt, syscall.SIGTERM) var stats traceroute.Stats var updates []traceroute.Update cfg := traceroute.Config{ - Topology: sd, + Topology: topo, Remote: remote, NextHop: nextHop, MTU: path.Metadata().MTU, diff --git a/scion/showpaths/showpaths.go b/scion/showpaths/showpaths.go index b0c301d378..e80ccbc1dd 100644 --- a/scion/showpaths/showpaths.go +++ b/scion/showpaths/showpaths.go @@ -309,10 +309,11 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) { return nil, serrors.Wrap("connecting to the SCION Daemon", err, "addr", cfg.Daemon) } defer sdConn.Close() - localIA, err := sdConn.LocalIA(ctx) + topo, err := daemon.LoadTopology(ctx, sdConn) if err != nil { - return nil, serrors.Wrap("determining local ISD-AS", err) + return nil, serrors.Wrap("loading topology", err) } + localIA := topo.LocalIA if dst == localIA { return &Result{ LocalIA: localIA, @@ -355,7 +356,7 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) { DstIA: dst, LocalIA: localIA, LocalIP: cfg.Local, - Topology: sdConn, + Topology: topo, }.GetStatuses(ctx, p, pathprobe.WithEPIC(cfg.Epic)) if err != nil { return nil, serrors.Wrap("getting statuses", err) diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 55b5a6ac24..03a0268f2d 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -134,13 +134,21 @@ func (s server) run() { sdConn := integration.SDConn() defer sdConn.Close() + + loadCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + topo, err := daemon.LoadTopology(loadCtx, sdConn) + if err != nil { + integration.LogFatal("Error loading topology", "err", err) + } + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, PacketConnMetrics: scionPacketConnMetrics, - Topology: sdConn, + Topology: topo, } conn, err := sn.Listen(context.Background(), "udp", integration.Local.Host) if err != nil { @@ -233,13 +241,21 @@ func (c *client) run() int { defer integration.Done(integration.Local.IA, remote.IA) c.sdConn = integration.SDConn() defer c.sdConn.Close() + + loadCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + topo, err := daemon.LoadTopology(loadCtx, c.sdConn) + if err != nil { + integration.LogFatal("Error loading topology", "err", err) + } + c.network = &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, PacketConnMetrics: scionPacketConnMetrics, - Topology: c.sdConn, + Topology: topo, } log.Info("Send", "local", fmt.Sprintf("%v,[%v] -> %v,[%v]", From 17794d65fc0ad8ad3324f880061fd19c2035786a Mon Sep 17 00:00:00 2001 From: jiceatscion <139873336+jiceatscion@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:02:21 +0100 Subject: [PATCH 5/6] doc: improve the SIG doc with the text from the ietf drafts (#4641) Fixes #4640 --- doc/sig.rst | 84 +++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/doc/sig.rst b/doc/sig.rst index ab6bf49d14..ca9e89a7a8 100644 --- a/doc/sig.rst +++ b/doc/sig.rst @@ -2,37 +2,47 @@ SCION-IP Gateway **************** -The SCION-IP Gateway (SIG) tunnels IP packets over the SCION Internet. +Introduction +============ -An ingress SIG encapsulates IP packets in a SCION packet and sends it to an egress SIG determined -by the configured routing rules, where the packet is decapsulated and forwarded toward its -destination IP address. -From the perspective of IP, a SIG looks like a router. -From the perspective of SCION, a SIG is a regular application. +The SCION IP Gateway (SIG) enables IP packets to be tunneled over SCION to support communication between hosts that do not run a SCION implementation. A SIG acts as a router from the perspective of IP, whilst acting as SCION endpoint from the perspective of the SCION network. It is typically deployed inside the same AS-internal network as its non-SCION hosts, or at the edge of an enterprise network. -.. admonition:: TODO +Tunneling IP traffic over SCION requires a pair of SIGs and it involves the following steps: + +1. A sender sends an IP packet towards an IP destination. + +2. The IP packet reaches a SIG in the sender’s network via standard IP routing. + +3. Based on the destination IP address, the source (ingress) SIG determines the destination (egress) SIG's ISD-AS endpoint address. To achieve this, SIGs are administratively configured with a set of partner ASes and discover SIGs present at these ASes. They then exchange IP prefixes. The description of that protocol is yet to be written. + +4. The ingress SIG encapsulates the original IP packet within one or more SCION packets and sends them to the egress SIG. If necessary, the ingress SIG performs SCION path lookups and selects a SCION path to the egress SIG. + +5. The egress SIG receives the SCION packet or packets and decapsulates the original IP packet. It then forwards the packet to the final IP destination using standard IP routing. + +This protocol is designed to: + +- provide independence from the underlying SCION path MTU which can increase and decrease over time. +- provide fast detection of packet loss and subsequent recovery of decapsulation for packets that weren't lost. +- support for multiple streams within a framing session such that independent packet sequences be tunneled in parallel. - SIG Overview and introduction SIG Framing Protocol ==================== -SIG Framing Protocol describes frames sent between two SIG instances. -The IP packets transported via SIG are encapsulated in SIG frames. -There can be multiple IP packets in a single SIG frame. -A single IP packet can also be split into multiple SIG frames. +IP packets are encapsulated into SIG frames, which are sent as SCION/UDP datagrams. -SIG traffic can be sent over multiple SIG sessions. SIG uses different -sessions to transport different classes of traffic (e.g. priority vs. normal.) +There may be multiple IP packets in a single SIG frame, and a single IP packet may be split into multiple SIG frames. -Within each session there may be multiple streams. Streams are useful to -distinguish between traffic sent by different SIG instances. For example, -if SIG is restarted, it will create a new stream ID for each session. That way, -the peer SIG will know that the new frame with a new stream ID does not -carry trailing part of the unfinished IP packet from a different stream. +The ingress SIG initiates unidirectional packet flows to the egress SIG simply by sending the corresponding SIG frames. There is no handshake. The egress SIG, should it accept the traffic, instantiates the necessary resources on-demand to process each flow. Each such flow forms an independent sequence of packets (a stream) ordered by an incrementing sequence number. Between a given SIG ingress/egress pair, a (session ID, stream ID) pair uniquely identifies a stream. -Each SIG frame has a sequence number. The remote SIG uses the sequence -number to reassemble the contained IP packets. +To preserve performance, IP packets that form a sequence leave the egress SIG in the order in which they entered the ingress SIG. To that end: + +- The ingress SIG encapsulates IP packets that cannot be proven independent (e.g., with the same IP 6-tuple) in the same stream. +- The ingress SIG encapsulates IP packets to a given stream in the order in which they were received. +- The ingress SIG sends all frames of a given stream over the same SCION path. +- The egress SIG reassembles and forward packets from each stream, ordered by frame sequence number and by packet within each frame. + +The session ID part of the (session ID, stream ID) pair has an implementation defined meaning. Existing implementations use different session IDs for different traffic classes: the ingress SIG is responsible for assigning a traffic class. On the egress SIG side, the session ID may inform the processing of frames and enables per-class metrics. The Stack --------- @@ -57,9 +67,9 @@ Each SIG frame starts with SIG frame header with the following format:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Version | Session | Index | + | Version | Session ID | Index | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reserved (12 bits) | Stream (20 bits) | + | Reserved (12 bits) | Stream ID (20 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Sequence number + @@ -68,35 +78,27 @@ Each SIG frame starts with SIG frame header with the following format:: All fields within SIG frame header are in network byte order. -- The ``Version`` field indicates the SIG framing version. It must be set to zero. - -- The ``Session`` field indicates the SIG session to be used. - -- The ``Index`` field is the byte offset of the first beginning of an IP packet - within the payload. If no IP packet starts in the payload, for example, if - the frame contains only a trailing part of an IP packet, the field must be set - to 0xFFFF. - -- The ``Reserved`` field is reserved and must be set to zero. - -- The ``Stream`` field, along with the session identifies a unique sequence of - SIG frames. +- ``Version`` (8 bits) indicates the SIG framing version. It MUST be set to zero if following this specification. +- ``Session ID`` (8 bits) identifies a tunneling session between a pair of SIGs. +- ``Index`` (16 bits) is the byte offset of the first beginning of an IP packet within the payload. If no IP packet starts in the payload, e.g. if the frame contains only the middle or trailing part of an IP packet, the field MUST be set to 0xFFFF. +- ``Reserved`` (12 bits): it MUST be set to zero. +- ``Stream ID`` (20 bits), along with the session, it identifies a unique sequence of SIG frames. Frames from the same stream are, on the egress SIG, put into the same reassembly queue. There may be multiple streams per session. +- ``Sequence Number`` (64 bits) indicates the position of the frame within a stream. Consecutive frames of a given stream have consecutive sequence numbers. IP packets split among multiple frames are re-assembled by concatenating the payloads of consecutive frames. -- The ``Sequence number`` field indicates a position of the frame within a - stream. Consecutive frames can be used to reassemble IP packets split among - multiple frames. +A SIG MAY drop frames. In the current implementation, the egress SIG does not buffer frames that are received out-ot-order. Instead it drops any out-of-order and following frames until it finds the begining of a new encapsulated IP packet. SIG frame payload ----------------- -SIG frame payload may contain multiple IPv4 or IPv6 packets, or parts +The SIG frame payload may contain multiple IPv4 or IPv6 packets, or parts thereof. No other types of packets can be encapsulated. The packets are placed one directly after another, with no padding. +Multicast traffic is not supported yet. SIG uses IPv4/6 "payload length" field to determine the size of the packet. To make the processing easier, it is required that the fixed part of the IP header is in the frame where the IP packet begins. In other words, the initial fragment -of an IPv4 packet must be at least 20 bytes long. Initial fragment of an IPv6 +of an IPv4 packet must be at least 20 bytes long. The initial fragment of an IPv6 packet must be at least 40 bytes long. Example From f49d3cda0b58a7572d0b97b8561f3b49777d458e Mon Sep 17 00:00:00 2001 From: jiceatscion <139873336+jiceatscion@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:34:17 +0100 Subject: [PATCH 6/6] build: bump golang.org/x/net from 0.25.0 to 0.33.0 (#4669) Fixes https://github.com/scionproto/scion/security/dependabot/85 --- go.mod | 2 +- go.sum | 4 ++-- go_deps.bzl | 4 ++-- licenses/data/org_golang_x_net/LICENSE | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index af7b2695b1..8d59f204b9 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.31.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.33.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d diff --git a/go.sum b/go.sum index c8f1c0c3aa..29ba6756e6 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/go_deps.bzl b/go_deps.bzl index d28e583874..bc7828680e 100644 --- a/go_deps.bzl +++ b/go_deps.bzl @@ -1602,8 +1602,8 @@ def go_deps(): go_repository( name = "org_golang_x_net", importpath = "golang.org/x/net", - sum = "h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=", - version = "v0.25.0", + sum = "h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=", + version = "v0.33.0", ) go_repository( name = "org_golang_x_oauth2", diff --git a/licenses/data/org_golang_x_net/LICENSE b/licenses/data/org_golang_x_net/LICENSE index 6a66aea5ea..2a7cf70da6 100644 --- a/licenses/data/org_golang_x_net/LICENSE +++ b/licenses/data/org_golang_x_net/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.