Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
46cc17c
New test using tiny topo.
juagargi Mar 28, 2025
300b45d
Refactor test.
juagargi Mar 28, 2025
c4c1a3a
First phase of multihoming test.
juagargi Mar 28, 2025
04cad6b
Temporary changes.
juagargi Mar 28, 2025
7b2db98
Multihomed behavior through UDP socket creation.
juagargi Sep 3, 2025
1854257
Limit cache size.
juagargi Sep 3, 2025
f5f8e43
Make multihomed tests more robust to concurrent running.
juagargi Sep 3, 2025
cd14a4d
Fix gateway/dataplane TestNoLeak by allowing to stop the continous st…
juagargi Sep 4, 2025
708e599
Do not obtain local address at Conn creation, do it at WriteTo.
juagargi Sep 10, 2025
2a286d4
More efficient & clear mutex locking in OutboundIP.
juagargi Sep 10, 2025
d2b1582
Remove no longer valid comment.
juagargi Sep 10, 2025
8da7d47
Remove not needed double locking of the cache in OutboundIP.
juagargi Sep 10, 2025
90228c0
The ticker object is local to continuousCheckInterfaces.
juagargi Sep 25, 2025
4c31ce3
Add benchmarks measuring sync.Map and RWMutex+map.
juagargi Sep 25, 2025
9e32756
Use slices.Equal instead of custom function.
juagargi Sep 25, 2025
15477a3
Use RUnlock() instead of RLocker().Unlock().
juagargi Sep 25, 2025
58aaa1a
Fix comment.
juagargi Sep 25, 2025
46fd443
Simplify storage of localAddresses to just a slice.
juagargi Sep 25, 2025
0e31eed
Add comment clarifying interactions with NAT+STUN.
juagargi Sep 25, 2025
ce9224b
Fix build after rebasing on last master.
juagargi May 22, 2026
9d5b5d5
Move the unit test to an acceptance test.
juagargi May 22, 2026
3162dd7
Simplify test: server at 110, clients at 111 and 112.
juagargi May 22, 2026
25f1483
Remove deprecated unit test.
juagargi May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions acceptance/multihomed/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
load("//acceptance/common:topogen.bzl", "topogen_test")

topogen_test(
name = "test",
src = "test.py",
args = [
"--executable=test-client:$(location //acceptance/multihomed/test-client)",
"--executable=test-server:$(location //acceptance/multihomed/test-server)",
],
data = [
"//acceptance/multihomed/test-client",
"//acceptance/multihomed/test-server",
],
topo = "//topology:tiny.topo",
)
19 changes: 19 additions & 0 deletions acceptance/multihomed/test-client/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "github.com/scionproto/scion/acceptance/multihomed/test-client",
visibility = ["//visibility:private"],
deps = [
"//pkg/daemon:go_default_library",
"//pkg/daemon/types:go_default_library",
"//pkg/snet:go_default_library",
],
)

