Skip to content

Policy-based path-aware SCION application library - #187

Merged
matzf merged 8 commits into
netsec-ethz:developfrom
matzf:pan
Dec 3, 2021
Merged

matzf merged 8 commits into
netsec-ethz:developfrom
matzf:pan

Conversation

@matzf

@matzf matzf commented Mar 24, 2021 •

Copy link
Copy Markdown
Contributor

Policy-based, path-aware SCION application library

Working title "pan" (for pan-ready Path-Aware Networking library (?)).

The goal is to have a common, high-level library that makes it easy to write correct, functional applications using SCION.

Overview

The main entry points for applications are:

  • DialUDP / ListenUDP
  • DialQUIC / ListenQUIC

Both forms of the Dial call allow to specify a Policy and a Selector.

Policy

A path policy defines the allowed paths and/or a preference order of the paths. Policies are generally stateless and, in particular, they don't look for any short term information like measured latency or path "liveness".
Connections allow to change the path policy at any time.

Built in policies:

  • Interactive selection, pinned or preferred path
  • Textual hop predicate sequence (e.g. for CLI)
  • PolicyChain (sequential policy application, And operation)
  • Union / Or operation
  • Sort by path metadata:
    • Lowest latency
    • Lowest latency enhanced by geo data (fill/correct latency with geo distance divided by speed of light)
    • Highest bandwidth
    • Fewest (total) hops / prefer direct links
  • Filter by path metadata (same list as above but filter with range of allowed values instead of sorting)
  • Textual policy format, e.g. for application configuration (see https://scion.docs.anapaya.net/en/latest/PathPolicy.html)

Selector

A path selector is a stateful controller associated with a connection/socket. It receives the paths filtered by the Policy as an input. For each packet sent, the selector choses the path.
The default selector keeps using the first chosen path unless SCMP path down notifications are encountered, in which case it will always switch to the next alive path.
Custom selectors implement e.g. active path probing, coupling of multiple connections to either use the same path or to use maximally disjoint paths, direct performance feedback from the application, etc.

Dialed vs Listening

pan differentiates between dialed and listening sockets. Dialed sockets (for "clients") define the path policy and a selector. The client side of a connection is in control of the path used.
The listening side (for "servers") only replies on the paths last used by each client, by means of a customizable reply path selector. The listening side does not implement any policy, nor does it do anything to keep the paths fresh.
The default reply path selector records a fixed number of paths last uses replies on the path last used by a client. It normally uses the path last used by the client, but does use other recorded paths to try routing around temporarily broken paths.

PUDP [experiment]

Removed from this branch for now: the idea was to look at including explicit path control information in the packets. This control information allows to send packets redundantly over multiple paths (for racing or redundancy), probing, and some path "negotiation". This experiment included this control data in the UDP payload, which is not great for interoperability. I now think that defining an End-to-End extension header option to transport this control information might be more appropriate.
See documentation in the now removed files for more details: https://github.com/netsec-ethz/scion-apps/blob/4329bc6a7bc6abc2264ad9fd0a3f6fab3e8c3c36/pkg/pan/pudp.go


Changes in this PR:

  • add pan library
    • code related to hostnames is almost identical to appnet, but different types etc. Kept mostly private to allow suitably reorganizing this at some point.
  • update pkg/shttp to use pan, now allows to specify paths with a Policy
  • update scion-bat to use pan, add new command line options to define path choice
  • update _examples/helloworld to use pan
  • add new _examples/helloquic, to illustrate and test usage of QUIC with pan
  • bugfixes backported to appnet: fix SplitHostPort (bad regex capture group index), extend addrFromString to work with addresses without the optional brackets.

This change is Reviewable

@matzf
matzf force-pushed the pan branch 2 times, most recently from 70f49ea to 0c8e970 Compare September 15, 2021 11:38
@matzf
matzf marked this pull request as ready for review September 24, 2021 12:04
@matzf matzf changed the title [WIP] Policy-based path-aware SCION application library Policy-based path-aware SCION application library Sep 24, 2021

@juagargi juagargi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great addition! We should merge it ASAP to let people use it in their applications.
I didn't do a full review, but I took some notes. Could you please check them? (non blockers)

  • How to hook a statistics processor?
    • Is a subscriber enough?
    • Could the processor push data into the statistics?
  • Use case for colibri, in the future we would need:
    • path type in fingerprints
    • refresher must also create renewals
    • scmp path down detection must also check for colibri scmp errors (which are not there yet)
  • Policy and Selector are very similar:
    • Both components filter paths.
    • The PingingSelector partially sorts paths (first one is always the less-than, the rest is unsorted)
    • If they are merged into one component, composition (of them) would be easier.

Reviewed 1 of 43 files at r2.
Reviewable status: 1 of 48 files reviewed, all discussions resolved / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained


pkg/pan/stats.go, line 23 at r2 (raw file):

)

