Skip to content

Commit

Permalink
prototype scion-socket
Browse files Browse the repository at this point in the history
this is required by bindings to languages whose networking libraries main abstraction are berkley sockets, not connections as in Go
  • Loading branch information
amdfxlucas committed Dec 22, 2023
1 parent 3afc9a9 commit 2b4324c
Show file tree
Hide file tree
Showing 8 changed files with 590 additions and 46 deletions.
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/_examples/helloworld_socket/helloworld_socket.go",
"args": ["-remote" ,"19-ffaa:1:1067,127.0.0.1:2222"]

}
]
}
Binary file added _examples/helloworld_socket/helloworld_socket
Binary file not shown.
118 changes: 118 additions & 0 deletions _examples/helloworld_socket/helloworld_socket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2018 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"
"errors"
"flag"
"fmt"
"net/netip"
"os"
"time"

"github.com/netsec-ethz/scion-apps/pkg/pan"
)

func main() {
var err error
// get local and remote addresses from program arguments:
var listen pan.IPPortValue
flag.Var(&listen, "listen", "[Server] local IP:port to listen on")
remoteAddr := flag.String("remote", "", "[Client] Remote (i.e. the server's) SCION Address (e.g. 17-ffaa:1:1,[127.0.0.1]:12345)")
count := flag.Uint("count", 1, "[Client] Number of messages to send")
flag.Parse()

if (listen.Get().Port() > 0) == (len(*remoteAddr) > 0) {
check(fmt.Errorf("either specify -listen for server or -remote for client"))
}

if listen.Get().Port() > 0 {
err = runServer(listen.Get())
check(err)
} else {
err = runClient(*remoteAddr, int(*count))
check(err)
}
}

func runServer(listen netip.AddrPort) error {
sock, err := pan.NewScionSocket(context.Background(), listen)

if err != nil {
return err
}
defer sock.Close()
fmt.Println(sock.LocalAddr())

buffer := make([]byte, 16*1024)
for {
n, from, err := sock.ReadFrom(buffer)
if err != nil {
return err
}
data := buffer[:n]
fmt.Printf("Received %s: %s\n", from, data)
msg := fmt.Sprintf("take it back! %s", time.Now().Format("15:04:05.0"))
n, err = sock.WriteTo([]byte(msg), from)
if err != nil {
return err
}
fmt.Printf("Wrote %d bytes.\n", n)
}
}

func runClient(address string, count int) error {
addr, err := pan.ResolveUDPAddr(context.TODO(), address)
if err != nil {
return err
}

sock, err := pan.NewScionSocket(context.Background(), netip.AddrPort{})
if err != nil {
return err
}
defer sock.Close()

for i := 0; i < count; i++ {
nBytes, err := sock.WriteTo([]byte(fmt.Sprintf("hello world %s", time.Now().Format("15:04:05.0"))), addr)
if err != nil {
return err
}
fmt.Printf("Wrote %d bytes.\n", nBytes)

buffer := make([]byte, 16*1024)
/* if err = conn.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
return err
} */
n, _, err := sock.ReadFrom(buffer)
if errors.Is(err, os.ErrDeadlineExceeded) {
continue
} else if err != nil {
return err
}
data := buffer[:n]
fmt.Printf("Received reply: %s\n", data)
}
return nil
}

// Check just ensures the error is nil, or complains and quits
func check(e error) {
if e != nil {
fmt.Fprintln(os.Stderr, "Fatal error:", e)
os.Exit(1)
}
}
188 changes: 188 additions & 0 deletions pkg/pan/combi_selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package pan

import (
"context"
"errors"
)

// socket roles (selector internals)
const dialer = 0
const listener = 1

/*
!
\brief this class provides the service of path selection for a remote address to scion-sockets
Every scion-socket has one.
It should not be cluttered with Refresh()/Update()/PathDown or any other technical
methods that are required by the pathStatedDB or pathPool to update the selectors state.
*/
type CombiSelector interface {
Close() error
// PathDown(PathFingerprint, PathInterface) // pathDownNotifyee

// called when the scion-socket is bound to an address
LocalAddrChanged(newlocal UDPAddr)
Path(remote UDPAddr) (*Path, error)
//Refresh([]*Path) Selector
// Refresh(paths []*Path, remote UDPAddr)
Record(remote UDPAddr, path *Path)
//Update( )#
// setter the respective defaults
SetReplySelector(ReplySelector)
SetSelector(sel func() Selector)
SetPolicy(pol func() Policy)

// setter for AS specific path policies
// SetSelectorFor(remote IA, sel Selector)
// SetPolicyFor(remote IA, pol Policy)
// SetPolicedSelectorFor(remote IA, sel Selector, pol Policy)

// initialize(local UDPAddr, remote UDPAddr, paths []*Path)
// maybe make this pubilc and let the ScionCocketCall it,
// in its ctor (or once the local addr is known i.e after Bind() was called )
}