go_binary(
name = "test-client",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
127 changes: 127 additions & 0 deletions acceptance/multihomed/test-client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2026 ETH Zurich
//
// 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 main

import (
"context"
"flag"
"log"
"os"

"github.com/scionproto/scion/pkg/daemon"
daemontypes "github.com/scionproto/scion/pkg/daemon/types"
"github.com/scionproto/scion/pkg/snet"
)

func main() {
log.SetOutput(os.Stdout)

// Parse test inputs. The remote is provided as a full SCION UDP address so the same
// client binary can probe server primary and secondary IPs without code changes.
var daemonAddr string
var localAddr snet.UDPAddr
var remoteAddr snet.UDPAddr
var expect string
var expectAddr *snet.UDPAddr

flag.StringVar(&daemonAddr, "daemon", "", "SCION daemon address")
flag.Var(&localAddr, "local", "Local SCION address")
flag.Var(&remoteAddr, "remote", "Remote SCION address")
flag.StringVar(&expect, "expect", "", "Expected remote SCION address")
flag.Parse()

if expect != "" {
parsed, err := snet.ParseUDPAddr(expect)
if err != nil {
log.Fatalf("parse expected remote address: %v", err)
}
expectAddr = parsed
}

if daemonAddr == "" {
daemonAddr = os.Getenv("SCION_DAEMON_ADDRESS")
}
if daemonAddr == "" {
daemonAddr = os.Getenv("SCION_DAEMON")
}
if daemonAddr == "" {
log.Fatal("daemon address missing: pass -daemon or set SCION_DAEMON_ADDRESS/SCION_DAEMON")
}

// Resolve a path from local IA to remote IA.
ctx := context.Background()
sd, err := daemon.NewService(daemonAddr).Connect(ctx)
if err != nil {
log.Fatalf("connect daemon: %v", err)
}
defer sd.Close()

paths, err := sd.Paths(ctx, remoteAddr.IA, localAddr.IA, daemontypes.PathReqFlags{Refresh: true})
if err != nil {
log.Fatalf("path lookup: %v", err)
}
if len(paths) == 0 {
log.Fatalf("no path from %s to %s", localAddr.IA, remoteAddr.IA)
}
sp := paths[0]

// Build a SCION connection pinned to the selected path.
topo, err := daemon.LoadTopology(ctx, sd)
if err != nil {
log.Fatalf("load topology: %v", err)
}
remoteAddr.Path = sp.Dataplane()
remoteAddr.NextHop = sp.UnderlayNextHop()

sn := snet.SCIONNetwork{
Topology: topo,
SCMPHandler: snet.DefaultSCMPHandler{
RevocationHandler: daemon.RevHandler{Connector: sd},
},
}

conn, err := sn.Dial(ctx, "udp", localAddr.Host, &remoteAddr)
if err != nil {
log.Fatalf("dial: %v", err)
}
defer conn.Close()

// Exchange ping/pong payloads and assert reply endpoint if requested by the caller.
_, err = conn.Write([]byte("ping"))
if err != nil {
log.Fatalf("write ping: %v", err)
}

buf := make([]byte, 2048)
n, from, err := conn.ReadFrom(buf)
if err != nil {
log.Fatalf("read pong: %v", err)
}
if string(buf[:n]) != "pong" {
log.Fatalf("unexpected payload: %q", string(buf[:n]))
}
if expectAddr != nil {
got, ok := from.(*snet.UDPAddr)
if !ok {
log.Fatalf("unexpected remote type %T", from)
}
if got.IA != expectAddr.IA || got.Host.Port != expectAddr.Host.Port ||
!got.Host.IP.Equal(expectAddr.Host.IP) {
log.Fatalf("unexpected remote. got=%s want=%s", got, expectAddr)
}
}

log.Printf("client success remote=%s", from)
}
15 changes: 15 additions & 0 deletions acceptance/multihomed/test-server/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
load("@rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "github.com/scionproto/scion/acceptance/multihomed/test-server",
visibility = ["//visibility:private"],
deps = ["//pkg/snet:go_default_library"],
)

go_binary(
name = "test-server",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
102 changes: 102 additions & 0 deletions acceptance/multihomed/test-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2026 ETH Zurich
//
// 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 main

import (
"flag"
"log"
"net"
"os"
"strconv"

"github.com/scionproto/scion/pkg/snet"
)

func main() {
log.SetOutput(os.Stdout)

// Parse test inputs. The same server binary is used for:
// - IPv4 unbound mode: bind to 0.0.0.0
// - IPv6 unbound mode: bind to ::
var bindAddr string
var port int

flag.StringVar(&bindAddr, "bind", "0.0.0.0", "Bind host")
flag.IntVar(&port, "port", 31000, "Bind UDP port")
flag.Parse()

// Bind a raw UDP socket in the tester namespace. Replies are created by reversing the
// received SCION packet, which preserves the destination address the client originally used.
local, err := net.ResolveUDPAddr("udp", net.JoinHostPort(bindAddr, portString(port)))
if err != nil {
log.Fatalf("parse bind address: %v", err)
}
conn, err := net.ListenUDP("udp", local)
if err != nil {
log.Fatalf("listen: %v", err)
}
defer conn.Close()

log.Printf("server running bind=%s:%d", bindAddr, port)

// Single ping/pong exchange; process exits afterwards so the test can restart with a fresh bind.
var pkt snet.Packet
pkt.Prepare()
n, lastHop, err := conn.ReadFrom(pkt.Bytes)
if err != nil {
log.Fatalf("read ping: %v", err)
}
pkt.Bytes = pkt.Bytes[:n]

if err := pkt.Decode(); err != nil {
log.Fatalf("decode packet: %v", err)
}
pld, ok := pkt.Payload.(snet.UDPPayload)
if !ok {
log.Fatalf("unexpected payload type %T", pkt.Payload)
}
if string(pld.Payload) != "ping" {
log.Fatalf("unexpected payload: %q", string(pld.Payload))
}

rawPath, ok := pkt.Path.(snet.RawPath)
if !ok {
log.Fatalf("unexpected path type %T", pkt.Path)
}
replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rawPath)
if err != nil {
log.Fatalf("reverse path: %v", err)
}

pkt.Destination, pkt.Source = pkt.Source, pkt.Destination
pkt.Path = replyPath
pkt.Payload = snet.UDPPayload{
SrcPort: pld.DstPort,
DstPort: pld.SrcPort,
Payload: []byte("pong"),
}
if err := pkt.Serialize(); err != nil {
log.Fatalf("serialize reply: %v", err)
}
if _, err := conn.WriteTo(pkt.Bytes, lastHop); err != nil {
log.Fatalf("write pong: %v", err)
}

log.Printf("served ping from %s", pkt.Destination)
}

func portString(port int) string {
return strconv.Itoa(port)
}
Loading
Loading