var stats pathStatsDB

Do we have any scenario where we need the info per connection instead of globally?


pkg/pan/stats.go, line 221 at r2 (raw file):

func (s *pathStatsDB) NotifyPathDown(pf PathFingerprint, pi PathInterface) {
	s.recordPathDown(pf, pi)
	for _, subscriber := range s.subscribers {

What about running the loop and callbacks in a separate go-routine?

@matzf matzf left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to hook a statistics processor?

There is nothing like this yet, and I don't have a clear idea on what the requirements for this would even be. The statistics are really bare bones at this point, and for this reason I've avoided exporting them from the package API for this version.

Use case for colibri, in the future we would need

Ok, makes sense.

Policy and Selector are very similar:

They may be similar on some level of abstraction, but I see them quite distinct; the Policy expresses the user's general preferences and rules. A policy is state-less, "long-lived" and is generically applicable to all destinations (but of course it can contain different rules for different sets of destinations).
The Selector, on the other hand, is stateful, tied to the life-time of a connection and only cares about the specific destination.

The idea is that the Policy filters and sorts paths based on the "static" path information. The Selector then takes into account "dynamic" information (dead paths, latency probes, etc) to select the "dynamically" best choice among the "statically" ranked paths. Perhaps, this makes more sense when I mention that the PingingSelector currently lacks any tuning parameters that weigh the "static" ranking against the "dynamic" information -- how much lower latency does rank i+1 need to have so we favor it over rank i path? I've left this out until now, as it just seems very tricky to parametrize right (and who would ever pick non-default values anyway?), but I should add a TODO for this.

I think I understand what you mean by the composability argument. Originally, this was in fact implemented as you suggest. The following observations made me change this to the current design with separate Policy/Selector concepts:

  • The policy result can be cached until the paths are refreshed. If we directly compose Selectors which potentially use "dynamic" information, we'd need additional quirks to avoid re-evaluating the full chain on every packet. Note that some of the ordering operations for the static information are rather expensive.
  • Applying a Policy always makes sense, regardless of which Selector is used.
  • The default use case, as I imagine it, is to only specify a custom policy and use the DefaultSelector. This gets more awkward if the policy is (part of) the selector.
  • Dynamically changing the policy of a connection (which is an important feature) appears to easier if the connection object contains both the policy and the selector separately.

With that, I'd prefer to keep these two concepts separate for this initial version. However, I'm very open if you have a specific suggestion on how this could be improved.

Reviewable status: 1 of 48 files reviewed, 1 unresolved discussion / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained


pkg/pan/stats.go, line 23 at r2 (raw file):

Previously, juagargi (Juan A. Garcia Pardo) wrote…

Do we have any scenario where we need the info per connection instead of globally?

Good point. The advantages of having this per connection would seem to be: general advantages of less global state (e.g. testability, compatibility of different versions of the library, etc) and information leakage / privacy concerns. The privacy concerns would matter e.g. in a web browser setting where we might want to isolate certain windows from each other.
What would make most sense, I think, would be to allow create multiple instances of the whole global state of the library (e.g. the connection to the sciond, the background runners, and this stats DB) and pass this (in some form) to all the API invocations. This would be quite clean, but I believe it would make the API less convenient to use in the default case, and/or more work to implement correctly. As far as I can tell, at least all the relevant globals are hidden from the API, so implementing this at a later point should be doable.


pkg/pan/stats.go, line 221 at r2 (raw file):

Previously, juagargi (Juan A. Garcia Pardo) wrote…

What about running the loop and callbacks in a separate go-routine?

Good point, will do.

@matzf
matzf force-pushed the pan branch 2 times, most recently from aeb8c96 to 105c7e7 Compare October 20, 2021 14:07
@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/pan.go, line 37 at r3 (raw file):

A path selector is a stateful controller associated with a connection/socket.
It receives the paths filtered by the Policy as an input. For each packet
sent, the selector choses the path.

chooses

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/pan.go, line 52 at r3 (raw file):

The default reply path selector records a fixed number of paths last uses
replies on the path last used by a client.

I think this sentence got broken in an edit.

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/pan.go, line 91 at r3 (raw file):

   will seem to work initially, but can't work once this path expires -- working
   around this requires tricksery.
 - policy/selector as the main concept that applications use to chose paths for

choose

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 60 at r3 (raw file):

}

// DialAddr establishes a new QUIC connection to a server at the remote address.

DialQUIC

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 62 at r3 (raw file):

raddr

There is no raddr. UDPAddr does not contain a path anymore.

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 27 at r3 (raw file):

// This is needed here because we use quic.Dial, not quic.DialAddr but we want
// the close-the-socket behaviour of quic.DialAddr.

Does it help to include this implementation detail here?

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 86 at r3 (raw file):

}

