Skip to content

Commit 850e1e3

Browse files
authored
[webapp] Full Local Topology Support and Multi-IA Selection (#84)
* [webapp] Full Local Topology Support and Multi-IA Selection - Removed use of gen/ia - Scans local gen dir endhosts for all local IAs - Allows local topology user to switch src IAs from nav bar. - Allows local topology user to switch dest IAs from bwtest server. - Saves src IA selection in persistent user setting. - Updates health checks to ignore gen/ia, using nav bar selection. - Sorts paths tree list consistently by # hops. - Adds full reverse-sorted list of server localhost IAs. - Minor Go linting. * Addresses comments from r1 review * Addresses r2 comments + added missing IA error msg
1 parent 93ed1f4 commit 850e1e3

File tree

14 files changed

+309
-143
lines changed

14 files changed

+309
-143
lines changed

webapp/lib/config.go

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package lib
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"io/ioutil"
67
"net"
78
"net/http"
89
"os"
910
"path"
11+
"path/filepath"
12+
"regexp"
1013
"sort"
1114
"strings"
1215

16+
log "github.com/inconshreveable/log15"
1317
. "github.com/netsec-ethz/scion-apps/webapp/util"
1418
)
1519

@@ -23,7 +27,6 @@ var LABROOT = "src/github.com/netsec-ethz/scion-apps"
2327
var GOPATH = os.Getenv("GOPATH")
2428

2529
// default params for localhost testing
26-
var cliIaDef = "1-ff00:0:111"
2730
var serIaDef = "1-ff00:0:112"
2831
var cliPortDef = "30001"
2932
var serPortDefBwt = "30100"
@@ -36,18 +39,66 @@ var cfgFileSerUser = "config/servers_user.json"
3639
var cfgFileCliDef = "config/clients_default.json"
3740
var cfgFileSerDef = "config/servers_default.json"
3841

