diff --git a/access_point_config.go b/access_point_config.go new file mode 100644 index 0000000..93c2cde --- /dev/null +++ b/access_point_config.go @@ -0,0 +1,113 @@ +// Copyright 2017 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Methods for configuring a Linksys WRT1900ACS access point running OpenWRT for team SSIDs and VLANs. + +package main + +import ( + "bytes" + "fmt" + "golang.org/x/crypto/ssh" + "os" + "sync" + "text/template" +) + +var accessPointSshPort = 22 + +const ( + red1Vlan = 10 + red2Vlan = 20 + red3Vlan = 30 + blue1Vlan = 40 + blue2Vlan = 50 + blue3Vlan = 60 +) + +var accessPointMutex sync.Mutex + +// Sets up wireless networks for the given set of teams. +func ConfigureTeamWifi(red1, red2, red3, blue1, blue2, blue3 *Team) error { + // Make sure multiple configurations aren't being set at the same time. + accessPointMutex.Lock() + defer accessPointMutex.Unlock() + + config, err := generateAccessPointConfig(red1, red2, red3, blue1, blue2, blue3) + if err != nil { + return err + } + command := fmt.Sprintf("cat < /etc/config/wireless && wifi radio0\n%sENDCONFIG\n", config) + return runAccessPointCommand(command) +} + +func generateAccessPointConfig(red1, red2, red3, blue1, blue2, blue3 *Team) (string, error) { + // Determine what new SSIDs are needed. + networks := make(map[int]*Team) + var err error + if err = addTeamNetwork(networks, red1, red1Vlan); err != nil { + return "", err + } + if err = addTeamNetwork(networks, red2, red2Vlan); err != nil { + return "", err + } + if err = addTeamNetwork(networks, red3, red3Vlan); err != nil { + return "", err + } + if err = addTeamNetwork(networks, blue1, blue1Vlan); err != nil { + return "", err + } + if err = addTeamNetwork(networks, blue2, blue2Vlan); err != nil { + return "", err + } + if err = addTeamNetwork(networks, blue3, blue3Vlan); err != nil { + return "", err + } + + // Generate the config file to be uploaded to the AP. + template, err := template.ParseFiles("templates/access_point.cfg") + if err != nil { + return "", err + } + var configFile bytes.Buffer + err = template.Execute(&configFile, networks) + if err != nil { + return "", err + } + + return configFile.String(), nil +} + +// Verifies the validity of the given team's WPA key and adds a network for it to the list to be configured. +func addTeamNetwork(networks map[int]*Team, team *Team, vlan int) error { + if team == nil { + return nil + } + if len(team.WpaKey) < 8 || len(team.WpaKey) > 63 { + return fmt.Errorf("Invalid WPA key '%s' configured for team %d.", team.WpaKey, team.Id) + } + networks[vlan] = team + return nil +} + +// Logs into the access point via SSH and runs the given shell command. +func runAccessPointCommand(command string) error { + // Open an SSH connection to the AP. + config := &ssh.ClientConfig{User: eventSettings.ApUsername, + Auth: []ssh.AuthMethod{ssh.Password(eventSettings.ApPassword)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey()} + conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", eventSettings.ApAddress, accessPointSshPort), config) + if err != nil { + return err + } + session, err := conn.NewSession() + if err != nil { + return err + } + defer session.Close() + defer conn.Close() + session.Stdout = os.Stdout + + // Run the command. An error will be returned if the exit status is non-zero. + return session.Run(command) +} diff --git a/access_point_config.tar.gz b/access_point_config.tar.gz new file mode 100644 index 0000000..7679723 Binary files /dev/null and b/access_point_config.tar.gz differ diff --git a/access_point_config_test.go b/access_point_config_test.go new file mode 100644 index 0000000..1fb1001 --- /dev/null +++ b/access_point_config_test.go @@ -0,0 +1,80 @@ +// Copyright 2014 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package main + +import ( + "github.com/stretchr/testify/assert" + "regexp" + "testing" +) + +func TestConfigureAccessPoint(t *testing.T) { + radioRe := regexp.MustCompile("option device 'radio0'") + ssidRe := regexp.MustCompile("option ssid '([-\\w ]+)'") + wpaKeyRe := regexp.MustCompile("option key '([-\\w ]+)'") + vlanRe := regexp.MustCompile("option network 'vlan(\\d+)'") + + // Should not configure any team SSIDs if there are no teams. + config, _ := generateAccessPointConfig(nil, nil, nil, nil, nil, nil) + assert.NotContains(t, config, "option device 'radio0'") + ssids := ssidRe.FindAllStringSubmatch(config, -1) + wpaKeys := wpaKeyRe.FindAllStringSubmatch(config, -1) + vlans := vlanRe.FindAllStringSubmatch(config, -1) + assert.Equal(t, "Cheesy Arena", ssids[0][1]) + assert.Equal(t, "1234Five", wpaKeys[0][1]) + assert.Equal(t, "100", vlans[0][1]) + + // Should configure two SSID for two teams. + config, _ = generateAccessPointConfig(&Team{Id: 254, WpaKey: "aaaaaaaa"}, nil, nil, nil, nil, + &Team{Id: 1114, WpaKey: "bbbbbbbb"}) + assert.Equal(t, 2, len(radioRe.FindAllString(config, -1))) + ssids = ssidRe.FindAllStringSubmatch(config, -1) + wpaKeys = wpaKeyRe.FindAllStringSubmatch(config, -1) + vlans = vlanRe.FindAllStringSubmatch(config, -1) + assert.Equal(t, "Cheesy Arena", ssids[0][1]) + assert.Equal(t, "1234Five", wpaKeys[0][1]) + assert.Equal(t, "100", vlans[0][1]) + assert.Equal(t, "254", ssids[1][1]) + assert.Equal(t, "aaaaaaaa", wpaKeys[1][1]) + assert.Equal(t, "10", vlans[1][1]) + assert.Equal(t, "1114", ssids[2][1]) + assert.Equal(t, "bbbbbbbb", wpaKeys[2][1]) + assert.Equal(t, "60", vlans[2][1]) + + // Should configure all SSIDs for six teams. + config, _ = generateAccessPointConfig(&Team{Id: 1, WpaKey: "11111111"}, + &Team{Id: 2, WpaKey: "22222222"}, &Team{Id: 3, WpaKey: "33333333"}, &Team{Id: 4, WpaKey: "44444444"}, + &Team{Id: 5, WpaKey: "55555555"}, &Team{Id: 6, WpaKey: "66666666"}) + assert.Equal(t, 6, len(radioRe.FindAllString(config, -1))) + ssids = ssidRe.FindAllStringSubmatch(config, -1) + wpaKeys = wpaKeyRe.FindAllStringSubmatch(config, -1) + vlans = vlanRe.FindAllStringSubmatch(config, -1) + assert.Equal(t, "Cheesy Arena", ssids[0][1]) + assert.Equal(t, "1234Five", wpaKeys[0][1]) + assert.Equal(t, "100", vlans[0][1]) + assert.Equal(t, "1", ssids[1][1]) + assert.Equal(t, "11111111", wpaKeys[1][1]) + assert.Equal(t, "10", vlans[1][1]) + assert.Equal(t, "2", ssids[2][1]) + assert.Equal(t, "22222222", wpaKeys[2][1]) + assert.Equal(t, "20", vlans[2][1]) + assert.Equal(t, "3", ssids[3][1]) + assert.Equal(t, "33333333", wpaKeys[3][1]) + assert.Equal(t, "30", vlans[3][1]) + assert.Equal(t, "4", ssids[4][1]) + assert.Equal(t, "44444444", wpaKeys[4][1]) + assert.Equal(t, "40", vlans[4][1]) + assert.Equal(t, "5", ssids[5][1]) + assert.Equal(t, "55555555", wpaKeys[5][1]) + assert.Equal(t, "50", vlans[5][1]) + assert.Equal(t, "6", ssids[6][1]) + assert.Equal(t, "66666666", wpaKeys[6][1]) + assert.Equal(t, "60", vlans[6][1]) + + // Should reject a missing WPA key. + _, err := generateAccessPointConfig(&Team{Id: 254}, nil, nil, nil, nil, nil) + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "Invalid WPA key") + } +} diff --git a/aironet.go b/aironet.go deleted file mode 100644 index c92560b..0000000 --- a/aironet.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2014 Team 254. All Rights Reserved. -// Author: pat@patfairbank.com (Patrick Fairbank) -// -// Methods for configuring a Cisco Aironet AP1252AG access point for team SSIDs and VLANs. - -package main - -import ( - "bufio" - "bytes" - "fmt" - "net" - "regexp" - "strconv" - "sync" -) - -var aironetTelnetPort = 23 - -const ( - red1Vlan = 11 - red2Vlan = 12 - red3Vlan = 13 - blue1Vlan = 14 - blue2Vlan = 15 - blue3Vlan = 16 -) - -var aironetMutex sync.Mutex - -// Sets up wireless networks for the given set of teams. -func ConfigureTeamWifi(red1, red2, red3, blue1, blue2, blue3 *Team) error { - // Make sure multiple configurations aren't being set at the same time. - aironetMutex.Lock() - defer aironetMutex.Unlock() - - for _, team := range []*Team{red1, red2, red3, blue1, blue2, blue3} { - if team != nil && (len(team.WpaKey) < 8 || len(team.WpaKey) > 63) { - return fmt.Errorf("Invalid WPA key '%s' configured for team %d.", team.WpaKey, team.Id) - } - } - - // Determine what new SSIDs are needed and build the commands to set them up. - oldSsids, err := getSsids() - if err != nil { - return err - } - addSsidsCommand := "" - associateSsidsCommand := "" - replaceSsid := func(team *Team, vlan int) { - if team == nil { - return - } - if oldSsids[strconv.Itoa(team.Id)] == vlan { - delete(oldSsids, strconv.Itoa(team.Id)) - } else { - addSsidsCommand += fmt.Sprintf("dot11 ssid %d\nvlan %d\nauthentication open\nauthentication "+ - "key-management wpa version 2\nmbssid guest-mode\nwpa-psk ascii %s\n", team.Id, vlan, team.WpaKey) - associateSsidsCommand += fmt.Sprintf("ssid %d\n", team.Id) - } - } - replaceSsid(red1, red1Vlan) - replaceSsid(red2, red2Vlan) - replaceSsid(red3, red3Vlan) - replaceSsid(blue1, blue1Vlan) - replaceSsid(blue2, blue2Vlan) - replaceSsid(blue3, blue3Vlan) - if len(addSsidsCommand) != 0 { - associateSsidsCommand = "interface Dot11Radio1\n" + associateSsidsCommand - } - - // Build the command to remove the SSIDs that are no longer needed. - removeSsidsCommand := "" - for ssid, _ := range oldSsids { - removeSsidsCommand += fmt.Sprintf("no dot11 ssid %s\n", ssid) - } - - // Build and run the overall command to do everything in a single telnet session. - command := removeSsidsCommand + addSsidsCommand + associateSsidsCommand - if len(command) > 0 { - _, err = runAironetConfigCommand(removeSsidsCommand + addSsidsCommand + associateSsidsCommand) - if err != nil { - return err - } - } - - return nil -} - -// Returns a map of currently-configured SSIDs to VLANs. -func getSsids() (map[string]int, error) { - // Get the entire config dump. - config, err := runAironetCommand("show running-config\n") - if err != nil { - return nil, err - } - - // Parse out the SSIDs and VLANs from the config dump. - re := regexp.MustCompile("(?s)dot11 ssid (\\w+)\\s+vlan (1[1-6])") - ssidMatches := re.FindAllStringSubmatch(config, -1) - if ssidMatches == nil { - // There are probably no SSIDs currently configured. - return nil, nil - } - - // Build the map of SSID to VLAN. - ssids := make(map[string]int) - for _, match := range ssidMatches { - vlan, _ := strconv.Atoi(match[2]) - ssids[match[1]] = vlan - } - return ssids, nil -} - -// Logs into the Aironet via Telnet and runs the given command in user exec mode. Reads the output and returns -// it as a string. -func runAironetCommand(command string) (string, error) { - // Open a Telnet connection to the AP. - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", eventSettings.ApAddress, aironetTelnetPort)) - if err != nil { - return "", err - } - defer conn.Close() - - // Login to the AP, send the command, and log out all at once. - writer := bufio.NewWriter(conn) - _, err = writer.WriteString(fmt.Sprintf("%s\n%s\nterminal length 0\n%sexit\n", eventSettings.ApUsername, - eventSettings.ApPassword, command)) - if err != nil { - return "", err - } - err = writer.Flush() - if err != nil { - return "", err - } - - // Read the response. - var reader bytes.Buffer - _, err = reader.ReadFrom(conn) - if err != nil { - return "", err - } - return reader.String(), nil -} - -// Logs into the Aironet via Telnet and runs the given command in global configuration mode. Reads the output -// and returns it as a string. -func runAironetConfigCommand(command string) (string, error) { - return runAironetCommand(fmt.Sprintf("config terminal\n%send\ncopy running-config startup-config\n\n", - command)) -} diff --git a/aironet_test.go b/aironet_test.go deleted file mode 100644 index 703c4d5..0000000 --- a/aironet_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2014 Team 254. All Rights Reserved. -// Author: pat@patfairbank.com (Patrick Fairbank) - -package main - -import ( - "bytes" - "fmt" - "github.com/stretchr/testify/assert" - "net" - "testing" - "time" -) - -func TestConfigureAironet(t *testing.T) { - aironetTelnetPort = 9023 - eventSettings = &EventSettings{ApAddress: "127.0.0.1", ApUsername: "user", ApPassword: "password"} - var command string - - // Should do nothing if current configuration is blank. - mockTelnet(t, aironetTelnetPort, "", &command) - assert.Nil(t, ConfigureTeamWifi(nil, nil, nil, nil, nil, nil)) - assert.Equal(t, "", command) - - // Should remove any existing teams but not other SSIDs. - aironetTelnetPort += 1 - mockTelnet(t, aironetTelnetPort, - "dot11 ssid 1\nvlan 1\ndot11 ssid 254\nvlan 12\ndot11 ssid Cheesy Arena\nvlan 17\n", &command) - assert.Nil(t, ConfigureTeamWifi(nil, nil, nil, nil, nil, nil)) - assert.Equal(t, "user\npassword\nterminal length 0\nconfig terminal\nno dot11 ssid 254\nend\n"+ - "copy running-config startup-config\n\nexit\n", command) - - // Should configure new teams and leave existing ones alone if still needed. - aironetTelnetPort += 1 - mockTelnet(t, aironetTelnetPort, "dot11 ssid 254\nvlan 11\n", &command) - assert.Nil(t, ConfigureTeamWifi(&Team{Id: 254, WpaKey: "aaaaaaaa"}, nil, nil, nil, nil, - &Team{Id: 1114, WpaKey: "bbbbbbbb"})) - assert.Equal(t, "user\npassword\nterminal length 0\nconfig terminal\ndot11 ssid 1114\nvlan 16\n"+ - "authentication open\nauthentication key-management wpa version 2\nmbssid guest-mode\nwpa-psk ascii "+ - "bbbbbbbb\ninterface Dot11Radio1\nssid 1114\nend\ncopy running-config startup-config\n\nexit\n", - command) - - // Should reject a missing WPA key. - aironetTelnetPort += 1 - mockTelnet(t, aironetTelnetPort, "", &command) - err := ConfigureTeamWifi(&Team{Id: 254}, nil, nil, nil, nil, nil) - if assert.NotNil(t, err) { - assert.Contains(t, err.Error(), "Invalid WPA key") - } -} - -func mockTelnet(t *testing.T, port int, response string, command *string) { - go func() { - // Fake the first connection which should just get the configuration. - ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - assert.Nil(t, err) - defer ln.Close() - conn, err := ln.Accept() - assert.Nil(t, err) - conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - var reader bytes.Buffer - reader.ReadFrom(conn) - assert.Contains(t, reader.String(), "terminal length 0\nshow running-config\nexit\n") - conn.Write([]byte(response)) - conn.Close() - - // Fake the second connection which should configure stuff. - conn2, err := ln.Accept() - assert.Nil(t, err) - conn2.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - var reader2 bytes.Buffer - reader2.ReadFrom(conn2) - *command = reader2.String() - conn2.Close() - }() - time.Sleep(100 * time.Millisecond) // Give it some time to open the socket. -} diff --git a/ap_config.txt b/ap_config.txt deleted file mode 100644 index 8fb8fde..0000000 --- a/ap_config.txt +++ /dev/null @@ -1,316 +0,0 @@ -! Baseline configuration for the Cisco Aironet AP1252AG access point. Load this into the AP prior to -! configuring Cheesy Arena to connect to it. Default user/pass is cheesyarena/1234Five. -! -version 15.2 -no service pad -service timestamps debug datetime msec -service timestamps log datetime msec -service password-encryption -! -hostname ChezyAP -! -logging rate-limit console 9 -! -aaa new-model -! -! -aaa authentication login default local -aaa authorization exec default local -! -! -! -! -! -aaa session-id common -! -! -dot11 syslog -dot11 vlan-name Blue1 vlan 14 -dot11 vlan-name Blue2 vlan 15 -dot11 vlan-name Blue3 vlan 16 -dot11 vlan-name CheesyArena vlan 2 -dot11 vlan-name Red1 vlan 11 -dot11 vlan-name Red2 vlan 12 -dot11 vlan-name Red3 vlan 13 -! -dot11 ssid 1 - vlan 11 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 0257550A5A575E701D -! -dot11 ssid 2 - vlan 12 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 06545D731E1C5B4B57 -! -dot11 ssid 3 - vlan 13 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 115A4A564441585F57 -! -dot11 ssid 4 - vlan 14 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 101A5D4D5143465F58 -! -dot11 ssid 5 - vlan 15 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 00514653510E5E535A -! -dot11 ssid 6 - vlan 16 - authentication open - authentication key-management wpa version 2 - mbssid guest-mode - wpa-psk ascii 7 1441445D5A527C7D72 -! -dot11 ssid Cheesy Arena - vlan 2 - authentication open - authentication key-management wpa version 2 - guest-mode - wpa-psk ascii 7 144640585822233D21 -! -crypto pki token default removal timeout 0 -! -! -username cheesyarena privilege 15 password 7 040A59555B0745580C -! -! -bridge irb -! -! -interface Dot11Radio0 - no ip address - no ip route-cache - ! - encryption mode ciphers aes-ccm tkip - ! - encryption vlan 2 mode ciphers aes-ccm tkip - ! - ssid Cheesy Arena - ! - antenna gain 0 - station-role root - no dot11 extension aironet - bridge-group 1 - bridge-group 1 subscriber-loop-control - bridge-group 1 spanning-disabled - bridge-group 1 block-unknown-source - no bridge-group 1 source-learning - no bridge-group 1 unicast-flooding -! -interface Dot11Radio0.2 - encapsulation dot1Q 2 - no ip route-cache - bridge-group 2 - bridge-group 2 subscriber-loop-control - bridge-group 2 spanning-disabled - bridge-group 2 block-unknown-source - no bridge-group 2 source-learning - no bridge-group 2 unicast-flooding -! -interface Dot11Radio1 - no ip address - no ip route-cache - ! - encryption mode ciphers aes-ccm tkip - ! - encryption vlan 11 mode ciphers aes-ccm tkip - ! - encryption vlan 12 mode ciphers aes-ccm tkip - ! - encryption vlan 13 mode ciphers aes-ccm tkip - ! - encryption vlan 14 mode ciphers aes-ccm tkip - ! - encryption vlan 15 mode ciphers aes-ccm tkip - ! - encryption vlan 16 mode ciphers aes-ccm tkip - ! - ssid 1 - ! - ssid 2 - ! - ssid 3 - ! - ssid 4 - ! - ssid 5 - ! - ssid 6 - ! - antenna gain 0 - dfs band 3 block - mbssid - channel width 40-above - channel dfs - station-role root - no dot11 extension aironet - bridge-group 1 - bridge-group 1 subscriber-loop-control - bridge-group 1 spanning-disabled - bridge-group 1 block-unknown-source - no bridge-group 1 source-learning - no bridge-group 1 unicast-flooding -! -interface Dot11Radio1.11 - encapsulation dot1Q 11 - no ip route-cache - bridge-group 11 - bridge-group 11 subscriber-loop-control - bridge-group 11 spanning-disabled - bridge-group 11 block-unknown-source - no bridge-group 11 source-learning - no bridge-group 11 unicast-flooding -! -interface Dot11Radio1.12 - encapsulation dot1Q 12 - no ip route-cache - bridge-group 12 - bridge-group 12 subscriber-loop-control - bridge-group 12 spanning-disabled - bridge-group 12 block-unknown-source - no bridge-group 12 source-learning - no bridge-group 12 unicast-flooding -! -interface Dot11Radio1.13 - encapsulation dot1Q 13 - no ip route-cache - bridge-group 13 - bridge-group 13 subscriber-loop-control - bridge-group 13 spanning-disabled - bridge-group 13 block-unknown-source - no bridge-group 13 source-learning - no bridge-group 13 unicast-flooding -! -interface Dot11Radio1.14 - encapsulation dot1Q 14 - no ip route-cache - bridge-group 14 - bridge-group 14 subscriber-loop-control - bridge-group 14 spanning-disabled - bridge-group 14 block-unknown-source - no bridge-group 14 source-learning - no bridge-group 14 unicast-flooding -! -interface Dot11Radio1.15 - encapsulation dot1Q 15 - no ip route-cache - bridge-group 15 - bridge-group 15 subscriber-loop-control - bridge-group 15 spanning-disabled - bridge-group 15 block-unknown-source - no bridge-group 15 source-learning - no bridge-group 15 unicast-flooding -! -interface Dot11Radio1.16 - encapsulation dot1Q 16 - no ip route-cache - bridge-group 16 - bridge-group 16 subscriber-loop-control - bridge-group 16 spanning-disabled - bridge-group 16 block-unknown-source - no bridge-group 16 source-learning - no bridge-group 16 unicast-flooding -! -interface GigabitEthernet0 - no ip address - no ip route-cache - duplex auto - speed auto - bridge-group 1 - bridge-group 1 spanning-disabled - no bridge-group 1 source-learning -! -interface GigabitEthernet0.2 - encapsulation dot1Q 2 - no ip route-cache - bridge-group 2 - bridge-group 2 spanning-disabled - no bridge-group 2 source-learning -! -interface GigabitEthernet0.11 - encapsulation dot1Q 11 - ip access-group 100 in - no ip route-cache - bridge-group 11 - bridge-group 11 spanning-disabled - no bridge-group 11 source-learning -! -interface GigabitEthernet0.12 - encapsulation dot1Q 12 - ip access-group 100 in - no ip route-cache - bridge-group 12 - bridge-group 12 spanning-disabled - no bridge-group 12 source-learning -! -interface GigabitEthernet0.13 - encapsulation dot1Q 13 - ip access-group 100 in - no ip route-cache - bridge-group 13 - bridge-group 13 spanning-disabled - no bridge-group 13 source-learning -! -interface GigabitEthernet0.14 - encapsulation dot1Q 14 - ip access-group 100 in - no ip route-cache - bridge-group 14 - bridge-group 14 spanning-disabled - no bridge-group 14 source-learning -! -interface GigabitEthernet0.15 - encapsulation dot1Q 15 - ip access-group 100 in - no ip route-cache - bridge-group 15 - bridge-group 15 spanning-disabled - no bridge-group 15 source-learning -! -interface GigabitEthernet0.16 - encapsulation dot1Q 16 - ip access-group 100 in - ip access-group 101 out - no ip route-cache - bridge-group 16 - bridge-group 16 spanning-disabled - no bridge-group 16 source-learning -! -interface BVI1 - ip address 10.0.0.60 255.0.0.0 - no ip route-cache -! -ip default-gateway 10.0.0.1 -ip http server -ip http authentication aaa -no ip http secure-server -ip http help-path http://www.cisco.com/warp/public/779/smbiz/prodconfig/help/eag -access-list 100 deny udp any any eq 1120 -access-list 100 permit ip any any -access-list 101 deny tcp any any eq 1750 -access-list 101 permit ip any any -! -bridge 1 route ip -! -! -! -line con 0 -line vty 0 4 - transport input all -! -sntp server 216.66.0.142 -end diff --git a/arena_test.go b/arena_test.go index 9cff24f..93b353f 100644 --- a/arena_test.go +++ b/arena_test.go @@ -495,8 +495,8 @@ func TestSetupNetwork(t *testing.T) { // Verify the setup ran by checking the log for the expected failure messages. eventSettings.NetworkSecurityEnabled = true - aironetTelnetPort = 10023 - catalystTelnetPort = 10023 + accessPointSshPort = 10022 + switchTelnetPort = 10023 mainArena.LoadMatch(&Match{Type: "test"}) var writer bytes.Buffer log.SetOutput(&writer) diff --git a/catalyst_test.go b/catalyst_test.go deleted file mode 100644 index a04c89a..0000000 --- a/catalyst_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2014 Team 254. All Rights Reserved. -// Author: pat@patfairbank.com (Patrick Fairbank) - -package main - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestConfigureCatalyst(t *testing.T) { - catalystTelnetPort = 9050 - eventSettings = &EventSettings{SwitchAddress: "127.0.0.1", SwitchPassword: "password"} - var command string - - // Should do nothing if current configuration is blank. - mockTelnet(t, catalystTelnetPort, "", &command) - assert.Nil(t, ConfigureTeamEthernet(nil, nil, nil, nil, nil, nil)) - assert.Equal(t, "", command) - - // Should remove any existing teams but not other SSIDs. - catalystTelnetPort += 1 - mockTelnet(t, catalystTelnetPort, - "interface Vlan2\nip address 10.0.100.2\ninterface Vlan15\nip address 10.2.54.61\n", &command) - assert.Nil(t, ConfigureTeamEthernet(nil, nil, nil, nil, nil, nil)) - assert.Equal(t, "password\nenable\npassword\nterminal length 0\nconfig terminal\ninterface Vlan15\nno ip"+ - " address\nno access-list 115\nend\ncopy running-config startup-config\n\nexit\n", command) - - // Should configure new teams and leave existing ones alone if still needed. - catalystTelnetPort += 1 - mockTelnet(t, catalystTelnetPort, "interface Vlan15\nip address 10.2.54.61\n", &command) - assert.Nil(t, ConfigureTeamEthernet(nil, &Team{Id: 1114}, nil, nil, &Team{Id: 254}, nil)) - assert.Equal(t, "password\nenable\npassword\nterminal length 0\nconfig terminal\n"+ - "ip dhcp excluded-address 10.11.14.1 10.11.14.100\nno ip dhcp pool dhcp12\nip dhcp pool dhcp12\n"+ - "network 10.11.14.0 255.255.255.0\ndefault-router 10.11.14.61\nlease 7\nno access-list 112\n"+ - "access-list 112 permit ip 10.11.14.0 0.0.0.255 host 10.0.100.5\n"+ - "access-list 112 permit udp any eq bootpc any eq bootps\ninterface Vlan12\n"+ - "ip address 10.11.14.61 255.255.255.0\nend\ncopy running-config startup-config\n\nexit\n", command) -} diff --git a/driver_station_connection_test.go b/driver_station_connection_test.go index 8c4fc22..fe8d1af 100644 --- a/driver_station_connection_test.go +++ b/driver_station_connection_test.go @@ -154,10 +154,12 @@ func TestListenForDriverStations(t *testing.T) { defer db.Close() eventSettings, _ = db.GetEventSettings() + oldAddress := driverStationTcpListenAddress driverStationTcpListenAddress = "127.0.0.1" go ListenForDriverStations() mainArena.Setup() time.Sleep(time.Millisecond * 10) + driverStationTcpListenAddress = oldAddress // Put it back to avoid affecting other tests. // Connect with an invalid initial packet. tcpConn, err := net.Dial("tcp", "127.0.0.1:1750") diff --git a/catalyst.go b/switch_config.go similarity index 82% rename from catalyst.go rename to switch_config.go index 3daa16d..4166bfa 100644 --- a/catalyst.go +++ b/switch_config.go @@ -1,7 +1,7 @@ // Copyright 2014 Team 254. All Rights Reserved. // Author: pat@patfairbank.com (Patrick Fairbank) // -// Methods for configuring a Cisco Catalyst 3750 switch for team VLANs. +// Methods for configuring a Cisco Switch 3500-series switch for team VLANs. package main @@ -15,15 +15,15 @@ import ( "sync" ) -var catalystTelnetPort = 23 +var switchTelnetPort = 23 -var catalystMutex sync.Mutex +var switchMutex sync.Mutex // Sets up wired networks for the given set of teams. func ConfigureTeamEthernet(red1, red2, red3, blue1, blue2, blue3 *Team) error { // Make sure multiple configurations aren't being set at the same time. - catalystMutex.Lock() - defer catalystMutex.Unlock() + switchMutex.Lock() + defer switchMutex.Unlock() // Determine what new team VLANs are needed and build the commands to set them up. oldTeamVlans, err := getTeamVlans() @@ -70,7 +70,7 @@ func ConfigureTeamEthernet(red1, red2, red3, blue1, blue2, blue3 *Team) error { // Build and run the overall command to do everything in a single telnet session. command := removeTeamVlansCommand + addTeamVlansCommand if len(command) > 0 { - _, err = runCatalystConfigCommand(removeTeamVlansCommand + addTeamVlansCommand) + _, err = runSwitchConfigCommand(removeTeamVlansCommand + addTeamVlansCommand) if err != nil { return err } @@ -82,7 +82,7 @@ func ConfigureTeamEthernet(red1, red2, red3, blue1, blue2, blue3 *Team) error { // Returns a map of currently-configured teams to VLANs. func getTeamVlans() (map[int]int, error) { // Get the entire config dump. - config, err := runCatalystCommand("show running-config\n") + config, err := runSwitchCommand("show running-config\n") if err != nil { return nil, err } @@ -107,11 +107,11 @@ func getTeamVlans() (map[int]int, error) { return teamVlans, nil } -// Logs into the Catalyst via Telnet and runs the given command in user exec mode. Reads the output and +// Logs into the switch via Telnet and runs the given command in user exec mode. Reads the output and // returns it as a string. -func runCatalystCommand(command string) (string, error) { +func runSwitchCommand(command string) (string, error) { // Open a Telnet connection to the switch. - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", eventSettings.SwitchAddress, catalystTelnetPort)) + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", eventSettings.SwitchAddress, switchTelnetPort)) if err != nil { return "", err } @@ -138,9 +138,9 @@ func runCatalystCommand(command string) (string, error) { return reader.String(), nil } -// Logs into the Catalyst via Telnet and runs the given command in global configuration mode. Reads the output +// Logs into the switch via Telnet and runs the given command in global configuration mode. Reads the output // and returns it as a string. -func runCatalystConfigCommand(command string) (string, error) { - return runCatalystCommand(fmt.Sprintf("config terminal\n%send\ncopy running-config startup-config\n\n", +func runSwitchConfigCommand(command string) (string, error) { + return runSwitchCommand(fmt.Sprintf("config terminal\n%send\ncopy running-config startup-config\n\n", command)) } diff --git a/switch_config.txt b/switch_config.txt index 950bdb0..00fa1b7 100644 --- a/switch_config.txt +++ b/switch_config.txt @@ -1,186 +1,299 @@ ! Baseline configuration for the Catalyst 3500-series switch. Load this into the switch prior to configuring ! Cheesy Arena to connect to it. Default password is 1234Five. ! -version 12.1 +version 12.2 no service pad -service timestamps debug uptime -service timestamps log uptime +service timestamps debug datetime msec +service timestamps log datetime msec no service password-encryption ! hostname ChezySwitch ! +boot-start-marker +boot-end-marker +! enable secret 5 $1$kKSW$fCMwnMdYvXui1TulfyYHN/ ! -ip subnet-zero +! +! +no aaa new-model +system mtu routing 1500 ip routing ip dhcp excluded-address 10.0.100.1 10.0.100.100 ! ip dhcp pool dhcppool network 10.0.100.0 255.255.255.0 - default-router 10.0.100.1 domain-name team254.com dns-server 8.8.8.8 8.8.4.4 lease 7 ! ! +! +! +! +! +! +! spanning-tree mode pvst spanning-tree portfast default spanning-tree extend system-id ! +vlan internal allocation policy ascending ! ! ! ! -interface FastEthernet0/1 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/2 - switchport access vlan 11 - switchport mode access -! -interface FastEthernet0/3 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/4 - switchport access vlan 12 - switchport mode access -! -interface FastEthernet0/5 - switchport mode access -! -interface FastEthernet0/6 - switchport access vlan 13 - switchport mode access -! -interface FastEthernet0/7 - switchport mode access -! -interface FastEthernet0/8 - switchport access vlan 14 - switchport mode access -! -interface FastEthernet0/9 - switchport trunk encapsulation dot1q - switchport mode trunk -! -interface FastEthernet0/10 - switchport access vlan 15 - switchport mode access -! -interface FastEthernet0/11 - switchport trunk encapsulation dot1q - switchport mode trunk -! -interface FastEthernet0/12 - switchport access vlan 16 - switchport mode access -! -interface FastEthernet0/13 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/14 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/15 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/16 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/17 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/18 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/19 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/20 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/21 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/22 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/23 - switchport access vlan 2 - switchport mode access -! -interface FastEthernet0/24 - switchport access vlan 2 - switchport mode access -! interface GigabitEthernet0/1 - switchport trunk encapsulation dot1q - switchport mode trunk + switchport access vlan 100 + switchport mode access ! interface GigabitEthernet0/2 - switchport access vlan 2 + switchport trunk encapsulation dot1q + switchport trunk native vlan 100 + switchport mode trunk +! +interface GigabitEthernet0/3 + switchport access vlan 100 switchport mode access ! +interface GigabitEthernet0/4 + switchport mode access +! +interface GigabitEthernet0/5 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/6 + switchport access vlan 10 + switchport mode access +! +interface GigabitEthernet0/7 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/8 + switchport access vlan 20 + switchport mode access +! +interface GigabitEthernet0/9 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/10 + switchport access vlan 30 + switchport mode access +! +interface GigabitEthernet0/11 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/12 + switchport access vlan 40 + switchport mode access +! +interface GigabitEthernet0/13 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/14 + switchport access vlan 50 + switchport mode access +! +interface GigabitEthernet0/15 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/16 + switchport access vlan 60 + switchport mode access +! +interface GigabitEthernet0/17 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/18 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/19 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/20 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/21 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/22 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/23 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/24 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/25 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/26 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/27 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/28 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/29 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/30 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/31 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/32 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/33 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/34 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/35 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/36 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/37 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/38 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/39 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/40 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/41 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/42 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/43 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/44 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/45 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/46 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/47 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/48 + switchport access vlan 100 + switchport mode access +! +interface GigabitEthernet0/49 +! +interface GigabitEthernet0/50 +! +interface GigabitEthernet0/51 +! +interface GigabitEthernet0/52 +! interface Vlan1 ip address 10.0.0.61 255.255.255.0 ! -interface Vlan2 +interface Vlan10 + ip address 10.0.1.61 255.255.255.0 + ip access-group 110 in +! +interface Vlan20 + ip address 10.0.2.61 255.255.255.0 + ip access-group 120 in +! +interface Vlan30 + ip address 10.0.3.61 255.255.255.0 + ip access-group 130 in +! +interface Vlan40 + ip address 10.0.4.61 255.255.255.0 + ip access-group 140 in +! +interface Vlan50 + ip address 10.0.5.61 255.255.255.0 + ip access-group 150 in +! +interface Vlan60 + ip address 10.0.6.61 255.255.255.0 + ip access-group 160 in +! +interface Vlan100 ip address 10.0.100.2 255.255.255.0 ! -interface Vlan11 - ip address 10.0.1.61 255.255.255.0 - ip access-group 111 in -! -interface Vlan12 - ip address 10.0.2.61 255.255.255.0 - ip access-group 112 in -! -interface Vlan13 - ip address 10.0.3.61 255.255.255.0 - ip access-group 113 in -! -interface Vlan14 - ip address 10.0.4.61 255.255.255.0 - ip access-group 114 in -! -interface Vlan15 - ip address 10.0.5.61 255.255.255.0 - ip access-group 115 in -! -interface Vlan16 - ip address 10.0.6.61 255.255.255.0 - ip access-group 116 in -! ip classless -ip http server +no ip http server +no ip http secure-server ! -access-list 111 permit ip 10.0.1.0 0.0.0.255 host 10.0.100.5 -access-list 111 permit udp any eq bootpc any eq bootps -access-list 112 permit ip 10.0.2.0 0.0.0.255 host 10.0.100.5 -access-list 112 permit udp any eq bootpc any eq bootps -access-list 113 permit ip 10.0.3.0 0.0.0.255 host 10.0.100.5 -access-list 113 permit udp any eq bootpc any eq bootps -access-list 114 permit ip 10.0.4.0 0.0.0.255 host 10.0.100.5 -access-list 114 permit udp any eq bootpc any eq bootps -access-list 115 permit ip 10.0.5.0 0.0.0.255 host 10.0.100.5 -access-list 115 permit udp any eq bootpc any eq bootps -access-list 116 permit ip 10.0.6.0 0.0.0.255 host 10.0.100.5 -access-list 116 permit udp any eq bootpc any eq bootps +! +access-list 110 permit ip 10.0.1.0 0.0.0.255 host 10.0.100.5 +access-list 110 permit udp any eq bootpc any eq bootps +access-list 120 permit ip 10.0.2.0 0.0.0.255 host 10.0.100.5 +access-list 120 permit udp any eq bootpc any eq bootps +access-list 130 permit ip 10.0.3.0 0.0.0.255 host 10.0.100.5 +access-list 130 permit udp any eq bootpc any eq bootps +access-list 140 permit ip 10.0.4.0 0.0.0.255 host 10.0.100.5 +access-list 140 permit udp any eq bootpc any eq bootps +access-list 150 permit ip 10.0.5.0 0.0.0.255 host 10.0.100.5 +access-list 150 permit udp any eq bootpc any eq bootps +access-list 160 permit ip 10.0.6.0 0.0.0.255 host 10.0.100.5 +access-list 160 permit udp any eq bootpc any eq bootps ! snmp-server community 1234Five RO ! +! line con 0 exec-timeout 0 0 line vty 0 4 @@ -190,5 +303,4 @@ line vty 5 15 password 1234Five login ! -! end diff --git a/switch_config_test.go b/switch_config_test.go new file mode 100644 index 0000000..f51d5d5 --- /dev/null +++ b/switch_config_test.go @@ -0,0 +1,70 @@ +// Copyright 2014 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package main + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/assert" + "net" + "testing" + "time" +) + +func TestConfigureSwitch(t *testing.T) { + switchTelnetPort = 9050 + eventSettings = &EventSettings{SwitchAddress: "127.0.0.1", SwitchPassword: "password"} + var command string + + // Should do nothing if current configuration is blank. + mockTelnet(t, switchTelnetPort, "", &command) + assert.Nil(t, ConfigureTeamEthernet(nil, nil, nil, nil, nil, nil)) + assert.Equal(t, "", command) + + // Should remove any existing teams but not other SSIDs. + switchTelnetPort += 1 + mockTelnet(t, switchTelnetPort, + "interface Vlan100\nip address 10.0.100.2\ninterface Vlan50\nip address 10.2.54.61\n", &command) + assert.Nil(t, ConfigureTeamEthernet(nil, nil, nil, nil, nil, nil)) + assert.Equal(t, "password\nenable\npassword\nterminal length 0\nconfig terminal\ninterface Vlan50\nno ip"+ + " address\nno access-list 150\nend\ncopy running-config startup-config\n\nexit\n", command) + + // Should configure new teams and leave existing ones alone if still needed. + switchTelnetPort += 1 + mockTelnet(t, switchTelnetPort, "interface Vlan50\nip address 10.2.54.61\n", &command) + assert.Nil(t, ConfigureTeamEthernet(nil, &Team{Id: 1114}, nil, nil, &Team{Id: 254}, nil)) + assert.Equal(t, "password\nenable\npassword\nterminal length 0\nconfig terminal\n"+ + "ip dhcp excluded-address 10.11.14.1 10.11.14.100\nno ip dhcp pool dhcp20\nip dhcp pool dhcp20\n"+ + "network 10.11.14.0 255.255.255.0\ndefault-router 10.11.14.61\nlease 7\nno access-list 120\n"+ + "access-list 120 permit ip 10.11.14.0 0.0.0.255 host 10.0.100.5\n"+ + "access-list 120 permit udp any eq bootpc any eq bootps\ninterface Vlan20\n"+ + "ip address 10.11.14.61 255.255.255.0\nend\ncopy running-config startup-config\n\nexit\n", command) +} + +func mockTelnet(t *testing.T, port int, response string, command *string) { + go func() { + // Fake the first connection which should just get the configuration. + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + assert.Nil(t, err) + defer ln.Close() + conn, err := ln.Accept() + assert.Nil(t, err) + conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + var reader bytes.Buffer + reader.ReadFrom(conn) + assert.Contains(t, reader.String(), "terminal length 0\nshow running-config\nexit\n") + conn.Write([]byte(response)) + conn.Close() + + // Fake the second connection which should configure stuff. + conn2, err := ln.Accept() + assert.Nil(t, err) + conn2.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + var reader2 bytes.Buffer + reader2.ReadFrom(conn2) + *command = reader2.String() + conn2.Close() + }() + time.Sleep(100 * time.Millisecond) // Give it some time to open the socket. +} diff --git a/templates/access_point.cfg b/templates/access_point.cfg new file mode 100644 index 0000000..3843de9 --- /dev/null +++ b/templates/access_point.cfg @@ -0,0 +1,46 @@ +config wifi-device 'radio1' + option type 'mac80211' + option channel '11' + option hwmode '11g' + option path 'soc/soc:pcie-controller/pci0000:00/0000:00:02.0/0000:02:00.0' + option htmode 'HT20' + option disabled '0' + option txpower '20' + option country 'US' + +config wifi-iface + option device 'radio1' + option mode 'ap' + option hidden '1' + option encryption 'psk2+ccmp' + option network 'vlan100' + option ssid 'Cheesy Arena' + option key '1234Five' + option macaddr '62:38:e0:12:6b:17' + +config wifi-device 'radio0' + option type 'mac80211' + option hwmode '11a' + option path 'soc/soc:pcie-controller/pci0000:00/0000:00:01.0/0000:01:00.0' + option txpower '23' + option channel '157' + option country 'US' + option htmode 'HT20' + option basic_rates '6500,7200' + option short_gi_20 '0' + +{{range $vlan, $team := .}} +config wifi-iface + option device 'radio0' + option mode 'ap' + option isolate '0' + option bgscan '0' + option wds '0' + option maxassoc '1' + option hidden '1' + option network 'vlan{{$vlan}}' + option encryption 'psk2+ccmp' + option ssid '{{$team.Id}}' + option key '{{$team.WpaKey}}' + option disabled '0' +{{end}} diff --git a/templates/setup_settings.html b/templates/setup_settings.html index 5d4096b..1210898 100644 --- a/templates/setup_settings.html +++ b/templates/setup_settings.html @@ -160,7 +160,7 @@
Networking -

Enable this setting if you have a Cisco Aironet AP1252AG access point and Catalyst 3500-series +

Enable this setting if you have a Linksys WRT1900ACS access point and Catalyst 3500-series switch available, for isolating each team to its own SSID and VLAN.