// DialAddrEarly establishes a new 0-RTT QUIC connection to a server. Analogous to DialAddr.

DialQUICEarly

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 86 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

DialQUICEarly

And DialQUIC instead of DialAddr

@marcfrei

Copy link
Copy Markdown
Member

pkg/pan/quic_dial.go, line 40 at r3 (raw file):

}

func (s *QUICSession) SetPolicy(policy Policy) {

Not sure, does it make sense to associate the Policy concept also with sessions or should clients call SetPolicy directly on the connection.

@marcfrei

marcfrei commented Nov 1, 2021

Copy link
Copy Markdown
Member

pkg/pan/udp_dial.go, line 36 at r4 (raw file):

}

func DialUDP(ctx context.Context, local *net.UDPAddr, remote UDPAddr,

Add doc comment?

@marcfrei

marcfrei commented Nov 1, 2021

Copy link
Copy Markdown
Member

pkg/pan/udp_dial.go, line 30 at r4 (raw file):

	// WritePath writes a message to the remote address via the given path.
	// This bypasses the path policy and selector used for Write.
	WritePath(path *Path, b []byte) (int, error)

How about WriteToPath

@marcfrei

marcfrei commented Nov 1, 2021

Copy link
Copy Markdown
Member

pkg/pan/udp_dial.go, line 33 at r4 (raw file):

	// ReadPath reads a message and returns the (return-)path via which the
	// message was received.
	ReadPath(b []byte) (int, *Path, error)

ReadFromPath?

@marcfrei marcfrei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 34 files at r1, 16 of 43 files at r2, 31 of 36 files at r3, 3 of 4 files at r4.
Reviewable status: 51 of 76 files reviewed, 22 unresolved discussions / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained


_examples/helloquic/helloquic.go, line 1 at r4 (raw file):

// Copyright 2021 ETH Zurich

For consistency, add simple README.md and mention the example in the global README in section ## _examples


_examples/helloworld/helloworld.go, line 82 at r4 (raw file):

	defer conn.Close()

	for i := 0; i < 1; i++ {

Maybe introduce a variable (and a command-line option) for the number of messages to send?


netcat/udp.go, line 47 at r4 (raw file):

func (conn *udpListenConn) Write(b []byte) (int, error) {
	return conn.write(b)

Take the mutex here too, for consistency?


pkg/pan/raw.go, line 57 at r4 (raw file):

	raw         snet.PacketConn
	readMutex   sync.Mutex
	readBuffer  snet.Bytes

readBuffer is always == nil, right?


pkg/pan/raw.go, line 59 at r4 (raw file):

	readBuffer  snet.Bytes
	writeMutex  sync.Mutex
	writeBuffer snet.Bytes

writeBuffer is always == nil, right?


pkg/pan/raw.go, line 165 at r4 (raw file):

			underlay: &lastHop,
		}
		n := copy(b, udp.Payload)

This copy hurts, doesn't it? Could we introduce an additional type that somehow wraps snet.Bytes/snet.Packet and that could be used to offer more efficient read operations?


pkg/pan/udp_dial.go, line 76 at r4 (raw file):

	remote     UDPAddr
	subscriber *pathRefreshSubscriber
	Selector   Selector

Why is Selectorexported?


pkg/quicutil/single.go, line 39 at r4 (raw file):

unidirectional

pkg/quicutil/single.go, line 42 at r4 (raw file):

unidirectional

And replace tabs in comment in this and the next line with spaces.


ssh/client/ssh/selector.go, line 66 at r4 (raw file):

	}
	p := s.paths[s.current]
	s.current += 1

% len(s.paths)?

@benthor benthor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 43 files at r2.
Reviewable status: 52 of 76 files reviewed, 23 unresolved discussions / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained


pkg/pan/selector.go, line 37 at r4 (raw file):

// OnPathDown is invoked when SCMP path down notifications are received.
type Selector interface {
	Path() *Path

Could this be Path(UDPAddr), to matchSetPath(UDPAddr, []*Path)?

@benthor

benthor commented Nov 2, 2021

Copy link
Copy Markdown
Contributor

(Turned my above suggestion into a PR matzf#1)

@marcfrei marcfrei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 3 of 34 files at r1, 17 of 43 files at r2, 4 of 36 files at r3, 1 of 4 files at r4.
Reviewable status: all files reviewed, 35 unresolved discussions / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained

a discussion (no related file):
TODO: search in all files for occurrences of appnet and check whether they are still correct/needed.



pkg/pan/cli.go, line 41 at r4 (raw file):

//
// The options should be presented to the user as:
//  -	a flag --interactve
  • space instead of tab after '- '
  • "interactve" => "interactive"

pkg/pan/def.go, line 38 at r4 (raw file):

	pathExpiryRefreshLeadTime = ...
	pathExpiryPruneLeadTime = ...

I think we could drop Expiry from both names.


pkg/pan/hosts.go, line 34 at r4 (raw file):

)

// ResolveUDPAddrAt parses the address and resolves the hostname.

resolveUDPAddrAt


pkg/pan/hosts.go, line 90 at r4 (raw file):

)

// ParseSCIONAddr converts an SCION address string to a SCION address.

parseSCIONAddr


pkg/pan/path.go, line 146 at r4 (raw file):

}