39-
// GetLocalIa reads locally generated file for this IA's name, if written
40-
func GetLocalIa() string {
41-
filepath := path.Join(GOPATH, SCIONROOT, "gen/ia")
42-
b, err := ioutil.ReadFile(filepath)
43-
if CheckError(err) {
44-
return ""
42+
// UserSetting holds the serialized structure for persistent user settings
43+
type UserSetting struct {
44+
MyIA string `json:"myIa"`
45+
}
46+
47+
// WriteUserSetting writes the settings to disk.
48+
func WriteUserSetting(srcpath string, settings UserSetting) {
49+
cliUserFp := path.Join(srcpath, cfgFileCliUser)
50+
settingsJSON, _ := json.Marshal(settings)
51+
52+
err := ioutil.WriteFile(cliUserFp, settingsJSON, 0644)
53+
CheckError(err)
54+
}
55+
56+
// ReadUserSetting reads the settings from disk.
57+
func ReadUserSetting(srcpath string) UserSetting {
58+
var settings UserSetting
59+
cliUserFp := path.Join(srcpath, cfgFileCliUser)
60+
61+
// no problem when user settings not set yet
62+
raw, err := ioutil.ReadFile(cliUserFp)
63+
log.Debug("ReadClientsUser", "settings", string(raw))
64+
if !CheckError(err) {
65+
json.Unmarshal([]byte(raw), &settings)
4566
}
46-
return strings.Replace(strings.TrimSpace(string(b)), "_", ":", -1)
67+
return settings
4768
}
4869

49-
func GetCliIaDef() string {
50-
return cliIaDef
70+
// ScanLocalIAs will load list of locally available IAs
71+
func ScanLocalIAs() []string {
72+
var localIAs []string
73+
var reIaFilePathCap = `\/ISD([0-9]+)\/AS(\w+)`
74+
re := regexp.MustCompile(reIaFilePathCap)
75+
var searchPath = path.Join(GOPATH, SCIONROOT, "gen")
76+
filepath.Walk(searchPath, func(path string, f os.FileInfo, _ error) error {
77+
if f != nil && f.IsDir() {
78+
capture := re.FindStringSubmatch(path)
79+
if len(capture) > 0 {
80+
ia := capture[1] + "-" + capture[2]
81+
ia = strings.Replace(ia, "_", ":", -1) // convert once
82+
if !StringInSlice(localIAs, ia) {
83+
log.Debug("Local IA Found:", "ia", ia)
84+
localIAs = append(localIAs, ia)
85+
}
86+
}
87+
}
88+
return nil
89+
})
90+
sort.Strings(localIAs)
91+
return localIAs
92+
}
93+
94+
// StringInSlice can check a slice for a unique string
95+
func StringInSlice(arr []string, i string) bool {
96+
for _, v := range arr {
97+
if v == i {
98+
return true
99+
}
100+
}
101+
return false
51102
}
52103

53104
// Makes interfaces sortable, by preferred name
@@ -87,39 +138,31 @@ func (c byPrefInterface) Less(i, j int) bool {
87138
}
88139

89140
// GenServerNodeDefaults creates server defaults for localhost testing
90-
func GenServerNodeDefaults(srcpath string) {
141+
func GenServerNodeDefaults(srcpath string, localIAs []string) {
142+
// reverse sort so that the default server will oppose the default client
143+
sort.Sort(sort.Reverse(sort.StringSlice(localIAs)))
144+
91145
serFp := path.Join(srcpath, cfgFileSerUser)
92-
jsonBuf := []byte(`{ `)
93-
json := []byte(`"bwtester": [{"name":"lo ` + serIaDef + `","isdas":"` +
94-
serIaDef + `", "addr":"` + serDefAddr + `","port":` + serPortDefBwt +
95-
`},{"name":"lo 2-ff00:0:222","isdas":"2-ff00:0:222", "addr":"127.0.0.22","port":30101}], `)
96-
jsonBuf = append(jsonBuf, json...)
97-
json = []byte(`"camerapp": [{"name":"localhost","isdas":"` +
98-
serIaDef + `", "addr":"` + serDefAddr + `","port":` + serPortDefImg + `}], `)
99-
jsonBuf = append(jsonBuf, json...)
100-
json = []byte(`"sensorapp": [{"name":"localhost","isdas":"` +
101-
serIaDef + `", "addr":"` + serDefAddr + `","port":` + serPortDefSen + `}], `)
102-
jsonBuf = append(jsonBuf, json...)
103-
json = []byte(`"echo": [{"name":"localhost","isdas":"` +
104-
serIaDef + `", "addr":"` + serDefAddr + `","port":` + serPortDefSen + `}], `)
105-
jsonBuf = append(jsonBuf, json...)
106-
json = []byte(`"traceroute": [{"name":"localhost","isdas":"` +
107-
serIaDef + `", "addr":"` + serDefAddr + `","port":` + serPortDefSen + `}]`)
108-
jsonBuf = append(jsonBuf, json...)
109-
110-
jsonBuf = append(jsonBuf, []byte(` }`)...)
146+
jsonBuf := []byte(`{ "all": [`)
147+
for i := 0; i < len(localIAs); i++ {
148+
// use all localhost endpoints as possible servers for bwtester as least
149+
ia := strings.Replace(localIAs[i], "_", ":", -1)
150+
json := []byte(`{"name":"lo ` + ia + `","isdas":"` + ia +
151+
`", "addr":"` + serDefAddr + `","port":` + serPortDefBwt + `}`)
152+
jsonBuf = append(jsonBuf, json...)
153+
if i < (len(localIAs) - 1) {
154+
jsonBuf = append(jsonBuf, []byte(`,`)...)
155+
}
156+
}
157+
jsonBuf = append(jsonBuf, []byte(`] }`)...)
111158
err := ioutil.WriteFile(serFp, jsonBuf, 0644)
112159
CheckError(err)
113160
}
114161

115162
// GenClientNodeDefaults queries network interfaces and writes local client
116163
// SCION addresses as json
117-
func GenClientNodeDefaults(srcpath string) {
164+
func GenClientNodeDefaults(srcpath, cisdas string) {
118165
cliFp := path.Join(srcpath, cfgFileCliDef)
119-
cisdas := GetLocalIa()
120-
if len(cisdas) == 0 {
121-
cisdas = cliIaDef
122-
}
123166
cport := cliPortDef
124167

125168
// find interface addresses

webapp/lib/health.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type ResHealthCheck struct {
4444

4545
// HealthCheckHandler handles calling the default health-check scripts and
4646
// returning the json-formatted results of each script.
47-
func HealthCheckHandler(w http.ResponseWriter, r *http.Request, srcpath string) {
47+
func HealthCheckHandler(w http.ResponseWriter, r *http.Request, srcpath string, ia string) {
4848
hcResFp := path.Join(srcpath, resFileHealthCheck)
4949
// read specified tests from json definition
5050
fp := path.Join(srcpath, defFileHealthCheck)
@@ -78,7 +78,7 @@ func HealthCheckHandler(w http.ResponseWriter, r *http.Request, srcpath string)
7878
pass := true
7979
log.Info(test.Script + ": " + test.Desc)
8080
// execute script
81-
cmd := exec.Command("bash", test.Script)
81+
cmd := exec.Command("bash", test.Script, ia)
8282
cmd.Dir = filepath.Dir(fp)
8383
var stdout, stderr bytes.Buffer
8484
cmd.Stdout = &stdout

webapp/lib/sciond.go

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ import (
1313
"path"
1414
"path/filepath"
1515
"runtime"
16+
"sort"
1617
"strconv"
1718
"strings"
1819

1920
log "github.com/inconshreveable/log15"
21+
"github.com/netsec-ethz/scion-apps/lib/scionutil"
2022
pathdb "github.com/netsec-ethz/scion-apps/webapp/models/path"
2123
. "github.com/netsec-ethz/scion-apps/webapp/util"
2224
"github.com/scionproto/scion/go/lib/addr"
2325
"github.com/scionproto/scion/go/lib/common"
24-
"github.com/scionproto/scion/go/lib/sciond"
2526
"github.com/scionproto/scion/go/lib/snet"
2627
"github.com/scionproto/scion/go/lib/spath/spathmeta"
2728
"github.com/scionproto/scion/go/proto"
@@ -38,14 +39,14 @@ func returnError(w http.ResponseWriter, err error) {
3839
fmt.Fprintf(w, `{"err":`+strconv.Quote(err.Error())+`}`)
3940
}
4041

41-
func returnPathHandler(w http.ResponseWriter, pathJson []byte, segJson []byte, err error) {
42+
func returnPathHandler(w http.ResponseWriter, pathJSON []byte, segJSON []byte, err error) {
4243
var buffer bytes.Buffer
4344
buffer.WriteString(`{"src":"sciond"`)
44-
if pathJson != nil {
45-
buffer.WriteString(fmt.Sprintf(`,"paths":%s`, pathJson))
45+
if pathJSON != nil {
46+
buffer.WriteString(fmt.Sprintf(`,"paths":%s`, pathJSON))
4647
}
47-
if segJson != nil {
48-
buffer.WriteString(fmt.Sprintf(`,"segments":%s`, segJson))
48+
if segJSON != nil {
49+
buffer.WriteString(fmt.Sprintf(`,"segments":%s`, segJSON))
4950
}
5051
if err != nil {
5152
buffer.WriteString(fmt.Sprintf(`,"err":%s`, strconv.Quote(err.Error())))
@@ -54,6 +55,26 @@ func returnPathHandler(w http.ResponseWriter, pathJson []byte, segJson []byte, e
5455
fmt.Fprintf(w, buffer.String())
5556
}
5657

58+
func getNetworkByIA(iaCli string) (*snet.SCIONNetwork, error) {
59+
ia, err := addr.IAFromString(iaCli)
60+
if CheckError(err) {
61+
return nil, err
62+
}
63+
dispatcherPath := "/run/shm/dispatcher/default.sock"
64+
sciondPath := scionutil.GetSCIONDPath(&ia)
65+
if snet.DefNetwork == nil {
66+
err := snet.Init(ia, sciondPath, dispatcherPath)
67+
if CheckError(err) {
68+
return nil, err
69+
}
70+
}
71+
network, err := snet.NewNetwork(ia, sciondPath, dispatcherPath)
72+
if CheckError(err) {
73+
return nil, err
74+
}
75+
return network, nil
76+
}
77+
5778
// sciond data sources and calls
5879

5980
// PathTopoHandler handles requests for paths, returning results from sciond.
@@ -76,25 +97,13 @@ func PathTopoHandler(w http.ResponseWriter, r *http.Request) {
7697
clientCCAddr, _ := snet.AddrFromString(optClient)
7798
serverCCAddr, _ := snet.AddrFromString(optServer)
7899

79-
if snet.DefNetwork == nil {
80-
dispatcherPath := "/run/shm/dispatcher/default.sock"
81-
82-
var sciondPath string
83-
isdCli, _ := strconv.Atoi(strings.Split(CIa, "-")[0])
84-
if isdCli < 16 {
85-
sciondPath = sciond.GetDefaultSCIONDPath(&clientCCAddr.IA)
86-
} else {
87-
sciondPath = sciond.GetDefaultSCIONDPath(nil)
88-
}
89-
90-
err := snet.Init(clientCCAddr.IA, sciondPath, dispatcherPath)
91-
if CheckError(err) {
92-
returnError(w, err)
93-
return
94-
}
100+
network, err := getNetworkByIA(CIa)
101+
if CheckError(err) {
102+
returnError(w, err)
103+
return
95104
}
96105

97-
paths, err := getPathsJSON(*clientCCAddr, *serverCCAddr)
106+
paths, err := getPathsJSON(*clientCCAddr, *serverCCAddr, *network)
98107
if CheckError(err) {
99108
returnError(w, err)
100109
return
@@ -138,6 +147,16 @@ func getSegmentsJSON(local snet.Addr) ([]byte, error) {
138147
if err != nil {
139148
return nil, err
140149
}
150+
sort.Slice(segments, func(i, j int) bool {
151+
// sort by segment type, then shortest # hops
152+
if segments[i].SegType < segments[j].SegType {
153+
return true
154+
}
155+
if segments[i].SegType > segments[j].SegType {
156+
return false
157+
}
158+
return len(segments[i].Interfaces) < len(segments[j].Interfaces)
159+
})
141160
jsonSegsInfo, err := json.Marshal(segments)
142161
if err != nil {
143162
return nil, err
@@ -196,8 +215,8 @@ func removeAllDir(dirName string) {
196215
CheckError(err)
197216
}
198217

199-
func getPathsJSON(local snet.Addr, remote snet.Addr) ([]byte, error) {
200-
pathMgr := snet.DefNetwork.PathResolver()
218+
func getPathsJSON(local snet.Addr, remote snet.Addr, network snet.SCIONNetwork) ([]byte, error) {
219+
pathMgr := network.PathResolver()
201220
pathSet := pathMgr.Query(context.Background(), local.IA, remote.IA)
202221
if len(pathSet) == 0 {
203222
return nil, fmt.Errorf("No paths from %s to %s", local.IA, remote.IA)
@@ -206,6 +225,16 @@ func getPathsJSON(local snet.Addr, remote snet.Addr) ([]byte, error) {
206225
for _, path := range pathSet {
207226
appPaths = append(appPaths, path)
208227
}
228+
sort.Slice(appPaths, func(i, j int) bool {
229+
// sort by shortest # hops, then by IA/interface
230+
if len(appPaths[i].Entry.Path.Interfaces) < len(appPaths[j].Entry.Path.Interfaces) {
231+
return true
232+
}
233+
if len(appPaths[i].Entry.Path.Interfaces) > len(appPaths[j].Entry.Path.Interfaces) {
234+
return false
235+
}
236+
return appPaths[i].Entry.String() < appPaths[j].Entry.String()
237+
})
209238
jsonPathInfo, err := json.Marshal(appPaths)
210239
if err != nil {
211240
return nil, err
@@ -217,30 +246,13 @@ func getPathsJSON(local snet.Addr, remote snet.Addr) ([]byte, error) {
217246
func AsTopoHandler(w http.ResponseWriter, r *http.Request) {
218247
r.ParseForm()
219248
CIa := r.PostFormValue("src")
220-
ia, err := addr.IAFromString(CIa)
249+
250+
network, err := getNetworkByIA(CIa)
221251
if CheckError(err) {
222252
returnError(w, err)
223253
return
224254
}
225-
if snet.DefNetwork == nil {
226-
dispatcherPath := "/run/shm/dispatcher/default.sock"
227-
228-
var sciondPath string
229-
isdCli, _ := strconv.Atoi(strings.Split(CIa, "-")[0])
230-
if isdCli < 16 {
231-
sciondPath = sciond.GetDefaultSCIONDPath(&ia)
232-
} else {
233-
sciondPath = sciond.GetDefaultSCIONDPath(nil)
234-
}
235-
236-
err := snet.Init(ia, sciondPath, dispatcherPath)
237-
if CheckError(err) {
238-
returnError(w, err)
239-
return
240-
}
241-
}
242-
243-
c := snet.DefNetwork.Sciond()
255+
c := network.Sciond()
244256

245257
asir, err := c.ASInfo(context.Background(), addr.IA{})
246258
if CheckError(err) {

webapp/models/path/db.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"database/sql"
55
)
66

7+
// InitDB controls the opening connection to the database.
78
func InitDB(filepath string) (*sql.DB, error) {
89
//var err error
910
db, err := sql.Open("sqlite3", filepath+"?mode=ro")
@@ -17,6 +18,7 @@ func InitDB(filepath string) (*sql.DB, error) {
1718
return db, nil
1819
}
1920

21+
// CloseDB will close the database, only use when app closes.
2022
func CloseDB(db *sql.DB) error {
2123
err := db.Close()
2224
return err

webapp/models/path/segments.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func newSegment(segType proto.PathSegType, srcI addr.ISD, srcA addr.AS, dstI add
5656
Interfaces: interfaces, Updated: time.Unix(0, updateTime), Expiry: time.Unix(expiryTime, 0)}
5757
}
5858

59+
// ReadSegTypesAll operates on the DB to return all SegType rows.
5960
func ReadSegTypesAll(db *sql.DB) (map[int64]proto.PathSegType, error) {
6061
sqlReadAll := `
6162
SELECT
@@ -84,6 +85,7 @@ func ReadSegTypesAll(db *sql.DB) (map[int64]proto.PathSegType, error) {
8485
return result, nil
8586
}
8687

88+
// ReadIntfToSegAll operates on the DB to return all IntfToSeg rows.
8789
func ReadIntfToSegAll(db *sql.DB) (map[int64][]asIface, error) {
8890
sqlReadAll := `
8991
SELECT
@@ -118,6 +120,7 @@ func ReadIntfToSegAll(db *sql.DB) (map[int64][]asIface, error) {
118120
return result, nil
119121
}
120122

123+
// ReadSegmentsAll operates on the DB to return all Segments rows.
121124
func ReadSegmentsAll(db *sql.DB, segTypes map[int64]proto.PathSegType) ([]segment, error) {
122125
sqlReadAll := `
123126
SELECT

0 commit comments

Comments
 (0)