type DefaultCombiSelector struct {
local UDPAddr
roles map[UDPAddr]int // is this host the dialer or listener for the connection to this remote host
// decided from which method is called first for a remote address X
// Record(X)->listener or Path(X)->dialer

// maybe make map of pair (, ,) ?!
// policies map[UDPAddr]Policy
policy_factory func() Policy
selector_factory func() Selector

// TODO: this state should be confined in size somehow
// i.e. drop selectors with LRU scheme
// Note that this is not an attack vector, as this state can only be increased
// by deliberate decisions of this host to dial a remote for which it does not yet has a selector
selectors map[IA]Selector
subscribers map[IA]*pathRefreshSubscriber
replyselector ReplySelector
}

func (s *DefaultCombiSelector) needPathTo(remote UDPAddr) bool {
return s.local.IA != remote.IA
}

func (s *DefaultCombiSelector) Close() error {

for _, v := range s.subscribers {
if e := v.Close(); e != nil {
return e
}
}

for _, v := range s.selectors {
if e := v.Close(); e != nil {
return e
}
}
if e := s.replyselector.Close(); e != nil {
return e
}

return nil
}

func NewDefaultCombiSelector(local UDPAddr) (CombiSelector, error) {
selector := &DefaultCombiSelector{
local: local,
roles: make(map[UDPAddr]int),
// policies: make(map[UDPAddr]Policy),
policy_factory: func() Policy {
var s Policy = nil
return s
},
selector_factory: func() Selector { return NewDefaultSelector() },
selectors: make(map[IA]Selector),
subscribers: make(map[IA]*pathRefreshSubscriber),
replyselector: NewDefaultReplySelector(),
}

selector.replyselector.Initialize(local)

return selector, nil
}

func (s *DefaultCombiSelector) SetReplySelector(rep ReplySelector) {
s.replyselector = rep
}

func (s *DefaultCombiSelector) LocalAddrChanged(newlocal UDPAddr) {
s.local = newlocal
}

func (s *DefaultCombiSelector) Path(remote UDPAddr) (*Path, error) {
if r, ok := s.roles[remote]; ok {
// the role is already decided
if r == dialer {
if s.needPathTo(remote) {
sel := s.selectors[remote.IA]
sel.NewRemote(remote)
return sel.Path(remote)
} else {
return nil, errors.New("if src and dst are in same AS and no scion path is required, the connection shouldnt request one")
}
} else {
return s.replyselector.Path(remote)
}
} else {
// no role yet -> no path to remote has been requested yet Path()
// so we are acting as a server
s.roles[remote] = dialer

// set up a refresherSubscriber etc ..
if s.needPathTo(remote) {
var selector Selector
var policy Policy

if s.policy_factory != nil {
policy = s.policy_factory()
}
if s.selector_factory != nil {
selector = s.selector_factory()
} else {
selector = NewDefaultSelector()
}
var ctx context.Context = context.Background()
// Todo: set timeout for path request
subscriber, err := openPathRefreshSubscriber(ctx, s.local, remote, policy, selector)
if err != nil {

return nil, err
}
s.selectors[remote.IA] = selector
s.subscribers[remote.IA] = subscriber

return selector.Path(remote)
} else {
return nil, errors.New("if src and dst are in same AS and no scion path is required, the connection shouldnt request one")
}
}
}

func (s *DefaultCombiSelector) SetPolicy(pol func() Policy) {
s.policy_factory = pol
}

func (s *DefaultCombiSelector) SetSelector(sel func() Selector) {
s.selector_factory = sel
}

func (s *DefaultCombiSelector) Record(remote UDPAddr, path *Path) {

if r, ok := s.roles[remote]; ok {
// the role is already decided
if r == listener {
s.replyselector.Record(remote, path)
}
} else {
// no role yet -> no path to remote has been requested yet Path()
// so we are acting as a server
s.roles[remote] = listener
}

}
Loading

0 comments on commit 2b4324c

Please sign in to comment.