├── demo
├── radius
│ └── freeradius
│ │ ├── certs
│ │ ├── ca.serial
│ │ ├── ca.ext
│ │ ├── server.ext
│ │ ├── dh
│ │ ├── ca.csr
│ │ ├── server.csr
│ │ ├── generate_cert.sh
│ │ ├── ca.key
│ │ ├── server.key
│ │ ├── server.pem
│ │ └── ca.pem
│ │ ├── dictionary
│ │ ├── sites-enabled
│ │ ├── default
│ │ └── inner-tunnel
│ │ ├── users
│ │ ├── eap.conf
│ │ └── radiusd.conf
├── faucet_ovs
│ ├── ovs.png
│ ├── interface_layout.png
│ ├── cleanup.sh
│ ├── faucet.yaml
│ ├── gauge.yaml
│ ├── README.md
│ └── setup.sh
├── facuet_hw
│ ├── ovs_hw.png
│ ├── interface_layout.png
│ ├── cleanup.sh
│ ├── at.conf
│ ├── README.md
│ ├── faucet.yaml
│ └── setup.sh
├── Link022_diagrams-demo.png
├── link022-gasket-diagram.png
├── samples
│ └── gnmi_set.sh
├── gasket
│ └── faucet
│ │ ├── faucet.yaml
│ │ └── gasket
│ │ ├── rules.yaml
│ │ ├── base-no-authed-acls.yaml
│ │ └── auth.yaml
├── util
│ ├── servers.conf
│ ├── cleanup_servers.sh
│ └── server.sh
├── cert
│ ├── generate_cert.sh
│ ├── client
│ │ ├── client.crt
│ │ ├── ca.crt
│ │ └── client.key
│ └── server
│ │ ├── ca.crt
│ │ ├── server.crt
│ │ └── server.key
├── README.gasket.md
├── ap_config_1r.json
├── ap_config_2r.json
└── README.md
├── tests
├── ap_provision.json
├── README.md
├── ap_dot11r.json
├── ap_apmanagers.json
├── link022_ap_config.json
├── ap_config.json
└── integration.py
├── .travis.yml
├── testkit
├── testdata
│ ├── provisionaps_test.json
│ ├── dot11r_test.json
│ ├── apmanagers_test.json
│ └── simple_test.json
├── gnmitest
│ ├── testfile
│ │ └── ap_config.json
│ └── worker_test.go
├── common
│ └── common.go
├── test_kit.go
└── util
│ └── gnmiutil
│ ├── helper_test.go
│ └── helper.go
├── doc.go
├── agent
├── syscmd
│ ├── states.go
│ ├── device.go
│ ├── hostapd.go
│ ├── bridge.go
│ ├── base.go
│ ├── syscmd_test.go
│ └── interface.go
├── context
│ └── context.go
├── monitoring
│ └── prometheus
│ │ ├── exposition_server.go
│ │ └── README.md
├── README.md
├── service
│ ├── base.go
│ └── interface.go
├── util
│ ├── mock
│ │ └── openconfig_test.go
│ └── ocutil
│ │ ├── openconfig.go
│ │ └── openconfig_test.go
├── controller
│ └── controller.go
├── gnmi
│ ├── handler.go
│ └── server.go
└── agent.go
├── test.sh
├── CONTRIBUTING.md
├── openconfig
├── models
│ └── gasket.yang
├── examples
│ └── openconfig.go
└── scripts
│ └── generate_wifi_oc_schema.sh
├── README.md
└── emulator
├── emulator.py
└── README.md
/demo/radius/freeradius/certs/ca.serial:
--------------------------------------------------------------------------------
1 | 02
2 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/dictionary:
--------------------------------------------------------------------------------
1 | $INCLUDE /usr/share/freeradius/dictionary
2 |
--------------------------------------------------------------------------------
/demo/faucet_ovs/ovs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/faucet_ovs/ovs.png
--------------------------------------------------------------------------------
/demo/facuet_hw/ovs_hw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/facuet_hw/ovs_hw.png
--------------------------------------------------------------------------------
/demo/Link022_diagrams-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/Link022_diagrams-demo.png
--------------------------------------------------------------------------------
/demo/link022-gasket-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/link022-gasket-diagram.png
--------------------------------------------------------------------------------
/demo/facuet_hw/interface_layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/facuet_hw/interface_layout.png
--------------------------------------------------------------------------------
/demo/faucet_ovs/interface_layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/link022/HEAD/demo/faucet_ovs/interface_layout.png
--------------------------------------------------------------------------------
/tests/ap_provision.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-ap-manager:config":{
3 | "mac":"00:11:22:33:44:55",
4 | "country-code":"US",
5 | "hostname":"link022-pi-ap"
6 | }
7 | }
--------------------------------------------------------------------------------
/demo/radius/freeradius/sites-enabled/default:
--------------------------------------------------------------------------------
1 | authorize {
2 | eap {
3 | ok = return
4 | }
5 | users
6 | }
7 |
8 | authenticate {
9 | eap
10 | }
11 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/ca.ext:
--------------------------------------------------------------------------------
1 | extensions = x509v3
2 |
3 | [ x509v3 ]
4 | basicConstraints = CA:true,pathlen:0
5 | nsCertType = sslCA,emailCA,objCA
6 | nsComment = "Sunnyvale CA"
7 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/server.ext:
--------------------------------------------------------------------------------
1 | extensions = x509v3
2 |
3 | [ x509v3 ]
4 | nsCertType = server
5 | keyUsage = digitalSignature,nonRepudiation,keyEncipherment
6 | extendedKeyUsage = msSGC,nsSGC,serverAuth
7 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/dh:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIGHAoGBAIdyYPu0soYoH6urV2CodQL7wYX3upco6BYd+Y+9YP5VTMsK9X+q84WE
3 | Qic9CqFjYr0GU3j8ETtHCkKo9wzLVbJsUUsTcd2YG1N0g4skzW5bJZm27UpcKf34
4 | Ye2wlQqSEMcXKM3jK5HjEkE/6pF1fJszxiTrbAcVJw3DYUJLHnCrAgEC
5 | -----END DH PARAMETERS-----
6 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/users:
--------------------------------------------------------------------------------
1 | host-authed Cleartext-Password := "authedpwd"
2 | Tunnel-Type = VLAN,
3 | Tunnel-Medium-Type = IEEE-802,
4 | Tunnel-Private-Group-Id = "authed",
5 | Reply-Message = "welcome-host-authed",
6 | Session-Timeout = 3600,
7 | Termination-Action = RADIUS-Request
8 |
--------------------------------------------------------------------------------
/demo/samples/gnmi_set.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export PATH=$PATH:~/go/bin
4 | sudo env PATH=$PATH ip netns exec lk22 gnmi_set \
5 | -ca=../cert/client/ca.crt \
6 | -cert=../cert/client/client.crt \
7 | -key=../cert/client/client.key \
8 | -target_name=www.example.com \
9 | -target_addr=192.168.11.8:10162 \
10 | -replace=/:@../ap_config_1r.json
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - master
5 | - 1.10.x
6 |
7 | notifications:
8 | email: false
9 |
10 | before_install:
11 | - go get -t -v ./...
12 |
13 | script:
14 | - ./test.sh
15 | - go vet ./...
16 | - go build -o binary/link022_agent agent/agent.go
17 |
18 | after_success:
19 | - bash <(curl -s https://codecov.io/bash)
20 |
--------------------------------------------------------------------------------
/testkit/testdata/provisionaps_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"Assign hostname to a MAC & set it's country-code.",
3 | "description":"Day 0 provisioning.",
4 | "test_cases":[
5 | {
6 | "name":"Push Hostname & CC",
7 | "description":"Initial assignment of hostname & CC.",
8 | "ops":[
9 | {
10 | "type":"update",
11 | "path":"/provision-aps/provision-ap[mac=00:11:22:33:44:55]",
12 | "val":"@../tests/ap_provision.json"
13 | }
14 | ]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/sites-enabled/inner-tunnel:
--------------------------------------------------------------------------------
1 | ######################################################################
2 | #
3 | # This is a virtual server that handles *only* inner tunnel
4 | # requests for EAP-TTLS and PEAP types.
5 | #
6 | #
7 | ######################################################################
8 |
9 | server inner-tunnel {
10 |
11 | authorize {
12 | eap {
13 | ok = return
14 | }
15 | users
16 | }
17 |
18 | authenticate {
19 | eap
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/gasket/faucet/faucet.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | vlans:
3 | 100:
4 | 200:
5 | 300:
6 | dps:
7 | faucet-1:
8 | dp_id: 0x01
9 | hardware: "Open vSwitch"
10 | interfaces:
11 | 1:
12 | native_vlan: 100
13 | acl_in: port_faucet-1_1
14 | tagged_vlans: [200, 300]
15 | 2:
16 | native_vlan: 100
17 | 3:
18 | native_vlan: 100
19 | tagged_vlans: [200, 300]
20 | include:
21 | - /etc/ryu/faucet/faucet-acls.yaml
22 |
23 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 | https://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software
7 | distributed under the License is distributed on an "AS IS" BASIS,
8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | See the License for the specific language governing permissions and
10 | limitations under the License.
11 | */
12 |
13 | package link022
14 |
--------------------------------------------------------------------------------
/demo/util/servers.conf:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | NS=lk22
16 |
--------------------------------------------------------------------------------
/demo/cert/generate_cert.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p tls_cert_key
4 |
5 | mkdir -p tls_cert_key/server
6 | openssl req -x509 -newkey rsa:4096 -keyout tls_cert_key/server/server.key -out tls_cert_key/server/server.crt -days 3650 -nodes -subj "/C=US/ST=CA/L=Sunnyvale/O=GNMI/OU=Org/CN=www.example.com" -sha256
7 |
8 | mkdir -p tls_cert_key/client
9 | openssl req -x509 -newkey rsa:4096 -keyout tls_cert_key/client/client.key -out tls_cert_key/client/client.crt -days 3650 -nodes -subj "/C=US/ST=CA/L=Sunnyvale/O=GNMI/OU=Test/CN=AP" -sha256
10 |
11 | cp tls_cert_key/server/server.crt tls_cert_key/client/ca.crt
12 | cp tls_cert_key/client/client.crt tls_cert_key/server/ca.crt
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/ca.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIBoTCCAQoCAQAwYTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH
3 | DA1Nb3VudGFpbiBWaWV3MREwDwYDVQQKDAhMSU5LMDAyMjENMAsGA1UECwwEVGVz
4 | dDELMAkGA1UEAwwCQVAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKSlC08i
5 | gNJjUO4XOzyZy5NeU2l/G5GORofXMf/akI8U07yoj6itXUIW6ZiGtCx0Fo3Ed4lp
6 | FUfAaLAIdYBT5TT2KJ+C0F4wBB+fvRFPMZz1E5SVYFqOlEmcGvzdAMch5loq2iuf
7 | YMc63t69bC0EmQifyIjdgQ3btOUEABm5xWidAgMBAAGgADANBgkqhkiG9w0BAQsF
8 | AAOBgQCYypJYJ3CWqggZy7nS2UeWbWr9gNiHeWnl7vr69tc40HiA7ggj75SCqAk7
9 | gTwqlEBZYGN0RgvoKNlWIbeprDfGo95QpiSZzyxjgW+SddGqlmI8UYVoZH+jNNvm
10 | EEz781BWpM51uydVlasoU94s85Cj/eaO/dj9FhmIRPisNB44Rw==
11 | -----END CERTIFICATE REQUEST-----
12 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/server.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIBoTCCAQoCAQAwYTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH
3 | DA1Nb3VudGFpbiBWaWV3MREwDwYDVQQKDAhMSU5LMDAyMjENMAsGA1UECwwEVGVz
4 | dDELMAkGA1UEAwwCQVAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKUtbjHV
5 | DRnptj9T/VMnYhFmGkHNmcC6N4b4NIOKDqS5/HefFbAcyncBeOpDGgjVLv2v26wk
6 | tyL5NswbPz4DG+2DYoCcs+rWWLw1rPyE08FDjM3utbB0shVmYcgZ5hclPnIlqDxV
7 | ZjPxeCnjycnN6G83DE3GEYJiom8utDLhVkURAgMBAAGgADANBgkqhkiG9w0BAQsF
8 | AAOBgQBVz+H73MGWCrv6erH4yj4M/O0YZIvRTVlHjQkrPxh2iKlSt0byrZQFC77K
9 | UCQ6An/ph2pvnv/taILLbJp5PnruEtBo76wdeecJzE/s8r4WT4qF/FuFDXLA7Uym
10 | XNK0yePXkh5utFm5J4k/t22GGoAyfUq/ZWcPxchs4rlgEmS4Mw==
11 | -----END CERTIFICATE REQUEST-----
12 |
--------------------------------------------------------------------------------
/demo/gasket/faucet/gasket/rules.yaml:
--------------------------------------------------------------------------------
1 | rules:
2 |
3 | acls:
4 | allow-all:
5 | _authport_:
6 | *allowall
7 |
8 | block-tcp:
9 | _authport_:
10 | - rule:
11 | _name_: _user-name_
12 | _mac_: _user-mac_
13 | dl_src: _user-mac_
14 | dl_type: 0x800
15 | ip_proto: 6
16 | actions:
17 | allow: 0
18 |
19 | block-udp:
20 | _authport_:
21 | - rule:
22 | _name_: _user-name_
23 | _mac_: _user-mac_
24 | dl_src: _user-mac_
25 | dl_type: 0x800
26 | ip_proto: 17
27 | actions:
28 | allow: 0
29 |
30 |
--------------------------------------------------------------------------------
/demo/faucet_ovs/cleanup.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 |
17 | sudo killall dnsmasq
18 | sudo killall freeradius
19 | sudo ip netns del gw
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/generate_cert.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Create A Self-Signed CA Certificate
4 | openssl genrsa -out ca.key 1024
5 | openssl req -new -subj \
6 | '/C=US/ST=CA/L=Mountain View/O=LINK0022/OU=Test/CN=AP' -key ca.key -out ca.csr
7 | openssl x509 -days 1095 -extfile ca.ext -signkey ca.key -in ca.csr -req -out ca.pem
8 |
9 | # Create A Server Certificate
10 | openssl genrsa -out server.key 1024
11 | echo -ne '01' > ca.serial
12 | openssl req -subj \
13 | '/C=US/ST=CA/L=Mountain View/O=LINK0022/OU=Test/CN=AP' -new -key server.key -out server.csr
14 | openssl x509 -days 730 -extfile server.ext -CA ca.pem -CAkey ca.key -CAserial ca.serial -in server.csr -req -out server.pem
15 |
16 | # Create dh.
17 | openssl dhparam -out dh 1024
18 |
--------------------------------------------------------------------------------
/demo/util/cleanup_servers.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 | # A utility script to start clean up the servers created by servers.sh
17 | source servers.conf
18 | sudo killall dnsmasq
19 | sudo killall freeradius
20 | sudo ip netns del ${NS}
21 |
--------------------------------------------------------------------------------
/demo/gasket/faucet/gasket/base-no-authed-acls.yaml:
--------------------------------------------------------------------------------
1 | acls:
2 | port_faucet-1_2:
3 | - authed-rules
4 | # drop unauthenticated hosts on the tagged auth VLAN.
5 | # this could possibly be removed, as the AP (hostapd) controls the access.
6 | # and if you're on the vlan, you must be allowed.
7 | - rule:
8 | vlan_vid: 300
9 | actions:
10 | allow: 0
11 | # allow all traffic on the guest VLAN.
12 | - rule:
13 | vlan_vid: 200
14 | actions:
15 | allow: 1
16 | # Allow the control (RADIUS, hostapd UDP, SSH to AP)
17 | - rule:
18 | dl_type: 0x800
19 | actions:
20 | allow: 1
21 | - rule:
22 | dl_type: 0x806
23 | actions:
24 | allow: 1
25 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/eap.conf:
--------------------------------------------------------------------------------
1 | eap {
2 | default_eap_type = md5
3 |
4 | md5 {
5 | }
6 |
7 | tls {
8 | certdir = ${confdir}/certs
9 | cadir = ${confdir}/certs
10 | private_key_file = ${certdir}/server.key
11 | certificate_file = ${certdir}/server.pem
12 | ca_file = ${cadir}/ca.pem
13 | dh_file = ${certdir}/dh
14 | random_file = /dev/urandom
15 | }
16 |
17 | ttls {
18 | default_eap_type = md5
19 | copy_request_to_tunnel = no
20 | use_tunneled_reply = no
21 | virtual_server = "inner-tunnel"
22 | }
23 |
24 | peap {
25 | default_eap_type = md5
26 | copy_request_to_tunnel = no
27 | use_tunneled_reply = no
28 | virtual_server = "inner-tunnel"
29 | }
30 | }
--------------------------------------------------------------------------------
/agent/syscmd/states.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | // GetAPStates get ap states on target
19 | func (r *CommandRunner) GetAPStates() (string, error) {
20 | //log.Info("fetch latest AP states.")
21 | wlanInfo, err := r.ExecCommand(true, "iw", "dev")
22 | if err != nil {
23 | return "", err
24 | }
25 | return wlanInfo, nil
26 | }
27 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2018 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 | echo "" > coverage.txt
19 |
20 | for d in $(go list ./... | grep -v vendor); do
21 | go test -race -coverprofile=profile.out -covermode=atomic $d
22 | if [ -f profile.out ]; then
23 | cat profile.out >> coverage.txt
24 | rm profile.out
25 | fi
26 | done
--------------------------------------------------------------------------------
/testkit/testdata/dot11r_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"Enable 802.11r",
3 | "description":"Enable and configure 802.11r.",
4 | "test_cases":[
5 | {
6 | "name":"Push 802.11r",
7 | "description":"Push only 802.11r related configuration to AP device.",
8 | "ops":[
9 | {
10 | "type":"replace",
11 | "path":"/access-points/access-point[hostname=link022-pi-ap]",
12 | "val":"@../tests/ap_dot11r.json"
13 | }
14 | ]
15 | },
16 | {
17 | "name":"Update 802.11r Method",
18 | "ops":[
19 | {
20 | "type":"update",
21 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Auth-Link022]/dot11r/config/dot11r-method",
22 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Auth-Link022]/dot11r/state/dot11r-method",
23 | "val": "ODS"
24 | }
25 | ]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXAIBAAKBgQCkpQtPIoDSY1DuFzs8mcuTXlNpfxuRjkaH1zH/2pCPFNO8qI+o
3 | rV1CFumYhrQsdBaNxHeJaRVHwGiwCHWAU+U09iifgtBeMAQfn70RTzGc9ROUlWBa
4 | jpRJnBr83QDHIeZaKtorn2DHOt7evWwtBJkIn8iI3YEN27TlBAAZucVonQIDAQAB
5 | AoGAFUGNlTjiIsdZsnNjXW6JnswFvs//MiqJJo580QhZs28/6tJkQaWoLZkhrWRu
6 | rHKMWt8rg50Cd1H68gcVjGzIYXoAyw1Jo2GL4zr+iDAAvRqRw2FuODB6zGqoe7IV
7 | Msslr/IRJyi9WNgFYLwL60yXLhcw1hMoBtwHhmgKF1DW6KECQQDT1lAOl9cNK235
8 | wcbNP41DcFR4Vhuw8g5TcdW/+Z3mYbtFwB6iLjzvgNllPLD2FJuHSJt+WLtHoRjH
9 | AOrMCDqpAkEAxvgVeuq336tBZoK+Z2mxMZDqog8/19Mo0EX2HG0Rj7zN3EOORzg9
10 | tEJ+sLn1P6tn+JySc4Rcvi7F/QUDFtIK1QJAFkb3bFFzgCMNEbLbIUKJrInnmDAW
11 | AWmzL/tMqz+o501n0FH/5rZOGjHGqurTAU3xfSyZjZPvPf0nCeaCyqoi4QJABXPf
12 | AtNT3qlOzYRfu23GEVmrVG2ejkeXIr4IFJdYl9vQuGyRhZog9sOrvSQbeHc2DjYc
13 | PVYvRHb7rgdpIkL3UQJBAKknDfQ2zKI3j3DXlLvr0F3cTiQAGVIEs9fAVd8QJwJ2
14 | HECoOcDNZSMk0dVFkndeLOFB/PfMm3EtRZqaxx0lwy8=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXAIBAAKBgQClLW4x1Q0Z6bY/U/1TJ2IRZhpBzZnAujeG+DSDig6kufx3nxWw
3 | HMp3AXjqQxoI1S79r9usJLci+TbMGz8+Axvtg2KAnLPq1li8Naz8hNPBQ4zN7rWw
4 | dLIVZmHIGeYXJT5yJag8VWYz8Xgp48nJzehvNwxNxhGCYqJvLrQy4VZFEQIDAQAB
5 | AoGAJv4diFgCGr7oTfTW1X1zkiXnRudxR2TuXMdVJkQ+Brb9GYQNi6CQTx2i3LJw
6 | f+tLh4mvMukL7o6kV/SKrmnvenYA47yRRJSxhueF1LU9Xo3TQfiMNZ+YG+i3jVBH
7 | gngc8CSb/+8gh2j/JfHFTidYpept8NX23dZeC1THDhuASsECQQDbi8X6lZkZuXh5
8 | OJpc6155wqfayAJJqjJxFkVVGojBcCAsfLMHFJE8EGeDmb7OzsrEUjMave5vCKgb
9 | sUEpQF7ZAkEAwJqeJe61+s3mpDNy5n7vSU37lpCRKg8XvwvFW2SjMh/1lPVioh9K
10 | WuIs9PVaB/kB1rC3s+LhBiblbLC5QE+k+QJBANYLnPblWUH6UJmm9OB43UBK5snn
11 | zEszs0AMHYaOexxVDBkzbnwNsldkCvgEMkKA3LGxDoAZAtigEP4Gv+fPCWkCQGhO
12 | U/9u+5bME3AxfLRfYe9tECdGO5dvfQi1szf/lHH85N661aO/FmGJw5cVmKBgtiIy
13 | qgn9gKf4QJ9QRcMSXuECQBZJE7yEQO3+gxYHF0bQKtU2Wbl2z99Sndahmp+KF/yB
14 | qEyvOczPNksogOsK7mtaPf7cR0TsvlB/eFLa9I4cz0o=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/demo/facuet_hw/cleanup.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 |
17 | sudo killall dnsmasq
18 | sudo killall freeradius
19 | sudo ip netns del gw
20 | for br in $(sudo ovs-vsctl list-br); do
21 | sudo ovs-vsctl del-br $br
22 | done
23 |
24 | IFS=$'\n';
25 | for i in $(sudo ip netns list); do
26 |
27 | NS=$(echo $i | cut -d ' ' -f1)
28 | sudo ip netns delete $NS;
29 | done
30 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/server.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIChjCCAe+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEL
3 | MAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxETAPBgNVBAoMCExJ
4 | TkswMDIyMQ0wCwYDVQQLDARUZXN0MQswCQYDVQQDDAJBUDAeFw0xNzEwMTEwMTAz
5 | NTRaFw0xOTEwMTEwMTAzNTRaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEW
6 | MBQGA1UEBwwNTW91bnRhaW4gVmlldzERMA8GA1UECgwITElOSzAwMjIxDTALBgNV
7 | BAsMBFRlc3QxCzAJBgNVBAMMAkFQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
8 | gQClLW4x1Q0Z6bY/U/1TJ2IRZhpBzZnAujeG+DSDig6kufx3nxWwHMp3AXjqQxoI
9 | 1S79r9usJLci+TbMGz8+Axvtg2KAnLPq1li8Naz8hNPBQ4zN7rWwdLIVZmHIGeYX
10 | JT5yJag8VWYz8Xgp48nJzehvNwxNxhGCYqJvLrQy4VZFEQIDAQABo04wTDARBglg
11 | hkgBhvhCAQEEBAMCBkAwCwYDVR0PBAQDAgXgMCoGA1UdJQQjMCEGCisGAQQBgjcK
12 | AwMGCWCGSAGG+EIEAQYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADgYEAXiGbGp0W
13 | HE6Whyev8epCBlKmCxg/ddNCWgn1JzdRYRDTyiI1BcbG7xSSCAIIwX3KEtEtGKoL
14 | yOP22pDfzoDPqck6XYseCZLqllPunJqOW9SPthcqh7TSpH8GjCsGaY7Pq3qNM0Bh
15 | 5dopMHrhqQPz37MtUUNM+1C6YEumk+2ynGk=
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/testkit/testdata/apmanagers_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"Assign AP Managers",
3 | "description":"Enable and configure AP Managers.",
4 | "test_cases":[
5 | {
6 | "name":"Push AP Managers",
7 | "description":"Push assigned-ap-managers to AP device.",
8 | "ops":[
9 | {
10 | "type":"replace",
11 | "path":"/access-points/access-point[hostname=link022-pi-ap]",
12 | "val":"@../tests/ap_apmanagers.json"
13 | }
14 | ]
15 | },
16 | {
17 | "name":"Change secondary AP Manager IPv4 address",
18 | "ops":[
19 | {
20 | "type":"update",
21 | "path":"/access-points/access-point[hostname=link022-pi-ap]/assigned-ap-managers/ap-manager[id=secondary]/config/ap-manager-ipv4-address",
22 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/assigned-ap-managers/ap-manager[id=secondary]/state/ap-manager-ipv4-address",
23 | "val": "192.168.3.100"
24 | }
25 | ]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/demo/faucet_ovs/faucet.yaml:
--------------------------------------------------------------------------------
1 | vlans:
2 | vlan100:
3 | vid: 100
4 | vlan200:
5 | vid: 200
6 | vlan300:
7 | vid: 300
8 | dps:
9 | sw1:
10 | dp_id: 0x1
11 | hardware: "Open vSwitch"
12 | interfaces:
13 | 1:
14 | name: "gw"
15 | description: "gw for dhcp and dnsmasq"
16 | tagged_vlans: [vlan200,vlan300]
17 | native_vlan: vlan100
18 | 2:
19 | name: "host2"
20 | tagged_vlans: [vlan200,vlan300]
21 | native_vlan: vlan100
22 | 3:
23 | name: "host3"
24 | tagged_vlans: [vlan200,vlan300]
25 | native_vlan: vlan100
26 | 4:
27 | name: "host4"
28 | tagged_vlans: [vlan200,vlan300]
29 | native_vlan: vlan100
30 | 5:
31 | name: "host5"
32 | tagged_vlans: [vlan200,vlan300]
33 | native_vlan: vlan100
--------------------------------------------------------------------------------
/demo/faucet_ovs/gauge.yaml:
--------------------------------------------------------------------------------
1 | # Recommended configuration is Prometheus for all monitoring, with all_dps: True
2 | faucet_configs:
3 | - '/etc/faucet/faucet.yaml'
4 | watchers:
5 | port_status_poller:
6 | type: 'port_state'
7 | all_dps: True
8 | db: 'prometheus'
9 | port_stats_poller:
10 | type: 'port_stats'
11 | all_dps: True
12 | interval: 10
13 | db: 'prometheus'
14 | flow_table_poller:
15 | type: 'flow_table'
16 | all_dps: True
17 | interval: 60
18 | db: 'prometheus'
19 | dbs:
20 | prometheus:
21 | type: 'prometheus'
22 | prometheus_addr: '0.0.0.0'
23 | prometheus_port: 9303
24 | ft_file:
25 | type: 'text'
26 | compress: True
27 | file: 'flow_table.yaml.gz'
28 | influx:
29 | type: 'influx'
30 | influx_db: 'faucet'
31 | influx_host: 'influxdb'
32 | influx_port: 8086
33 | influx_user: 'faucet'
34 | influx_pwd: 'faucet'
35 | influx_timeout: 10
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/certs/ca.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC/DCCAmWgAwIBAgIJAOe9FfiPUDOVMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
3 | BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzERMA8G
4 | A1UECgwITElOSzAwMjIxDTALBgNVBAsMBFRlc3QxCzAJBgNVBAMMAkFQMB4XDTE3
5 | MTAxMTAxMDM1NFoXDTIwMTAxMDAxMDM1NFowYTELMAkGA1UEBhMCVVMxCzAJBgNV
6 | BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MREwDwYDVQQKDAhMSU5LMDAy
7 | MjENMAsGA1UECwwEVGVzdDELMAkGA1UEAwwCQVAwgZ8wDQYJKoZIhvcNAQEBBQAD
8 | gY0AMIGJAoGBAKSlC08igNJjUO4XOzyZy5NeU2l/G5GORofXMf/akI8U07yoj6it
9 | XUIW6ZiGtCx0Fo3Ed4lpFUfAaLAIdYBT5TT2KJ+C0F4wBB+fvRFPMZz1E5SVYFqO
10 | lEmcGvzdAMch5loq2iufYMc63t69bC0EmQifyIjdgQ3btOUEABm5xWidAgMBAAGj
11 | gbswgbgwDwYDVR0TBAgwBgEB/wIBADAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8v
12 | ZXNkbi5nb29nbGUuY29tMBEGCWCGSAGG+EIBAQQEAwIABzAlBglghkgBhvhCAQgE
13 | GBYWaHR0cDovL2VzZG4uZ29vZ2xlLmNvbTAlBglghkgBhvhCAQQEGBYWaHR0cDov
14 | L2VzZG4uZ29vZ2xlLmNvbTAbBglghkgBhvhCAQ0EDhYMU3Vubnl2YWxlIENBMA0G
15 | CSqGSIb3DQEBCwUAA4GBAHottq+TTJ689B/hXa+Q8FafLtbi0STCLRV/H9ZCMGjt
16 | p3PtSsb+FD74vqK2kqWAlME9jV1yfjGWfxrBsQXFbVGkg6zngrSKWPw+8GPwFH0m
17 | IkDOFukpAj7ERsWKPfHcXwC4q/PXBCnkX+Cr8RbZfdgqPZxnBGdb070TPcrXCQJ1
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/demo/facuet_hw/at.conf:
--------------------------------------------------------------------------------
1 | no service telnet
2 | no service http
3 | ssh server v2only
4 | ssh server allow-users manager
5 | service ssh ip
6 | clock timezone PST minus 8:00
7 | clock summer-time PDT recurring 2 Sun Mar 02:00 1 Sun Nov 02:00 60
8 | !
9 | aaa authentication enable default local
10 | aaa authentication login default local
11 | !
12 | no ip domain-lookup
13 | no service dhcp-server
14 | no ip multicast-routing
15 | no lldp run
16 | !
17 | openflow controller tcp 192.168.12.1 6653
18 | openflow controller tcp 192.168.12.1 6654
19 |
20 |
21 | openflow native vlan 3021
22 | service power-inline
23 | no lacp global-passive-mode enable
24 | no spanning-tree rstp enable
25 | !
26 | vlan database
27 | vlan 200,300,3020-3021 state enable
28 | !
29 | interface port1.0.1-1.0.7
30 | openflow
31 | switchport
32 | switchport mode access
33 | !
34 | interface port1.0.8
35 | switchport
36 | switchport mode access
37 | switchport access vlan 3020
38 | !
39 | interface port1.0.9-1.0.10
40 | openflow
41 | switchport
42 | switchport mode access
43 | !
44 | interface vlan3020
45 | ip address dhcp
46 | !
47 | line con 0
48 | exec-timeout 0 0
49 | line vty 0 4
50 | exec-timeout 0 0
51 | !
52 | end
53 |
--------------------------------------------------------------------------------
/openconfig/models/gasket.yang:
--------------------------------------------------------------------------------
1 | module openconfig-gasket {
2 |
3 | yang-version "1";
4 |
5 | // namespace
6 | namespace "http://google.com/yang/gasket";
7 |
8 | // Assign this module a prefix to be used by other modules, when imported.
9 | prefix "gasket";
10 |
11 | // meta
12 | organization "Google, Inc.";
13 |
14 | contact
15 | "Google, Inc.
16 | 1600 Amphitheatre Pkway
17 | Mountain View, CA 94043
18 | www.google.com";
19 |
20 | description
21 | "This module defines the top level Gasket Configurations.";
22 |
23 | revision "2018-03-30" {
24 | description
25 | "Initial version";
26 | reference "0.1.0";
27 | }
28 |
29 | grouping gasket-top {
30 | description
31 | "Top-level grouping for Gasket configuration data.";
32 |
33 | container gasket {
34 | description
35 | "Top most container for configuration data for Gasket.";
36 |
37 | leaf ctrl-interface {
38 | type string;
39 | description
40 | "The interface for separate hostapd control prgram.";
41 | }
42 |
43 | leaf radius-attribute {
44 | type string;
45 | description
46 | "The gasket related radius attributes.";
47 | }
48 | }
49 | }
50 |
51 | uses gasket-top;
52 | }
--------------------------------------------------------------------------------
/agent/context/context.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package context stores context data of the Link022 device.
17 | package context
18 |
19 | import (
20 | "sync"
21 | )
22 |
23 | // DeviceConfig contains the configuration specific to this device.
24 | type DeviceConfig struct {
25 | ETHINTFName string
26 | WLANINTFName string
27 | Hostname string
28 | ControllerAddr string
29 | GNMIServerAddr string
30 | }
31 |
32 | var (
33 | deviceConfig *DeviceConfig
34 | once sync.Once
35 | )
36 |
37 | // GetDeviceConfig returns the singleton DeviceConfig object.
38 | func GetDeviceConfig() *DeviceConfig {
39 | once.Do(func() {
40 | deviceConfig = &DeviceConfig{}
41 | })
42 | return deviceConfig
43 | }
44 |
--------------------------------------------------------------------------------
/demo/gasket/faucet/gasket/auth.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 0
3 |
4 | logger_location: /var/log/ryu/faucet/gasket/auth_app.log
5 |
6 | faucet:
7 | prometheus_port: 9302
8 | ip: 127.0.0.1
9 |
10 | files:
11 | # the location of files. pid should contain the process id (pid) of the main faucet-process (ryu-manager)
12 | # should be obtainable with "ps aux | grep faucet.faucet | head -n1 awk '{print $2}' > /etc/ryu/faucet/contr_pid"
13 | controller_pid: /var/run/faucet.pid
14 | faucet_config: /etc/ryu/faucet/faucet.yaml
15 | acl_config: /etc/ryu/faucet/faucet-acls.yaml
16 | base_config: /etc/ryu/faucet/gasket/base-acls.yaml
17 |
18 | # rules to be applied for a user once authenticated.
19 | auth-rules:
20 | file: /etc/ryu/faucet/gasket/rules.yaml
21 | dps:
22 | faucet-1:
23 | interfaces:
24 | 1:
25 | auth_mode: access
26 |
27 | hostapd:
28 | host: 192.168.11.6
29 | port: 8888
30 | # in hostapd.conf 'ctl_interface...:udp:8888' might be how you specifiy what to do
31 | # socket_path: /path/to/hostapd/ctrl/socket # may also be used instead of host & port, when using the unix socket interface
32 |
33 | request_timeout: 4
34 | unsolicited_timeout: 4
35 | request_socket_type: ping
36 | unsolicited_socket_type: ping
37 |
38 |
--------------------------------------------------------------------------------
/demo/radius/freeradius/radiusd.conf:
--------------------------------------------------------------------------------
1 | # Minimal FreeRADIUS configuration.
2 |
3 | # directory where this file, at runtime, resides
4 | here = .
5 |
6 |
7 | prefix = /usr
8 | exec_prefix = /usr
9 | sysconfdir = /etc
10 | sbindir = ${exec_prefix}/sbin
11 | libdir = /usr/lib/freeradius
12 |
13 | name = freeradius
14 |
15 | raddbdir = ${here}/freeradius
16 | confdir = ${raddbdir}
17 |
18 | localstatedir = ${here}/var
19 | logdir = ${localstatedir}
20 | radacctdir = ${localstatedir}
21 | run_dir = ${localstatedir}
22 | db_dir = ${localstatedir}
23 | pidfile = ${run_dir}/${name}.pid
24 |
25 |
26 | listen {
27 | type = auth
28 | ipaddr = *
29 | port = 0
30 | }
31 |
32 | log {
33 | destination = files
34 | file = ${logdir}/radius.log
35 | }
36 |
37 | security {
38 | reject_delay = 0
39 | }
40 |
41 | client localhost {
42 | ipaddr = 127.0.0.1
43 | secret = "testing123"
44 | nastype = other
45 | }
46 | client aps {
47 | ipaddr = 192.168.0.0
48 | netmask = 16
49 | secret = "radiuspwd"
50 | nastype = other
51 | }
52 |
53 | modules {
54 | $INCLUDE eap.conf
55 |
56 | always reject {
57 | rcode = reject
58 | }
59 | files users {
60 | usersfile = ${confdir}/users
61 | }
62 | }
63 |
64 | instantiate {
65 | }
66 |
67 | #
68 | # Load virtual servers.
69 | #
70 | $INCLUDE sites-enabled/
71 |
--------------------------------------------------------------------------------
/agent/syscmd/device.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | import (
19 | "errors"
20 | "net"
21 | "strings"
22 |
23 | log "github.com/golang/glog"
24 | )
25 |
26 | // DeviceIPv4 fetches the first IPv4 address of the device.
27 | func (r *CommandRunner) DeviceIPv4() (string, error) {
28 | ipInfo, err := r.ExecCommand(true, "hostname", "-I")
29 | if err != nil {
30 | return "", err
31 | }
32 |
33 | // Find the first IPv4 address.
34 | ipList := strings.Fields(ipInfo)
35 | for _, ipString := range ipList {
36 | ipAddr := net.ParseIP(ipString)
37 | if ipAddr != nil && ipAddr.To4() != nil {
38 | log.Infof("The device has IPv4 address %s.", ipString)
39 | return ipString, nil
40 | }
41 | }
42 | return "", errors.New("no IPv4 address found on this device")
43 | }
44 |
--------------------------------------------------------------------------------
/agent/syscmd/hostapd.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | import (
19 | log "github.com/golang/glog"
20 | )
21 |
22 | // StartHostapd starts a hostapd process link to the given WLAN interface.
23 | func (r *CommandRunner) StartHostapd(configFilePath string) error {
24 | log.Infof("Starting hostapd process with config file: %v...", configFilePath)
25 | if _, err := r.ExecCommand(false, "hostapd", configFilePath); err != nil {
26 | return err
27 | }
28 | log.Infof("Started a hostapd with config file: %v.", configFilePath)
29 | return nil
30 | }
31 |
32 | // StopAllHostapd kills all running hostapd processes.
33 | func (r *CommandRunner) StopAllHostapd() error {
34 | log.Info("Stopping all hostapd processes...")
35 | if _, err := r.ExecCommand(true, "killall", "-q", "hostapd"); err != nil {
36 | return err
37 | }
38 | log.Info("Stopped all hostapd processes.")
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/agent/monitoring/prometheus/exposition_server.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package main
17 |
18 | import (
19 | "context"
20 | "flag"
21 | "net/http"
22 |
23 | log "github.com/golang/glog"
24 | "github.com/prometheus/client_golang/prometheus"
25 | "github.com/prometheus/client_golang/prometheus/promhttp"
26 | )
27 |
28 | var (
29 | targetAddr = flag.String("target_addr", "localhost:10161", "The target address in the format of host:port")
30 | targetName = flag.String("target_name", "hostname.com", "The target name is used to verify the hostname returned by TLS handshake")
31 | listenAddr = flag.String("listen_addr", "localhost:8080", "The address to listen on for HTTP requests.")
32 | )
33 |
34 | func main() {
35 | flag.Parse()
36 | targetState := &TargetState{}
37 | ctx := context.Background()
38 | go monitoringAPStats(ctx, *targetAddr, *targetName, targetState)
39 | gnmiExport := newAPStateCollector(targetState)
40 | prometheus.MustRegister(gnmiExport)
41 |
42 | http.Handle("/metrics", promhttp.Handler())
43 | log.Fatal(http.ListenAndServe(*listenAddr, nil))
44 | }
45 |
--------------------------------------------------------------------------------
/demo/faucet_ovs/README.md:
--------------------------------------------------------------------------------
1 | # Link022 Demo With Faucet controlled OVS switch
2 | This demo connects Link022 to an OpenVswitch controlled by the Faucet Openflow controller.
3 | The difference of this demo with the [Gasket](../README.gasket.md) demo is that we rely on Link022 to control the ACL instead
4 | of Faucet ACL through 802.1x.
5 |
6 | ## Hardware
7 | We use a computer with several network interfaces to run the Faucet controller, OVS and the services
8 | needed by Link022 (DHCP, Radius and NAT'ed routing).
9 |
10 | Here is the naming layout of the interfaces.
11 | 
12 |
13 |
14 | ## OVS port configuration
15 | Here is the layout of the interfaces in the OVS bridge.
16 | 
17 |
18 | ## Software setup
19 | 1. Install OVS.
20 | A good guide is [here](https://faucet.readthedocs.io/en/latest/tutorials/first_time.html#connect-your-first-datapath)
21 |
22 | 1. Create the OVS bridge and link022 services by running [setup.sh](./setup.sh)
23 |
24 | 1. Install docker and docker-compose.
25 | A good guide is [here](https://docs.docker.com/install/)
26 |
27 | 1. Install the configuration files.
28 | ```
29 | sudo mkdir /etc/faucet
30 | sudo cp faucet.yaml /etc/faucet
31 | sudo cp guage.yaml /etc/faucet
32 | ```
33 |
34 | 1. Bring up the faucet services.
35 | ```
36 | git clone https://github.com/faucetsdn/faucet.git
37 | cd faucet
38 | sudo docker-compose pull
39 | sudo docker-compose up
40 | ```
41 | 1. Follow the [directions](https://faucet.readthedocs.io/en/latest/docker.html#docker-compose) to configure prometheus and influxdb, and access the Grafana UI.
42 |
--------------------------------------------------------------------------------
/demo/facuet_hw/README.md:
--------------------------------------------------------------------------------
1 | # Link022 Demo With Faucet controlled AT switch
2 | This demo connects Link022 to an Allied Telesis switch (AT-x230-10GP) or an OVS switch controlled by the Faucet Openflow controller.
3 |
4 | ## Hardware
5 | We use a computer with several network interfaces to run the Faucet controller, OVS and the services
6 | needed by Link022 (DHCP, Radius and NAT'ed routing).
7 |
8 | Here is the naming layout of the interfaces.
9 | 
10 |
11 |
12 | ## OVS port configuration
13 | Here is the layout of the interfaces in the OVS bridge and the services container.
14 | 
15 |
16 | ## Software setup
17 | 1. Install OVS.
18 | A good guide is [here](https://faucet.readthedocs.io/en/latest/tutorials/first_time.html#connect-your-first-datapath)
19 |
20 | 1. Install docker and docker-compose.
21 | A good guide is [here](https://docs.docker.com/install/)
22 |
23 | 1. Create the OVS bridge and link022 services by running [setup.sh](./setup.sh)
24 |
25 | 1. Install the configuration files.
26 | ```
27 | sudo mkdir /etc/faucet
28 | sudo cp faucet.yaml /etc/faucet
29 | sudo cp ../faucet_ovs/guage.yaml /etc/faucet
30 | ```
31 | 1. Configure the hardware switch using the config at [here](at.conf)
32 |
33 | 1. Bring up the faucet services.
34 | ```
35 | git clone https://github.com/faucetsdn/faucet.git
36 | cd faucet
37 | sudo docker-compose pull
38 | sudo docker-compose up
39 | ```
40 | 1. Follow the [directions](https://faucet.readthedocs.io/en/latest/docker.html#docker-compose) to configure prometheus and influxdb, and access the Grafana UI.
41 |
42 | ## Troubleshoot
43 | * When rebooting the gateway machine, run docker-compose down/up to reset the network containers.
44 |
45 |
--------------------------------------------------------------------------------
/agent/syscmd/bridge.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | import (
19 | log "github.com/golang/glog"
20 | )
21 |
22 | // CreateBridge creates a network bridge with a certain name.
23 | func (r *CommandRunner) CreateBridge(bridgeName string) error {
24 | log.Infof("Creating bridge %v...", bridgeName)
25 | if _, err := r.ExecCommand(true, "brctl", "addbr", bridgeName); err != nil {
26 | return err
27 | }
28 | log.Infof("Created bridge %v.", bridgeName)
29 | return nil
30 | }
31 |
32 | // DeleteBridge deletes a network bridge with a certain name.
33 | func (r *CommandRunner) DeleteBridge(bridgeName string) error {
34 | log.Infof("Deleting bridge %v...", bridgeName)
35 | if _, err := r.ExecCommand(true, "brctl", "delbr", bridgeName); err != nil {
36 | return err
37 | }
38 | log.Infof("Deleted bridge %v.", bridgeName)
39 | return nil
40 | }
41 |
42 | // AddBridgeIntf adds an interface to a network bridge.
43 | func (r *CommandRunner) AddBridgeIntf(bridgeName, intfName string) error {
44 | log.Infof("Adding interface %v to bridge %v...", intfName, bridgeName)
45 | if _, err := r.ExecCommand(true, "brctl", "addif", bridgeName, intfName); err != nil {
46 | return err
47 | }
48 | log.Infof("Added interface %v to bridge %v.", intfName, bridgeName)
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # Link022 Integration tests
2 | This doc contains the steps to run the integration tests against the link022 agent.
3 |
4 | ## Setup testing environment
5 | The setup needs Python 2.7 environment and some additional packages.
6 | ```
7 | apt-get install python python-netaddr mininet
8 | ```
9 |
10 | ## Compiling Link022 binaries
11 | Follow the [instructions](../agent/README.md) to compile the Link022 binary and set up the certificates.
12 |
13 | ## Run the tests
14 | - Set up the required paths
15 | - GOPATH points to the Go directory for the compiled files of link022 and its dependencies.
16 | - LINK022 points to root directory of the LINK022 source code.
17 | - Run the tests with the Link022 emulator
18 | ```
19 | sudo python integration.py \
20 | --target_cmd "${GOPATH}/bin/agent -ca ${LINK022}/demo/cert/server/ca.crt \
21 | -cert ${LINK022}/demo/cert/server/server.crt \
22 | -key ${LINK022}/demo/cert/server/server.key -eth_intf_name target-eth1 \
23 | -wlan_intf_name target-eth2" \
24 | --gnmi_set ${GOPATH}/bin/gnmi_set \
25 | --ca ${LINK022}/demo/cert/client/ca.crt \
26 | --cert ${LINK022}/demo/cert/client/client.crt \
27 | --key ${LINK022}/demo/cert/client/client.key \
28 | --target_name www.example.com \
29 | --target_addr 10.0.0.1:10162 \
30 | --json_conf ${LINK022}/demo/ap_config.json
31 |
32 | ```
33 | - Run the tests with an external target
34 | ```
35 | sudo python integration.py \
36 | --ext_target \
37 | --gnmi_set ${GOPATH}/bin/gnmi_set \
38 | --ca ${LINK022}/demo/cert/client/ca.crt \
39 | --cert ${LINK022}/demo/cert/client/client.crt \
40 | --key ${LINK022}/demo/cert/client/client.key \
41 | --target_name www.example.com \
42 | --target_addr 10.0.0.1:10162 \
43 | --json_conf ${LINK022}/demo/ap_config.json
44 |
45 | ```
46 | If your target device doesn't support multiple radios, Please replace /demo/ap_config.json with /tests/ap_config.json.
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://opensource.org/licenses/Apache-2.0)
2 | [](https://godoc.org/github.com/google/link022)
3 | [](https://goreportcard.com/report/github.com/google/link022)
4 | [](https://travis-ci.org/google/link022)
5 | [](https://codecov.io/gh/google/link022)
6 |
7 | # Link022: an open WiFi access point
8 | Link022 is an open reference implementation and experimental platform for an OpenConfig and gNMI
9 | controlled WiFi access point.
10 |
11 | The central part of Link022 is an gNMI agent that runs on a Linux host with WiFi capability. The
12 | agent turns the host into an gNMI capable wireless access point which can be configured using
13 | OpenConfig models.
14 |
15 | * See [gNMI Protocol documentation](https://github.com/openconfig/reference/tree/master/rpc/gnmi).
16 | * See [Openconfig documentation](http://www.openconfig.net/).
17 |
18 | ## Get Started
19 | This repository contains following components.
20 |
21 | ### Link022 agent
22 | A WiFi management component that runs on a Link022 AP, with OpenConfig and gNMI implemented.
23 | It supports gNMI "SET" and "GET" opertions for AP configuration.
24 |
25 | To run the agent on a Raspberry Pi device, see the [start guide](agent/README.md).
26 |
27 | ### Link022 demo
28 | A demo for configuring Link022 AP though gNMI. [demo guide](demo/README.md)
29 |
30 | ### Link022 emulator
31 | An emulator that runs Link022 agent inside a Linux namespace. [start guide](emulator/README.md)
32 |
33 | ### gNMI test kit.
34 | A tool to test the gNMI functionality of an AP device. [start guide](testkit/README.md)
35 |
36 | ## Disclaimer
37 | * This is not an official Google product.
38 | * See [how to contribute](CONTRIBUTING.md).
39 |
--------------------------------------------------------------------------------
/openconfig/examples/openconfig.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // The openconfig program contains an example demonstrating how to use
17 | // the auto-generated wireless openconfig module.
18 | package main
19 |
20 | import (
21 | "flag"
22 |
23 | log "github.com/golang/glog"
24 | "github.com/google/link022/agent/util/mock"
25 | "github.com/google/link022/generated/ocstruct"
26 | "github.com/openconfig/ygot/ygot"
27 | )
28 |
29 | func main() {
30 | flag.Parse()
31 | apConfig := mock.GenerateConfig(true)
32 |
33 | jsonString, err := ygot.EmitJSON(apConfig, &ygot.EmitJSONConfig{
34 | Format: ygot.RFC7951,
35 | Indent: " ",
36 | RFC7951Config: &ygot.RFC7951JSONConfig{
37 | AppendModuleName: false,
38 | },
39 | })
40 | if err != nil {
41 | log.Exitf("Error outputting the configuration to JSON: %v", err)
42 | }
43 | log.Infof("Original AP configJSON output:\n%v\n", jsonString)
44 |
45 | loadedAP := &ocstruct.Device{}
46 | err = ocstruct.Unmarshal([]byte(jsonString), loadedAP)
47 | if err != nil {
48 | log.Exitf("Error unmarshal JSON: %v", err)
49 | }
50 |
51 | loadedJSONString, err := ygot.EmitJSON(loadedAP, &ygot.EmitJSONConfig{
52 | Format: ygot.RFC7951,
53 | Indent: " ",
54 | RFC7951Config: &ygot.RFC7951JSONConfig{
55 | AppendModuleName: false,
56 | },
57 | })
58 | if err != nil {
59 | log.Exitf("Error outputting the loaded configuration to JSON: %v", err)
60 | }
61 | log.Infof("Loaded config JSON output:\n%v\n", loadedJSONString)
62 | }
63 |
--------------------------------------------------------------------------------
/demo/cert/client/client.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFiDCCA3CgAwIBAgIJAKkfmX89R4llMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
3 | BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMQ0wCwYDVQQK
4 | DARHTk1JMQ0wCwYDVQQLDARUZXN0MQswCQYDVQQDDAJBUDAeFw0xODA1MzExODA3
5 | MzZaFw0yODA1MjgxODA3MzZaMFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES
6 | MBAGA1UEBwwJU3Vubnl2YWxlMQ0wCwYDVQQKDARHTk1JMQ0wCwYDVQQLDARUZXN0
7 | MQswCQYDVQQDDAJBUDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMwV
8 | Hpr2wZ+r/C4y0fmNb6TSCgM4wlanbcHAmeRZA09N0MaGpNgOto64bFtLfFPQmcnk
9 | op2jQWGB7avy1saEjfRnQZzI3YuMoLxph2zud8vQkVaSQ/Aett1llHlTNf/1FYdT
10 | yGrdmo7sy3KuO47GoDZ/UTRlwUhajKro7nQ4cri0EzmaaZB93WSqzCvDTFTHOSPa
11 | bXoVOWPKke1NloOvBHtPf7K9V0LH5i51FzfWZsL0m7Fna1DwmGEZPz7KKlP39GdM
12 | uomv92IpC+hz6DUtLz3saRh/4QvKmuuigTSp8wZostr5wsgO0nggDK3NJHVdLDBG
13 | QFOcoy3OzRt3o9eoR36k/eKaAzka1j5lxRuEQLNyAiTMqhuV23WMophBPWmmR1SL
14 | xzD7j5L4i3vEGFwkjivPTx30aqFqn+uvXPrvP8z9RHQln5mHFrDgs2JTZz09iOdx
15 | s+rmPDEIRUQrGNiKcYkr94S5mJZDiPffwbYupnEtLG/JJxeM06HH/L9rc57LXL/B
16 | HMy4SLKGRw20CckaF4WZnr1vJ2TSYzWLi4MM377eO2uqPu4NojkpK60wjYItdN3x
17 | CUuOTeqhNjQacbJNUReDUtI953p+rm4W49HzWPLw5eIPntV9YU7rM9Lyc1PiaSfY
18 | GRqv/CDL/HSHKP8Vaz3/EyU13C7QNF40bA4hOICbAgMBAAGjUzBRMB0GA1UdDgQW
19 | BBRjM0wnJXqS+MxBGKrHKxjEk8Sj9DAfBgNVHSMEGDAWgBRjM0wnJXqS+MxBGKrH
20 | KxjEk8Sj9DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCGODfp
21 | QFAByk9oD/r8TLmCzXE4yiwZgglio0kaXI4RFvZLDbJDkTCQotsieRr+zlSJBRIW
22 | Ac6cU3XEkaDAI0jJ+6WH/NKFSAHuTkKp063dnfqZkl0GPkxq4sGJ37XzHa78Q/F0
23 | OxYUTd9yKXdexbVJ5+4Y/p3EMSzWbuxnCB6V38czR6T/ny7nZ0K8+J0oafb9J5f9
24 | 1wN18k8wk7AA+BknGsm9b+btJBNPVYRfLlGIS8NFuCgE4lSoOKB9tJ9bK8xcB0QA
25 | WRsaaKRtMCx7/m60inkvmJqCAp6F+5r940FiPi1L7hdp6mBLk11dyzPQ2Gar1z2U
26 | iYLOm7y9Tk3kwsrDWwCsq3/V8y638E04JMaXB8G/OfAAdvBYfjqeITQyZbsXpDWt
27 | h70W1sAJeOgi2YeLh4i4x9f7zFeDDbOz/tM8E+BgVHoIFMlelR/psTSYo5BCXluE
28 | Hpbs7wmUCw/WRFTSN2W5U2M8i9QLk3Eg2uYzxPoedAhNOP/drfpl39U8oOlaTMR7
29 | uMN0a2JHw2gMmk8rX6O5coJMUHBZcDnRCunMiYBGMxeK2ygOQIV00OnR9PMTWIaO
30 | OrxJnK6bvoYvh9jkuxN1y3B6UpEKT4UMcHfIXiQRZogHdL4eKEn2JMl8Wl33dJZn
31 | EqLzuPqDc+I3w6yg0qC+5AIaHScfq1GozZuaow==
32 | -----END CERTIFICATE-----
33 |
--------------------------------------------------------------------------------
/demo/cert/server/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFiDCCA3CgAwIBAgIJAKkfmX89R4llMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
3 | BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMQ0wCwYDVQQK
4 | DARHTk1JMQ0wCwYDVQQLDARUZXN0MQswCQYDVQQDDAJBUDAeFw0xODA1MzExODA3
5 | MzZaFw0yODA1MjgxODA3MzZaMFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES
6 | MBAGA1UEBwwJU3Vubnl2YWxlMQ0wCwYDVQQKDARHTk1JMQ0wCwYDVQQLDARUZXN0
7 | MQswCQYDVQQDDAJBUDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMwV
8 | Hpr2wZ+r/C4y0fmNb6TSCgM4wlanbcHAmeRZA09N0MaGpNgOto64bFtLfFPQmcnk
9 | op2jQWGB7avy1saEjfRnQZzI3YuMoLxph2zud8vQkVaSQ/Aett1llHlTNf/1FYdT
10 | yGrdmo7sy3KuO47GoDZ/UTRlwUhajKro7nQ4cri0EzmaaZB93WSqzCvDTFTHOSPa
11 | bXoVOWPKke1NloOvBHtPf7K9V0LH5i51FzfWZsL0m7Fna1DwmGEZPz7KKlP39GdM
12 | uomv92IpC+hz6DUtLz3saRh/4QvKmuuigTSp8wZostr5wsgO0nggDK3NJHVdLDBG
13 | QFOcoy3OzRt3o9eoR36k/eKaAzka1j5lxRuEQLNyAiTMqhuV23WMophBPWmmR1SL
14 | xzD7j5L4i3vEGFwkjivPTx30aqFqn+uvXPrvP8z9RHQln5mHFrDgs2JTZz09iOdx
15 | s+rmPDEIRUQrGNiKcYkr94S5mJZDiPffwbYupnEtLG/JJxeM06HH/L9rc57LXL/B
16 | HMy4SLKGRw20CckaF4WZnr1vJ2TSYzWLi4MM377eO2uqPu4NojkpK60wjYItdN3x
17 | CUuOTeqhNjQacbJNUReDUtI953p+rm4W49HzWPLw5eIPntV9YU7rM9Lyc1PiaSfY
18 | GRqv/CDL/HSHKP8Vaz3/EyU13C7QNF40bA4hOICbAgMBAAGjUzBRMB0GA1UdDgQW
19 | BBRjM0wnJXqS+MxBGKrHKxjEk8Sj9DAfBgNVHSMEGDAWgBRjM0wnJXqS+MxBGKrH
20 | KxjEk8Sj9DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCGODfp
21 | QFAByk9oD/r8TLmCzXE4yiwZgglio0kaXI4RFvZLDbJDkTCQotsieRr+zlSJBRIW
22 | Ac6cU3XEkaDAI0jJ+6WH/NKFSAHuTkKp063dnfqZkl0GPkxq4sGJ37XzHa78Q/F0
23 | OxYUTd9yKXdexbVJ5+4Y/p3EMSzWbuxnCB6V38czR6T/ny7nZ0K8+J0oafb9J5f9
24 | 1wN18k8wk7AA+BknGsm9b+btJBNPVYRfLlGIS8NFuCgE4lSoOKB9tJ9bK8xcB0QA
25 | WRsaaKRtMCx7/m60inkvmJqCAp6F+5r940FiPi1L7hdp6mBLk11dyzPQ2Gar1z2U
26 | iYLOm7y9Tk3kwsrDWwCsq3/V8y638E04JMaXB8G/OfAAdvBYfjqeITQyZbsXpDWt
27 | h70W1sAJeOgi2YeLh4i4x9f7zFeDDbOz/tM8E+BgVHoIFMlelR/psTSYo5BCXluE
28 | Hpbs7wmUCw/WRFTSN2W5U2M8i9QLk3Eg2uYzxPoedAhNOP/drfpl39U8oOlaTMR7
29 | uMN0a2JHw2gMmk8rX6O5coJMUHBZcDnRCunMiYBGMxeK2ygOQIV00OnR9PMTWIaO
30 | OrxJnK6bvoYvh9jkuxN1y3B6UpEKT4UMcHfIXiQRZogHdL4eKEn2JMl8Wl33dJZn
31 | EqLzuPqDc+I3w6yg0qC+5AIaHScfq1GozZuaow==
32 | -----END CERTIFICATE-----
33 |
--------------------------------------------------------------------------------
/demo/cert/client/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | U32jzw==
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/demo/cert/server/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | U32jzw==
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/openconfig/scripts/generate_wifi_oc_schema.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 | #
17 | # Generates the latest version of the OpenConfig textproto schema descriptor and
18 | # model visualization from the source YANG modules.
19 | #
20 | # To use this, run thr scripts directory:
21 | # ./generate_wifi_oc_schema.sh
22 |
23 | export GOPATH=$HOME/go
24 |
25 | # Tools
26 | YANG_CONVERTER=$GOPATH/src/github.com/openconfig/ygot/generator/generator.go
27 |
28 | # Download OpenConfig models from https://github.com/openconfig/public
29 | # Download ietf models from https://github.com/openconfig/yang/tree/master/standard/ietf/RFC
30 | # Move downloaded models to a specific folder.
31 | OC_FOLDER=
32 |
33 | # OpenConfig modules
34 | YANG_MODELS=$OC_FOLDER/public/release/models
35 | IETF_MODELS=$OC_FOLDER/yang/standard/ietf/RFC
36 | AP_TOP_MODULE=$OC_FOLDER/public/release/models/wifi/access-points/openconfig-access-points.yang
37 | IGNORED_MODULES=openconfig-wifi-phy,openconfig-wifi-mac,openconfig-system,openconfig-extensions,openconfig-inet-types,openconfig-platform
38 | GASKET_MODULES=../models/gasket.yang
39 |
40 | # Output path
41 | OUTPUT_PACKAGE_NAME=ocstruct
42 | OUTPUT_FILE_PATH=../../generated/$OUTPUT_PACKAGE_NAME/$OUTPUT_PACKAGE_NAME.go
43 |
44 | go run $YANG_CONVERTER \
45 | -path=$YANG_MODELS,$IETF_MODELS,$GASKET_MODULES \
46 | -generate_fakeroot -fakeroot_name=device \
47 | -package_name=ocstruct -compress_paths=false \
48 | -exclude_modules=$IGNORED_MODULES \
49 | -output_file=$OUTPUT_FILE_PATH \
50 | $AP_TOP_MODULE $GASKET_MODULES
51 |
--------------------------------------------------------------------------------
/demo/facuet_hw/faucet.yaml:
--------------------------------------------------------------------------------
1 | vlans:
2 | vlan100:
3 | vid: 100
4 | vlan200:
5 | vid: 200
6 | vlan300:
7 | vid: 300
8 | dps:
9 | sw1:
10 | dp_id: 0x1
11 | hardware: "Open vSwitch"
12 | interfaces:
13 | 1:
14 | name: "gw"
15 | description: "gw for dhcp and dnsmasq"
16 | tagged_vlans: [vlan200,vlan300]
17 | native_vlan: vlan100
18 | 2:
19 | name: "host2"
20 | tagged_vlans: [vlan200,vlan300]
21 | native_vlan: vlan100
22 | 3:
23 | name: "host3"
24 | tagged_vlans: [vlan200,vlan300]
25 | native_vlan: vlan100
26 | 4:
27 | name: "host4"
28 | tagged_vlans: [vlan200,vlan300]
29 | native_vlan: vlan100
30 | 5:
31 | name: "host5"
32 | tagged_vlans: [vlan200,vlan300]
33 | native_vlan: vlan100
34 | sw2:
35 | dp_id: 0x0000001aeb965d18
36 | hardware: "Allied-Telesis"
37 | interfaces:
38 | 1:
39 | name: "gw"
40 | description: "gw for dhcp and dnsmasq"
41 | tagged_vlans: [vlan200,vlan300]
42 | native_vlan: vlan100
43 | 2:
44 | name: "host2"
45 | tagged_vlans: [vlan200,vlan300]
46 | native_vlan: vlan100
47 | 3:
48 | name: "host3"
49 | tagged_vlans: [vlan200,vlan300]
50 | native_vlan: vlan100
51 | 4:
52 | name: "host4"
53 | tagged_vlans: [vlan200,vlan300]
54 | native_vlan: vlan100
55 | 5:
56 | name: "host5"
57 | tagged_vlans: [vlan200,vlan300]
58 | native_vlan: vlan100
59 | 6:
60 | name: "host6"
61 | tagged_vlans: [vlan200,vlan300]
62 | native_vlan: vlan100
63 | 7:
64 | name: "host7"
65 | tagged_vlans: [vlan200,vlan300]
66 | native_vlan: vlan100
67 |
--------------------------------------------------------------------------------
/agent/README.md:
--------------------------------------------------------------------------------
1 | # Agent
2 |
3 | This directory contains the WiFi management component that runs on the
4 | Link022 Pi AP.
5 |
6 | ## Get Started
7 | For documentation on installing a full demo system (with both a gNMI client and link022 access point)
8 | please see the [full demo documentation](../demo/README.md) which explains a fully complete system.
9 |
10 | The following instructions will get you a Link022 AP on a Linux-based device.
11 |
12 | ### Prerequisites
13 | Have a device set up. (Tested with Raspian Stretch)
14 |
15 | Install Golang.
16 | ```
17 | If running on Raspberry Pi:
18 | wget https://storage.googleapis.com/golang/go1.7.linux-armv6l.tar.gz
19 | sudo tar -C /usr/local -xzf go1.7.linux-armv6l.tar.gz
20 |
21 | For other systems:
22 | Install golang 1.7+ (get it from: https://golang.org/doc/install#install)
23 | ```
24 |
25 | Set up Path:
26 | ```
27 | export PATH=$PATH:/usr/local/go/bin
28 | ```
29 |
30 | Install dependencies.
31 | ```
32 | sudo apt-get install udhcpc bridge-utils hostapd git
33 | ```
34 |
35 | ### Download Link022 agent
36 | ```
37 | export GOPATH=$HOME/go
38 | go get github.com/google/link022/agent
39 | ```
40 |
41 | ### Download certificates
42 | Download sample certificates from [the demo directory](../demo/cert/server/).
43 | Or you can use your own cert. Sample commands to generate certificates can be found [here](../demo/cert/generate_cert.sh).
44 |
45 | ### Configuring network interfaces of device
46 | Editing the file /etc/network/interfaces on device.
47 | ```
48 | auto lo
49 | iface lo inet loopback
50 |
51 | auto eth0
52 | iface eth0 inet dhcp
53 |
54 | # Disable all WLAN interfaces.
55 | auto wlan0
56 | iface wlan0 inet static
57 | address 0.0.0.0
58 |
59 | # Repeat for other WLAN interfaces.
60 | ```
61 | Note: Reboot the device to make change take effect.
62 |
63 | ### Running Link022 agent
64 | ```
65 | export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
66 | sudo env PATH=$PATH agent -ca= -cert= -key= -eth_intf_name= -wlan_intf_name= -gnmi_port=
67 | ```
68 |
69 | The default log file is "/tmp/agent.INFO". It can be modified by "-log_dir" option.
70 |
71 | Note: Make sure the chosen wireless device supports AP mode and has enough
72 | capability.
--------------------------------------------------------------------------------
/agent/service/base.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package service contains methods that manage Link022 AP device.
17 | package service
18 |
19 | import (
20 | log "github.com/golang/glog"
21 | "github.com/google/link022/agent/syscmd"
22 | "github.com/google/link022/agent/util/ocutil"
23 | "github.com/google/link022/generated/ocstruct"
24 | )
25 |
26 | var (
27 | cmdRunner = syscmd.Runner()
28 | runFolder = "/var/run/link022"
29 | )
30 |
31 | // ApplyConfig configures this device to a Link022 AP based on the given configuration.
32 | func ApplyConfig(officeAP *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint, gasketConfig *ocstruct.OpenconfigGasket_Gasket, setupIntf bool, ethIntfName, wlanINTFName string) error {
33 | log.Infof("Configuring AP %s...", *officeAP.Hostname)
34 |
35 | if setupIntf {
36 | // Configure eth interface.
37 | if err := configEthIntf(ethIntfName, ocutil.VLANIDs(officeAP)); err != nil {
38 | return err
39 | }
40 |
41 | //Configure WLAN interface.
42 | if err := configWLANIntf(wlanINTFName); err != nil {
43 | return err
44 | }
45 | }
46 |
47 | // Configure hostapd.
48 | return configHostapd(officeAP, gasketConfig, wlanINTFName)
49 | }
50 |
51 | // CleanupConfig cleans up the current AP configuration on this device.
52 | // It goes through all cleanup steps even if some failures are detected, and returns all errors.
53 | func CleanupConfig(ethIntfName string, vlanIDs []int) []error {
54 | var errs []error
55 |
56 | // Stop hostapd processes.
57 | if err := cmdRunner.StopAllHostapd(); err != nil {
58 | errs = append(errs, err)
59 | }
60 |
61 | // Clean up eth interfaces.
62 | if len(vlanIDs) > 0 {
63 | errs = append(errs, cleanupEthIntf(ethIntfName, vlanIDs)...)
64 | }
65 |
66 | log.Infof("Cleaned up AP. Number of errors = %d.", len(errs))
67 | return errs
68 | }
69 |
--------------------------------------------------------------------------------
/tests/ap_dot11r.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:hostname":"link022-pi-ap",
3 | "openconfig-access-points:system":{
4 | "aaa":{
5 | "server-groups":{
6 | "server-group":[
7 | {
8 | "servers":{
9 | "server":[
10 | {
11 | "address":"192.168.11.1",
12 | "config":{
13 | "address":"192.168.11.1",
14 | "timeout":5,
15 | "name":"radius-server"
16 | },
17 | "radius":{
18 | "config":{
19 | "auth-port":1812,
20 | "secret-key":"radiuspwd"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | "name":"freeradius",
27 | "config":{
28 | "name":"freeradius",
29 | "type":"openconfig-aaa-radius:RADIUS"
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | },
36 | "openconfig-access-points:radios":{
37 | "radio":[
38 | {
39 | "config":{
40 | "channel":157,
41 | "channel-width":40,
42 | "dca":false,
43 | "enabled":true,
44 | "id":0,
45 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
46 | "scanning":false,
47 | "scanning-defer-traffic":true,
48 | "transmit-power":9
49 | },
50 | "id":0
51 | },
52 | {
53 | "config":{
54 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
55 | "channel-width":20,
56 | "transmit-power":3,
57 | "id":1,
58 | "channel":6
59 | },
60 | "id":1
61 | }
62 | ]
63 | },
64 | "openconfig-access-points:ssids":{
65 | "ssid":[
66 | {
67 | "dot11r":{
68 | "config":{
69 | "dot11r-domainid":5,
70 | "dot11r-method":"OVA",
71 | "dot11r":true
72 | }
73 | },
74 | "config":{
75 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
76 | "name":"Auth-Link022",
77 | "default-vlan":300,
78 | "enabled":true,
79 | "server-group":"freeradius",
80 | "opmode":"WPA2_ENTERPRISE"
81 | },
82 | "name":"Auth-Link022"
83 | }
84 | ]
85 | }
86 | }
--------------------------------------------------------------------------------
/tests/ap_apmanagers.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:hostname":"link022-pi-ap",
3 | "openconfig-access-points:system":{
4 | "aaa":{
5 | "server-groups":{
6 | "server-group":[
7 | {
8 | "servers":{
9 | "server":[
10 | {
11 | "address":"192.168.11.1",
12 | "config":{
13 | "address":"192.168.11.1",
14 | "timeout":5,
15 | "name":"radius-server"
16 | },
17 | "radius":{
18 | "config":{
19 | "auth-port":1812,
20 | "secret-key":"radiuspwd"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | "name":"freeradius",
27 | "config":{
28 | "name":"freeradius",
29 | "type":"openconfig-aaa-radius:RADIUS"
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | },
36 | "openconfig-access-points:radios":{
37 | "radio":[
38 | {
39 | "config":{
40 | "channel":157,
41 | "channel-width":40,
42 | "dca":false,
43 | "enabled":true,
44 | "id":0,
45 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
46 | "scanning":false,
47 | "scanning-defer-traffic":true,
48 | "transmit-power":9
49 | },
50 | "id":0
51 | },
52 | {
53 | "config":{
54 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
55 | "channel-width":20,
56 | "transmit-power":3,
57 | "id":1,
58 | "channel":6
59 | },
60 | "id":1
61 | }
62 | ]
63 | },
64 | "openconfig-access-points:ssids":{
65 | "ssid":[
66 | {
67 | "config":{
68 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
69 | "name":"Auth-Link022",
70 | "default-vlan":300,
71 | "enabled":true,
72 | "server-group":"freeradius",
73 | "opmode":"WPA2_ENTERPRISE"
74 | },
75 | "name":"Auth-Link022"
76 | }
77 | ]
78 | },
79 | "openconfig-access-points:assigned-ap-managers":{
80 | "ap-manager":[
81 | {
82 | "config":{
83 | "ap-manager-ipv4-address":"192.168.1.2",
84 | "id":"primary",
85 | "fqdn":"primary-manager.example.com"
86 | },
87 | "id":"primary"
88 | },
89 | {
90 | "config":{
91 | "ap-manager-ipv4-address":"192.168.2.2",
92 | "id":"secondary",
93 | "fqdn":"secondary-manager.example.com"
94 | },
95 | "id":"secondary"
96 | }
97 | ]
98 | }
99 | }
--------------------------------------------------------------------------------
/agent/util/mock/openconfig_test.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package mock
17 |
18 | import (
19 | "testing"
20 |
21 | "github.com/google/link022/generated/ocstruct"
22 | "github.com/openconfig/ygot/ygot"
23 | )
24 |
25 | // TestYGOT verifies the current version of ygot library works with Link022 agent.
26 | func TestYGOT(t *testing.T) {
27 | verifyOfficeConfig(t, GenerateConfig(true))
28 | verifyOfficeConfig(t, GenerateConfig(false))
29 | verifyOfficeConfig(t, GenerateConfig(true))
30 | verifyOfficeConfig(t, GenerateConfig(false))
31 | verifyOfficeConfig(t, GenerateConfig(true))
32 | verifyOfficeConfig(t, GenerateConfig(false))
33 | }
34 |
35 | func verifyOfficeConfig(t *testing.T, apConfig *ocstruct.Device) {
36 | // Test validation.
37 | if err := apConfig.Validate(); err != nil {
38 | t.Errorf("AP configuration is not valid. Error: %v.", err)
39 | }
40 |
41 | // Test marshalling.
42 | jsonString, err := ygot.EmitJSON(apConfig, &ygot.EmitJSONConfig{
43 | Format: ygot.RFC7951,
44 | Indent: " ",
45 | RFC7951Config: &ygot.RFC7951JSONConfig{
46 | AppendModuleName: true,
47 | },
48 | })
49 | if err != nil {
50 | t.Errorf("Marshalling the AP configuration to JSON failed. Error: %v.", err)
51 | }
52 |
53 | // Test unmarshalling.
54 | loadedConfig := &ocstruct.Device{}
55 | if err = ocstruct.Unmarshal([]byte(jsonString), loadedConfig); err != nil {
56 | t.Errorf("Unmarshalling AP configuration JSON string to OpenConfig structs failed. Error: %v.", err)
57 | }
58 |
59 | // Test validation.
60 | if err := loadedConfig.Validate(); err != nil {
61 | t.Errorf("The loaded AP configuration is not valid. Error: %v.", err)
62 | }
63 |
64 | // Test marshalling again.
65 | loadedJSONString, err := ygot.EmitJSON(loadedConfig, &ygot.EmitJSONConfig{
66 | Format: ygot.RFC7951,
67 | Indent: " ",
68 | RFC7951Config: &ygot.RFC7951JSONConfig{
69 | AppendModuleName: true,
70 | },
71 | })
72 | if err != nil {
73 | t.Errorf("Marshalling the loaded AP configuration to JSON failed. Error: %v.", err)
74 | }
75 |
76 | if jsonString != loadedJSONString {
77 | t.Errorf("The loaded AP configuration does not match the original one, got: %s, expected: %s.", jsonString, loadedJSONString)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/agent/monitoring/prometheus/README.md:
--------------------------------------------------------------------------------
1 | # OpenConfig Telemetry Exporter (Prometheus)
2 |
3 | This directory contains an exporter that periodically collects AP's info via gNMI and exposes converted metrics to a web page. Prometheus can monitor AP's status by scraping that web page.
4 |
5 | ## Get Started
6 |
7 | Follow steps below to set up environment and start exposition server
8 |
9 | ### Prerequisites
10 |
11 | Download entire Link022 repository.
12 | Install golang 1.10+ (get it from: https://golang.org/doc/install#install)
13 | Install dependencies:
14 | Change directory in terminal to this folder, then run below command.
15 |
16 | ```
17 | go get -t ./...
18 | ```
19 |
20 | ### Compile Exporter
21 |
22 | ```
23 | go build exposition_server.go openconfig_ap_exporter.go
24 | ```
25 |
26 | This command will generate binary file named exposition_server.
27 |
28 | ### Start Exporter
29 | Run the exporter binary. It takes three categories of input parameters:
30 |
31 | 1. gNMI client certs config:
32 | * ca: CA certificate file
33 | * cert: Certificate file
34 | * key: Private key file
35 | 2. gNMI target config:
36 | * target_addr: the target address in the format of host:port
37 | * target_name: the target name for verifing the hostname returned by TLS handshake
38 | 3. Exposition server config
39 | * listen_addr: the address for server to listen HTTP requests. The HTTP resource for scraping is /metrics.
40 |
41 | Here is one example:
42 |
43 | ```
44 | ./exposition_server \
45 | -ca ../../../demo/cert/client/ca.crt \
46 | -cert ../../../demo/cert/client/client.crt \
47 | -key ../../../demo/cert/client/client.key \
48 | -target_name www.example.com \
49 | -target_addr 10.0.0.1:8080 \
50 | -listen_addr 127.0.0.1:8080
51 | ```
52 |
53 | Note: The default location of exporter log file is "/tmp/exposition_server.INFO"
54 |
55 | ## Monitoring In Prometheus
56 |
57 | Follow steps below to set up Prometheus and monitoring AP status.
58 |
59 | ### Prometheus Getting Started
60 |
61 | Download [Prometheus](https://prometheus.io/download/). Follow official Prometheus [tutorial](https://prometheus.io/docs/prometheus/latest/getting_started/) to learn how to configure and start it.
62 |
63 | ### Configuring Prometheus to monitor AP
64 |
65 | If exposition server is listening on 127.0.0.1:8080
66 | Save the following basic Prometheus configuration as a file named prometheus.yml
67 |
68 | ```
69 | global:
70 | scrape_interval: 15s # Exposition server sends gNMI request every 15 seconds.
71 |
72 | scrape_configs:
73 | # The job name is added as a label `job=` to any timeseries scraped from this config.
74 | - job_name: 'link022-pi-ap'
75 | static_configs:
76 | - targets: ['127.0.0.1:8080']
77 | ```
78 |
79 | ### Start Prometheus
80 |
81 | Start Prometheus according to its official tutorial.
82 | By default, Prometheus admin page is localhost:9090.
83 | You can see all exported AP status metrics in that page.
--------------------------------------------------------------------------------
/agent/controller/controller.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package controller contains methods that are related to AP controller.
17 | package controller
18 |
19 | import (
20 | "errors"
21 | "time"
22 |
23 | devctx "github.com/google/link022/agent/context"
24 | "golang.org/x/net/context"
25 | "google.golang.org/grpc"
26 | "google.golang.org/grpc/metadata"
27 |
28 | log "github.com/golang/glog"
29 | pb "github.com/openconfig/gnmi/proto/gnmi"
30 | )
31 |
32 | const (
33 | hostnameHeader = "hostname"
34 | gnmiAddrHeader = "gnmi_address"
35 | resyncRequiredHeader = "resync_required"
36 |
37 | heartbeatInterval = 15 * time.Second
38 | )
39 |
40 | // ReportAPInfo reports the current AP information to the assigned controller.
41 | // It returns error if failures detected.
42 | func ReportAPInfo(syncRequried bool) error {
43 | deviceConfig := devctx.GetDeviceConfig()
44 | if deviceConfig.ControllerAddr == "" {
45 | return errors.New("no controller assigned, unable to report AP information")
46 | }
47 |
48 | // Creating a gNMI client.
49 | conn, err := grpc.Dial(deviceConfig.ControllerAddr, grpc.WithInsecure())
50 | if err != nil {
51 | return err
52 | }
53 | defer conn.Close()
54 |
55 | getRequest := &pb.GetRequest{
56 | Path: []*pb.Path{},
57 | }
58 |
59 | // Add AP's gNMI information to the request header.
60 | headerMap := map[string]string{
61 | hostnameHeader: deviceConfig.Hostname,
62 | gnmiAddrHeader: deviceConfig.GNMIServerAddr,
63 | }
64 | if syncRequried {
65 | headerMap[resyncRequiredHeader] = "True"
66 | }
67 | ctx := metadata.NewOutgoingContext(context.Background(), metadata.New(headerMap))
68 |
69 | cli := pb.NewGNMIClient(conn)
70 | if _, err := cli.Get(ctx, getRequest); err != nil {
71 | return err
72 | }
73 | return nil
74 | }
75 |
76 | // Connect sends heartbeat messages to the assigned controller periodically.
77 | // The first (received) message contains a sync request to fetch the latest AP configuration.
78 | // This function never returns, should be run in the background.
79 | func Connect() {
80 | syncRequired := true
81 | for {
82 | if err := ReportAPInfo(syncRequired); err != nil {
83 | log.Errorf("Cannot connect to the controller, retry in %s. Error: %v.", heartbeatInterval, err)
84 | // Disconncetion detected, do a re-sync.
85 | syncRequired = true
86 | } else if syncRequired {
87 | log.Info("Controller connected and received the sync request.")
88 | syncRequired = false
89 | }
90 | time.Sleep(heartbeatInterval)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/agent/syscmd/base.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package syscmd contains methods that run external commands on device.
17 | package syscmd
18 |
19 | import (
20 | "fmt"
21 | "io/ioutil"
22 | "os"
23 | "os/exec"
24 | "path"
25 |
26 | log "github.com/golang/glog"
27 | )
28 |
29 | // CommandRunner contains methods executing external commands.
30 | type CommandRunner struct {
31 | // ExecCommand runs the given command with arguments.
32 | // It returns the content of stdout or stderr, and error if command failed.
33 | ExecCommand func(wait bool, cmd string, args ...string) (string, error)
34 | }
35 |
36 | // Runner executes external commands in the real environment.
37 | func Runner() *CommandRunner {
38 | return &CommandRunner{
39 | ExecCommand: execute,
40 | }
41 | }
42 |
43 | func execute(wait bool, cmd string, args ...string) (string, error) {
44 | command := exec.Command(cmd, args...)
45 | var output []byte
46 | var err error
47 |
48 | if wait {
49 | output, err = command.CombinedOutput()
50 | } else {
51 | command.Stdout = os.Stdout
52 | err = command.Start()
53 | }
54 |
55 | outputString := string(output)
56 | if err != nil {
57 | log.Errorf("Command (%v %v) failed. Error: %v.\nOutput:\n%v", cmd, args, err, outputString)
58 | } else {
59 | log.V(2).Infof("Command (%v %v) succeeded.\nOutput:\n%v", cmd, args, outputString)
60 | }
61 |
62 | return outputString, err
63 | }
64 |
65 | func vlanINTFName(intfName string, vlanID int) string {
66 | return fmt.Sprintf("%s.%d", intfName, vlanID)
67 | }
68 |
69 | // SaveToFile saves the input string into a file in the file system.
70 | // It creates the file and all parent folder if not exist.
71 | func SaveToFile(folderPath, fileName, content string) error {
72 | fileFullPath := path.Join(folderPath, fileName)
73 | log.Infof("Saving content to file %v...", fileFullPath)
74 |
75 | fileInfo, err := os.Stat(folderPath)
76 | if os.IsNotExist(err) {
77 | // Config folder does not exist, create one.
78 | if err = os.MkdirAll(folderPath, 0755); err != nil {
79 | log.Errorf("Creating folder %v failed. Error: %v.", folderPath, err)
80 | return err
81 | }
82 | log.Infof("Created folder %v.", folderPath)
83 | } else if !fileInfo.Mode().IsDir() {
84 | log.Errorf("Unable to save content to %v, since %v points an existing file.", fileFullPath, folderPath)
85 | return fmt.Errorf("%s points an existing file", folderPath)
86 | }
87 |
88 | if err := ioutil.WriteFile(fileFullPath, []byte(content), 0600); err != nil {
89 | log.Errorf("Saving content to file %v failed. Error: %v.", fileFullPath, err)
90 | return err
91 | }
92 | log.Infof("Saved content to file %v.", fileFullPath)
93 | return nil
94 | }
95 |
--------------------------------------------------------------------------------
/tests/link022_ap_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:hostname":"link022-pi-ap",
3 | "openconfig-access-points:system":{
4 | "aaa":{
5 | "server-groups":{
6 | "server-group":[
7 | {
8 | "servers":{
9 | "server":[
10 | {
11 | "address":"192.168.11.1",
12 | "config":{
13 | "address":"192.168.11.1",
14 | "timeout":5,
15 | "name":"radius-server"
16 | },
17 | "radius":{
18 | "config":{
19 | "auth-port":1812,
20 | "secret-key":"radiuspwd"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | "name":"freeradius",
27 | "config":{
28 | "name":"freeradius",
29 | "type":"openconfig-aaa-radius:RADIUS"
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | },
36 | "openconfig-access-points:radios":{
37 | "radio":[
38 | {
39 | "config":{
40 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
41 | "channel-width":20,
42 | "dca":false,
43 | "dtp":false,
44 | "transmit-power":3,
45 | "id":1,
46 | "channel":6
47 | },
48 | "id":1
49 | }
50 | ]
51 | },
52 | "openconfig-access-points:ssids":{
53 | "ssid":[
54 | {
55 | "config":{
56 | "qbss-load":true,
57 | "dva":true,
58 | "station-isolation":true,
59 | "operating-frequency":"openconfig-wifi-types:FREQ_2_5_GHZ",
60 | "name":"Auth-Link022",
61 | "default-vlan":300,
62 | "dot11k":true,
63 | "supported-data-rates":[
64 | "openconfig-wifi-types:RATE_36MB",
65 | "openconfig-wifi-types:RATE_48MB",
66 | "openconfig-wifi-types:RATE_54MB"
67 | ],
68 | "basic-data-rates":[
69 | "openconfig-wifi-types:RATE_36MB",
70 | "openconfig-wifi-types:RATE_48MB",
71 | "openconfig-wifi-types:RATE_54MB"
72 | ],
73 | "broadcast-filter":true,
74 | "enabled":true,
75 | "server-group":"freeradius",
76 | "csa":true,
77 | "hidden":false,
78 | "opmode":"WPA2_ENTERPRISE"
79 | },
80 | "name":"Auth-Link022"
81 | },
82 | {
83 | "config":{
84 | "advertise-apname":true,
85 | "basic-data-rates":[
86 | "openconfig-wifi-types:RATE_11MB",
87 | "openconfig-wifi-types:RATE_24MB"
88 | ],
89 | "broadcast-filter":true,
90 | "csa":false,
91 | "dot11k":false,
92 | "dva":false,
93 | "enabled":true,
94 | "hidden":false,
95 | "name":"Guest-Link022",
96 | "operating-frequency":"openconfig-wifi-types:FREQ_2_5_GHZ",
97 | "opmode":"OPEN",
98 | "supported-data-rates":[
99 | "openconfig-wifi-types:RATE_11MB",
100 | "openconfig-wifi-types:RATE_24MB"
101 | ],
102 | "default-vlan":200
103 | },
104 | "name":"Guest-Link022"
105 | }
106 | ]
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/demo/cert/server/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | pKbgDYwzysCuagMLjAY5nkUfLrd7
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/testkit/gnmitest/testfile/ap_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:hostname":"link022-pi-ap",
3 | "openconfig-access-points:system":{
4 | "aaa":{
5 | "server-groups":{
6 | "server-group":[
7 | {
8 | "servers":{
9 | "server":[
10 | {
11 | "address":"192.168.11.1",
12 | "config":{
13 | "address":"192.168.11.1",
14 | "timeout":5,
15 | "name":"radius-server"
16 | },
17 | "radius":{
18 | "config":{
19 | "auth-port":1812,
20 | "secret-key":"radiuspwd"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | "name":"freeradius",
27 | "config":{
28 | "name":"freeradius",
29 | "type":"openconfig-aaa-radius:RADIUS"
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | },
36 | "openconfig-access-points:radios":{
37 | "radio":[
38 | {
39 | "config":{
40 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
41 | "channel-width":20,
42 | "dca":false,
43 | "dtp":false,
44 | "transmit-power":3,
45 | "id":1,
46 | "channel":6
47 | },
48 | "id":1
49 | }
50 | ]
51 | },
52 | "openconfig-access-points:ssids":{
53 | "ssid":[
54 | {
55 | "config":{
56 | "qbss-load":true,
57 | "dva":true,
58 | "station-isolation":true,
59 | "operating-frequency":"openconfig-wifi-types:FREQ_2_5_GHZ",
60 | "name":"Auth-Link022",
61 | "default-vlan":300,
62 | "dot11k":true,
63 | "supported-data-rates":[
64 | "openconfig-wifi-types:RATE_36MB",
65 | "openconfig-wifi-types:RATE_48MB",
66 | "openconfig-wifi-types:RATE_54MB"
67 | ],
68 | "basic-data-rates":[
69 | "openconfig-wifi-types:RATE_36MB",
70 | "openconfig-wifi-types:RATE_48MB",
71 | "openconfig-wifi-types:RATE_54MB"
72 | ],
73 | "broadcast-filter":true,
74 | "enabled":true,
75 | "server-group":"freeradius",
76 | "csa":true,
77 | "hidden":false,
78 | "opmode":"WPA2_ENTERPRISE"
79 | },
80 | "name":"Auth-Link022"
81 | },
82 | {
83 | "config":{
84 | "advertise-apname":true,
85 | "basic-data-rates":[
86 | "openconfig-wifi-types:RATE_11MB",
87 | "openconfig-wifi-types:RATE_24MB"
88 | ],
89 | "broadcast-filter":true,
90 | "csa":false,
91 | "dot11k":false,
92 | "dva":false,
93 | "enabled":true,
94 | "hidden":false,
95 | "name":"Guest-Link022",
96 | "operating-frequency":"openconfig-wifi-types:FREQ_2_5_GHZ",
97 | "opmode":"OPEN",
98 | "supported-data-rates":[
99 | "openconfig-wifi-types:RATE_11MB",
100 | "openconfig-wifi-types:RATE_24MB"
101 | ],
102 | "default-vlan":200
103 | },
104 | "name":"Guest-Link022"
105 | }
106 | ]
107 | }
108 | }
--------------------------------------------------------------------------------
/demo/cert/client/client.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | OeDa/b+KF+Ri3ggvBzMkNUxQpAZ3w3p0
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/tests/ap_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:hostname":"link022-pi-ap",
3 | "openconfig-access-points:system":{
4 | "aaa":{
5 | "server-groups":{
6 | "server-group":[
7 | {
8 | "servers":{
9 | "server":[
10 | {
11 | "address":"192.168.11.1",
12 | "config":{
13 | "address":"192.168.11.1",
14 | "timeout":5,
15 | "name":"radius-server"
16 | },
17 | "radius":{
18 | "config":{
19 | "auth-port":1812,
20 | "secret-key":"radiuspwd"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | "name":"freeradius",
27 | "config":{
28 | "name":"freeradius",
29 | "type":"openconfig-aaa-radius:RADIUS"
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | },
36 | "openconfig-access-points:radios":{
37 | "radio":[
38 | {
39 | "config":{
40 | "channel":157,
41 | "channel-width":40,
42 | "dca":false,
43 | "enabled":true,
44 | "id":0,
45 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
46 | "scanning":false,
47 | "scanning-defer-traffic":true,
48 | "transmit-power":9
49 | },
50 | "id":0
51 | },
52 | {
53 | "config":{
54 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
55 | "channel-width":20,
56 | "dca":false,
57 | "enabled":true,
58 | "dtp":false,
59 | "transmit-power":3,
60 | "id":1,
61 | "channel":6
62 | },
63 | "id":1
64 | }
65 | ]
66 | },
67 | "openconfig-access-points:ssids":{
68 | "ssid":[
69 | {
70 | "config":{
71 | "qbss-load":true,
72 | "dva":true,
73 | "station-isolation":true,
74 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
75 | "name":"Auth-Link022",
76 | "default-vlan":300,
77 | "dot11k":true,
78 | "supported-data-rates":[
79 | "openconfig-wifi-types:RATE_36MB",
80 | "openconfig-wifi-types:RATE_48MB",
81 | "openconfig-wifi-types:RATE_54MB"
82 | ],
83 | "basic-data-rates":[
84 | "openconfig-wifi-types:RATE_36MB",
85 | "openconfig-wifi-types:RATE_48MB",
86 | "openconfig-wifi-types:RATE_54MB"
87 | ],
88 | "broadcast-filter":true,
89 | "enabled":true,
90 | "server-group":"freeradius",
91 | "csa":true,
92 | "hidden":false,
93 | "opmode":"WPA2_ENTERPRISE"
94 | },
95 | "name":"Auth-Link022"
96 | },
97 | {
98 | "config":{
99 | "advertise-apname":true,
100 | "basic-data-rates":[
101 | "openconfig-wifi-types:RATE_11MB",
102 | "openconfig-wifi-types:RATE_24MB"
103 | ],
104 | "broadcast-filter":true,
105 | "csa":false,
106 | "dot11k":false,
107 | "dva":false,
108 | "enabled":true,
109 | "hidden":false,
110 | "name":"Guest-Link022",
111 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
112 | "opmode":"OPEN",
113 | "supported-data-rates":[
114 | "openconfig-wifi-types:RATE_11MB",
115 | "openconfig-wifi-types:RATE_24MB"
116 | ],
117 | "default-vlan":200
118 | },
119 | "name":"Guest-Link022"
120 | }
121 | ]
122 | }
123 | }
--------------------------------------------------------------------------------
/testkit/common/common.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package common contains functions and models shared by all components.
17 | package common
18 |
19 | // OPType is the type of gNMI operation.
20 | type OPType string
21 |
22 | const (
23 | // OPReplace is the gNMI replace operation.
24 | OPReplace OPType = "replace"
25 | // OPUpdate is the gNMI update operation.
26 | OPUpdate OPType = "update"
27 | // OPDelete is the gNMI delete operation.
28 | OPDelete OPType = "delete"
29 | // OPGet is the gNMI get operation.
30 | OPGet OPType = "get"
31 | // OPSubscribe is the gNMI subscribe operation.
32 | OPSubscribe OPType = "subscribe"
33 | )
34 |
35 | // GNMITest is top-level model of gNMI test.
36 | type GNMITest struct {
37 | // Name is the test name.
38 | Name string `json:"name"`
39 | // Description is the detail description of this test.
40 | Description string `json:"description"`
41 | // GNMITestCase is the list of test cases to run in this test.
42 | GNMITestCase []*TestCase `json:"test_cases"`
43 | }
44 |
45 | // TestCase describes a gNMI test cases.
46 | type TestCase struct {
47 | // Name is the test case name.
48 | Name string `json:"name"`
49 | // Description is the detail description of this test case.
50 | Description string `json:"description"`
51 | // Model is used to construct the UseModels property in gNMI requests.
52 | // If not specified, all gNMI requests are sent without UseModels.
53 | Model *ModelData `json:"model"`
54 | // OPs contains a list of operations need to be processed in this test case.
55 | // All operations are processed in one single gNMI message.
56 | OPs []*Operation `json:"ops"`
57 | }
58 |
59 | // ModelData describes the OpenConfig model used in this test case.
60 | type ModelData struct {
61 | Name string `json:"name"`
62 | Organization string `json:"organization"`
63 | Version string `json:"version"`
64 | }
65 |
66 | // Operation represents a gNMI operation.
67 | type Operation struct {
68 | // Type is the gNMI operation type.
69 | Type OPType `json:"type"`
70 | // Path is the xPath of the target field/branch.
71 | Path string `json:"path"`
72 | // StatePath is the xPath of the corresponding state field/branch.
73 | // If specified, testkit will verify the state update.
74 | StatePath string `json:"state_path"`
75 | // Val is the string format of the desired value.
76 | // Val should be unset for gNMI delete operation.
77 | // Supported types:
78 | // Integer: "1", "2"
79 | // Float: "1.5", "2.4"
80 | // String: "abc", "defg"
81 | // Boolean: "true", "false"
82 | // IETF JSON from file: "@ap_config.json"
83 | Val string `json:"val"`
84 | }
85 |
86 | // TestResult contains the result of one gNMI test.
87 | type TestResult struct {
88 | // Name is the name of the test.
89 | Name string
90 | // PassedNum is the number of passed test cases.
91 | PassedNum int
92 | // FailedNum is the number of failed test cases.
93 | FailedNum int
94 | // Details contains detailed test results of each test cases.
95 | Details []*TestCaseResult
96 | }
97 |
98 | // TestCaseResult contains the result of one gNMI test case.
99 | type TestCaseResult struct {
100 | // Name is the test case name.
101 | Name string
102 | // Err is the error detected in this test case. nil if test passed.
103 | Err error
104 | }
105 |
--------------------------------------------------------------------------------
/emulator/emulator.py:
--------------------------------------------------------------------------------
1 | """ Copyright 2018 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | """
15 |
16 | """Emulator for link022. Refer to the README.md file for instructions.
17 | """
18 |
19 | import argparse
20 | import netaddr
21 | import logging
22 |
23 | import mininet.net
24 | import mininet.node
25 | import mininet.cli
26 |
27 |
28 | FLAGS = None
29 | logging.basicConfig(filename='/tmp/link022_emulator.log', level=logging.INFO)
30 | logger = logging.getLogger()
31 |
32 |
33 | def set_flags():
34 | """Set the global FLAGS."""
35 | global FLAGS
36 | parser = argparse.ArgumentParser()
37 | parser.add_argument(
38 | '--target_cmd',
39 | help='Command line to start the target.')
40 | FLAGS = parser.parse_args()
41 |
42 |
43 | TARGET_NAME = 'target'
44 | CONTROLLER_NAME = 'ctrlr'
45 | DUMMY_NAME = 'dummy'
46 |
47 |
48 | def get_ip_spec(addr, subnet=None):
49 | """Get the IP address with the subnet prefix length.
50 |
51 | Args:
52 | addr: network.addr object
53 | subnet: network.net object
54 |
55 | Returns:
56 | A string of the IP address with prefix length.
57 |
58 | Raises:
59 | Exception: if ip not in subnet
60 | """
61 | if subnet is None:
62 | if addr.version == 6:
63 | ip_spec = str(addr) + '/128'
64 | else:
65 | ip_spec = str(addr) + '/32'
66 | elif addr in subnet:
67 | ip_spec = '%s/%s' % (addr, subnet.prefixlen)
68 | else:
69 | raise Exception('ip %s is not in subnet %s' % (addr, subnet))
70 | return ip_spec
71 |
72 |
73 | class Emulator(object):
74 | """Link022 emulator."""
75 |
76 | def __init__(self):
77 | self._net = None
78 | self._ctrlr = None
79 | self._target = None
80 | self._target_popen = None
81 |
82 | def start(self):
83 | self._start_topo()
84 | logger.info('Emulator started.')
85 | mininet.cli.CLI(self._net)
86 |
87 | def _start_topo(self):
88 | """Create an empty network and add nodes to it.
89 | """
90 |
91 | subnet = netaddr.IPNetwork('10.0.0.0/24')
92 | hosts_iter = subnet.iter_hosts()
93 | self._net = mininet.net.Mininet(controller=None)
94 |
95 | self._net.addHost(TARGET_NAME)
96 | self._net.addHost(CONTROLLER_NAME)
97 | # We add a dummy host to create the eth and wlan interfaces for the
98 | # Target
99 | self._net.addHost(DUMMY_NAME)
100 | params1 = {'ip': get_ip_spec(hosts_iter.next(), subnet)}
101 | params2 = {'ip': get_ip_spec(hosts_iter.next(), subnet)}
102 | self._net.addLink(TARGET_NAME, CONTROLLER_NAME,
103 | params1=params1, params2=params2)
104 | self._net.addLink(TARGET_NAME, DUMMY_NAME)
105 | self._net.addLink(TARGET_NAME, DUMMY_NAME)
106 |
107 | self._target = self._net[TARGET_NAME]
108 | self._ctrlr = self._net[CONTROLLER_NAME]
109 |
110 | self._net.start()
111 | logger.info('Running Link022 target command: %s', FLAGS.target_cmd)
112 | self._target_popen = self._target.popen(FLAGS.target_cmd)
113 |
114 | def cleanup(self):
115 | """Clean up emulator."""
116 | if self._target_popen:
117 | self._target_popen.kill()
118 | self._target_popen = None
119 | if self._net:
120 | self._net.stop()
121 | logger.info('Emulator cleaned up.')
122 |
123 | if __name__ == '__main__':
124 | set_flags()
125 | emulator = Emulator()
126 | try:
127 | emulator.start()
128 | finally:
129 | emulator.cleanup()
130 |
131 |
--------------------------------------------------------------------------------
/agent/gnmi/handler.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package gnmi
17 |
18 | import (
19 | "errors"
20 | "fmt"
21 | "time"
22 |
23 | "github.com/google/link022/agent/context"
24 | "github.com/google/link022/agent/service"
25 | "github.com/google/link022/agent/syscmd"
26 | "github.com/google/link022/agent/util/ocutil"
27 | "github.com/google/link022/generated/ocstruct"
28 | "github.com/openconfig/ygot/ygot"
29 |
30 | log "github.com/golang/glog"
31 | )
32 |
33 | var (
34 | cmdRunner = syscmd.Runner()
35 | )
36 |
37 | // handleSet is the callback function of the GNMI SET call.
38 | // It is triggered by the GNMI server.
39 | func handleSet(updatedConfig ygot.ValidatedGoStruct) (err error) {
40 | // Recover the panic and return error.
41 | defer func() {
42 | if r := recover(); r != nil {
43 | err = fmt.Errorf("panic detected when handling updated config: %v", r)
44 | }
45 | }()
46 |
47 | return handleSetInternal(updatedConfig)
48 | }
49 |
50 | func handleSetInternal(updatedConfig ygot.ValidatedGoStruct) error {
51 | // TODO: Handle delta change. Currently the GNMI server only supports replacing root.
52 | officeAPs, ok := updatedConfig.(*ocstruct.Device)
53 | if !ok {
54 | return errors.New("new configuration has invalid type")
55 | }
56 |
57 | configString, err := ygot.EmitJSON(officeAPs, &ygot.EmitJSONConfig{
58 | Format: ygot.RFC7951,
59 | Indent: " ",
60 | RFC7951Config: &ygot.RFC7951JSONConfig{
61 | AppendModuleName: false,
62 | },
63 | })
64 | if err != nil {
65 | return err
66 | }
67 | log.Infof("Received a new configuration:\n%v\n", configString)
68 |
69 | // TODO: Validate the OpenConfig module.
70 | deviceConfig := context.GetDeviceConfig()
71 |
72 | // Fetch the target AP configuration.
73 | apConfig := ocutil.FindAPConfig(officeAPs, deviceConfig.Hostname)
74 | if apConfig == nil {
75 | return fmt.Errorf("not found the configuration for this AP (hostname = %s)", deviceConfig.Hostname)
76 | }
77 |
78 | // Check and clean up the existing configuration.
79 | var changedVLANIDs []int
80 | existingVLANIDs, err := cmdRunner.VLANOnIntf(deviceConfig.ETHINTFName)
81 | if err != nil {
82 | return fmt.Errorf("unable to fetch the existing VLAN with error (%v), may need to reboot the device.", err)
83 | }
84 |
85 | resetIntf := false
86 | newVLANIDs := ocutil.VLANIDs(apConfig)
87 | if ocutil.VLANChanged(existingVLANIDs, newVLANIDs) {
88 | log.Infof("VLAN changes (%v -> %v) on interface %s.", existingVLANIDs, newVLANIDs, deviceConfig.ETHINTFName)
89 | changedVLANIDs = existingVLANIDs
90 | resetIntf = true
91 | } else {
92 | log.Infof("No VLAN change on interface %s.", deviceConfig.ETHINTFName)
93 | }
94 |
95 | // Clean up the existing configuration.
96 | service.CleanupConfig(deviceConfig.ETHINTFName, changedVLANIDs)
97 |
98 | // Wait for link to be available again.
99 | time.Sleep(5 * time.Second)
100 |
101 | // Process the incoming configuration.
102 | if err = service.ApplyConfig(apConfig, officeAPs.Gasket, resetIntf, deviceConfig.ETHINTFName,
103 | deviceConfig.WLANINTFName); err != nil {
104 | return err
105 | }
106 | log.Info("Device configuration succeeded.")
107 |
108 | // Save the succeeded config file.
109 | if err := syscmd.SaveToFile(runFolder, apConfigFileName, configString); err != nil {
110 | return err
111 | }
112 | log.Info("Saved the configuration to file.")
113 | return nil
114 | }
115 |
--------------------------------------------------------------------------------
/demo/util/server.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 | # A utility script to start services on another machine, from which the operator
17 | # can manage the link022 device.
18 | # A typical setup is to create a peer to peer ethernet link between link022 and
19 | # the service machine.
20 | # Interface connected to the link022 host
21 | source servers.conf
22 | INTF=eth0
23 | OUT_INTF=wlan0 # Interface to connect to the Internet
24 | CTRL_INTF=eth1 # Interface to connect to a controlling machine, ssh is enabled here.
25 | GWIP=192.168.11.1
26 |
27 | sudo ip netns add ${NS}
28 | sudo ip link set dev ${INTF} netns ${NS}
29 | sudo ip netns exec ${NS} ip addr add ${GWIP}/24 dev ${INTF}
30 | sudo ip netns exec ${NS} ip link set dev ${INTF} up
31 |
32 | # Start DHCP
33 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
34 | -F set:s0,192.168.11.2,192.168.11.10 \
35 | -O tag:s0,3,192.168.11.1 -O option:dns-server,8.8.8.8 -I lo -z \
36 | -l /tmp/link022.leases -8 /tmp/link022.dhcp.log -i ${INTF} -a ${GWIP} --conf-file= &
37 |
38 | ########### Get Internet access for link022
39 | TO_DEF=to_def
40 | TO_NS=to_${NS}
41 |
42 | # enable forwarding
43 | sudo sysctl net.ipv4.ip_forward=1
44 | sudo ip netns exec ${NS} sysctl net.ipv4.ip_forward=1
45 |
46 | # create veth pair
47 | sudo ip link add name ${TO_NS} type veth peer name ${TO_DEF} netns ${NS}
48 | # configure interfaces and routes
49 | sudo ip addr add 192.168.22.1/30 dev ${TO_NS}
50 | sudo ip link set ${TO_NS} up
51 | # sudo ip route add 192.168.22.0/30 dev ${TO_NS}
52 | sudo ip netns exec ${NS} ip addr add 192.168.22.2/30 dev ${TO_DEF}
53 | sudo ip netns exec ${NS} ip link set ${TO_DEF} up
54 | sudo ip netns exec ${NS} ip route add default via 192.168.22.1
55 | # NAT in LK22
56 | sudo ip netns exec ${NS} iptables -t nat -F
57 | sudo ip netns exec ${NS} iptables -t nat -A POSTROUTING -o ${TO_DEF} -j MASQUERADE
58 | # NAT in default
59 | sudo iptables -P FORWARD DROP
60 | sudo iptables -F FORWARD
61 | # Assuming the host does not have other NAT rules.
62 | sudo iptables -t nat -F
63 | sudo iptables -t nat -A POSTROUTING -s 192.168.22.0/30 -o ${OUT_INTF} -j MASQUERADE
64 | sudo iptables -A FORWARD -i ${OUT_INTF} -o ${TO_NS} -j ACCEPT
65 | sudo iptables -A FORWARD -i ${TO_NS} -o ${OUT_INTF} -j ACCEPT
66 |
67 | ########### Adding vlans
68 | function add_vlan {
69 | vlan_name=$1
70 | vlan_id=$2
71 | vlan_net=$3
72 | vlan_gw=${vlan_net}.1
73 | sudo ip netns exec ${NS} ip link add link ${INTF} name ${vlan_name} type vlan id ${vlan_id}
74 | sudo ip netns exec ${NS} ip addr add ${vlan_gw}/24 dev ${vlan_name}
75 | sudo ip netns exec ${NS} ip link set dev ${vlan_name} up
76 |
77 | # Start DHCP
78 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
79 | -F set:s0,${vlan_net}.2,${vlan_net}.100 \
80 | -O tag:s0,3,${vlan_gw} -O option:dns-server,8.8.8.8 -I lo -z \
81 | -l /tmp/link022.${vlan_name}.leases -8 /tmp/link022.${vlan_name}.dhcp.log -i ${vlan_name} -a ${vlan_gw} --conf-file= &
82 | }
83 | add_vlan guest 200 192.168.33
84 | add_vlan auth 300 192.168.44
85 |
86 | RADIUS_PATH=../radius/freeradius
87 | sudo ip netns exec ${NS} freeradius -X -d ${RADIUS_PATH} > /tmp/radius.log &
88 |
89 | ########### Set up controlling machine interface
90 | CTRL_NET=192.168.55
91 | CTRL_GW=${CTRL_NET}.1
92 | sudo ip addr add ${CTRL_GW}/24 dev ${CTRL_INTF}
93 | sudo ip link set dev ${CTRL_INTF} up
94 | # Start DHCP
95 | sudo dnsmasq --no-ping -p 0 -k \
96 | -F set:s0,${CTRL_NET}.2,${CTRL_NET}.10 \
97 | -O option:dns-server,8.8.8.8 -I lo -z \
98 | -l /tmp/link022.ctrl.leases -8 /tmp/link022.dhcp.ctrl.log -i ${CTRL_INTF} -a ${CTRL_GW} --conf-file= &
99 |
--------------------------------------------------------------------------------
/agent/agent.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // The agent program is the link022 AP agent.
17 | // It converts the target device to an Link022 AP and runs in the background as
18 | // a management daemon.
19 | package main
20 |
21 | import (
22 | //ctx "context"
23 | "flag"
24 | "fmt"
25 | "net"
26 | "os"
27 |
28 | "github.com/google/gnxi/utils/credentials"
29 | "github.com/google/link022/agent/context"
30 | "github.com/google/link022/agent/controller"
31 | "github.com/google/link022/agent/gnmi"
32 | //"github.com/google/link022/agent/monitoring"
33 | "github.com/google/link022/agent/syscmd"
34 | "google.golang.org/grpc"
35 | "google.golang.org/grpc/reflection"
36 |
37 | log "github.com/golang/glog"
38 | pb "github.com/openconfig/gnmi/proto/gnmi"
39 | )
40 |
41 | var (
42 | ethINTFName = flag.String("eth_intf_name", "eth0", "The management network interface on this device.")
43 | wlanINTFName = flag.String("wlan_intf_name", "wlan0", "The WLAN interface on this device for AP radio.")
44 | gnmiPort = flag.Int("gnmi_port", 10162, "The port GNMI server listening on.")
45 | controllerAddr = flag.String("controller_address", "", "The WiFi Controller of this device.")
46 |
47 | cmdRunner = syscmd.Runner()
48 | )
49 |
50 | func main() {
51 | flag.Parse()
52 | log.Info("Link022 agent started.")
53 |
54 | deviceConfig := context.GetDeviceConfig()
55 | // Load hostname
56 | hostname, err := os.Hostname()
57 | if err != nil {
58 | log.Exit("Failed to load the device hostname.")
59 | }
60 | deviceConfig.Hostname = hostname
61 | log.Infof("Hostname = %s.", hostname)
62 |
63 | // Load AP network interface configuration.
64 | deviceConfig.ETHINTFName = *ethINTFName
65 | deviceConfig.WLANINTFName = *wlanINTFName
66 | log.Infof("Eth interface = %s. WLAN interface = %s.", *ethINTFName, *wlanINTFName)
67 |
68 | // Get gNMI server address.
69 | deviceIPv4, err := cmdRunner.DeviceIPv4()
70 | if err != nil {
71 | log.Exit("Failed to load the device IPv4 address.")
72 | }
73 | gNMIServerAddr := fmt.Sprintf("%s:%d", deviceIPv4, *gnmiPort)
74 | deviceConfig.GNMIServerAddr = gNMIServerAddr
75 |
76 | // Load controlle Info.
77 | deviceConfig.ControllerAddr = *controllerAddr
78 | if *controllerAddr != "" {
79 | log.Infof("AP controller = %s", *controllerAddr)
80 | go controller.Connect()
81 | } else {
82 | log.Info("No AP controller assigned.")
83 | }
84 |
85 | // Create GNMI server.
86 | gnmiServer, err := gnmi.NewServer()
87 | if err != nil {
88 | log.Exitf("Failed to create the GNMI server. Error: %v.", err)
89 | }
90 |
91 | // Start a goroutine to collect states periodically
92 | // Monitoing service is disabled because it has a bug that causes internal OpenConfig model being invalid.
93 | //backgroundContext := ctx.Background()
94 | //go monitoring.UpdateDeviceStatus(backgroundContext, gnmiServer)
95 |
96 | // Start the GNMI server.
97 | var opts []grpc.ServerOption
98 | if *controllerAddr == "" {
99 | // Add credential check if no controller specified.
100 | opts = credentials.ServerCredentials()
101 | }
102 | g := grpc.NewServer(opts...)
103 | pb.RegisterGNMIServer(g, gnmiServer)
104 | reflection.Register(g)
105 | listen, err := net.Listen("tcp", gNMIServerAddr)
106 | if err != nil {
107 | log.Exitf("Failed to listen on %s. Error: %v.", gNMIServerAddr, err)
108 | }
109 |
110 | log.Infof("Running GNMI server. Listen on %s.", gNMIServerAddr)
111 | if err := g.Serve(listen); err != nil {
112 | log.Exitf("Failed to run GNMI server on %s. Error: %v.", gNMIServerAddr, err)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/testkit/gnmitest/worker_test.go:
--------------------------------------------------------------------------------
1 | package gnmitest
2 |
3 | import (
4 | "net"
5 | "reflect"
6 | "testing"
7 | "time"
8 |
9 | "github.com/google/gnxi/gnmi"
10 | "github.com/google/link022/generated/ocstruct"
11 | "github.com/google/link022/testkit/common"
12 | pb "github.com/openconfig/gnmi/proto/gnmi"
13 | "github.com/openconfig/ygot/ygot"
14 | "google.golang.org/grpc"
15 | )
16 |
17 | func fakeHandleSet(ygot.ValidatedGoStruct) error {
18 | return nil
19 | }
20 |
21 | func startMockServer(t *testing.T) (string, func()) {
22 | // Create the GNMI server.
23 | model := gnmi.NewModel([]*pb.ModelData{{
24 | Name: "openconfig-access-points",
25 | Organization: "OpenConfig working group",
26 | Version: "0.1.0",
27 | }},
28 | reflect.TypeOf((*ocstruct.Device)(nil)),
29 | ocstruct.SchemaTree["Device"],
30 | ocstruct.Unmarshal,
31 | ocstruct.ΛEnum)
32 |
33 | s, err := gnmi.NewServer(model,
34 | nil,
35 | fakeHandleSet)
36 | if err != nil {
37 | t.Fatalf("Failed to create gNMI server: %v", err)
38 | }
39 |
40 | g := grpc.NewServer()
41 | pb.RegisterGNMIServer(g, s)
42 |
43 | lis, err := net.Listen("tcp", "")
44 | if err != nil {
45 | t.Fatalf("Failed in net.Listen: %v", err)
46 | }
47 |
48 | go g.Serve(lis)
49 | return lis.Addr().String(), func() {
50 | lis.Close()
51 | }
52 | }
53 |
54 | func TestRunTest(t *testing.T) {
55 | // Start mock gNMI server.
56 | serverAddr, teardown := startMockServer(t)
57 | defer teardown()
58 |
59 | // Create gNMI client.
60 | conn, err := grpc.Dial(serverAddr, grpc.WithInsecure())
61 | if err != nil {
62 | t.Fatalf("Dialing to %q failed: %v", serverAddr, err)
63 | }
64 | defer conn.Close()
65 | client := pb.NewGNMIClient(conn)
66 |
67 | // Define test cases.
68 | testCases := []struct {
69 | targetTest *common.TestCase
70 | succeeded bool
71 | }{
72 | {
73 | targetTest: &common.TestCase{
74 | Name: "push all configurations",
75 | OPs: []*common.Operation{
76 | {
77 | Type: common.OPReplace,
78 | Path: "/access-points/access-point[hostname=link022-pi-ap]",
79 | Val: "@testfile/ap_config.json",
80 | },
81 | },
82 | },
83 | succeeded: true,
84 | },
85 | {
86 | targetTest: &common.TestCase{
87 | Name: "update radio channel",
88 | OPs: []*common.Operation{
89 | {
90 | Type: common.OPUpdate,
91 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel",
92 | Val: "11",
93 | },
94 | {
95 | Type: common.OPUpdate,
96 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
97 | Val: "20",
98 | },
99 | },
100 | },
101 | succeeded: true,
102 | },
103 | {
104 | targetTest: &common.TestCase{
105 | Name: "get radio config",
106 | OPs: []*common.Operation{
107 | {
108 | Type: common.OPGet,
109 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel",
110 | Val: "11",
111 | },
112 | {
113 | Type: common.OPGet,
114 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
115 | Val: "20",
116 | },
117 | },
118 | },
119 | succeeded: true,
120 | },
121 | {
122 | targetTest: &common.TestCase{
123 | Name: "invalid config",
124 | OPs: []*common.Operation{
125 | {
126 | Type: common.OPGet,
127 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel",
128 | Val: "11",
129 | },
130 | {
131 | Type: common.OPUpdate,
132 | Path: "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
133 | Val: "20",
134 | },
135 | },
136 | },
137 | succeeded: false,
138 | },
139 | }
140 |
141 | // Run test cases.
142 | for _, testCase := range testCases {
143 | err := RunTest(client, testCase.targetTest, 10*time.Second, 10*time.Second)
144 | testResult := err == nil
145 | if testResult != testCase.succeeded {
146 | t.Errorf("[%s] incorrect test result, actual = %v [%v], expected = %v", testCase.targetTest.Name, testResult, err, testCase.succeeded)
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/demo/faucet_ovs/setup.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 |
17 | NS=gw
18 | OUT_INTF=wlx00c0ca902f38 # Interface to connect to the Internet
19 | CTRL_INTF=eth1 # Interface to connect to a controlling machine, ssh is enabled here.
20 | GWIP=192.168.11.1
21 |
22 |
23 | # Create veth pair, one side for ovs, the other side for the host itself
24 | INTF_ON_OVS=ovs2${NS}
25 | INTF=to_ovs # inside the host
26 | sudo ip netns add ${NS}
27 | sudo ip link add name ${INTF_ON_OVS} type veth peer name ${INTF} netns ${NS}
28 |
29 | sudo ip link set dev ${INTF_ON_OVS} up
30 | sudo ip netns exec ${NS} ip addr add ${GWIP}/24 dev ${INTF}
31 | sudo ip netns exec ${NS} ip link set dev ${INTF} up
32 | sudo ip netns exec ${NS} ip link set dev lo up
33 |
34 | # Start DHCP
35 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
36 | -F set:s0,192.168.11.2,192.168.11.10 \
37 | -O tag:s0,3,192.168.11.1 -O option:dns-server,8.8.8.8 -I lo -z \
38 | -l /tmp/link022.leases -8 /tmp/link022.dhcp.log -i ${INTF} -a ${GWIP} --conf-file= &
39 |
40 | ########### Get Internet access for the NS
41 | TO_DEF=to_def
42 | TO_NS=def2${NS}
43 |
44 | # enable forwarding
45 | sudo sysctl net.ipv4.ip_forward=1
46 | sudo ip netns exec ${NS} sysctl net.ipv4.ip_forward=1
47 |
48 | # create veth pair
49 | sudo ip link add name ${TO_NS} type veth peer name ${TO_DEF} netns ${NS}
50 | # configure interfaces and routes
51 | sudo ip addr add 192.168.22.1/30 dev ${TO_NS}
52 | sudo ip link set ${TO_NS} up
53 | # sudo ip route add 192.168.22.0/30 dev ${TO_NS}
54 | sudo ip netns exec ${NS} ip addr add 192.168.22.2/30 dev ${TO_DEF}
55 | sudo ip netns exec ${NS} ip link set ${TO_DEF} up
56 | sudo ip netns exec ${NS} ip route add default via 192.168.22.1
57 | # NAT in LK22
58 | sudo ip netns exec ${NS} iptables -t nat -F
59 | sudo ip netns exec ${NS} iptables -t nat -A POSTROUTING -o ${TO_DEF} -j MASQUERADE
60 | # NAT in default
61 | sudo iptables -P FORWARD DROP
62 | sudo iptables -F FORWARD
63 | # Assuming the host does not have other NAT rules.
64 | sudo iptables -t nat -F
65 | sudo iptables -t nat -A POSTROUTING -s 192.168.22.0/30 -o ${OUT_INTF} -j MASQUERADE
66 | sudo iptables -A FORWARD -i ${OUT_INTF} -o ${TO_NS} -j ACCEPT
67 | sudo iptables -A FORWARD -i ${TO_NS} -o ${OUT_INTF} -j ACCEPT
68 |
69 | ########### Adding vlans
70 | function add_vlan {
71 | vlan_name=$1
72 | vlan_id=$2
73 | vlan_net=$3
74 | vlan_gw=${vlan_net}.1
75 | sudo ip netns exec ${NS} ip link add link ${INTF} name ${vlan_name} type vlan id ${vlan_id}
76 | sudo ip netns exec ${NS} ip addr add ${vlan_gw}/24 dev ${vlan_name}
77 | sudo ip netns exec ${NS} ip link set dev ${vlan_name} up
78 |
79 | # Start DHCP
80 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
81 | -F set:s0,${vlan_net}.2,${vlan_net}.100 \
82 | -O tag:s0,3,${vlan_gw} -O option:dns-server,8.8.8.8 -I lo -z \
83 | -l /tmp/link022.${vlan_name}.leases -8 /tmp/link022.${vlan_name}.dhcp.log -i ${vlan_name} -a ${vlan_gw} --conf-file= &
84 | }
85 | add_vlan guest 200 192.168.33
86 | add_vlan auth 300 192.168.44
87 |
88 | RADIUS_PATH=../radius/freeradius
89 | sudo ip netns exec ${NS} freeradius -X -d ${RADIUS_PATH} > /tmp/${NS}_radius.log &
90 |
91 | #############create ovs
92 | sudo ovs-vsctl add-br br0 \
93 | -- set bridge br0 other-config:datapath-id=0000000000000001 \
94 | -- set bridge br0 other-config:disable-in-band=true \
95 | -- set bridge br0 fail_mode=secure \
96 | -- add-port br0 ${INTF_ON_OVS} -- set interface ${INTF_ON_OVS} ofport_request=1 \
97 | -- add-port br0 enp2s0 -- set interface enp2s0 ofport_request=2 \
98 | -- add-port br0 enp3s0 -- set interface enp3s0 ofport_request=3 \
99 | -- add-port br0 enp4s0 -- set interface enp4s0 ofport_request=4 \
100 | -- add-port br0 enp5s0 -- set interface enp5s0 ofport_request=5 \
101 | -- set-controller br0 tcp:127.0.0.1:6653 tcp:127.0.0.1:6654
--------------------------------------------------------------------------------
/agent/service/interface.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package service
17 |
18 | import (
19 | "fmt"
20 |
21 | log "github.com/golang/glog"
22 | )
23 |
24 | // configEthIntf configures the network interfaces on this device based on the given configuration.
25 | func configEthIntf(ethIntfName string, vlanIDs []int) error {
26 | log.Infof("Configuring interface %v. VLAN: %v.", ethIntfName, vlanIDs)
27 | vlanIntfNames := make(map[int]string) // VLAN ID -> VLAN Intf name
28 | for _, vlanID := range vlanIDs {
29 | // Add VLAN interface
30 | vlanIntfName, err := cmdRunner.CreateVLAN(ethIntfName, vlanID)
31 | if err != nil {
32 | return err
33 | }
34 | vlanIntfNames[vlanID] = vlanIntfName
35 | }
36 |
37 | // Restart eth interface.
38 | if err := cmdRunner.RestartIntf(ethIntfName); err != nil {
39 | return err
40 | }
41 |
42 | for vlanID, vlanIntfName := range vlanIntfNames {
43 | // Wipe out IP on VLAN interface.
44 | if err := cmdRunner.WipeOutIntfIP(vlanIntfName); err != nil {
45 | return err
46 | }
47 |
48 | // Add a network bridge
49 | bridgeName := getBridgeName(vlanID)
50 | if err := cmdRunner.CreateBridge(bridgeName); err != nil {
51 | return err
52 | }
53 |
54 | // Link VLAN intf to bridge
55 | if err := cmdRunner.AddBridgeIntf(bridgeName, vlanIntfName); err != nil {
56 | return err
57 | }
58 |
59 | // Bring up bridge
60 | if err := cmdRunner.BringUpIntf(bridgeName); err != nil {
61 | return err
62 | }
63 | }
64 |
65 | log.Infof("Configured interface %v.", ethIntfName)
66 | return nil
67 | }
68 |
69 | // cleanupEthIntf cleans up the network interfaces on this device based on the given configuration.
70 | // It goes through all cleanup steps even if some failures are detected, and returns all errors.
71 | func cleanupEthIntf(ethIntfName string, vlanIDs []int) []error {
72 | log.Infof("Cleaning up interface %s. VLAN: %d.", ethIntfName, vlanIDs)
73 | var errs []error
74 |
75 | for _, vlanID := range vlanIDs {
76 | // Delete VLAN interface.
77 | if err := cmdRunner.DeleteVLAN(ethIntfName, vlanID); err != nil {
78 | errs = append(errs, err)
79 | }
80 |
81 | // Turn down network bridge.
82 | bridgeName := getBridgeName(vlanID)
83 | if err := cmdRunner.TurnDownIntf(bridgeName); err != nil {
84 | errs = append(errs, err)
85 | }
86 |
87 | // Remove network bridge.
88 | if err := cmdRunner.DeleteBridge(bridgeName); err != nil {
89 | errs = append(errs, err)
90 | }
91 | }
92 |
93 | log.Infof("Cleaned up interface %s. Number of errors = %d.", ethIntfName, len(errs))
94 | return errs
95 | }
96 |
97 | // configWLANIntf configures the network interfaces to make it work with hostapd.
98 | func configWLANIntf(wlanIntfName string) error {
99 | log.Infof("Configuring WLAN interface %v.", wlanIntfName)
100 |
101 | // Wipe out IP on WLAN interface.
102 | if err := cmdRunner.WipeOutIntfIP(wlanIntfName); err != nil {
103 | return err
104 | }
105 |
106 | if err := cmdRunner.TurnDownIntf(wlanIntfName); err != nil {
107 | return err
108 | }
109 |
110 | wlanIntfMAC, err := cmdRunner.IntfMAC(wlanIntfName)
111 | if err != nil {
112 | return err
113 | }
114 |
115 | // Change the MAC address of the wireless interface to avoid conflict with other devices.
116 | updatedMAC := generateWLANIntfMAC(wlanIntfMAC)
117 | if err = cmdRunner.UpdateIntfMAC(wlanIntfName, updatedMAC); err != nil {
118 | return err
119 | }
120 |
121 | if err = cmdRunner.BringUpIntf(wlanIntfName); err != nil {
122 | return err
123 | }
124 |
125 | log.Infof("Configured WLAN interface %v.", wlanIntfName)
126 | return nil
127 | }
128 |
129 | func getBridgeName(vlanID int) string {
130 | return fmt.Sprintf("br_%d", vlanID)
131 | }
132 |
133 | func generateWLANIntfMAC(originalMAC string) string {
134 | return fmt.Sprintf("02%s0", originalMAC[2:len(originalMAC)-1])
135 | }
136 |
--------------------------------------------------------------------------------
/agent/gnmi/server.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package gnmi contains GNMI server and related methods.
17 | package gnmi
18 |
19 | import (
20 | "errors"
21 | "fmt"
22 | "io/ioutil"
23 | "os"
24 | "path"
25 | "reflect"
26 | "strings"
27 |
28 | "github.com/google/gnxi/gnmi"
29 | "github.com/google/link022/generated/ocstruct"
30 |
31 | log "github.com/golang/glog"
32 | pb "github.com/openconfig/gnmi/proto/gnmi"
33 | "github.com/openconfig/ygot/ygot"
34 | "github.com/openconfig/ygot/ytypes"
35 | )
36 |
37 | const (
38 | runFolder = "/var/run/link022"
39 | apConfigFileName = "link022.conf"
40 | )
41 |
42 | var (
43 | // link022ModelData is a list of models supported in this GNMI server.
44 | link022ModelData = []*pb.ModelData{{
45 | Name: "openconfig-access-points",
46 | Organization: "OpenConfig working group",
47 | Version: "0.1.0",
48 | }}
49 | )
50 |
51 | // Server is a GNMI server.
52 | type Server struct {
53 | *gnmi.Server
54 | }
55 |
56 | type serverStateOperator func(path *pb.Path, val interface{}, config ygot.ValidatedGoStruct) error
57 |
58 | // NewServer creates a GNMI server.
59 | func NewServer() (*Server, error) {
60 | // Load existing config
61 | initConfigContent, err := loadExistingConfigContent()
62 | if err != nil {
63 | log.Errorf("Failed to load the existing configuration. Error: %v.", err)
64 | initConfigContent = nil
65 | }
66 |
67 | // Create the GNMI server.
68 | model := gnmi.NewModel(link022ModelData,
69 | reflect.TypeOf((*ocstruct.Device)(nil)),
70 | ocstruct.SchemaTree["Device"],
71 | ocstruct.Unmarshal,
72 | ocstruct.ΛEnum)
73 |
74 | s, err := gnmi.NewServer(model,
75 | initConfigContent,
76 | handleSet)
77 | if err != nil {
78 | return nil, err
79 | }
80 |
81 | gnmiServer := &Server{s}
82 | log.Info("GNMI server created.")
83 | return gnmiServer, nil
84 | }
85 |
86 | func loadExistingConfigContent() ([]byte, error) {
87 | existingConfigFilePath := path.Join(runFolder, apConfigFileName)
88 |
89 | if _, err := os.Stat(existingConfigFilePath); os.IsNotExist(err) {
90 | log.Info("No existing configuration found.")
91 | return nil, nil
92 | }
93 |
94 | existingConfigContent, err := ioutil.ReadFile(existingConfigFilePath)
95 | if err != nil {
96 | return nil, err
97 | }
98 |
99 | log.Info("Loaded existing configuration.")
100 | return existingConfigContent, nil
101 | }
102 |
103 | // GNXIStateOptGenerator decorate a given function to a gNXI state operator function
104 | func GNXIStateOptGenerator(path *pb.Path, val interface{}, stateOpt serverStateOperator) func(config ygot.ValidatedGoStruct) error {
105 | fp := func(config ygot.ValidatedGoStruct) error {
106 | return stateOpt(path, val, config)
107 | }
108 | return fp
109 | }
110 |
111 | // InternalUpdateState update state node in Server config. When updating,
112 | // call server's InternalUpdate method and send this function as parameter.
113 | // The type of val must exactly matchs node's type.
114 | func InternalUpdateState(path *pb.Path, val interface{}, config ygot.ValidatedGoStruct) error {
115 | checkStateNode := false
116 | for _, i := range path.GetElem() {
117 | if strings.Compare(i.GetName(), "state") == 0 {
118 | checkStateNode = true
119 | break
120 | }
121 | }
122 | if !checkStateNode {
123 | log.Error("failed update state: target node is not state node")
124 | return errors.New("target node is not state node")
125 | }
126 |
127 | node, _, err := ytypes.GetOrCreateNode(ocstruct.SchemaTree["Device"], config, path)
128 | if err != nil {
129 | return fmt.Errorf("failed retrive target node: %v", err)
130 | }
131 |
132 | if reflect.ValueOf(node).Kind() != reflect.Ptr {
133 | return fmt.Errorf("type of node is %v, not go struct pointer", reflect.ValueOf(node).Kind())
134 | }
135 | reflect.ValueOf(node).Elem().Set(reflect.ValueOf(val))
136 | return nil
137 | }
138 |
--------------------------------------------------------------------------------
/agent/util/ocutil/openconfig.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package ocutil contains helper functions related to OpenConfig models.
17 | package ocutil
18 |
19 | import (
20 | "reflect"
21 | "sort"
22 |
23 | "github.com/google/link022/generated/ocstruct"
24 | )
25 |
26 | // FindAPConfig finds the configuration of the AP with a specific hostname.
27 | // It returns nil if not matching AP found.
28 | func FindAPConfig(apConfigs *ocstruct.Device, hostname string) *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint {
29 | if apConfigs.AccessPoints == nil {
30 | return nil
31 | }
32 |
33 | apConfig, ok := apConfigs.AccessPoints.AccessPoint[hostname]
34 | if !ok {
35 | return nil
36 | }
37 | return apConfig
38 | }
39 |
40 | // VLANChanged checkes whether there is any difference between the given two VLAN ID lists.
41 | func VLANChanged(existingVLANIDs, updatedVLANIDs []int) bool {
42 | sort.Ints(existingVLANIDs)
43 | sort.Ints(updatedVLANIDs)
44 | return !reflect.DeepEqual(existingVLANIDs, updatedVLANIDs)
45 | }
46 |
47 | // VLANIDs fetches the ID of all VLANs appears in the given office configuration.
48 | func VLANIDs(apConfig *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint) []int {
49 | vlanIDs := []int{}
50 |
51 | if apConfig == nil {
52 | return vlanIDs
53 | }
54 |
55 | wlans := apConfig.Ssids
56 | if wlans == nil || len(wlans.Ssid) == 0 {
57 | return vlanIDs
58 | }
59 |
60 | for _, wlan := range wlans.Ssid {
61 | vlanIDs = append(vlanIDs, int(*wlan.Config.DefaultVlan))
62 | }
63 |
64 | return vlanIDs
65 | }
66 |
67 | // RadiusServers fetches the radius server assigned to the given AP.
68 | // It returns a SSID -> RadiusServer map.
69 | func RadiusServers(ap *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint) map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server {
70 | wlanRadiusMap := make(map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server)
71 | if ap.Ssids == nil {
72 | return wlanRadiusMap
73 | }
74 |
75 | apServerGPs := aaaServerGroups(ap)
76 | if len(apServerGPs) == 0 {
77 | return wlanRadiusMap
78 | }
79 |
80 | for wlanName, wlan := range ap.Ssids.Ssid {
81 | if wlan.Config.ServerGroup == nil {
82 | continue
83 | }
84 |
85 | aaaServerGPName := *wlan.Config.ServerGroup
86 | if serverGP, ok := apServerGPs[aaaServerGPName]; ok {
87 | if radiusServer := aaaRadiusServer(serverGP); radiusServer != nil {
88 | wlanRadiusMap[wlanName] = radiusServer
89 | }
90 | }
91 | }
92 |
93 | return wlanRadiusMap
94 | }
95 |
96 | func aaaServerGroups(ap *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint) map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup {
97 | apSystemInfo := ap.System
98 | if apSystemInfo == nil {
99 | return nil
100 | }
101 |
102 | aaaInfo := apSystemInfo.Aaa
103 | if aaaInfo == nil {
104 | return nil
105 | }
106 |
107 | serverGP := aaaInfo.ServerGroups
108 | if serverGP == nil {
109 | return nil
110 | }
111 |
112 | return serverGP.ServerGroup
113 | }
114 |
115 | func aaaRadiusServer(serverGP *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup) *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server {
116 | if serverGP == nil || serverGP.Config == nil || serverGP.Config.Type != ocstruct.OpenconfigAaaTypes_AAA_SERVER_TYPE_RADIUS {
117 | return nil
118 | }
119 |
120 | if serverGP.Servers == nil {
121 | return nil
122 | }
123 |
124 | for _, radiusServer := range serverGP.Servers.Server {
125 | if radiusServer != nil {
126 | // Return the first radius server specified.
127 | return radiusServer
128 | }
129 | }
130 | // Not found Radius server
131 | return nil
132 | }
133 |
--------------------------------------------------------------------------------
/demo/README.gasket.md:
--------------------------------------------------------------------------------
1 | # Gasket & [Link022](https://github.com/google/link022)
2 |
3 | ## Getting Started
4 | First *read* over [Gasket's readme](https://github.com/bairdo/gasket/blob/master/docs/README.authentication.md) for a general overview of Gasket's authentication, and the [link022 readme](./README.md).
5 |
6 |
7 | 
8 |
9 | We will use the [link022 demo](https://github.com/google/link022/tree/master/demo) and add an OpenFlow switch controlled by Gasket & Faucet in between the gateway and AP.
10 | - The Controller is connected to both the dataplane (untagged) and the control plane.
11 |
12 |
13 | ## Link022 AP
14 | Setting up a Link022 AP for use with Gasket is similar however we require a patched version of hostapd, which fixes a bug and adds a feature to allow RADIUS attributes to be saved on Access-Accept.
15 | This is required so that we can apply ACL rules based on what user is authenticated.
16 | Follow the steps on the Link022 github to setup except do not install the 'hostapd' application.
17 |
18 | To install the patched hostapd:
19 | ```bash
20 | git clone https://github.com/bairdo/hostapd-d1xf.git -b faucet-con
21 | make
22 | sudo make install
23 | ```
24 |
25 | This provides a default compile configuration, that includes the UDP-remote control interface, which is used by Gasket over the network.
26 |
27 |
28 | ## Link022 Gateway
29 | Follow the instructions [here](./README.md) to setup the gateway.
30 |
31 | Two more configurations changes are needed:
32 |
33 | 1. Add Faucet RADIUS type to 'dictionary', and 'Faucet-ACL-ID' attribute to users in the users file.
34 | Examples [here](https://github.com/bairdo/gasket/blob/master/docs/README.authentication.md#radius-server)
35 |
36 | 2. Add the 'vendor-config' configuration (see below) to the top level of [demo/ap_config.json](./ap_config.json)
37 |
38 | ```json
39 | {
40 | "openconfig-gasket:gasket": {
41 | "ctrl-interface": "udp:8888",
42 | "radius-attribute": "26:12345:1:s"
43 | },
44 |
45 | ###### Rest of standard link022 config.
46 | "openconfig-access-points:access-points": {
47 | "access-point": [
48 | {
49 | "hostname": "raspberrypi",
50 | "system": {
51 | "aaa": {
52 | "server-groups": {
53 | "server-group": [
54 | {
55 | "servers": {
56 | "server": [
57 | {
58 | "address": "192.168.11.1",
59 | "config": {
60 | "address": "192.168.11.1",
61 | "timeout": 5,
62 | "name": "radius-server"
63 | },
64 |
65 | ```
66 |
67 |
68 | ## Gasket.
69 |
70 | At the moment the location (which switch and port) of the AP is hard-coded.
71 | In gasket/auth_app.py change the return statement of _get_dp_name_and_port to "return , "
72 |
73 | e.g. "return "faucet-1", 1"
74 |
75 |
76 | The demo gasket/faucet/... folder provides a good starting configuration for the network in the diagram above.
77 |
78 | Configure [auth.yaml](./gasket/faucet/gasket/auth.yaml) to point to the hostapd on the Link022 AP.
79 |
80 | See [base-no-authed-acls.yaml](./gasket/faucet/gasket/base-no-authed-acls.yaml) for an example ACL configuration.
81 |
82 |
83 |
84 | Build and run Gasket & Faucet:
85 | ```bash
86 | docker build -t bairdo/gasket -f Dockerfile.auth .
87 | docker run --privileged -v ~/link022/demo/gasket/faucet:/etc/ryu/faucet/ -v :/var/log/ryu/faucet/ -p 6663:6663 -p 6653:6653 -p 9244:9244 -ti bairdo/gasket
88 | ```
89 | If using a Raspberry pi use [Dockerfile.pi](https://github.com/bairdo/gasket/blob/master/Dockerfile.pi) instead of Dockerfile.auth.
90 |
91 |
92 |
93 | ## Notes:
94 | - Access (which VLAN do clients belong to) to the guest and auth VLANs is provided by hostapd.
95 | - No ACLs are applied when a user connects to the guest SSID.
96 | - As access is controlled by hostapd, in theory you do not *need* to have ACLs for each user, assuming you have the base-acl config setup to allow all traffic on the VLAN.
97 | - Rules will be added to the switch for users authenticated on the auth SSID.
98 |
99 |
100 | ## TODO:
101 | - Support multiple hostapd control interfaces in Gasket.
102 | This will allow multiple Auth SSIDs/VLANs - [issue 32](https://github.com/Bairdo/gasket/issues/32)
103 | - Remove the hard-coded switch and port.
104 | Will probably take into account which hostapd authentication has occurred on.
105 |
--------------------------------------------------------------------------------
/demo/ap_config_1r.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:access-points": {
3 | "access-point": [
4 | {
5 | "hostname": "link022-ap",
6 | "system": {
7 | "aaa": {
8 | "server-groups": {
9 | "server-group": [
10 | {
11 | "servers": {
12 | "server": [
13 | {
14 | "address": "192.168.11.1",
15 | "config": {
16 | "address": "192.168.11.1",
17 | "timeout": 5,
18 | "name": "radius-server"
19 | },
20 | "radius": {
21 | "config": {
22 | "auth-port": 1812,
23 | "secret-key": "radiuspwd"
24 | }
25 | }
26 | }
27 | ]
28 | },
29 | "name": "freeradius",
30 | "config": {
31 | "name": "freeradius",
32 | "type": "openconfig-aaa-radius:RADIUS"
33 | }
34 | }
35 | ]
36 | }
37 | }
38 | },
39 | "radios": {
40 | "radio": [
41 | {
42 | "config": {
43 | "operating-frequency": "openconfig-wifi-types:FREQ_2GHZ",
44 | "channel-width": 20,
45 | "dca": false,
46 | "dtp": false,
47 | "transmit-power": 3,
48 | "id": 1,
49 | "scanning": false,
50 | "channel": 6
51 | },
52 | "id": 1
53 | }
54 | ]
55 | },
56 | "ssids": {
57 | "ssid": [
58 | {
59 | "config": {
60 | "qbss-load": true,
61 | "dva": true,
62 | "gtk-timeout": 3600,
63 | "station-isolation": true,
64 | "multicast-filter": true,
65 | "ptk-timeout": 28800,
66 | "operating-frequency": "openconfig-wifi-types:FREQ_2_5_GHZ",
67 | "name": "Auth-Link022",
68 | "default-vlan": 300,
69 | "advertise-apname": true,
70 | "ipv6-ndp-filter-timer": 300,
71 | "dot11k": true,
72 | "supported-data-rates": [
73 | "openconfig-wifi-types:RATE_36MB",
74 | "openconfig-wifi-types:RATE_48MB",
75 | "openconfig-wifi-types:RATE_54MB"
76 | ],
77 | "basic-data-rates": [
78 | "openconfig-wifi-types:RATE_36MB",
79 | "openconfig-wifi-types:RATE_48MB",
80 | "openconfig-wifi-types:RATE_54MB"
81 | ],
82 | "broadcast-filter": true,
83 | "enabled": true,
84 | "server-group": "freeradius",
85 | "csa": true,
86 | "dhcp-required": false,
87 | "hidden": false,
88 | "opmode": "WPA2_ENTERPRISE",
89 | "ipv6-ndp-filter": true
90 | },
91 | "name": "Auth-Link022"
92 | },
93 | {
94 | "config": {
95 | "advertise-apname": true,
96 | "basic-data-rates": [
97 | "openconfig-wifi-types:RATE_11MB",
98 | "openconfig-wifi-types:RATE_24MB"
99 | ],
100 | "broadcast-filter": true,
101 | "csa": false,
102 | "dhcp-required": true,
103 | "dot11k": false,
104 | "dva": false,
105 | "enabled": true,
106 | "gtk-timeout": 1000,
107 | "hidden": false,
108 | "multicast-filter": false,
109 | "name": "Guest-Link022",
110 | "operating-frequency": "openconfig-wifi-types:FREQ_2_5_GHZ",
111 | "opmode": "OPEN",
112 | "ptk-timeout": 1000,
113 | "supported-data-rates": [
114 | "openconfig-wifi-types:RATE_11MB",
115 | "openconfig-wifi-types:RATE_24MB"
116 | ],
117 | "default-vlan": 200
118 | },
119 | "name": "Guest-Link022"
120 | }
121 | ]
122 | }
123 | }
124 | ]
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/agent/util/ocutil/openconfig_test.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package ocutil
17 |
18 | import (
19 | "reflect"
20 | "sort"
21 | "testing"
22 |
23 | "github.com/google/link022/agent/util/mock"
24 | "github.com/google/link022/generated/ocstruct"
25 | )
26 |
27 | func TestFindAPConfig(t *testing.T) {
28 | // Define test cases.
29 | tests := []struct {
30 | apConfigs *ocstruct.Device
31 | targetAPName string
32 | found bool
33 | }{{
34 | apConfigs: mock.GenerateConfig(true),
35 | targetAPName: "fake AP",
36 | found: false,
37 | }, {
38 | apConfigs: mock.GenerateConfig(true),
39 | targetAPName: "test-pi-1",
40 | found: true,
41 | }}
42 |
43 | for _, test := range tests {
44 | matchedConfig := FindAPConfig(test.apConfigs, test.targetAPName)
45 | foundMatch := matchedConfig != nil
46 |
47 | if foundMatch != test.found {
48 | t.Errorf("Incorrect FindAPConfig result (got: %v, want:%v).", foundMatch, test.found)
49 | }
50 | }
51 | }
52 |
53 | func TestVLANIDs(t *testing.T) {
54 | // Define test cases.
55 | tests := []struct {
56 | apConfig *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint
57 | vlanIDs []int
58 | }{{
59 | apConfig: mock.GenerateAPConfig(true),
60 | vlanIDs: []int{250, 666},
61 | }, {
62 | apConfig: mock.GenerateAPConfig(false),
63 | vlanIDs: []int{666},
64 | }, {
65 | apConfig: nil,
66 | vlanIDs: []int{},
67 | }}
68 |
69 | for _, test := range tests {
70 | got := VLANIDs(test.apConfig)
71 | want := test.vlanIDs
72 | sort.Ints(got)
73 | sort.Ints(want)
74 | if !reflect.DeepEqual(got, want) {
75 | t.Errorf("Incorrect VLAN IDs (got: %v, want: %v).", got, want)
76 | }
77 | }
78 | }
79 |
80 | func TestVLANChanged(t *testing.T) {
81 | // Define test cases.
82 | tests := []struct {
83 | existingConfig *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint
84 | updatedConfigConfig *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint
85 | vlanChanged bool
86 | }{{
87 | existingConfig: mock.GenerateAPConfig(true),
88 | updatedConfigConfig: mock.GenerateAPConfig(true),
89 | vlanChanged: false,
90 | }, {
91 | existingConfig: mock.GenerateAPConfig(true),
92 | updatedConfigConfig: mock.GenerateAPConfig(false),
93 | vlanChanged: true,
94 | }, {
95 | existingConfig: mock.GenerateAPConfig(false),
96 | updatedConfigConfig: mock.GenerateAPConfig(true),
97 | vlanChanged: true,
98 | }, {
99 | existingConfig: nil,
100 | updatedConfigConfig: mock.GenerateAPConfig(true),
101 | vlanChanged: true,
102 | }, {
103 | existingConfig: mock.GenerateAPConfig(true),
104 | updatedConfigConfig: nil,
105 | vlanChanged: true,
106 | }, {
107 | existingConfig: nil,
108 | updatedConfigConfig: nil,
109 | vlanChanged: false,
110 | }}
111 |
112 | for _, test := range tests {
113 | got := VLANChanged(VLANIDs(test.existingConfig), VLANIDs(test.updatedConfigConfig))
114 | want := test.vlanChanged
115 | if got != want {
116 | t.Errorf("Incorrect result (got: %v, want: %v).", got, want)
117 | }
118 | }
119 | }
120 |
121 | func TestRadiusServers(t *testing.T) {
122 | // Define test cases.
123 | tests := []struct {
124 | apConfig *ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint
125 | radiusServers map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server
126 | }{{
127 | apConfig: mock.GenerateAPConfig(false),
128 | radiusServers: make(map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server),
129 | }, {
130 | apConfig: mock.GenerateAPConfig(true),
131 | radiusServers: map[string]*ocstruct.OpenconfigAccessPoints_AccessPoints_AccessPoint_System_Aaa_ServerGroups_ServerGroup_Servers_Server{
132 | mock.AuthWLANName: mock.RadiusServer(),
133 | },
134 | }}
135 |
136 | for _, test := range tests {
137 | got := RadiusServers(test.apConfig)
138 | want := test.radiusServers
139 | if !reflect.DeepEqual(got, want) {
140 | t.Errorf("Incorrect result (got: %v, want: %v).", got, want)
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/demo/ap_config_2r.json:
--------------------------------------------------------------------------------
1 | {
2 | "openconfig-access-points:access-points": {
3 | "access-point": [
4 | {
5 | "hostname": "link022-ap",
6 | "system": {
7 | "aaa": {
8 | "server-groups": {
9 | "server-group": [
10 | {
11 | "servers": {
12 | "server": [
13 | {
14 | "address": "192.168.11.1",
15 | "config": {
16 | "address": "192.168.11.1",
17 | "timeout": 5,
18 | "name": "radius-server"
19 | },
20 | "radius": {
21 | "config": {
22 | "auth-port": 1812,
23 | "secret-key": "radiuspwd"
24 | }
25 | }
26 | }
27 | ]
28 | },
29 | "name": "freeradius",
30 | "config": {
31 | "name": "freeradius",
32 | "type": "openconfig-aaa-radius:RADIUS"
33 | }
34 | }
35 | ]
36 | }
37 | }
38 | },
39 | "radios": {
40 | "radio": [
41 | {
42 | "config": {
43 | "operating-frequency": "openconfig-wifi-types:FREQ_5GHZ",
44 | "channel-width": 40,
45 | "dca": false,
46 | "dtp": false,
47 | "transmit-power": 3,
48 | "scanning": false,
49 | "id": 0,
50 | "channel": 44
51 | },
52 | "id": 0
53 | },
54 | {
55 | "config": {
56 | "operating-frequency": "openconfig-wifi-types:FREQ_2GHZ",
57 | "channel-width": 20,
58 | "dca": false,
59 | "dtp": false,
60 | "transmit-power": 3,
61 | "id": 1,
62 | "scanning": false,
63 | "channel": 6
64 | },
65 | "id": 1
66 | }
67 | ]
68 | },
69 | "ssids": {
70 | "ssid": [
71 | {
72 | "config": {
73 | "qbss-load": true,
74 | "dva": true,
75 | "gtk-timeout": 3600,
76 | "station-isolation": true,
77 | "multicast-filter": true,
78 | "ptk-timeout": 28800,
79 | "operating-frequency": "openconfig-wifi-types:FREQ_2_5_GHZ",
80 | "name": "Auth-Link022",
81 | "default-vlan": 300,
82 | "advertise-apname": true,
83 | "ipv6-ndp-filter-timer": 300,
84 | "dot11k": true,
85 | "supported-data-rates": [
86 | "openconfig-wifi-types:RATE_36MB",
87 | "openconfig-wifi-types:RATE_48MB",
88 | "openconfig-wifi-types:RATE_54MB"
89 | ],
90 | "basic-data-rates": [
91 | "openconfig-wifi-types:RATE_36MB",
92 | "openconfig-wifi-types:RATE_48MB",
93 | "openconfig-wifi-types:RATE_54MB"
94 | ],
95 | "broadcast-filter": true,
96 | "enabled": true,
97 | "server-group": "freeradius",
98 | "csa": true,
99 | "dhcp-required": false,
100 | "hidden": false,
101 | "opmode": "WPA2_ENTERPRISE",
102 | "ipv6-ndp-filter": true
103 | },
104 | "name": "Auth-Link022"
105 | },
106 | {
107 | "config": {
108 | "advertise-apname": true,
109 | "basic-data-rates": [
110 | "RATE_11MB",
111 | "RATE_24MB"
112 | ],
113 | "broadcast-filter": true,
114 | "csa": false,
115 | "dhcp-required": true,
116 | "dot11k": false,
117 | "dva": false,
118 | "enabled": true,
119 | "gtk-timeout": 1000,
120 | "hidden": false,
121 | "multicast-filter": false,
122 | "name": "Guest-Link022",
123 | "operating-frequency": "openconfig-wifi-types:FREQ_2_5_GHZ",
124 | "opmode": "OPEN",
125 | "ptk-timeout": 1000,
126 | "supported-data-rates": [
127 | "openconfig-wifi-types:RATE_11MB",
128 | "openconfig-wifi-types:RATE_24MB"
129 | ],
130 | "default-vlan": 200
131 | },
132 | "name": "Guest-Link022"
133 | }
134 | ]
135 | }
136 | }
137 | ]
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Link022 Demo
2 | This doc contains the steps to run a demo.
3 |
4 | ## Setup demo environment
5 | Here is the structure of the demo setup.
6 | 
7 |
8 | The setup has two components, linked directly by an ethernet cable.
9 | - One Raspberry Pi device as the Link022 AP (gnmi target).
10 | - The other device (such as Raspberry Pi) as the gateway. It provides reqiured services (dhcp, radius and gnmi client).
11 |
12 | ### Setup Link022 AP
13 | On the Link022 AP device, run the commands in this [instruction](../agent/README.md) with the sample certificates.
14 |
15 | ### Setup Gateway
16 | On the device for gateway follow the steps below.
17 | 1. Install Golang.
18 |
19 | To install manually on ARM architecture:
20 | ```
21 | wget https://storage.googleapis.com/golang/go1.7.linux-armv6l.tar.gz
22 | sudo tar -C /usr/local -xzf go1.7.linux-armv6l.tar.gz
23 | export PATH=$PATH:/usr/local/go/bin
24 | ```
25 | To install automatically on Debian based systems:
26 | ```
27 | sudo apt-get install golang-go
28 | ```
29 | 2. Install dependencies.
30 | ```
31 | sudo apt-get install --no-install-recommends dnsmasq freeradius git
32 | ```
33 | 3. Download GNMI clients.
34 | ```
35 | export GOPATH=$HOME/go
36 | go get github.com/google/gnxi/gnmi_set
37 | go get github.com/google/gnxi/gnmi_get
38 | ```
39 | 4. Download the [demo folder](./).
40 | 5. Enter the demo folder.
41 | ```
42 | cd
43 | ```
44 | 6. Setup the gateway.
45 | ```
46 | cd util
47 | ./server.sh
48 | ```
49 | This script creates a network namespace (lk22 by default), which has access to the Link022 AP device.
50 |
51 | Note: Reboot the device or run 'cleanup_servers.sh' script to clean up the gateway.
52 |
53 | ## Demo
54 | *All demo commands are executed inside the lk22 namspace on the gateway device.*
55 | To enter the namespace, run
56 | ```
57 | sudo ip netns exec lk22 bash
58 | ```
59 |
60 | ### Push the full configuration to AP
61 | Pushing the entire configuration to AP. It wipes out the existing configuration and applies the incoming one.
62 | Use the [sample configuration](./ap_config.json) here.
63 | ```
64 | export PATH=$PATH:/usr/local/go/bin:/home/pi/go/bin
65 | sudo env PATH=$PATH gnmi_set \
66 | -ca=cert/client/ca.crt \
67 | -cert=cert/client/client.crt \
68 | -key=cert/client/client.key \
69 | -target_name=www.example.com \
70 | -target_addr=: \
71 | -replace=/:@ap_config.json
72 | ```
73 | After configuration pushed, two WIFI SSIDs should appear.
74 | - Auth-Link022: The authenticated network with Radius (username: host-authed, password: authedpwd).
75 | - Guest-Link022: The open network. No authentication requried.
76 |
77 | ### Update AP configuration
78 | Besides replacing the entire configuration, it is also able to update part of the existing configuration.
79 |
80 | For example, updating the radio channel.
81 | ```
82 | export PATH=$PATH:/usr/local/go/bin:/home/pi/go/bin
83 | sudo env PATH=$PATH gnmi_set -logtostderr \
84 | -ca=cert/client/ca.crt \
85 | -cert=cert/client/client.crt \
86 | -key=cert/client/client.key \
87 | -target_name=www.example.com \
88 | -target_addr=: \
89 | -update=/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel:6
90 | ```
91 |
92 | Note: Check [gnxi set client](https://github.com/google/gnxi/tree/master/gnmi_set) for more use cases.
93 | ### Fetch AP configuration
94 | Check the existing configuration on AP device with a specific path.
95 | ```
96 | export PATH=$PATH:/usr/local/go/bin:/home/pi/go/bin
97 | sudo env PATH=$PATH gnmi_get -logtostderr \
98 | -ca=cert/client/ca.crt \
99 | -cert=cert/client/client.crt \
100 | -key=cert/client/client.key \
101 | -target_name=www.example.com \
102 | -target_addr=link022 AP IP address>: \
103 | -xpath="/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel"
104 | ```
105 | The output should be similar to:
106 | ```
107 | == getResponse:
108 | notification: <
109 | timestamp: 1521145574058185274
110 | update: <
111 | path: <
112 | elem: <
113 | name: "access-points"
114 | >
115 | elem: <
116 | name: "access-point"
117 | key: <
118 | key: "hostname"
119 | value: "link022-pi-ap"
120 | >
121 | >
122 | elem: <
123 | name: "radios"
124 | >
125 | elem: <
126 | name: "radio"
127 | key: <
128 | key: "id"
129 | value: "1"
130 | >
131 | >
132 | elem: <
133 | name: "config"
134 | >
135 | elem: <
136 | name: "channel"
137 | >
138 | >
139 | val: <
140 | uint_val: 8
141 | >
142 | >
143 | >
144 | ```
145 | Note: Check [gnxi get client](https://github.com/google/gnxi/tree/master/gnmi_get) for more use cases.
146 |
--------------------------------------------------------------------------------
/agent/syscmd/syscmd_test.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | import (
19 | "fmt"
20 | "reflect"
21 | "sort"
22 | "testing"
23 | )
24 |
25 | const (
26 | testIPLinkInfo = `1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0
27 | 2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ef:4e:b6 brd ff:ff:ff:ff:ff:ff promiscuity 2
28 | 3: wlan0: mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ba:1b:e3 brd ff:ff:ff:ff:ff:ff promiscuity 0
29 | 4: wlan1: mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 1000\ link/ether 02:c0:ca:90:2f:50 brd ff:ff:ff:ff:ff:ff promiscuity 0
30 | 5: eth0.250@eth0: mtu 1500 qdisc noqueue master br_250 state UP mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ef:4e:b6 brd ff:ff:ff:ff:ff:ff promiscuity 1 \ vlan protocol 802.1Q id 250 \ bridge_slave
31 | 6: eth0.666@eth0: mtu 1500 qdisc noqueue master br_666 state UP mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ef:4e:b6 brd ff:ff:ff:ff:ff:ff promiscuity 1 \ vlan protocol 802.1Q id 666 \ bridge_slave
32 | 7: br_250: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ef:4e:b6 brd ff:ff:ff:ff:ff:ff promiscuity 0 \ bridge
33 | 8: br_666: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000\ link/ether b8:27:eb:ef:4e:b6 brd ff:ff:ff:ff:ff:ff promiscuity 0 \ bridge
34 | `
35 |
36 | testIntf = "eth0"
37 | testWLANIntf = "wlan0"
38 | testVLANID = 10
39 |
40 | bridgeName = "br_0"
41 | )
42 |
43 | var (
44 | testIPVLANLink = []int{250, 666}
45 |
46 | runner = &CommandRunner{
47 | ExecCommand: func(wait bool, command string, args ...string) (string, error) {
48 | if command == "ip" && reflect.DeepEqual(args, []string{"-o", "-d", "link", "show"}) {
49 | return testIPLinkInfo, nil
50 | }
51 |
52 | // No ops.
53 | return "", nil
54 | },
55 | }
56 | )
57 |
58 | // Testing Interface commands.
59 |
60 | func TestCreateVLAN(t *testing.T) {
61 | vlanIntfName, err := runner.CreateVLAN(testIntf, testVLANID)
62 | if err != nil {
63 | t.Errorf("Creating VLAN interface failed. Error: %v.", err)
64 | return
65 | }
66 |
67 | expectedVLANIntfName := fmt.Sprintf("%s.%d", testIntf, testVLANID)
68 | if vlanIntfName != expectedVLANIntfName {
69 | t.Errorf("Incorrect VLAN interface name. (actual: %v, expected: %v)",
70 | vlanIntfName, expectedVLANIntfName)
71 | }
72 | }
73 |
74 | func TestDeleteVLAN(t *testing.T) {
75 | if err := runner.DeleteVLAN(testIntf, testVLANID); err != nil {
76 | t.Errorf("Deleting VLAN interface failed. Error: %v.", err)
77 | }
78 | }
79 |
80 | func TestRestartIntf(t *testing.T) {
81 | if err := runner.RestartIntf(testIntf); err != nil {
82 | t.Errorf("Restarting interface failed. Error: %v.", err)
83 | }
84 | }
85 |
86 | func TestWipeOutIntfIP(t *testing.T) {
87 | if err := runner.WipeOutIntfIP(testIntf); err != nil {
88 | t.Errorf("Wiping out interface IP failed. Error: %v.", err)
89 | }
90 | }
91 |
92 | func TestVLANOnIntf(t *testing.T) {
93 | if vlanIDs, err := runner.VLANOnIntf(testIntf); err != nil {
94 | t.Errorf("Fetching VLAN interface failed. Error: %v.", err)
95 | } else {
96 | sort.Ints(vlanIDs)
97 | if !reflect.DeepEqual(vlanIDs, testIPVLANLink) {
98 | t.Errorf("Incorrect result of VLANOnIntf, actual: %v, expected: %v.", vlanIDs, testIPVLANLink)
99 | }
100 | }
101 | }
102 |
103 | // Testing bridging commands.
104 |
105 | func TestCreateBridge(t *testing.T) {
106 | if err := runner.CreateBridge(bridgeName); err != nil {
107 | t.Errorf("Creating bridge failed. Error: %v.", err)
108 | }
109 | }
110 |
111 | func TestDeleteBridge(t *testing.T) {
112 | if err := runner.DeleteBridge(bridgeName); err != nil {
113 | t.Errorf("Deleting bridge failed. Error: %v.", err)
114 | }
115 | }
116 |
117 | func TestAddBridgeIntf(t *testing.T) {
118 | if err := runner.AddBridgeIntf(bridgeName, testIntf); err != nil {
119 | t.Errorf("Adding bridge interface failed. Error: %v.", err)
120 | }
121 | }
122 |
123 | // Test hostapd commands.
124 |
125 | func TestStartHostapd(t *testing.T) {
126 | if err := runner.StartHostapd(testWLANIntf); err != nil {
127 | t.Errorf("Starting hostapd process failed. Error: %v.", err)
128 | }
129 | }
130 |
131 | func TestStopAllHostapd(t *testing.T) {
132 | if err := runner.StopAllHostapd(); err != nil {
133 | t.Errorf("Stopping hostapd processes failed. Error: %v.", err)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/testkit/testdata/simple_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"Simple Test",
3 | "description":"This is an example of gNMI test.",
4 | "test_cases":[
5 | {
6 | "name":"Push entire config",
7 | "description":"Push the entire configuration to AP device.",
8 | "ops":[
9 | {
10 | "type":"replace",
11 | "path":"/access-points/access-point[hostname=link022-pi-ap]",
12 | "val":"@../tests/ap_config.json"
13 | }
14 | ]
15 | },
16 | {
17 | "name":"Update Radio Channel",
18 | "ops":[
19 | {
20 | "type":"update",
21 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel",
22 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/channel",
23 | "val":"11"
24 | },
25 | {
26 | "type":"update",
27 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
28 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/channel-width",
29 | "val":"20"
30 | }
31 | ]
32 | },
33 | {
34 | "name":"Update Radio Transmit Power",
35 | "ops":[
36 | {
37 | "type":"update",
38 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/transmit-power",
39 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/transmit-power",
40 | "val":"9"
41 | },
42 | {
43 | "type":"update",
44 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
45 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/channel-width",
46 | "val":"20"
47 | }
48 | ]
49 | },
50 | {
51 | "name":"Update Radio channel-width",
52 | "ops":[
53 | {
54 | "type":"update",
55 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel-width",
56 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/channel-width",
57 | "val":"40"
58 | }
59 | ]
60 | },
61 | {
62 | "name":"Disable scanning on the Radio",
63 | "ops":[
64 | {
65 | "type":"update",
66 | "path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/scanning",
67 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/state/scanning",
68 | "val":"false"
69 | }
70 | ]
71 | },
72 | {
73 | "name":"Disable broadcast filter on Guest SSID",
74 | "ops":[
75 | {
76 | "type":"update",
77 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/broadcast-filter",
78 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/state/broadcast-filter",
79 | "val":"false"
80 | }
81 | ]
82 | },
83 | {
84 | "name":"Disable station-isolation on Guest SSID",
85 | "ops":[
86 | {
87 | "type":"update",
88 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/station-isolation",
89 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/state/station-isolation",
90 | "val":"false"
91 | }
92 | ]
93 | },
94 | {
95 | "name":"Update SSID frequency",
96 | "ops":[
97 | {
98 | "type":"update",
99 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/operating-frequency",
100 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/state/operating-frequency",
101 | "val":"FREQ_5GHZ"
102 | }
103 | ]
104 | },
105 | {
106 | "name":"Change opmode of Guest SSID to WPA-Personal",
107 | "ops":[
108 | {
109 | "type":"update",
110 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/opmode",
111 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/state/opmode",
112 | "val":"WPA2_PERSONAL"
113 | },
114 | {
115 | "type":"update",
116 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/wpa2-psk",
117 | "state_path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/state/wpa2-psk",
118 | "val":"testing123"
119 | }
120 | ]
121 | },
122 | {
123 | "name":"Fetch SSID Config",
124 | "ops":[
125 | {
126 | "type":"get",
127 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Auth-Link022]/config/default-vlan",
128 | "val":"300"
129 | },
130 | {
131 | "type":"get",
132 | "path":"/access-points/access-point[hostname=link022-pi-ap]/ssids/ssid[name=Guest-Link022]/config/default-vlan",
133 | "val":"200"
134 | }
135 | ]
136 | }
137 | ]
138 | }
--------------------------------------------------------------------------------
/agent/syscmd/interface.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | package syscmd
17 |
18 | import (
19 | "fmt"
20 | "strconv"
21 | "strings"
22 |
23 | log "github.com/golang/glog"
24 | )
25 |
26 | // CreateVLAN creates vlan with a specific ID on target interface.
27 | // It returns created interface name if succeeded, or error if failed.
28 | func (r *CommandRunner) CreateVLAN(intfName string, vlanID int) (string, error) {
29 | vlanINTFName := vlanINTFName(intfName, vlanID)
30 | log.Infof("Creating VLAN interface %v...", vlanINTFName)
31 | if _, err := r.ExecCommand(true, "ip", "link", "add", "link", intfName, "name", vlanINTFName, "type", "vlan", "id", strconv.Itoa(vlanID)); err != nil {
32 | return "", err
33 | }
34 | log.Infof("Created VLAN interface %v.", vlanINTFName)
35 | return vlanINTFName, nil
36 | }
37 |
38 | // DeleteVLAN deletes the vlan with a specific ID on target interface.
39 | func (r *CommandRunner) DeleteVLAN(intfName string, vlanID int) error {
40 | vlanINTFName := vlanINTFName(intfName, vlanID)
41 | log.Infof("Deleting VLAN interface %v...", vlanINTFName)
42 | if _, err := r.ExecCommand(true, "ip", "link", "delete", vlanINTFName); err != nil {
43 | return err
44 | }
45 | log.Infof("Deleted VLAN interface %v.", vlanINTFName)
46 | return nil
47 | }
48 |
49 | // RestartIntf restarts a specific network interface.
50 | func (r *CommandRunner) RestartIntf(intfName string) error {
51 | log.Infof("Restarting interface %v...", intfName)
52 |
53 | if err := r.TurnDownIntf(intfName); err != nil {
54 | return err
55 | }
56 |
57 | if err := r.BringUpIntf(intfName); err != nil {
58 | return err
59 | }
60 |
61 | log.Infof("Restarted the interface %v.", intfName)
62 | return nil
63 | }
64 |
65 | // BringUpIntf brings up a certain network interface.
66 | func (r *CommandRunner) BringUpIntf(intfName string) error {
67 | if _, err := r.ExecCommand(true, "ifconfig", intfName, "up"); err != nil {
68 | return err
69 | }
70 | log.Infof("Interface %v is UP.", intfName)
71 | return nil
72 | }
73 |
74 | // TurnDownIntf turns down a certain network interface.
75 | func (r *CommandRunner) TurnDownIntf(intfName string) error {
76 | if _, err := r.ExecCommand(true, "ifconfig", intfName, "down"); err != nil {
77 | return err
78 | }
79 | log.Infof("Interface %v is DOWN.", intfName)
80 | return nil
81 | }
82 |
83 | // WipeOutIntfIP cleans up the IP address on a certain network interface.
84 | func (r *CommandRunner) WipeOutIntfIP(intfName string) error {
85 | if _, err := r.ExecCommand(true, "ifconfig", intfName, "0.0.0.0"); err != nil {
86 | return err
87 | }
88 | log.Infof("Wiped out the IP on interface %v.", intfName)
89 | return nil
90 | }
91 |
92 | // IntfMAC returns the MAC address of a certain interface.
93 | func (r *CommandRunner) IntfMAC(intfName string) (string, error) {
94 | mac, err := r.ExecCommand(true, "cat", fmt.Sprintf("/sys/class/net/%s/address", intfName))
95 | if err != nil {
96 | return "", err
97 | }
98 | mac = mac[0 : len(mac)-1] // The last character is '\n'
99 | log.Infof("MAC address of %v is %v.", intfName, mac)
100 | return mac, nil
101 | }
102 |
103 | // VLANOnIntf returns IDs of all VLAN on the given interface.
104 | func (r *CommandRunner) VLANOnIntf(intfName string) ([]int, error) {
105 | // Fetch all interface information on the device.
106 | linkInfo, err := r.ExecCommand(true, "ip", "-o", "-d", "link", "show")
107 | if err != nil {
108 | return nil, err
109 | }
110 |
111 | // Parse the result to find the target VLAN interface.
112 | vlanIDs := vlanIDsInIPLinkResult(intfName, linkInfo)
113 | log.Infof("Interface %s has VLAN %v.", intfName, vlanIDs)
114 | return vlanIDs, nil
115 | }
116 |
117 | func vlanIDsInIPLinkResult(intfName, linkInfo string) []int {
118 | var vlanIDs []int
119 | for _, intfInfo := range strings.Split(linkInfo, "\n") {
120 | if !strings.Contains(intfInfo, fmt.Sprintf("@%s", intfName)) || !strings.Contains(intfInfo, "vlan") {
121 | continue
122 | }
123 | for _, infoElem := range strings.Split(intfInfo, "\\") {
124 | if !strings.Contains(infoElem, "vlan") {
125 | continue
126 | }
127 | foundID := false
128 | for _, vlanInfoElem := range strings.Fields(infoElem) {
129 | if foundID {
130 | if vlanID, err := strconv.Atoi(vlanInfoElem); err == nil {
131 | vlanIDs = append(vlanIDs, vlanID)
132 | }
133 | break
134 | }
135 | if vlanInfoElem == "id" {
136 | foundID = true
137 | }
138 | }
139 | }
140 | }
141 | return vlanIDs
142 | }
143 |
144 | // UpdateIntfMAC changes the MAC address of a certain interface to the inputed one.
145 | func (r *CommandRunner) UpdateIntfMAC(intfName, updatedMAC string) error {
146 | if _, err := r.ExecCommand(true, "ifconfig", intfName, "hw", "ether", updatedMAC); err != nil {
147 | return err
148 | }
149 | log.Infof("The MAC address of %v updated to %v.", intfName, updatedMAC)
150 | return nil
151 | }
152 |
153 | // SendDHCPRequest sends a DHCP request for a certain network interface with the hostname in parameter.
154 | func (r *CommandRunner) SendDHCPRequest(intfName, hostname string) error {
155 | if _, err := r.ExecCommand(false, "udhcpc", "-i", intfName, "-x", fmt.Sprintf("hostname:%s", hostname)); err != nil {
156 | return err
157 | }
158 | log.Infof("Send DHCP request on interface %s with hostname %s.", intfName, hostname)
159 | return nil
160 | }
161 |
--------------------------------------------------------------------------------
/tests/integration.py:
--------------------------------------------------------------------------------
1 | """
2 | Integration tests for link022 gnmi functions.
3 |
4 | Refer to the README.md file for instructions on running the tests.
5 | """
6 |
7 | import argparse
8 | import json
9 | import netaddr
10 | import logging
11 | import platform
12 | import sys
13 | import time
14 | import tempfile
15 | import unittest
16 |
17 | import mininet.net
18 | import mininet.node
19 | import mininet.cli
20 |
21 |
22 | FLAGS = None
23 | logging.basicConfig(stream=sys.stdout, level=logging.INFO)
24 | logger = logging.getLogger()
25 |
26 |
27 | def set_flags():
28 | """Set the global FLAGS
29 | """
30 | global FLAGS
31 | parser = argparse.ArgumentParser()
32 | parser.add_argument(
33 | "--target_cmd",
34 | help="Command line to start the target. Needed if ext_target is False")
35 | parser.add_argument(
36 | "--ext_target", action='store_true', default=False,
37 | help='Use an external target.')
38 | parser.add_argument(
39 | "--emulator", action='store_true', default=False,
40 | help='Start the emulator and run no tests.')
41 | parser.add_argument("--gnmi_set", help="Path to the gnmi_set command")
42 | parser.add_argument("--ca", help="CA Certificate")
43 | parser.add_argument("--cert", help="Client Certificate")
44 | parser.add_argument("--key", help="Client key")
45 | parser.add_argument(
46 | "--target_name", help="Target name for cert verification")
47 | parser.add_argument("--target_addr", help="Target IP:port")
48 | parser.add_argument("--json_conf", help="File name of JSON config")
49 | FLAGS = parser.parse_args()
50 |
51 |
52 | TARGET_NAME = 'target'
53 | CONTROLLER_NAME = 'ctrlr'
54 | DUMMY_NAME = 'dummy'
55 | DEFAULT_NS = 'lk022_def'
56 |
57 |
58 | def get_ip_spec(addr, subnet=None):
59 | """Get the IP address with the subnet prefix length.
60 |
61 | Args:
62 | addr: network.addr object
63 | subnet: network.net object
64 |
65 | Returns:
66 | A string of the IP address with prefix length.
67 |
68 | Raises:
69 | Exception: if ip not in subnet
70 | """
71 | if subnet is None:
72 | if addr.version == 6:
73 | ip_spec = str(addr) + '/128'
74 | else:
75 | ip_spec = str(addr) + '/32'
76 | elif addr in subnet:
77 | ip_spec = '%s/%s' % (addr, subnet.prefixlen)
78 | else:
79 | raise Exception('ip %s is not in subnet %s' % (addr, subnet))
80 | return ip_spec
81 |
82 |
83 | class ConfigTest(unittest.TestCase):
84 | """Define the test driver.
85 | """
86 | @classmethod
87 | def setUpClass(cls):
88 | cls._target_popen = None
89 | if FLAGS.ext_target:
90 | cls._ctrlr = mininet.node.Node(DEFAULT_NS, inNamespace=False)
91 | else:
92 | cls._start_topo()
93 |
94 | @classmethod
95 | def _start_topo(cls):
96 | """Create an empty network and add nodes to it.
97 | """
98 |
99 | subnet = netaddr.IPNetwork('10.0.0.0/24')
100 | hosts_iter = subnet.iter_hosts()
101 | cls._net = mininet.net.Mininet(controller=None)
102 |
103 | cls._net.addHost(TARGET_NAME)
104 | cls._net.addHost(CONTROLLER_NAME)
105 | # We add a dummy host to create the eth and wlan interfaces for the
106 | # Target
107 | cls._net.addHost(DUMMY_NAME)
108 | params1 = {'ip': get_ip_spec(hosts_iter.next(), subnet)}
109 | params2 = {'ip': get_ip_spec(hosts_iter.next(), subnet)}
110 | cls._net.addLink(TARGET_NAME, CONTROLLER_NAME,
111 | params1=params1, params2=params2)
112 | cls._net.addLink(TARGET_NAME, DUMMY_NAME)
113 | cls._net.addLink(TARGET_NAME, DUMMY_NAME)
114 |
115 | cls._target = cls._net[TARGET_NAME]
116 | cls._ctrlr = cls._net[CONTROLLER_NAME]
117 |
118 | cls._net.start()
119 | logger.info('Running target command: %s', FLAGS.target_cmd)
120 | cls._target_popen = cls._target.popen(FLAGS.target_cmd)
121 |
122 | if FLAGS.emulator:
123 | mininet.cli.CLI(cls._net)
124 | else:
125 | # Wait for the agent to start up
126 | time.sleep(20)
127 |
128 | @classmethod
129 | def tearDownClass(cls):
130 | """Test clean up.
131 | """
132 | if cls._target_popen:
133 | cls._target_popen.kill()
134 | cls._target_popen = None
135 |
136 | def runTest(self):
137 | """Run the test
138 | """
139 | if FLAGS.emulator:
140 | self.skipTest('Skip the test in emulator mode.')
141 | ap_conf_file = FLAGS.json_conf
142 | if not FLAGS.ext_target:
143 | # Rewrite the hostname for emulator based tests.
144 | ap_conf = json.load(open(ap_conf_file, 'r'))
145 | for ap in ap_conf['openconfig-access-points:access-points']['access-point']:
146 | ap['hostname'] = platform.node()
147 | json_h = tempfile.NamedTemporaryFile()
148 | json.dump(ap_conf, json_h)
149 | json_h.flush()
150 | ap_conf_file = json_h.name
151 | gnmi_set_cmd_list = (
152 | FLAGS.gnmi_set,
153 | '-ca=' + FLAGS.ca,
154 | '-cert=' + FLAGS.cert,
155 | '-key=' + FLAGS.key,
156 | '-target_name=' + FLAGS.target_name,
157 | '-target_addr=' + FLAGS.target_addr,
158 | '-replace=' + '/:@' + ap_conf_file)
159 | gnmi_set_cmd = ' '.join(gnmi_set_cmd_list)
160 |
161 | logger.info('Running gnmi_set command: %s', gnmi_set_cmd)
162 | _, _, code = self._ctrlr.pexec(gnmi_set_cmd)
163 | self.assertEqual(code, 0)
164 | if not FLAGS.ext_target:
165 | json_h.close()
166 |
167 |
168 | if __name__ == '__main__':
169 | set_flags()
170 | tests = unittest.TestSuite()
171 | tests.addTest(ConfigTest())
172 | unittest.TextTestRunner().run(tests)
173 |
--------------------------------------------------------------------------------
/testkit/test_kit.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // The test_kit program is a tool that tests gNMI functionalities of an AP device.
17 | package main
18 |
19 | import (
20 | "bufio"
21 | "encoding/json"
22 | "flag"
23 | "fmt"
24 | "io/ioutil"
25 | "os"
26 | "time"
27 |
28 | log "github.com/golang/glog"
29 | "google.golang.org/grpc"
30 |
31 | "github.com/google/gnxi/utils/credentials"
32 |
33 | "github.com/google/link022/testkit/common"
34 | "github.com/google/link022/testkit/gnmitest"
35 |
36 | pb "github.com/openconfig/gnmi/proto/gnmi"
37 | )
38 |
39 | type arrayFlags []string
40 |
41 | func (i *arrayFlags) String() string {
42 | return "my string representation"
43 | }
44 |
45 | func (i *arrayFlags) Set(value string) error {
46 | *i = append(*i, value)
47 | return nil
48 | }
49 |
50 | var (
51 | gnmiTests arrayFlags
52 | targetAddr = flag.String("target_addr", "localhost:10161", "The target address in the format of host:port")
53 | targetName = flag.String("target_name", "hostname.com", "The target name used to verify the hostname returned by TLS handshake")
54 | timeout = flag.Duration("time_out", 30*time.Second, "Timeout for each request, 30 seconds by default")
55 | stateUpdateMaxDelay = flag.Duration("state_update_max_delay", 30*time.Second, "The maximum delay of the corresponding state field updating after a config field changes")
56 | pauseMode = flag.Bool("pause_mode", false, "Pause after each test case")
57 | )
58 |
59 | func loadTests(testFiles []string) ([]*common.GNMITest, error) {
60 | var tests []*common.GNMITest
61 | for _, testFile := range testFiles {
62 | testContent, err := ioutil.ReadFile(testFile)
63 | if err != nil {
64 | return nil, err
65 | }
66 | test := &common.GNMITest{}
67 | if err := json.Unmarshal(testContent, test); err != nil {
68 | return nil, err
69 | }
70 | tests = append(tests, test)
71 | log.Infof("Loaded [%s].", test.Name)
72 | }
73 | return tests, nil
74 | }
75 |
76 | func runTest(client pb.GNMIClient, gNMITest *common.GNMITest, timeout time.Duration, stateUpdateDelay time.Duration) *common.TestResult {
77 | var testCaseResults []*common.TestCaseResult
78 |
79 | // Run gNMI config tests.
80 | log.Infof("Running [%s].", gNMITest.Name)
81 | var passedNum, failedNum int
82 | totalNum := len(gNMITest.GNMITestCase)
83 | for i, testcase := range gNMITest.GNMITestCase {
84 | log.Infof("Started [%s].", testcase.Name)
85 | err := gnmitest.RunTest(client, testcase, timeout, stateUpdateDelay)
86 | if err != nil {
87 | failedNum += 1
88 | log.Errorf("[%d/%d] [%s] failed: %v.", i+1, totalNum, testcase.Name, err)
89 | } else {
90 | passedNum += 1
91 | log.Infof("[%d/%d] [%s] succeeded.", i+1, totalNum, testcase.Name)
92 | }
93 |
94 | result := &common.TestCaseResult{
95 | Name: testcase.Name,
96 | Err: err,
97 | }
98 | testCaseResults = append(testCaseResults, result)
99 |
100 | if *pauseMode && i < totalNum {
101 | reader := bufio.NewReader(os.Stdin)
102 | // Pause until user triggers next test case manually.
103 | fmt.Println("Press ENTER to start the next test case.")
104 | _, _ = reader.ReadString('\n')
105 | }
106 | }
107 |
108 | if failedNum > 0 {
109 | log.Errorf("[%s] failed.", gNMITest.Name)
110 | } else {
111 | log.Infof("[%s] succeeded.", gNMITest.Name)
112 | }
113 |
114 | return &common.TestResult{
115 | Name: gNMITest.Name,
116 | PassedNum: passedNum,
117 | FailedNum: failedNum,
118 | Details: testCaseResults,
119 | }
120 | }
121 |
122 | func resultString(passed bool) string {
123 | if passed {
124 | return "PASS"
125 | }
126 | return "FAIL"
127 | }
128 |
129 | func printResult(results []*common.TestResult) {
130 | fmt.Println("=Test results=")
131 | // Print details of each test.
132 | for _, test := range results {
133 | fmt.Println("--------------------")
134 | fmt.Printf("[%s] %s\n", resultString(test.FailedNum == 0), test.Name)
135 | for _, testCase := range test.Details {
136 | passed := testCase.Err == nil
137 | fmt.Printf("|-[%s] %s\n", resultString(passed), testCase.Name)
138 | if !passed {
139 | fmt.Printf(" \\- %v\n", testCase.Err)
140 | }
141 | }
142 | }
143 | // Print test result summay.
144 | fmt.Println("--------------------")
145 | for _, test := range results {
146 | fmt.Printf("[%s] [%s] Passed - %d, Failed - %d\n", resultString(test.FailedNum == 0), test.Name, test.PassedNum, test.FailedNum)
147 | }
148 | }
149 |
150 | func main() {
151 | flag.Var(&gnmiTests, "test_file", "The file containing gNMI test.")
152 | flag.Parse()
153 |
154 | log.Info("Test kit started.")
155 |
156 | // Load test cases.
157 | tests, err := loadTests(gnmiTests)
158 | if err != nil {
159 | log.Fatalf("Failed to load tests. Error: %v.", err)
160 | }
161 | log.Infof("Loaded %d test files..", len(tests))
162 |
163 | // Create gNMI client.
164 | opts := credentials.ClientCredentials(*targetName)
165 | conn, err := grpc.Dial(*targetAddr, opts...)
166 | if err != nil {
167 | log.Fatalf("Dialing to %q failed: %v", *targetAddr, err)
168 | }
169 | defer conn.Close()
170 | client := pb.NewGNMIClient(conn)
171 |
172 | // Run all tests.
173 | var results []*common.TestResult
174 | for _, test := range tests {
175 | results = append(results, runTest(client, test, *timeout, *stateUpdateMaxDelay))
176 | }
177 |
178 | // Print out the result.
179 | printResult(results)
180 | }
181 |
--------------------------------------------------------------------------------
/demo/facuet_hw/setup.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #!/bin/bash
16 |
17 | NS=gw
18 | OUT_OVS_DATA_INTF=wlx00c0ca902f38 # Interface to connect to the Internet
19 | GWIP=192.168.11.1
20 |
21 | # Create veth pair, one side for ovs, the other side for the host itself
22 | OVS_DATA_INTF_ON_OVS=ovs2${NS}
23 | OVS_DATA_INTF=to_ovs # inside the host
24 | sudo ip netns add ${NS}
25 | sudo ip link add name ${OVS_DATA_INTF_ON_OVS} type veth peer name ${OVS_DATA_INTF} netns ${NS}
26 |
27 | sudo ip link set dev ${OVS_DATA_INTF_ON_OVS} up
28 | sudo ip netns exec ${NS} ip addr add ${GWIP}/24 dev ${OVS_DATA_INTF}
29 | sudo ip netns exec ${NS} ip link set dev ${OVS_DATA_INTF} up
30 | sudo ip netns exec ${NS} ip link set dev lo up
31 |
32 | # Start DHCP
33 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
34 | -F set:s0,192.168.11.2,192.168.11.10 \
35 | -O tag:s0,3,192.168.11.1 -O option:dns-server,8.8.8.8 -I lo -z \
36 | -l /tmp/link022.leases -8 /tmp/link022.dhcp.log -i ${OVS_DATA_INTF} -a ${GWIP} --conf-file= &
37 |
38 | ########### Get Internet access for the NS
39 | TO_DEF=to_def
40 | TO_NS=def2${NS}
41 |
42 | # enable forwarding
43 | sudo sysctl net.ipv4.ip_forward=1
44 | sudo ip netns exec ${NS} sysctl net.ipv4.ip_forward=1
45 |
46 | # create veth pair
47 | sudo ip link add name ${TO_NS} type veth peer name ${TO_DEF} netns ${NS}
48 | # configure interfaces and routes
49 | sudo ip addr add 192.168.22.1/30 dev ${TO_NS}
50 | sudo ip link set ${TO_NS} up
51 | # sudo ip route add 192.168.22.0/30 dev ${TO_NS}
52 | sudo ip netns exec ${NS} ip addr add 192.168.22.2/30 dev ${TO_DEF}
53 | sudo ip netns exec ${NS} ip link set ${TO_DEF} up
54 | sudo ip netns exec ${NS} ip route add default via 192.168.22.1
55 | # NAT in LK22
56 | sudo ip netns exec ${NS} iptables -t nat -F
57 | sudo ip netns exec ${NS} iptables -t nat -A POSTROUTING -o ${TO_DEF} -j MASQUERADE
58 | # NAT in default
59 | sudo iptables -P FORWARD DROP
60 | sudo iptables -F FORWARD
61 | # Assuming the host does not have other NAT rules.
62 | sudo iptables -t nat -F
63 | sudo iptables -t nat -A POSTROUTING -s 192.168.22.0/30 -o ${OUT_OVS_DATA_INTF} -j MASQUERADE
64 | sudo iptables -A FORWARD -i ${OUT_OVS_DATA_INTF} -o ${TO_NS} -j ACCEPT
65 | sudo iptables -A FORWARD -i ${TO_NS} -o ${OUT_OVS_DATA_INTF} -j ACCEPT
66 |
67 | ########### Adding vlans
68 | function add_vlan {
69 | vlan_name=$1
70 | vlan_id=$2
71 | vlan_net=$3
72 | data_intf=$4
73 | vlan_gw=${vlan_net}.1
74 | sudo ip netns exec ${NS} ip link add link ${data_intf} name ${vlan_name} type vlan id ${vlan_id}
75 | sudo ip netns exec ${NS} ip addr add ${vlan_gw}/24 dev ${vlan_name}
76 | sudo ip netns exec ${NS} ip link set dev ${vlan_name} up
77 |
78 | # Start DHCP
79 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
80 | -F set:s0,${vlan_net}.2,${vlan_net}.100 \
81 | -O tag:s0,3,${vlan_gw} -O option:dns-server,8.8.8.8 -I lo -z \
82 | -l /tmp/link022.${vlan_name}.leases -8 /tmp/link022.${vlan_name}.dhcp.log -i ${vlan_name} -a ${vlan_gw} --conf-file= &
83 | }
84 | add_vlan guest 200 192.168.33 ${OVS_DATA_INTF}
85 | add_vlan auth 300 192.168.44 ${OVS_DATA_INTF}
86 |
87 | RADIUS_PATH=../radius/freeradius
88 | sudo ip netns exec ${NS} freeradius -X -d ${RADIUS_PATH} > /tmp/${NS}_radius.log &
89 |
90 | #############create ovs
91 | sudo ovs-vsctl add-br br0 \
92 | -- set bridge br0 other-config:datapath-id=0000000000000001 \
93 | -- set bridge br0 other-config:disable-in-band=true \
94 | -- set bridge br0 fail_mode=secure \
95 | -- add-port br0 ${OVS_DATA_INTF_ON_OVS} -- set interface ${OVS_DATA_INTF_ON_OVS} ofport_request=1 \
96 | -- add-port br0 enp2s0 -- set interface enp2s0 ofport_request=2 \
97 | -- add-port br0 enp3s0 -- set interface enp3s0 ofport_request=3 \
98 | -- add-port br0 enp4s0 -- set interface enp4s0 ofport_request=4 \
99 | -- set-controller br0 tcp:127.0.0.1:6653 tcp:127.0.0.1:6654
100 |
101 | #############hardware switch
102 | ln -f -s /proc/1/ns/net /var/run/netns/default
103 | HW_CPN_GW=192.168.12
104 | HW_CPN_GW_IP=${HW_CPN_GW}.1
105 | CPN_NS_INTF=enp6s0
106 | HW_DATA_INTF=enp5s0
107 | sudo ip link set dev ${CPN_NS_INTF} netns ${NS}
108 | sudo ip netns exec ${NS} ip addr add ${HW_CPN_GW_IP}/24 dev ${CPN_NS_INTF}
109 | sudo ip netns exec ${NS} ip link set dev ${CPN_NS_INTF} up
110 | for port in 6653 6654; do
111 | sudo ip netns exec ${NS} socat -d TCP-LISTEN:${port},bind=${HW_CPN_GW_IP},reuseaddr,fork EXEC:"ip netns exec default socat -d STDIO TCP\:127.0.0.1\:${port},keepalive" &
112 | done
113 |
114 |
115 | sudo ip netns exec gw dnsmasq --no-ping -p 0 -k -F set:s0,${HW_CPN_GW}.2,${HW_CPN_GW}.10 -O tag:s0,3,${HW_CPN_GW_IP} -O option:dns-server,8.8.8.8 -I lo -z -l /tmp/cpn_hw.leases -8 /tmp/cpn_hw.dhcp.log -i ${CPN_NS_INTF} -a ${HW_CPN_GW_IP} --conf-file= &
116 |
117 |
118 | HW_DATA_GW=192.168.13
119 | HW_DATA_GW_IP=${HW_DATA_GW}.1
120 | sudo ip link set dev ${HW_DATA_INTF} netns ${NS}
121 | sudo ip netns exec ${NS} ip addr add ${HW_DATA_GW_IP}/24 dev ${HW_DATA_INTF}
122 | # Start DHCP
123 | sudo ip netns exec ${NS} dnsmasq --no-ping -p 0 -k \
124 | -F set:s0,${HW_DATA_GW}.2,${HW_DATA_GW}.10 \
125 | -O tag:s0,3,${HW_DATA_GW_IP} -O option:dns-server,8.8.8.8 -I lo -z \
126 | --dhcp-option-force=vendor:vendorclass,102,1.2.3.4 \
127 | -l /tmp/hw_data.leases -8 /tmp/hw_data.dhcp.log -i ${HW_DATA_INTF} -a ${HW_DATA_GW_IP} --conf-file= &
128 | sudo ip netns exec ${NS} ip link set dev ${HW_DATA_INTF} up
129 | add_vlan guesthw 200 192.168.55 ${HW_DATA_INTF}
130 | add_vlan authhw 300 192.168.66 ${HW_DATA_INTF}
131 |
--------------------------------------------------------------------------------
/testkit/util/gnmiutil/helper_test.go:
--------------------------------------------------------------------------------
1 | package gnmiutil
2 |
3 | import (
4 | "testing"
5 |
6 | pb "github.com/openconfig/gnmi/proto/gnmi"
7 | )
8 |
9 | const (
10 | sampleConfigJSON = `{
11 | "radio":[
12 | {
13 | "config":{
14 | "channel":157,
15 | "channel-width":40,
16 | "enabled":true,
17 | "id":0,
18 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
19 | "transmit-power":9
20 | },
21 | "id":0
22 | },
23 | {
24 | "config":{
25 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
26 | "channel-width":20,
27 | "enabled":true,
28 | "dtp":false,
29 | "transmit-power":3,
30 | "id":1,
31 | "channel":6
32 | },
33 | "id":1
34 | }
35 | ]
36 | }`
37 | sampleConfigStateJSON = `{
38 | "radio":[
39 | {
40 | "config":{
41 | "channel":157,
42 | "channel-width":40,
43 | "enabled":true,
44 | "id":0,
45 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
46 | "transmit-power":9
47 | },
48 | "id":0,
49 | "state":{
50 | "base-radio-mac":"5c:5b:35:00:2b:00",
51 | "channel":157,
52 | "channel-width":40,
53 | "counters":{
54 | "noise-floor":-86
55 | },
56 | "enabled":true,
57 | "id":0,
58 | "operating-frequency":"openconfig-wifi-types:FREQ_5GHZ",
59 | "rx-noise-channel-utilization":19,
60 | "total-channel-utilization":20,
61 | "transmit-power":9
62 | }
63 | },
64 | {
65 | "config":{
66 | "channel":6,
67 | "channel-width":20,
68 | "dtp":false,
69 | "enabled":true,
70 | "id":1,
71 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
72 | "transmit-power":3
73 | },
74 | "id":1,
75 | "state":{
76 | "base-radio-mac":"5c:5b:35:00:2a:f0",
77 | "channel":6,
78 | "channel-width":20,
79 | "counters":{
80 | "noise-floor":-73
81 | },
82 | "dtp":false,
83 | "enabled":true,
84 | "id":1,
85 | "operating-frequency":"openconfig-wifi-types:FREQ_2GHZ",
86 | "rx-noise-channel-utilization":82,
87 | "total-channel-utilization":83,
88 | "transmit-power":3
89 | }
90 | }
91 | ]
92 | }`
93 | )
94 |
95 | func TestValEqual(t *testing.T) {
96 | testCases := []struct {
97 | actual *pb.TypedValue
98 | expected *pb.TypedValue
99 | isEqual bool
100 | }{
101 | {
102 | actual: &pb.TypedValue{
103 | Value: &pb.TypedValue_IntVal{
104 | IntVal: 6,
105 | },
106 | },
107 | expected: &pb.TypedValue{
108 | Value: &pb.TypedValue_IntVal{
109 | IntVal: 6,
110 | },
111 | },
112 | isEqual: true,
113 | },
114 | {
115 | actual: &pb.TypedValue{
116 | Value: &pb.TypedValue_IntVal{
117 | IntVal: 6,
118 | },
119 | },
120 | expected: &pb.TypedValue{
121 | Value: &pb.TypedValue_IntVal{
122 | IntVal: 10,
123 | },
124 | },
125 | isEqual: false,
126 | },
127 | {
128 | actual: &pb.TypedValue{
129 | Value: &pb.TypedValue_FloatVal{
130 | FloatVal: 3.33,
131 | },
132 | },
133 | expected: &pb.TypedValue{
134 | Value: &pb.TypedValue_FloatVal{
135 | FloatVal: 3.33,
136 | },
137 | },
138 | isEqual: true,
139 | },
140 | {
141 | actual: &pb.TypedValue{
142 | Value: &pb.TypedValue_FloatVal{
143 | FloatVal: 3.33,
144 | },
145 | },
146 | expected: &pb.TypedValue{
147 | Value: &pb.TypedValue_FloatVal{
148 | FloatVal: 6.666,
149 | },
150 | },
151 | isEqual: false,
152 | },
153 | {
154 | actual: &pb.TypedValue{
155 | Value: &pb.TypedValue_BoolVal{
156 | BoolVal: true,
157 | },
158 | },
159 | expected: &pb.TypedValue{
160 | Value: &pb.TypedValue_BoolVal{
161 | BoolVal: true,
162 | },
163 | },
164 | isEqual: true,
165 | },
166 | {
167 | actual: &pb.TypedValue{
168 | Value: &pb.TypedValue_BoolVal{
169 | BoolVal: true,
170 | },
171 | },
172 | expected: &pb.TypedValue{
173 | Value: &pb.TypedValue_BoolVal{
174 | BoolVal: false,
175 | },
176 | },
177 | isEqual: false,
178 | },
179 | {
180 | actual: &pb.TypedValue{
181 | Value: &pb.TypedValue_StringVal{
182 | StringVal: "One",
183 | },
184 | },
185 | expected: &pb.TypedValue{
186 | Value: &pb.TypedValue_StringVal{
187 | StringVal: "One",
188 | },
189 | },
190 | isEqual: true,
191 | },
192 | {
193 | actual: &pb.TypedValue{
194 | Value: &pb.TypedValue_StringVal{
195 | StringVal: "One",
196 | },
197 | },
198 | expected: &pb.TypedValue{
199 | Value: &pb.TypedValue_StringVal{
200 | StringVal: "Two",
201 | },
202 | },
203 | isEqual: false,
204 | },
205 | {
206 | actual: &pb.TypedValue{
207 | Value: &pb.TypedValue_JsonIetfVal{
208 | JsonIetfVal: []byte(sampleConfigStateJSON),
209 | },
210 | },
211 | expected: &pb.TypedValue{
212 | Value: &pb.TypedValue_JsonIetfVal{
213 | JsonIetfVal: []byte(sampleConfigJSON),
214 | },
215 | },
216 | isEqual: true,
217 | },
218 | {
219 | actual: &pb.TypedValue{
220 | Value: &pb.TypedValue_BoolVal{
221 | BoolVal: true,
222 | },
223 | },
224 | expected: &pb.TypedValue{
225 | Value: &pb.TypedValue_StringVal{
226 | StringVal: "Two",
227 | },
228 | },
229 | isEqual: false,
230 | },
231 | }
232 |
233 | for _, testCase := range testCases {
234 | if err := ValEqual(nil, testCase.actual, testCase.expected); (err == nil) != testCase.isEqual {
235 | t.Errorf("incorrect matching result, actual = %v, expected = %v. (matching candidate: %v, %v)", err == nil, testCase.isEqual, testCase.actual, testCase.expected)
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/emulator/README.md:
--------------------------------------------------------------------------------
1 | # Link022 emulator
2 | This doc contains steps to run a Link022 emulator.
3 |
4 | The emulator builds a local testing environment with Link022 agent running inside a mininet node.
5 |
6 | ### Prerequisites
7 | Download the Link022 [repository](../).
8 |
9 | ### 1. Setup environment
10 | The setup needs Python 2.7 environment and some additional packages.
11 | ```
12 | apt-get install python python-netaddr mininet udhcpc bridge-utils hostapd
13 | go get github.com/openconfig/goyang/pkg/yang
14 | go get github.com/openconfig/ygot/experimental/ygotutils
15 | ```
16 |
17 | ### 2. Compile Link022 agent
18 | Run the [build script](../build.sh) to compile the Link022 agent.
19 | It stores output binary file in the "binary" folder.
20 | ```
21 | ./build.sh
22 | ```
23 |
24 | ### 3. Start emulator
25 | Run the following command to start the emulator:
26 | ```
27 | cd emulator
28 |
29 | sudo env -u TERM python emulator.py \
30 | --target_cmd "../binary/link022_agent -ca ../demo/cert/server/ca.crt \
31 | -cert ../demo/cert/server/server.crt \
32 | -key ../demo/cert/server/server.key -eth_intf_name target-eth1 \
33 | -wlan_intf_name target-eth2"
34 | ```
35 |
36 | The mininet CLI should appear after emulator started.
37 | ```
38 | mininet>
39 | ```
40 |
41 | ### 4. Verify the setup
42 |
43 | ### Check mininet nodes
44 | ```
45 | mininet> nodes
46 | available nodes are:
47 | ctrlr dummy target
48 | ```
49 |
50 | There are three nodes in mininet.
51 | * ctrlr: where gNMI client runs on.
52 | * target: where Link022 agent runs on.
53 | * dummy: a dummy host to contain the emulated eth and wlan interfaces.
54 |
55 | ### Check Link022 agent
56 |
57 | The "target" node contains the following interfaces:
58 | ```
59 | mininet> target ifconfig
60 | lo: flags=73 mtu 65536
61 | inet 127.0.0.1 netmask 255.0.0.0
62 | inet6 ::1 prefixlen 128 scopeid 0x10
63 | loop txqueuelen 1 (Local Loopback)
64 | RX packets 0 bytes 0 (0.0 B)
65 | RX errors 0 dropped 0 overruns 0 frame 0
66 | TX packets 0 bytes 0 (0.0 B)
67 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
68 |
69 | target-eth0: flags=4163 mtu 1500
70 | inet 10.0.0.1 netmask 255.0.0.0 broadcast 10.255.255.255
71 | inet6 fe80::7cfb:bff:fe9c:7355 prefixlen 64 scopeid 0x20
72 | ether 7e:fb:0b:9c:73:55 txqueuelen 1000 (Ethernet)
73 | RX packets 6 bytes 508 (508.0 B)
74 | RX errors 0 dropped 0 overruns 0 frame 0
75 | TX packets 5 bytes 418 (418.0 B)
76 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
77 |
78 | target-eth1: flags=4163 mtu 1500
79 | inet6 fe80::38b1:64ff:fee8:ef6c prefixlen 64 scopeid 0x20
80 | ether 3a:b1:64:e8:ef:6c txqueuelen 1000 (Ethernet)
81 | RX packets 6 bytes 508 (508.0 B)
82 | RX errors 0 dropped 0 overruns 0 frame 0
83 | TX packets 6 bytes 508 (508.0 B)
84 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
85 |
86 | target-eth2: flags=4163 mtu 1500
87 | inet6 fe80::1490:40ff:fe65:1c4 prefixlen 64 scopeid 0x20
88 | ether 16:90:40:65:01:c4 txqueuelen 1000 (Ethernet)
89 | RX packets 6 bytes 508 (508.0 B)
90 | RX errors 0 dropped 0 overruns 0 frame 0
91 | TX packets 6 bytes 508 (508.0 B)
92 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
93 | ```
94 | "target-eth0" is the management interface of the emulated AP, where gNMI target listens on (default port 10162).
95 |
96 | The link022 agent process runs inside "target" node:
97 | ```
98 | mininet> target ps aux | grep ../binary/link022_agent
99 | root 28499 0.0 0.0 777732 14664 ? Ssl 16:08 0:00 ../binary/link022_agent -ca ../demo/cert/server/ca.crt -cert ../demo/cert/server/server.crt -key ../demo/cert/server/server.key -eth_intf_name target-eth1 -wlan_intf_name target-eth2
100 | ```
101 |
102 | The link022 log is "/tmp/link022_agent.INFO" by default.
103 |
104 | ### 5. Config Link022 AP
105 | All gNMI requests working on a physical Link022 AP should also work on the emulated one.
106 | Run gNMI client in "ctrlr" node.
107 |
108 | Here are some examples to start with:
109 |
110 | 1. Download GNMI clients.
111 | * Download and compile [gNXI "SET" client](https://github.com/google/gnxi/tree/master/gnmi_set).
112 | * Download and compile [gNXI "GET" client](https://github.com/google/gnxi/tree/master/gnmi_get).
113 |
114 | 2. Pushing the entire configuration to AP. It wipes out the existing configuration and applies the incoming one.
115 | ```
116 | mininet> xterm ctrlr
117 | {path to gnmi_set binary} \
118 | -ca ../demo/cert/client/ca.crt \
119 | -cert ../demo/cert/client/client.crt \
120 | -key ../demo/cert/client/client.key \
121 | -target_name www.example.com \
122 | -target_addr 10.0.0.1:10162 \
123 | -replace=/:@../tests/ap_config.json
124 | ```
125 |
126 | 3. Fetch AP configuration
127 | ```
128 | mininet> xterm ctrlr
129 | {path to gnmi_get binary} \
130 | -ca ../demo/cert/client/ca.crt \
131 | -cert ../demo/cert/client/client.crt \
132 | -key ../demo/cert/client/client.key \
133 | -target_name www.example.com \
134 | -target_addr 10.0.0.1:10162 \
135 | -xpath "/access-points/access-point[hostname=link022-pi-ap]/radios/radio[id=1]/config/channel"
136 | ```
137 |
138 | The output should be similar to:
139 | ```
140 | == getResponse:
141 | notification: <
142 | timestamp: 1521145574058185274
143 | update: <
144 | path: <
145 | elem: <
146 | name: "access-points"
147 | >
148 | elem: <
149 | name: "access-point"
150 | key: <
151 | key: "hostname"
152 | value: "link022-pi-ap"
153 | >
154 | >
155 | elem: <
156 | name: "radios"
157 | >
158 | elem: <
159 | name: "radio"
160 | key: <
161 | key: "id"
162 | value: "1"
163 | >
164 | >
165 | elem: <
166 | name: "config"
167 | >
168 | elem: <
169 | name: "channel"
170 | >
171 | >
172 | val: <
173 | uint_val: 8
174 | >
175 | >
176 | >
177 | ```
178 |
--------------------------------------------------------------------------------
/testkit/util/gnmiutil/helper.go:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 Google Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 |
16 | // Package gnmiutil contains helper functions related to gNMI.
17 | package gnmiutil
18 |
19 | import (
20 | "bytes"
21 | "encoding/json"
22 | "fmt"
23 | "io/ioutil"
24 | "reflect"
25 | "strconv"
26 |
27 | pb "github.com/openconfig/gnmi/proto/gnmi"
28 | )
29 |
30 | // GNMIFullPath builds the full path from the prefix and path.
31 | func GNMIFullPath(prefix, path *pb.Path) *pb.Path {
32 | if prefix == nil {
33 | return path
34 | }
35 |
36 | fullPath := &pb.Path{Origin: path.Origin}
37 | if path.GetElem() != nil {
38 | fullPath.Elem = append(prefix.GetElem(), path.GetElem()...)
39 | }
40 | return fullPath
41 | }
42 |
43 | // ToPbVal convert string to TypedValue defined in gNMI proto.
44 | // Supported types:
45 | // Integer: "1", "2"
46 | // Float: "1.5", "2.4"
47 | // String: "abc", "defg"
48 | // Boolean: "true", "false"
49 | // IETF JSON from file: "@ap_config.json"
50 | func ToPbVal(stringVal string) (*pb.TypedValue, error) {
51 | if stringVal[0] == '@' {
52 | jsonFile := stringVal[1:]
53 | jsonConfig, err := ioutil.ReadFile(jsonFile)
54 | if err != nil {
55 | return nil, fmt.Errorf("cannot read data from file %v: %v", jsonFile, err)
56 | }
57 | jsonConfig = bytes.Trim(jsonConfig, " \r\n\t")
58 | return &pb.TypedValue{
59 | Value: &pb.TypedValue_JsonIetfVal{
60 | JsonIetfVal: jsonConfig,
61 | },
62 | }, nil
63 | }
64 |
65 | if strVal, err := strconv.Unquote(stringVal); err == nil {
66 | return &pb.TypedValue{
67 | Value: &pb.TypedValue_StringVal{
68 | StringVal: strVal,
69 | },
70 | }, nil
71 | }
72 |
73 | if intVal, err := strconv.ParseInt(stringVal, 10, 64); err == nil {
74 | return &pb.TypedValue{
75 | Value: &pb.TypedValue_IntVal{
76 | IntVal: intVal,
77 | },
78 | }, nil
79 | }
80 |
81 | if floatVal, err := strconv.ParseFloat(stringVal, 32); err == nil {
82 | return &pb.TypedValue{
83 | Value: &pb.TypedValue_FloatVal{
84 | FloatVal: float32(floatVal),
85 | },
86 | }, nil
87 | }
88 |
89 | if boolVal, err := strconv.ParseBool(stringVal); err == nil {
90 | return &pb.TypedValue{
91 | Value: &pb.TypedValue_BoolVal{
92 | BoolVal: boolVal,
93 | },
94 | }, nil
95 | }
96 |
97 | return &pb.TypedValue{
98 | Value: &pb.TypedValue_StringVal{
99 | StringVal: stringVal,
100 | },
101 | }, nil
102 | }
103 |
104 | // GNMIPathEquals checks whether the two given gNMI path equal to each other.
105 | func GNMIPathEquals(actual, expected *pb.Path) bool {
106 | if len(actual.Elem) != len(expected.Elem) {
107 | return false
108 | }
109 |
110 | for i := 0; i < len(actual.Elem); i++ {
111 | actualElem := actual.Elem[i]
112 | expectedElem := expected.Elem[i]
113 | if actualElem.Name != expectedElem.Name || !reflect.DeepEqual(actualElem.Key, expectedElem.Key) {
114 | return false
115 | }
116 | }
117 |
118 | return true
119 | }
120 |
121 | // ValEqual checks whether the given two input value equal.
122 | // It returns error if values are not equal.
123 | func ValEqual(gnmiPath *pb.Path, actual *pb.TypedValue, expected *pb.TypedValue) error {
124 | actualValue := actual.Value
125 | expectedValue := expected.Value
126 |
127 | // JSON value.
128 | actualJsonValue, okA := actualValue.(*pb.TypedValue_JsonIetfVal)
129 | expectedJsonValue, okE := expectedValue.(*pb.TypedValue_JsonIetfVal)
130 | if okA && okE {
131 | var actualJson, expectedJson interface{}
132 | if err := json.Unmarshal(actualJsonValue.JsonIetfVal, &actualJson); err != nil {
133 | return fmt.Errorf("invalid value %v: %v", string(actualJsonValue.JsonIetfVal), err)
134 | }
135 | if err := json.Unmarshal(expectedJsonValue.JsonIetfVal, &expectedJson); err != nil {
136 | return fmt.Errorf("invalid value %v: %v", string(expectedJsonValue.JsonIetfVal), err)
137 | }
138 | if err := jsonValEquals(actualJson, expectedJson); err != nil {
139 | return fmt.Errorf("incorrect json config on %v, actual = %v, expected = %v, detail = %v", gnmiPath, string(actualJsonValue.JsonIetfVal), string(expectedJsonValue.JsonIetfVal), err)
140 | }
141 | return nil
142 | }
143 |
144 | // Other value types.
145 |
146 | // No need to check uint64/int64 type matching.
147 | if uintValue, ok := actualValue.(*pb.TypedValue_UintVal); ok {
148 | actualValue = &pb.TypedValue_IntVal{
149 | IntVal: int64(uintValue.UintVal),
150 | }
151 | }
152 | if !reflect.DeepEqual(actualValue, expectedValue) {
153 | return fmt.Errorf("incorrect config on %v, actual = %v, expected = %v", gnmiPath, actualValue, expectedValue)
154 | }
155 | return nil
156 | }
157 |
158 | func jsonValEquals(actual, expected interface{}) error {
159 | // Both are JSON array.
160 | actualJSONArr, okA := actual.([]interface{})
161 | expectedJSONArr, okE := expected.([]interface{})
162 | if okA && okE {
163 | if len(actualJSONArr) != len(expectedJSONArr) {
164 | return fmt.Errorf("array length no equal, actual = %v, expected = %v", actual, expected)
165 | }
166 |
167 | for _, expectedElem := range expectedJSONArr {
168 | found := false
169 | for _, actualElem := range actualJSONArr {
170 | if err := jsonValEquals(actualElem, actualElem); err == nil {
171 | found = true
172 | break
173 | }
174 | }
175 | if !found {
176 | return fmt.Errorf("not found the expected element %v in array, actual = %v, expected = %v", expectedElem, actual, expected)
177 | }
178 | }
179 | return nil
180 | }
181 |
182 | // Both are JSON blob.
183 | actualJSON, okA := actual.(map[string]interface{})
184 | expectedJSON, okE := expected.(map[string]interface{})
185 | if okA && okE {
186 | for key, expectedVal := range expectedJSON {
187 | actualVal, ok := actualJSON[key]
188 | if !ok {
189 | return fmt.Errorf("miss the property %s, expected = %v", key, expectedVal)
190 | }
191 | if err := jsonValEquals(actualVal, expectedVal); err != nil {
192 | return err
193 | }
194 | }
195 | return nil
196 | }
197 |
198 | // Other value types.
199 | if !reflect.DeepEqual(actual, expected) {
200 | return fmt.Errorf("incorrect value, actual = %v, expected = %v", actual, expected)
201 | }
202 | return nil
203 | }
204 |
--------------------------------------------------------------------------------