// forwardingPathInfo contains information extracted from a dataplane forwardng path.

forwarding


pkg/pan/policy.go, line 95 at r4 (raw file):

}

// Sequence is a Policy filtering paths matching a textual pattern. The sequence pattern is

"policy"


pkg/pan/policy.go, line 182 at r4 (raw file):

func (p HighestMTU) Filter(paths []*Path) []*Path {
	sort.SliceStable(paths, func(i, j int) bool {
		return paths[i].Metadata.MTU < paths[j].Metadata.MTU

Not sure, shouldn't this be > if we want to sort in descending order?


pkg/pan/quic_listen.go, line 38 at r4 (raw file):

ListenQUIC

pkg/pan/quic_single.go, line 1 at r4 (raw file):

// Copyright 2021 ETH Zurich

Is this file needed?


pkg/pan/silence.go, line 44 at r4 (raw file):

		log.Default().SetOutput(logSilencerOriginal)
		logSilencerOriginal = nil
	}

Maybe assert (panic) that count is not negative at this point.


pkg/pan/stats.go, line 158 at r4 (raw file):

// Returns true if a does not have any recent down notifications and b does, or
// (more generally) if all down notifications for a are strictly older
// than any down notificating for b.

notification

@matzf matzf left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 55 of 80 files reviewed, 35 unresolved discussions / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained

a discussion (no related file):

Previously, marcfrei (Marc Frei) wrote…

TODO: search in all files for occurrences of appnet and check whether they are still correct/needed.

Thanks, will do. I'm planning to remove appnet with this PR -- just a few more applications to convert:

  • bwtester (this one actually requires some thought and work)
  • burster / cbrtester
  • camerapp ; will be deleted instead (separate PR #208 )


_examples/helloquic/helloquic.go, line 1 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

For consistency, add simple README.md and mention the example in the global README in section ## _examples

Done.


_examples/helloworld/helloworld.go, line 82 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Maybe introduce a variable (and a command-line option) for the number of messages to send?

Good idea, done.


netcat/udp.go, line 47 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Take the mutex here too, for consistency?

No I think this wouldn't work, Read and Write can be (safely) called concurrently. As far as i understand, this is actually used concurrently -- we copy (io.Copy) in both directions, from stdin to the connection and from the connection to stdout.

The mutex protects the requests/responses channels. I've added a comment to clarify this -- this code could definitely be improved a lot though. I'm not convinced this "UDP Listener" abstraction here even makes much sense...


pkg/pan/cli.go, line 41 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…
  • space instead of tab after '- '
  • "interactve" => "interactive"

Done.


pkg/pan/def.go, line 38 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…
	pathExpiryRefreshLeadTime = ...
	pathExpiryPruneLeadTime = ...

I think we could drop Expiry from both names.

Done.


pkg/pan/hosts.go, line 34 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

resolveUDPAddrAt

Done.


pkg/pan/hosts.go, line 90 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

parseSCIONAddr

Done.


pkg/pan/pan.go, line 37 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

chooses

Done.


pkg/pan/pan.go, line 52 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…
The default reply path selector records a fixed number of paths last uses
replies on the path last used by a client.

I think this sentence got broken in an edit.

Done.


pkg/pan/pan.go, line 91 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

choose

Done.


pkg/pan/path.go, line 146 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

forwarding

Done.


pkg/pan/policy.go, line 95 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

"policy"

Done.


pkg/pan/policy.go, line 182 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Not sure, shouldn't this be > if we want to sort in descending order?

💯


pkg/pan/quic_dial.go, line 27 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…
// This is needed here because we use quic.Dial, not quic.DialAddr but we want
// the close-the-socket behaviour of quic.DialAddr.

Does it help to include this implementation detail here?

Hmm yeah, you're right. Removed.


pkg/pan/quic_dial.go, line 40 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Not sure, does it make sense to associate the Policy concept also with sessions or should clients call SetPolicy directly on the connection.

Not entirely sure either; I've removed it for now.


pkg/pan/quic_dial.go, line 60 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

DialQUIC

Done.


pkg/pan/quic_dial.go, line 62 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…
raddr

There is no raddr. UDPAddr does not contain a path anymore.

Done.


pkg/pan/quic_dial.go, line 86 at r3 (raw file):

Previously, marcfrei (Marc Frei) wrote…

And DialQUIC instead of DialAddr

Done.


pkg/pan/quic_listen.go, line 38 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…
ListenQUIC

Done.


pkg/pan/raw.go, line 57 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

readBuffer is always == nil, right?

Ooh, you're right, great catch! Allocations galore!
Fixed.


pkg/pan/raw.go, line 59 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

writeBuffer is always == nil, right?

Done.


pkg/pan/raw.go, line 165 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

This copy hurts, doesn't it? Could we introduce an additional type that somehow wraps snet.Bytes/snet.Packet and that could be used to offer more efficient read operations?

It does hurt a bit, yes. As far as I can tell there is really no way around this though. The io.Reader API lets a caller specify a buffer to fill. The result type for this is only number of bytes written to this user buffer, not a slice -- if it was a slice, we could maybe read directly into the user buffer and then cut the headers off.
So we could provide our own, non-standard ReadSlice (or whatever) function which could avoid this copy, but in many cases we want/need to integrate with existing functionality in libraries that expect the io.Reader API and so we wouldn't really gain much.

FWIW, snet does the same thing here. In the grand scheme of things, this particular copy does not seem to stand out much (at this point in the end host stack, the packet was already copied at least 3 times from kernel to user space buffers or vice versa...).

Btw. I've also thought about whether recvmsg's scatter/gather API could be used to help in any way to avoid this copy (i.e. to make the kernel copy the Payload part directly to the result buffer). With the varying size of the SCION headers, i doubt this is at all possible though.


pkg/pan/selector.go, line 37 at r4 (raw file):

Previously, benthor wrote…

Could this be Path(UDPAddr), to matchSetPath(UDPAddr, []*Path)?

The idea here is that a selector is used as part of a connected socket, so the remote address never changes -- thus, no need to pass it to Path.
Btw, it's a not very clean to provide this remote address as part of SetPaths -- I did that, just to reduce the number of functions on the interface, maybe that's not a great idea. Perhaps it would be better to split this, something like this:

Initialize(remoteAddress UDPAddr, paths []*Path)
UpdatePaths(paths []*Path) 

pkg/pan/silence.go, line 44 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Maybe assert (panic) that count is not negative at this point.

Done.


pkg/pan/stats.go, line 158 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

notification

Done.


pkg/pan/udp_dial.go, line 30 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

How about WriteToPath

I'd prefer not to use WriteTo as prefix as this already has an existing meaning -- WriteTois used for unconnected datagram sockets and let's the caller specify the destination address. I think this could be confusing, as WriteToPath would then exist on both connected and unconnected sockets, but with differing signature (with/without destination address).
Maybe WriteWithPath and ReadWithPath? Or WriteByPath and ReadByPath? Seem a bit forced... I don't know. How strong is your dislike for WritePath? 😅


pkg/pan/udp_dial.go, line 76 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Why is Selectorexported?

Done.


pkg/quicutil/single.go, line 39 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…
unidirectional

Done.


pkg/quicutil/single.go, line 42 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…
unidirectional

And replace tabs in comment in this and the next line with spaces.

Done.


ssh/client/ssh/selector.go, line 66 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

% len(s.paths)?

Uh, yes that'd be better, thanks! 😅


pkg/pan/quic_single.go, line 1 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Is this file needed?

Nope, done.

@matzf matzf left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 54 of 80 files reviewed, 35 unresolved discussions / 0 of 1 LGTMs obtained / 0 of 1 approvals obtained


pkg/pan/udp_dial.go, line 36 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

Add doc comment?

Done.

@matzf
matzf force-pushed the pan branch 4 times, most recently from 665b9bd to 11b6299 Compare November 25, 2021 15:17
@marcfrei

marcfrei commented Dec 1, 2021

Copy link
Copy Markdown
Member

_examples/helloworld/README.md, line 3 at r10 (raw file):

# Hello World

A simple application using SCION that sends one packet from a client to a server.

Remove '.'

@marcfrei marcfrei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 1 of 4 files at r5, 1 of 2 files at r6, 6 of 18 files at r7, 64 of 64 files at r10.
Reviewable status: :shipit: complete! 1 of 1 LGTMs obtained


pkg/pan/silence.go, line 28 at r10 (raw file):

// silenceLog redirects the log.Default writer to a black hole.
// It can be reenabled by calling unsilenceLog.
// These functions can safely be called from multiple goroutines concurrently;

What about the following example scenario?

  • goroutine A executes unsilenceLog, evaluates count == 0 to true, and gets preempted
  • while goroutine A is passivated, goroutine B executes and completes silenceLog.
  • goroutine A gets scheduled again and installs logSilencerOriginal.

pkg/pan/udp_dial.go, line 30 at r4 (raw file):

Previously, matzf (Matthias Frei) wrote…

I'd prefer not to use WriteTo as prefix as this already has an existing meaning -- WriteTois used for unconnected datagram sockets and let's the caller specify the destination address. I think this could be confusing, as WriteToPath would then exist on both connected and unconnected sockets, but with differing signature (with/without destination address).
Maybe WriteWithPath and ReadWithPath? Or WriteByPath and ReadByPath? Seem a bit forced... I don't know. How strong is your dislike for WritePath? 😅

It's not a strong dislike. Other ideas: WriteViaPath. Or just Writep? Not sure, so maybe WritePath is the best alternative after all.

@matzf matzf left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 1 of 1 LGTMs obtained


pkg/pan/silence.go, line 28 at r10 (raw file):

Previously, marcfrei (Marc Frei) wrote…

What about the following example scenario?

  • goroutine A executes unsilenceLog, evaluates count == 0 to true, and gets preempted
  • while goroutine A is passivated, goroutine B executes and completes silenceLog.
  • goroutine A gets scheduled again and installs logSilencerOriginal.

You're right of course, thanks! I'll just change this to a mutex.


pkg/pan/stats.go, line 221 at r2 (raw file):

Previously, matzf (Matthias Frei) wrote…

Good point, will do.

Done.


pkg/pan/udp_dial.go, line 30 at r4 (raw file):

Previously, marcfrei (Marc Frei) wrote…

It's not a strong dislike. Other ideas: WriteViaPath. Or just Writep? Not sure, so maybe WritePath is the best alternative after all.

Via sounds good, I like it!

Borrowed from Latin viā (“by the way (of)”), ablative singular of via (“way, road”).

Road is close enough to "path", so maybe we can omit the redundancy and just use WriteVia etc :)

type Conn interface {
    Write(b []byte) (int, error)
    WriteVia(path *Path, b []byte) (int, error)
    Read([] byte) (int, error)
    ReadVia([] byte) (int, *Path, error)
}

type ReplyConn interface {
   WriteTo(b []byte, addr net.Addr) (int,  error)
   WriteToVia(b []byte, addr net.Addr, path *Path) (int, error)
   ReadFrom(b []byte) (int, net.Addr,  error)
   ReadFromVia(b []byte) (int,  net.Addr, *Path,  error)
}

I admit that WriteToVia and ReadFromVia still seem somewhat forced, but to me it seems good enough.

@marcfrei marcfrei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewable status: :shipit: complete! 1 of 1 LGTMs obtained

@marcfrei marcfrei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 4 of 4 files at r11.
Reviewable status: :shipit: complete! 1 of 1 LGTMs obtained

Burster and cbrtester, while useful at the time for debugging specific
issues with the routers, are somewhat redundant with the functionality
of the bwtester applications.
Moving forward, the idea to integrate the useful bits of these two tools
into the bwtester instead; see netsec-ethz#210.
@matzf

matzf commented Dec 3, 2021

Copy link
Copy Markdown
Contributor Author

Pushed a cleaned up history (to be merged with a merge commit).

  • add pkg/pan, pkg/quicutils as new libraries
  • convert applications to pan (individual commits):
    • web (pkg/shttp, bat, skip, ...)
    • bwtester
    • ssh
    • netcat
    • sensorapp
  • remove appnet, appquic

@matzf
matzf changed the base branch from master to develop December 3, 2021 13:57
matzf added 7 commits December 3, 2021 15:00
This is a first version of the pan "Path-Aware Networking" library.

The pan library represents the path selection process for SCION
connections by Policies, which filter and order the paths based on the
information included in the path metadata, and Selectors, which
dynamically select the final path for each packet sent.
This gives a relatively simple interface for applications that directly
translates to an application user interface (e.g. command line
interface), and connections can automatically failover in case of SCMP
errors messages. The pan library can do the work for querying and
updating path information in the background.
A small number of default Policies and Selectors are built-in. In the
future, we might add more of these out of the box, but the idea is that
applications can also define custom Policies and Selectors.
See the package documentation for more information.

Currently uses inet.af/netaddr to represent IP addresses. This will be
replaced by the very similar net/netip once this becomes available,
expectedly in go-1.18.
Other than this, pan attempts to not expose external libraries in its
API and provide a self-contained interface.

All applications in scion-apps have been converted to pan, but these
changes will be committed separately.

Known issues in this first version:
- the DefaultReplySelector, i.e. the path selector on the listening
  side, is susceptible to session hijacking by spoofing source
  address.
- the stats DB is intentionally kept private as this requires more
  thought. For now, applications need to store their own stats separate
  from pans internal store.
- internal state of DefaultReplySelector and stats DB are never cleaned
  up and can grow without bounds.
- far too many globals

Add package quicutil:
- single stream quic utilities
  Using a single quic stream is common in our demo applications.
  Add a utility function for this, extracted from previous internal
  implementation in shttp.
  Incompatible to the previous version in shttp. Now either side can
  start sending (by using two unidirectional streams), plus it can now
  reliably close. The listener is also more robust; previously, if a
  remote session was opened without ever opening a stream, this would
  have blocked the listener Accept indefinitely.
- generate self-signed certificate (previously in appquic)

Updated example code:
- extend helloworld example and port to pan.
- add helloquic example, using pan

Misc:
- integration: use addr= instead of only ia= as "ready signal"
- remove appnet/appquic from main README as these packages are slated for removal.
- bugfixes backported to appnet: fix SplitHostPort (bad regex capture
  group index), extend addrFromString to work with addresses without the
  optional brackets.
Convert pkg/shttp, pkg/shttp3, bat, skip, and web-gateway to use pan.

The shttp package now uses the quicutil.SingleStream, making it
incompatible with previous versions.

The shttp and shttp3 libraries now allow specifying a pan Policy. This
Policy can be changed at any time.

Add new path policy commandline options to bat.
Add default path policy options to command line for bwtestclient.

For the data channel, we want to ensure to use the path used for the
control channel and stick to this, i.e. no more fail-over once
connection is established. On the client side, we use the Pin policy to,
well, pin the path for the data channel. On the server side, we
initialize a default reply path selector with the path known from the
control channel. With this approach, we stick to the original forwarding
path, but the path can still be refreshed if it expires (however
unlikely that is, given the short max duration of tests). We could
perhaps go a bit further and use dumb selectors that always return the
fixed, initial path.
- update pty dependency (old version is broken for go >= 1.15)
- redo the path policy and selector configuration, now based on pan.
  Make more useful selectors available.
  Drop related helper packages, merge ssh/sssh into ssh/client/ssh (the
  package organization here is still rather wacky).
- Now uses quicutils.SingleStream, making this incompatible with
  previous versions.
- when executing a command, don't set Uid/Gid if the selected user
  is already the current user. Only appears to work when sshd is run
  with root otherwise.
- misc fixes and cleanup related to logging and error handling
- clean up import order

Replace the custom
Add path policy options to command line.

Use quicutils.SingleStream, making this incompatible with previous
versions.
Removed the long list of supported protocols in netcat again -- that was
a bit of a brain fart. The delicate handling of the quic streams is
enough of a protocol to warrant having a dedicated identifier.

Updated netcat QUIC integration tests (dont need the -b now, as both
peers can now start transmitting first).

Only tangentially related, fix concurrent Read/Close in the
udpListenConn.
Add path policy options to command line.
The appnet and appquic libraries have been replaced with "pan".
@matzf
matzf merged commit c26494e into netsec-ethz:develop Dec 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants