0 ? new Date(value * 1e3).toUTCString() : 'Never'}
19 | onChange={(v) => onChange(parseInt(v))}
20 | />
21 | )
22 | }
23 |
24 | export default DeviceExpiry
25 |
26 | DeviceExpiry.propTypes = {
27 | value: PropTypes.number,
28 | onChange: PropTypes.func
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/components/Devices/DeviceList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { FlatList } from '@gluestack-ui/themed'
4 |
5 | import {Device} from 'components/Devices/Device'
6 |
7 | const DeviceList = ({ list, deleteListItem, notifyChange, ...props }) => {
8 | const renderItem = ({ item }) => (
9 |
15 | )
16 |
17 | return (
18 |
24 | )
25 | }
26 |
27 | export default DeviceList
28 |
--------------------------------------------------------------------------------
/frontend/src/components/Devices/DeviceQRCode.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import PropTypes from 'prop-types'
3 | import QRCode from 'react-qr-code'
4 |
5 | import { Box } from '@gluestack-ui/themed'
6 |
7 | // NOTE issues with type on ios: wpa2 vs wpa3, WPA try both
8 | const generateQRCode = (_ssid, password, type, hidden = false) => {
9 | type = 'WPA' //type.toUpperCase()
10 | return `WIFI:S:${_ssid};P:${password};T:${type};${hidden};`
11 | }
12 |
13 | // note can have multiple ssids
14 | const DeviceQRCode = ({ ssid, psk, type, ...props }) => {
15 | const [value, setValue] = useState(null)
16 |
17 | useEffect(() => {
18 | //device.PSKEntry.Psk, device.PSKEntry.Type
19 | let value = generateQRCode(ssid, psk, type)
20 | setValue(value)
21 | }, [ssid, psk, type])
22 |
23 | if (!value) {
24 | return <>>
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | //
35 |
36 | DeviceQRCode.propTypes = {
37 | //device: PropTypes.object.isRequired,
38 | psk: PropTypes.string.isRequired,
39 | type: PropTypes.string,
40 | ssid: PropTypes.string.isRequired
41 | }
42 |
43 | export default DeviceQRCode
44 |
--------------------------------------------------------------------------------
/frontend/src/components/Firewall/ICMP.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | import { firewallAPI } from 'api'
4 | import { alertState } from 'AppContext'
5 |
6 | import { VStack, Switch, Text } from '@gluestack-ui/themed'
7 |
8 | import { ListHeader, ListItem } from 'components/List'
9 |
10 | const ICMP = (props) => {
11 | const [status, setStatus] = useState({ PingLan: false, PingWan: false })
12 |
13 | useEffect(() => {
14 | firewallAPI.config().then((config) => {
15 | setStatus({ PingLan: config.PingLan, PingWan: config.PingWan })
16 | })
17 | }, [])
18 |
19 | const togglePing = (key) => {
20 | let updated = { ...status, [key]: !status[key] }
21 | firewallAPI
22 | .setICMP(updated)
23 | .then(() => {
24 | alertState.success('Updated Ping Settings')
25 | })
26 | .catch((err) => alertState.error(err))
27 |
28 | setStatus(updated)
29 | }
30 |
31 | return (
32 |
33 |
37 |
38 | {['PingLan', 'PingWan'].map((d) => (
39 |
40 | {d.replace(/^Ping/, '').toUpperCase()}
41 |
42 | togglePing(d)} />
43 |
44 | ))}
45 |
46 | )
47 | }
48 |
49 | export default ICMP
50 |
--------------------------------------------------------------------------------
/frontend/src/components/Flow/AddFlowCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Dimensions } from 'react-native'
4 |
5 | import { FlowCard } from './FlowCard'
6 | import { getCards } from './FlowCards'
7 |
8 | import { FlatList, Pressable, VStack, Text } from '@gluestack-ui/themed'
9 |
10 | // TODO we should just list available flow cards for cardType here
11 |
12 | //type = trigger or action
13 | const AddFlowCard = ({ cardType, onSubmit, ...props }) => {
14 | const handleSelect = (item) => {
15 | onSubmit(item)
16 | }
17 |
18 | const cards = getCards(cardType)
19 |
20 | let numColumns = Dimensions.get('window').width > 1024 ? 2 : 1
21 | let cardWidth = numColumns == 2 ? '$1/2' : '$full'
22 |
23 | //TODO only if Device.width>=1024
24 | return (
25 |
26 | item.title}
30 | renderItem={({ item }) => (
31 | handleSelect(item)} w={cardWidth} px="$1">
32 |
33 |
34 | )}
35 | />
36 |
37 | )
38 | }
39 |
40 | AddFlowCard.propTypes = {
41 | cardType: PropTypes.string.isRequired,
42 | onSubmit: PropTypes.func.isRequired
43 | }
44 |
45 | export default AddFlowCard
46 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/ProtocolRadio.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {
4 | Radio,
5 | RadioGroup,
6 | RadioIndicator,
7 | RadioIcon,
8 | RadioLabel,
9 | HStack,
10 | CircleIcon
11 | } from '@gluestack-ui/themed'
12 |
13 | const ProtocolRadio = ({ value, onChange, ...props }) => {
14 | return (
15 |
21 |
22 |
23 |
24 |
25 |
26 | TCP
27 |
28 |
29 |
30 |
31 |
32 | UDP
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default ProtocolRadio
40 |
--------------------------------------------------------------------------------
/frontend/src/components/IPInfo.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import { wifiAPI } from 'api'
5 | import { AlertContext } from 'layouts/Admin'
6 |
7 | const IPInfo = (props) => {
8 | const contextType = useContext(AlertContext)
9 |
10 | const handleClick = (e) => {
11 | let ip = e.target.innerText
12 | wifiAPI
13 | .asn(ip)
14 | .then((asn) => {
15 | contextType.alert(
16 | 'IP ASN information',
17 | <>
18 |
19 | {asn.ASN}
20 |
21 |
22 | {asn.Name}
23 |
24 |
25 | {asn.Country}
26 |
27 | {/**/}
32 | >
33 | )
34 | })
35 | .catch((err) => {
36 | contextType.error('IP info', `No ASN info for IP address: ${ip}`)
37 | })
38 | }
39 | return (
40 | {props.value || props.ip || props.children}
41 | )
42 | }
43 |
44 | IPInfo.propTypes = {}
45 |
46 | export default IPInfo
47 |
--------------------------------------------------------------------------------
/frontend/src/components/List/AddButton.js:
--------------------------------------------------------------------------------
1 | //TODO
2 | import React from 'react'
3 |
4 | import { Button, ButtonIcon, ButtonText, AddIcon } from '@gluestack-ui/themed'
5 |
6 | const AddButton = ({ onPress, ...props }) => {
7 | return (
8 |
19 | )
20 | }
21 | export default AddButton
22 |
--------------------------------------------------------------------------------
/frontend/src/components/List/ListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Box, HStack } from '@gluestack-ui/themed'
4 |
5 | const ListItem = ({ p, children, ...props }) => {
6 | return (
7 |
23 | {children}
24 |
25 | )
26 | }
27 |
28 | export default ListItem
29 |
--------------------------------------------------------------------------------
/frontend/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import ListHeader from './ListHeader'
2 | import ListItem from './ListItem'
3 |
4 | export { ListHeader, ListItem }
5 |
--------------------------------------------------------------------------------
/frontend/src/components/Menu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Menu, MenuItem, MenuItemLabel } from '@gluestack-ui/themed'
4 |
5 | const TestMenu = (props) => {
6 | return (
7 |
17 | )
18 | }
19 |
20 | //Menu, Menu.Item
21 | TestMenu.Item = MenuItem
22 |
23 | let MenuExport = Menu
24 |
25 | export default MenuExport
26 |
27 | export { MenuExport as Menu, MenuItem, MenuItemLabel }
28 |
--------------------------------------------------------------------------------
/frontend/src/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import {
5 | Button,
6 | ButtonText,
7 | ButtonIcon,
8 | HStack,
9 | ArrowLeftIcon,
10 | ArrowRightIcon
11 | } from '@gluestack-ui/themed'
12 |
13 | const Pagination = ({
14 | page,
15 | perPage,
16 | total,
17 | onChange,
18 | onPrevPage,
19 | onNextPage,
20 | ...props
21 | }) => {
22 | return (
23 |
24 |
33 |
42 |
43 | )
44 | }
45 |
46 | Pagination.propTypes = {
47 | page: PropTypes.number.isRequired,
48 | pages: PropTypes.number.isRequired,
49 | perPage: PropTypes.number.isRequired,
50 | onChange: PropTypes.func.isRequired
51 | }
52 |
53 | export default Pagination
54 |
--------------------------------------------------------------------------------
/frontend/src/components/Plugins/CustomPlugin.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 |
3 | export default () => {
4 | return <>>
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/components/Select.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {
4 | Icon,
5 | Select as SelectGS,
6 | SelectTrigger,
7 | SelectInput,
8 | SelectIcon,
9 | SelectPortal,
10 | SelectBackdrop,
11 | SelectContent,
12 | SelectDragIndicatorWrapper,
13 | SelectDragIndicator,
14 | SelectItem,
15 | SelectScrollView,
16 | ChevronDownIcon
17 | } from '@gluestack-ui/themed'
18 |
19 | /*
20 | //import { Select as NBSelect } //OLD
21 |
22 | const Select = NBSelect
23 | export default Select
24 |
25 | export { Select }
26 | */
27 |
28 | const Select = (props) => {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {props.children}
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | // behave like old Select
51 | Select.Item = SelectItem
52 |
53 | export { Select, SelectItem }
54 |
--------------------------------------------------------------------------------
/frontend/src/components/Select.web.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {
4 | Icon,
5 | Select as SelectGS,
6 | SelectTrigger,
7 | SelectInput,
8 | SelectIcon,
9 | SelectPortal,
10 | SelectBackdrop,
11 | SelectContent,
12 | SelectDragIndicatorWrapper,
13 | SelectDragIndicator,
14 | SelectItem,
15 | ChevronDownIcon
16 | } from '@gluestack-ui/themed'
17 |
18 | /*
19 | //import { Select as NBSelect } //OLD
20 |
21 | const Select = (props) => {
22 | const isSafari = () =>
23 | /Safari/.test(navigator.userAgent) &&
24 | /Apple Computer/.test(navigator.vendor);
25 |
26 |
27 | return (
28 |
29 | );
30 | };
31 |
32 | Select.Item = NBSelect.Item
33 | export default Select
34 | export { Select }
35 | */
36 |
37 | const Select = (props) => {
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {props.children}
53 |
54 |
55 |
56 | )
57 | }
58 |
59 | // behave like old Select
60 | Select.Item = SelectItem
61 |
62 | export { Select, SelectItem }
63 |
--------------------------------------------------------------------------------
/frontend/src/components/SwipeListView/index.js:
--------------------------------------------------------------------------------
1 | import SwipeListView from './SwipeListView'
2 | import SwipeRow from './SwipeRow'
3 |
4 | export { SwipeListView, SwipeRow }
5 |
--------------------------------------------------------------------------------
/frontend/src/components/Toggle.css:
--------------------------------------------------------------------------------
1 | .switch {
2 | position: relative;
3 | display: inline-block;
4 | width: 60px;
5 | height: 34px;
6 | height: 1.875rem;
7 | width: 3.65rem;
8 | line-height: normal;
9 | margin-top: 5px;
10 | margin-bottom: -5px;
11 | outline: none;
12 | }
13 | .switch input {
14 | position: absolute;
15 | top: -99999px;
16 | left: -99999px;
17 | }
18 | .slider {
19 | position: absolute;
20 | cursor: pointer;
21 | top: 0;
22 | left: 0;
23 | right: 0;
24 | bottom: 0;
25 | background-color: #ccc;
26 | -webkit-transition: .4s;
27 | transition: .4s;
28 | border-radius: 34px;
29 | }
30 | .slider:before {
31 | position: absolute;
32 | content: "";
33 | height: 24px;
34 | width: 24px;
35 | left: 4px;
36 | bottom: 3px;
37 | background-color: white;
38 | -webkit-transition: .4s;
39 | transition: .4s;
40 | border-radius: 50%;
41 | }
42 | input:checked + .slider {
43 | background-color: #51bcda;
44 | }
45 | input:focus + .slider {
46 | box-shadow: 0 0 1px #51bcda;
47 | }
48 | input:checked + .slider:before {
49 | -webkit-transform: translateX(26px);
50 | -ms-transform: translateX(26px);
51 | transform: translateX(26px);
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/components/TokenItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Platform } from 'react-native'
3 | import PropTypes from 'prop-types'
4 |
5 | import {
6 | Button,
7 | ButtonIcon,
8 | ButtonText,
9 | Tooltip,
10 | TooltipContent,
11 | TooltipText,
12 | CopyIcon
13 | } from '@gluestack-ui/themed'
14 |
15 | import { copy } from 'utils'
16 |
17 | const TokenItem = ({ token, ...props }) => {
18 | const showClipboard = true //Platform.OS !== 'web' || navigator.clipboard
19 |
20 | return (
21 | {
25 | return (
26 |
37 | )
38 | }}
39 | >
40 |
41 | {token}
42 |
43 |
44 | )
45 | }
46 | export default React.memo(TokenItem)
47 |
48 | export { TokenItem }
49 |
50 | TokenItem.propTypes = {
51 | token: PropTypes.string
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/components/Tooltip.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import {
5 | Pressable,
6 | Tooltip,
7 | TooltipContent,
8 | TooltipText
9 | } from '@gluestack-ui/themed'
10 |
11 | const T = ({ label, children, ...props }) => {
12 | return (
13 | {
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | }}
23 | >
24 |
25 | {label}
26 |
27 |
28 | )
29 | }
30 |
31 | export default T
32 | export { T as Tooltip }
33 |
34 | T.propTypes = {
35 | label: PropTypes.string
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/components/Traffic/TimeSeriesChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const TimeSeriesChart = (props) => {
5 | return <>>
6 | }
7 |
8 | TimeSeriesChart.propTypes = {
9 | type: PropTypes.string,
10 | title: PropTypes.string,
11 | data: PropTypes.object,
12 | handleTimeChange: PropTypes.func,
13 | onClick: PropTypes.func
14 | }
15 |
16 | export default TimeSeriesChart
17 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | // web app entrypoint
2 | import React from 'react'
3 | import { createRoot } from 'react-dom/client'
4 | //import ReactDOM from 'react-dom'
5 |
6 | import App from './App'
7 |
8 | const container = document.getElementById('root')
9 | const root = createRoot(container)
10 | root.render()
11 |
12 | //ReactDOM.render(, document.getElementById('root'))
13 |
--------------------------------------------------------------------------------
/frontend/src/layouts/Auth.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SafeAreaView } from 'react-native'
3 | import { Outlet } from 'react-router-dom'
4 | import { View, useColorMode } from '@gluestack-ui/themed'
5 |
6 | const AuthLayout = () => {
7 | let colorMode = useColorMode()
8 |
9 | const backgroundColor =
10 | colorMode === 'light' ? '$warmGray100' : '$blueGray900'
11 |
12 | return (
13 |
25 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default AuthLayout
37 |
--------------------------------------------------------------------------------
/frontend/src/layouts/Auth.web.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Outlet } from 'react-router-dom'
3 | import Footer from 'components/Footer/Footer'
4 | import { Box, Image, View, useColorMode } from '@gluestack-ui/themed'
5 |
6 | const AuthLayout = () => {
7 | const colorMode = useColorMode()
8 |
9 | return (
10 |
20 |
21 |
30 |
31 |
32 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default AuthLayout
48 |
--------------------------------------------------------------------------------
/frontend/src/test-utils.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react-native'
3 | import { GluestackUIProvider } from '@gluestack-ui/themed'
4 | import { config } from 'gluestack-ui.config'
5 |
6 | const AllTheProviders = ({ children }) => {
7 | return (
8 |
9 | {children}
10 |
11 | )
12 | }
13 |
14 | const customRender = (ui, options) =>
15 | render(ui, { wrapper: AllTheProviders, ...options })
16 |
17 | // re-export everything
18 | export * from '@testing-library/react-native'
19 |
20 | // override render method
21 | export { customRender as render }
22 |
--------------------------------------------------------------------------------
/frontend/src/useAsyncStorage.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import AsyncStorage from '@react-native-async-storage/async-storage'
3 |
4 | const useAsyncStorage = (key, initialValue) => {
5 | const [hasLoad, setHasLoad] = useState(false)
6 | const [data, setData] = useState(initialValue)
7 |
8 | const set = async (newData) => {
9 | setData(newData)
10 | return newData === null
11 | ? AsyncStorage.removeItem(key)
12 | : AsyncStorage.setItem(key, JSON.stringify(newData))
13 | }
14 |
15 | useEffect(() => {
16 | setHasLoad(false)
17 | }, [key])
18 |
19 | useEffect(() => {
20 | if (!hasLoad) {
21 | AsyncStorage.getItem(key).then((res) => {
22 | if (res === null) {
23 | AsyncStorage.setItem(key, JSON.stringify(data))
24 | setData(data)
25 | } else {
26 | setData(JSON.parse(res))
27 | }
28 | setHasLoad(true)
29 | })
30 | }
31 | }, [key, hasLoad, data])
32 |
33 | return [data, set]
34 | }
35 |
36 | export default useAsyncStorage
37 |
--------------------------------------------------------------------------------
/frontend/src/views/AlertsTabView.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 |
3 | import Alerts from 'views/Alerts'
4 | import AlertSettings from 'views/AlertSettings'
5 |
6 | import {
7 | AlertTriangleIcon,
8 | Settings2Icon,
9 | PlusIcon
10 | } from 'lucide-react-native'
11 |
12 | import TabView from 'components/TabView'
13 |
14 | const AlertsTabView = (props) => {
15 | const [activeTab, setActiveTab] = useState(0) // Default to first tab
16 |
17 | const tabs = [
18 | {
19 | title: 'Alerts',
20 | icon: AlertTriangleIcon,
21 | component: () => ,
22 | },
23 | {
24 | title: 'Settings',
25 | icon: Settings2Icon,
26 | component: () => ,
27 | },
28 | ]
29 |
30 | return (
31 |
34 | )
35 | }
36 |
37 | export default AlertsTabView
38 |
--------------------------------------------------------------------------------
/frontend/src/views/AuthSettings.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { View } from '@gluestack-ui/themed'
4 |
5 | import WebAuthn from 'components/Auth/WebAuthn'
6 | import AuthTokenList from 'components/Auth/AuthTokenList'
7 | import OTPSettings from 'components/Auth/OTPSettings'
8 |
9 | export default class AuthSettings extends Component {
10 | constructor(props) {
11 | super(props)
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
18 | {/**/}
19 |
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/views/DNS/DNSLogEdit.js:
--------------------------------------------------------------------------------
1 | import React, { Component, useContext } from 'react'
2 | import DNSLogList from 'components/DNS/DNSLogList'
3 | import PluginDisabled from 'views/PluginDisabled'
4 | import { logAPI } from 'api/DNS'
5 |
6 | import { View, VStack } from '@gluestack-ui/themed'
7 |
8 | export default class DNSLogEdit extends Component {
9 | state = { enabled: true }
10 | componentDidMount() {
11 | logAPI.config().catch((error) => this.setState({ enabled: false }))
12 | }
13 |
14 | render() {
15 | if (!this.state.enabled) {
16 | return
17 | }
18 |
19 | return (
20 |
21 |
22 |
27 |
32 |
33 |
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/views/Devices/AddDevice.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 |
4 | import { ScrollView } from '@gluestack-ui/themed'
5 |
6 | import useSwipe from 'components/useSwipe'
7 | import AddDevice from 'components/Devices/AddDevice'
8 |
9 | const AddDeviceView = () => {
10 | const navigate = useNavigate()
11 |
12 | const swipeHandlers = useSwipe({
13 | onSwipedRight: () => {
14 | navigate('/admin/devices')
15 | }
16 | })
17 |
18 | return (
19 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default AddDeviceView
33 |
--------------------------------------------------------------------------------
/frontend/src/views/Firewall/FirewallSettings.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | ActivityIcon, // For ICMP/Ping activity
4 | ServerIcon, // For Upstream Services/ports
5 | RadioIcon, // For MDNS/multicast
6 | } from 'lucide-react-native'
7 | import TabView from 'components/TabView'
8 | import UpstreamServicesList from 'components/Firewall/UpstreamServicesList'
9 | import ICMP from 'components/Firewall/ICMP'
10 | import MDNSAdvertise from 'components/Firewall/MDNSAdvertise'
11 |
12 | const FWSettings = (props) => {
13 | const tabs = [
14 | {
15 | title: 'Upstream Services',
16 | icon: ServerIcon, // Server icon for service ports
17 | component: () =>
18 | },
19 | {
20 | title: 'Ping/ICMP',
21 | icon: ActivityIcon, // Activity/ping icon makes sense for ICMP
22 | component: () =>
23 | },
24 | {
25 | title: 'Multicast DNS',
26 | icon: RadioIcon, // Broadcast icon for multicast names
27 | component: () =>
28 | }
29 | ]
30 |
31 | return (
32 |
35 | )
36 | }
37 |
38 | export default FWSettings
39 |
--------------------------------------------------------------------------------
/frontend/src/views/Firewall/Pfw.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { VStack } from '@gluestack-ui/themed'
3 |
4 | import FlowList from 'components/Flow/FlowList'
5 |
6 | const PFW = (props) => {
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default PFW
15 |
--------------------------------------------------------------------------------
/frontend/src/views/LinkConfigurationTabView.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import LANLinkConfiguration from 'views/LinkConfiguration/LANLinkConfiguration'
4 | import UplinkConfiguration from 'views/LinkConfiguration/UplinkConfiguration'
5 |
6 | import TabView from 'components/TabView'
7 |
8 | const LinkConfigurationTabView = (props) => {
9 | return (
10 |
19 | )
20 | }
21 |
22 | export default LinkConfigurationTabView
23 |
--------------------------------------------------------------------------------
/frontend/src/views/Logs.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 | import { useParams } from 'react-router-dom'
3 | import { View } from '@gluestack-ui/themed'
4 |
5 | import LogList from 'components/Logs/LogList'
6 | import LogListDb from 'components/Logs/LogListDb'
7 |
8 | const Logs = (props) => {
9 | const [containers, setContainers] = useState([])
10 | const params = useParams()
11 |
12 | useEffect(() => {
13 | //note: useNavigate needs to be re-enabled for this to work
14 | let { containers } = params
15 | if (containers && containers != ':containers') {
16 | setContainers(containers.split(','))
17 | }
18 | }, [])
19 |
20 | return (
21 |
22 |
23 | {/**/}
24 |
25 | )
26 | }
27 |
28 | export default Logs
29 |
--------------------------------------------------------------------------------
/frontend/src/views/PluginDisabled.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, Component } from 'react'
2 | import { Box, View, Heading, Link, Text } from '@gluestack-ui/themed'
3 |
4 | export default class PluginDisabled extends Component {
5 | render() {
6 | let title = this.props.title || 'Plugin not enabled'
7 |
8 | return (
9 |
10 |
11 | {title}
12 |
13 |
20 |
21 | Read more{' '}
22 |
26 | here
27 | {' '}
28 | on how to activate plugins.
29 |
30 |
31 |
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/views/System/SystemInfoContainers.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 |
3 | import {
4 | Box,
5 | Heading,
6 | HStack,
7 | FlatList,
8 | FormControl,
9 | FormControlLabel,
10 | FormControlLabelText,
11 | Input,
12 | InputField,
13 | Icon,
14 | Text,
15 | VStack,
16 | ScrollView,
17 | useColorMode
18 | } from '@gluestack-ui/themed'
19 |
20 | import { api } from 'api'
21 | import { AlertContext } from 'AppContext'
22 | import { ucFirst } from 'utils'
23 |
24 | import DockerInfo from 'views/System/Docker'
25 | import ContainerNetConfiguration from 'views/ContainerNetConfiguration'
26 | import Logs from 'views/Logs'
27 |
28 | const SystemInfoContainers = (props) => {
29 | const context = useContext(AlertContext)
30 |
31 | const colorMode = useColorMode()
32 | const item = {}
33 |
34 | return (
35 |
36 |
37 |
38 | Container Info
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | export default SystemInfoContainers
50 |
--------------------------------------------------------------------------------
/frontend/src/views/System/SystemInfoNetworkMisc.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 |
3 | import {
4 | Box,
5 | Heading,
6 | HStack,
7 | FlatList,
8 | FormControl,
9 | FormControlLabel,
10 | FormControlLabelText,
11 | Input,
12 | InputField,
13 | Icon,
14 | Text,
15 | VStack,
16 | ScrollView,
17 | useColorMode
18 | } from '@gluestack-ui/themed'
19 |
20 | import { api } from 'api'
21 | import { AlertContext } from 'AppContext'
22 | import { ucFirst } from 'utils'
23 |
24 | import Arp from 'views/Devices/Arp'
25 |
26 | const SystemInfoNetworkMisc = (props) => {
27 | const context = useContext(AlertContext)
28 |
29 | const colorMode = useColorMode()
30 | const item = {}
31 |
32 | return (
33 |
34 |
35 |
36 | Network Info
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default SystemInfoNetworkMisc
46 |
--------------------------------------------------------------------------------
/frontend/src/views/SystemInfoContainers.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 |
3 | import {
4 | Box,
5 | Heading,
6 | HStack,
7 | FlatList,
8 | FormControl,
9 | FormControlLabel,
10 | FormControlLabelText,
11 | Input,
12 | InputField,
13 | Icon,
14 | Text,
15 | VStack,
16 | ScrollView,
17 | useColorMode
18 | } from '@gluestack-ui/themed'
19 |
20 | import { api } from 'api'
21 | import { AlertContext } from 'AppContext'
22 | import { ucFirst } from 'utils'
23 |
24 | import DockerInfo from 'views/System/Docker'
25 | import ContainerNetConfiguration from 'views/ContainerNetConfiguration'
26 | import Logs from 'views/Logs'
27 |
28 | const SystemInfoContainers = (props) => {
29 | const context = useContext(AlertContext)
30 |
31 | const colorMode = useColorMode()
32 | const item = {}
33 |
34 | return (
35 |
36 |
37 |
38 | Container Info
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | export default SystemInfoContainers
50 |
--------------------------------------------------------------------------------
/frontend/src/views/SystemInfoNetworkMisc.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 |
3 | import {
4 | Box,
5 | Heading,
6 | HStack,
7 | FlatList,
8 | FormControl,
9 | FormControlLabel,
10 | FormControlLabelText,
11 | Input,
12 | InputField,
13 | Icon,
14 | Text,
15 | VStack,
16 | ScrollView,
17 | useColorMode
18 | } from '@gluestack-ui/themed'
19 |
20 | import { api } from 'api'
21 | import { AlertContext } from 'AppContext'
22 | import { ucFirst } from 'utils'
23 |
24 | import Arp from 'views/Devices/Arp'
25 |
26 | const SystemInfoNetworkMisc = (props) => {
27 | const context = useContext(AlertContext)
28 |
29 | const colorMode = useColorMode()
30 | const item = {}
31 |
32 | return (
33 |
34 |
35 |
36 | Network Info
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default SystemInfoNetworkMisc
46 |
--------------------------------------------------------------------------------
/frontend/src/views/SystemInfoTabView.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import SystemInfo from 'views/System/SystemInfo'
4 | import SystemInfoContainers from 'views/System/SystemInfoContainers'
5 | import SystemInfoNetworkMisc from 'views/System/SystemInfoNetworkMisc'
6 |
7 | import TabView from 'components/TabView'
8 |
9 | const SystemInfoTabView = (props) => {
10 | return (
11 |
18 | )
19 | }
20 |
21 | export default SystemInfoTabView
22 |
--------------------------------------------------------------------------------
/frontend/src/views/Traffic/Traffic.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { AlertContext } from 'layouts/Admin'
3 |
4 | import { Text } from '@gluestack-ui/themed'
5 |
6 | const Traffic = (props) => {
7 | //const context = useContext(AlertContext)
8 | return (
9 |
10 | TODO
11 |
12 | )
13 | }
14 |
15 | export default Traffic
16 |
--------------------------------------------------------------------------------
/frontend/src/views/WirelessConfiguration.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import WifiClients from 'components/Wifi/WifiClients'
4 | import WifiInterfaceList from 'components/Wifi/WifiInterfaceList'
5 | import WifiScan from 'components/Wifi/WifiScan'
6 | import WifiHostapd from 'components/Wifi/WifiHostapd'
7 | import WifiGuestNetwork from 'components/Wifi/WifiGuestNetwork'
8 |
9 | import { Platform } from 'react-native'
10 |
11 | import TabView from 'components/TabView'
12 |
13 | const WirelessConfiguration = (props) => {
14 | return (
15 |
27 | )
28 | }
29 |
30 | export default WirelessConfiguration
31 |
--------------------------------------------------------------------------------
/frontend/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack');
3 | const HtmlWebPackPlugin = require('html-webpack-plugin')
4 |
5 | module.exports = {
6 | output: {
7 | path: path.resolve(__dirname, 'build'),
8 | filename: 'bundle.js'
9 | },
10 | resolve: {
11 | modules: [path.join(__dirname, 'src'), 'node_modules'],
12 | alias: {
13 | react: path.join(__dirname, 'node_modules', 'react')
14 | }
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(js|jsx)$/,
20 | exclude: /node_modules/,
21 | use: {
22 | loader: 'babel-loader'
23 | }
24 | },
25 | {
26 | test: /\.css$/,
27 | use: [
28 | {
29 | loader: 'style-loader'
30 | },
31 | {
32 | loader: 'css-loader'
33 | }
34 | ]
35 | }
36 | ]
37 | },
38 | plugins: [
39 | new HtmlWebPackPlugin({
40 | template: './src/index.html'
41 | }),
42 | new webpack.DefinePlugin({
43 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
44 | __DEV__: process.env.NODE_ENV !== 'production' || true,
45 | }),
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/installer/README.md:
--------------------------------------------------------------------------------
1 | ### SPR Image builder
2 |
3 | If running on amd64 get the qemu support for aarch64:
4 | ./qemu-setup-linux.sh
5 |
6 | Next:
7 |
8 | 1) Run ./download-img.sh to pull the ubuntu image
9 |
10 | 2) Run ./run-docker-image-build.sh to prepare SPR to run
11 |
12 | 3) Run ./write-img.sh /dev/xyz (where xyz is your thumb drive/ sd card)
13 |
14 | 4) Optionally mount the image and modify /spr-environment.sh to change runtime behavior
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/installer/clearfog-arm64-image-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd ..
4 |
5 | shopt -s expand_aliases
6 | if ! which docker-compose > /dev/null 2>&1; then
7 | # Set an alias for docker-compose if it's missing
8 | alias docker-compose='docker compose'
9 | fi
10 |
11 | DOCKER_DEFAULT_PLATFORM=linux/arm64 docker-compose pull
12 | cd installer
13 | cp ./data/spr.clean.img ./data/spr.img
14 | ./scripts/containers.sh
15 |
16 | DOCKER_DEFAULT_PLATFORM="" docker pull ubuntu:24.04
17 | docker run --privileged -v /dev:/dev -v $PWD/data:/data -v $PWD/scripts:/scripts/ ubuntu:24.04 /scripts/resize-clearfog.sh
18 | docker run --privileged -v /dev:/dev -v $PWD/data:/data -v $PWD/scripts:/scripts/ --platform=aarch64 ubuntu:24.04 /scripts/go-clearfog.sh
19 |
--------------------------------------------------------------------------------
/installer/clearfog-download-image.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | mkdir data
6 | cd ./data
7 |
8 | IMG=ubuntu-cn9130-cf-pro-mmc.1.1.img
9 | TAG=spr-clearfog-noble
10 | if [ ! -f $IMG ]; then
11 | wget -q "https://github.com/spr-networks/cn913x_build/releases/download/${TAG}/${IMG}"
12 | fi
13 |
14 | cp $IMG spr.clean.img
15 | echo "[+] Extracted clearfog arm64 base image"
16 |
--------------------------------------------------------------------------------
/installer/pi-arm64-image-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | shopt -s expand_aliases
5 |
6 | if ! which docker-compose > /dev/null 2>&1; then
7 | # Set an alias for docker-compose if it's missing
8 | alias docker-compose='docker compose'
9 | fi
10 |
11 | cp ./data/spr.clean.img ./data/spr.img
12 |
13 | ./scripts/resize.sh
14 |
15 | #use host for next ubuntu
16 | DOCKER_DEFAULT_PLATFORM="" docker pull ubuntu:24.04
17 | docker run --privileged -v /dev:/dev -v $PWD/data:/data -v $PWD/scripts:/scripts/ ubuntu:24.04 /scripts/go-pi.sh
18 |
19 | pushd data
20 | qemu-system-aarch64 -machine virt -cpu cortex-a72 -smp 2 -m 1G -initrd initrd -kernel vmlinuz -append "root=/dev/vda2 rootfstype=ext4 rw panic=0 net.ifnames=0 biosdevname=0 init=/pi-target-install.sh" -drive file=spr.img,format=raw,if=none,id=hd0 -device virtio-blk-pci,drive=hd0 -netdev user,id=mynet -device virtio-net-pci,netdev=mynet -nographic
21 | popd
22 |
23 | #./scripts/shrink.sh
24 |
--------------------------------------------------------------------------------
/installer/pi-download-image.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | mkdir data
5 | # 25.04 is not yet ready due to mt7915e, r8125 driver bugs
6 | # 24.10 is needed for cm5 support
7 | VERSION="24.10"
8 | IMG="ubuntu-${VERSION}-preinstalled-server-arm64+raspi.img.xz"
9 |
10 | cd ./data
11 |
12 | if [ ! -f $IMG ]; then
13 | wget -q "https://cdimage.ubuntu.com/releases/${VERSION}/release/${IMG}"
14 | fi
15 |
16 | xzcat $IMG > spr.clean.img
17 | echo "[+] Extracted pi arm64 ubuntu image"
18 |
--------------------------------------------------------------------------------
/installer/qemu-setup-linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo apt-get update --fix-missing #try to fix azure ubuntu issue
3 | sudo apt-get install -y qemu qemu-utils binfmt-support qemu-user-static # Install the qemu packages
4 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # This step will execute the registering scripts
5 | sudo apt-get install -y qemu-system-aarch64
6 | sudo apt-get install -y cloud-guest-utils
7 |
8 |
--------------------------------------------------------------------------------
/installer/scripts/containers.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #pull containers and export them for setup to import
4 | # note: running dockerd inside the chroot is more difficult than this
5 |
6 | mkdir data/containers/
7 | cd data
8 | CONTAINERS=$(docker images "ghcr.io/spr-networks/*:latest" | awk '{print $1}' | grep -v REPOSITORY)
9 | docker save $CONTAINERS | gzip > containers/super.tar.gz
10 |
--------------------------------------------------------------------------------
/installer/scripts/go-clearfog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | /scripts/mount-clearfog.sh
3 | cp /scripts/install.sh /mnt/fs/tmp/install.sh
4 | cp /scripts/run-scripts/rc-local /mnt/fs/etc/rc.local
5 | cp /scripts/run-scripts/setup.sh /mnt/fs/tmp/
6 | cp /scripts/run-scripts/run.sh /mnt/fs/tmp/
7 | cp /scripts/spr-environment-clearfog.sh /mnt/fs/spr-environment.sh
8 | cp -R /data/containers /mnt/fs/containers/
9 | mount --bind /dev/ /mnt/fs/dev/
10 | chroot /mnt/fs /tmp/install.sh
11 | sed -i s/WANIF=eth0/WANIF=eth2/ /mnt/fs/home/spr/super/configs/base/config.sh
12 | /scripts/unmount-clearfog.sh
13 |
--------------------------------------------------------------------------------
/installer/scripts/go-pi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | /scripts/mount.sh
3 | cp /scripts/pi-cross-install.sh /mnt/fs/tmp/pi-cross-install.sh
4 | cp /scripts/run-scripts/rc-local /mnt/fs/etc/rc.local
5 | cp /scripts/run-scripts/setup.sh /mnt/fs/
6 | cp /scripts/run-scripts/run.sh /mnt/fs/
7 | cp /scripts/spr-environment.sh /mnt/fs/
8 | cp /scripts/pi-target-install.sh /mnt/fs/pi-target-install.sh
9 | mount --bind /dev/ /mnt/fs/dev/
10 | # this just downloads packages onto the image. not much else.
11 | # the rest is done on an aarch64 conatiner with pi-target-install.sh
12 | bash /scripts/pi-cross-install.sh
13 |
14 | # disable iface renaming on the boot image.
15 | if ! grep -q "net.ifnames=0 biosdevname=0" /mnt/boot/firmware/cmdline.txt; then
16 | sed -i '$s/$/ net.ifnames=0 biosdevname=0/' /mnt/boot/firmware/cmdline.txt
17 | fi
18 |
19 | cp /mnt/fs/boot/initrd.img /data/initrd
20 | cp /mnt/fs/boot/vmlinuz /data/vmlinuz
21 |
22 | /scripts/unmount.sh
23 |
--------------------------------------------------------------------------------
/installer/scripts/mount-clearfog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | IMG="/data/spr.img"
3 |
4 | if [ ! -f $IMG ]; then
5 | echo "- missing image"
6 | exit
7 | fi
8 |
9 | if [ $UID != 0 ]; then
10 | sudo $0
11 | exit
12 | fi
13 |
14 | echo $IMG
15 |
16 | losetup -Pf $IMG
17 |
18 | export LOOP=$(losetup -j $IMG | cut -d: -f1)
19 | export LOOP_ROOT="${LOOP}p1"
20 |
21 | echo "+ loop is $LOOP"
22 | echo "+ root is $LOOP_ROOT"
23 |
24 | mkdir /mnt/fs
25 | mount $LOOP_ROOT /mnt/fs
26 |
--------------------------------------------------------------------------------
/installer/scripts/mount.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | IMG="/data/spr.img"
3 |
4 | if [ ! -f $IMG ]; then
5 | echo "- missing image"
6 | exit
7 | fi
8 |
9 | if [ $UID != 0 ]; then
10 | sudo $0
11 | exit
12 | fi
13 |
14 | echo $IMG
15 |
16 | losetup -Pf $IMG
17 |
18 | export LOOP=$(losetup -j $IMG | cut -d: -f1)
19 | export LOOP_ROOT="${LOOP}p2"
20 | export LOOP_BOOT="${LOOP}p1"
21 |
22 | echo "+ loop is $LOOP"
23 | echo "+ boot is $LOOP_BOOT"
24 | echo "+ root is $LOOP_ROOT"
25 |
26 | mkdir /mnt/fs
27 | mount $LOOP_ROOT /mnt/fs
28 | mkdir -p /mnt/boot/firmware 2>/dev/null
29 | mount $LOOP_BOOT /mnt/boot/firmware
30 |
--------------------------------------------------------------------------------
/installer/scripts/resize-clearfog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | apt-get update
3 | apt-get install -y --no-install-recommends qemu-utils cloud-guest-utils
4 |
5 | IMG="./data/spr.img"
6 | qemu-img resize $IMG 10G
7 | growpart $IMG 1
8 |
9 | if [ ! -f $IMG ]; then
10 | echo "- missing image"
11 | exit
12 | fi
13 |
14 | if [ $UID != 0 ]; then
15 | sudo $0
16 | exit
17 | fi
18 |
19 |
20 | losetup -Pf $IMG
21 |
22 | export LOOP=$(losetup -j $IMG | cut -d: -f1)
23 | export LOOP_ROOT="${LOOP}p1"
24 |
25 | echo "+ loop is $LOOP"
26 |
27 | e2fsck -pf $LOOP_ROOT
28 | resize2fs $LOOP_ROOT
29 |
30 | losetup -d $LOOP 2>/dev/null
31 |
--------------------------------------------------------------------------------
/installer/scripts/resize.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | IMG="./data/spr.img"
3 | qemu-img resize $IMG 14G
4 | growpart $IMG 2
5 |
6 | if [ ! -f $IMG ]; then
7 | echo "- missing image"
8 | exit
9 | fi
10 |
11 | if [ $UID != 0 ]; then
12 | sudo $0
13 | exit
14 | fi
15 |
16 |
17 | losetup -Pf $IMG
18 |
19 | export LOOP=$(losetup -j $IMG | cut -d: -f1)
20 | export LOOP_ROOT="${LOOP}p2"
21 | export LOOP_BOOT="${LOOP}p1"
22 |
23 | echo "+ loop is $LOOP"
24 |
25 | RESIZE_CMD="e2fsck -f $LOOP_ROOT; e2fsck -f $LOOP_BOOT; resize2fs $LOOP_ROOT"
26 | DOCKER_DEFAULT_PLATFORM="" docker pull ubuntu:23.10
27 | DOCKER_DEFAULT_PLATFORM="" docker run --privileged -v $LOOP_ROOT:$LOOP_ROOT ubuntu:23.10 sh -c "$RESIZE_CMD"
28 | losetup -d $LOOP 2>/dev/null
29 |
--------------------------------------------------------------------------------
/installer/scripts/run-scripts/rc-local:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -f /home/spr/.spr-setup-done ]; then
4 | /home/spr/run.sh
5 | else
6 | /home/spr/setup.sh
7 | /home/spr/run.sh
8 | fi
9 |
--------------------------------------------------------------------------------
/installer/scripts/run-scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ### additional SPR setup
3 |
4 | set -a
5 | . /spr-environment.sh
6 |
7 | ip link set $WANIF up
8 | dhcpcd $WANIF || dhclient $WANIF
9 | dpkg-reconfigure openssh-server
10 |
11 | # Resize to full disk
12 | ROOTPART=$(mount | grep " / " | awk '{print $1}')
13 |
14 | PART=$(echo $ROOTPART | sed 's/[0-9]*$//')
15 | PART=$(echo $PART | sed 's/p$//')
16 | PARTNUM=$(echo $ROOTPART | sed 's/^.*[^0-9]\([0-9]\+\)$/\1/')
17 |
18 | growpart $PART $PARTNUM
19 | resize2fs $ROOTPART
20 |
21 | #try docker-compose pull, else, load the offline containers
22 |
23 | shopt -s expand_aliases
24 | if ! which docker-compose > /dev/null 2>&1; then
25 | # Set an alias for docker-compose if it's missing
26 | alias docker-compose='docker compose'
27 | fi
28 |
29 |
30 | cd /home/spr/super/
31 |
32 | # Generate self signed SSL certificates
33 | SKIPPASS="-password pass:1234" ./api/scripts/generate-certificate.sh
34 |
35 | docker-compose -f $COMPOSE_FILE pull
36 | ret=$?
37 |
38 | if [ "$ret" -ne "0" ]; then
39 | if [ -d /containers ]; then
40 | cd /containers
41 | for x in `ls *.tar.gz`
42 | do
43 | docker load -i $x
44 | done
45 | rm -f /containers
46 | fi
47 | fi
48 |
49 | if grep --quiet Raspberry /proc/cpuinfo; then
50 | # pi related tasks
51 | :
52 | else
53 | mv /lib/udev/rules.d/80-net-setup-link.rules /lib/udev/rules.d/80-net-setup-link.rules.bak
54 | ln -s /dev/null /lib/udev/rules.d/80-net-setup-link.rules
55 | fi
56 |
57 | touch /home/spr/.spr-setup-done
58 |
--------------------------------------------------------------------------------
/installer/scripts/shrink.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ $UID != 0 ]; then
3 | sudo $0
4 | exit
5 | fi
6 |
7 | IMG="./data/spr.img"
8 | # as a hack, we do losetup again
9 | losetup -Pf $IMG
10 | LOOP=$(losetup -j $IMG | cut -d: -f1)
11 | # fsck
12 | e2fsck -f ${LOOP}p2
13 | # resize
14 | resize2fs ${LOOP}p2 5G
15 | losetup -d $LOOP
16 |
17 | # truncate image
18 | truncate -s 6G $IMG
19 |
--------------------------------------------------------------------------------
/installer/scripts/spr-environment-clearfog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Modify these to change how the first setup will apply
3 | WANIF=eth2 #modify with your device's outbound interface
4 | COMPOSE_FILE=docker-compose.yml
5 | #COMPOSE_FILE=docker-compose-virt.yml # use this line instead to use SPR without wifi
6 | SUPERDIR=/home/spr/super/
7 |
8 |
--------------------------------------------------------------------------------
/installer/scripts/spr-environment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Modify these to change how the first setup will apply
3 | WANIF=eth0 #modify with your device's outbound interface
4 | COMPOSE_FILE=docker-compose.yml
5 | #COMPOSE_FILE=docker-compose-virt.yml # use this line instead to use SPR without wifi
6 | SUPERDIR=/home/spr/super/
7 |
8 |
--------------------------------------------------------------------------------
/installer/scripts/unmount-clearfog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ $UID != 0 ]; then
4 | sudo $0
5 | exit
6 | fi
7 |
8 | umount fs 2>/dev/null
9 | umount /mnt/fs
10 |
11 | IMG="./data/spr.img"
12 | LOOP=$(losetup -j $IMG | cut -d: -f1)
13 |
14 | losetup -d $LOOP 2>/dev/null
15 |
--------------------------------------------------------------------------------
/installer/scripts/unmount.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ $UID != 0 ]; then
3 | sudo $0
4 | exit
5 | fi
6 |
7 | umount boot 2>/dev/null
8 | umount fs 2>/dev/null
9 | umount /mnt/boot/firmware
10 | umount /mnt/fs
11 |
12 | IMG="./data/spr.img"
13 | LOOP=$(losetup -j $IMG | cut -d: -f1)
14 | losetup -d $LOOP 2>/dev/null
15 |
--------------------------------------------------------------------------------
/installer/upstream-docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # install upstream docker
3 | apt-get -y install ca-certificates curl gnupg
4 | install -m 0755 -d /etc/apt/keyrings
5 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
6 | chmod a+r /etc/apt/keyrings/docker.gpg
7 |
8 | echo \
9 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
10 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
11 | tee /etc/apt/sources.list.d/docker.list > /dev/null
12 |
13 | apt update
14 | apt-get -y install --no-install-recommends docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/installer/write-img.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo umount boot 2>/dev/null
4 | sudo umount fs 2>/dev/null
5 |
6 | IMG=./data/spr.img
7 | OF=$1
8 |
9 | umount ${OF}1
10 | umount ${OF}2
11 |
12 | if [ ! -f $IMG ]; then
13 | echo "- missing ${IMG}. exiting"
14 | exit
15 | fi
16 |
17 | echo "[+] writing SPR to ${OF}..."
18 | sudo dd if=$IMG of=$OF bs=32M status=progress
19 |
20 | sync
21 | eject $OF
22 |
23 | # Resize to full disk -> done in setup now
24 | #growpart $OF 2
25 | #e2fsck -f ${OF}2
26 | #resize2fs ${OF}2
27 |
--------------------------------------------------------------------------------
/multicast_udp_proxy/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04 AS builder
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update
4 | RUN apt-get install -y --no-install-recommends nftables iproute2 netcat-traditional inetutils-ping net-tools nano ca-certificates git curl wget
5 | RUN apt-get install -y --no-install-recommends clang
6 | RUN mkdir /code
7 | WORKDIR /code
8 | ARG TARGETARCH
9 | RUN wget https://dl.google.com/go/go1.24.2.linux-${TARGETARCH}.tar.gz
10 | RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.2.linux-${TARGETARCH}.tar.gz
11 | ENV PATH="/usr/local/go/bin:$PATH"
12 | ENV CC=clang
13 | ARG USE_TMPFS=true
14 | COPY code/ /code/
15 | RUN --mount=type=tmpfs,target=/tmpfs \
16 | [ "$USE_TMPFS" = "true" ] && ln -s /tmpfs /root/go; \
17 | go build -ldflags="-s -w" multicastproxy.go
18 |
19 | FROM ghcr.io/spr-networks/container_template:latest
20 | ENV DEBIAN_FRONTEND=noninteractive
21 | RUN mkdir /code/
22 | COPY --from=builder /code/multicastproxy /code/
23 | COPY scripts /scripts/
24 |
25 | ENTRYPOINT ["/scripts/startup.sh"]
26 |
--------------------------------------------------------------------------------
/multicast_udp_proxy/code/go.mod:
--------------------------------------------------------------------------------
1 | module test
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.2
6 |
7 | require (
8 | github.com/pion/mdns v0.0.12
9 | github.com/vishvananda/netlink v1.3.0
10 | golang.org/x/net v0.39.0
11 | golang.org/x/sys v0.32.0
12 | )
13 |
14 | require (
15 | github.com/pion/logging v0.2.3 // indirect
16 | github.com/vishvananda/netns v0.0.5 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/multicast_udp_proxy/scripts/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/multicast_udp_proxy/scripts/.gitignore
--------------------------------------------------------------------------------
/multicast_udp_proxy/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -a
3 | . /configs/base/config.sh
4 |
5 | # Do not run in mesh mode for now
6 | if [ -f state/plugins/mesh/enabled ]; then
7 | exit 0
8 | fi
9 |
10 | /code/multicastproxy
11 |
--------------------------------------------------------------------------------
/multicast_udp_proxy/tbd.txt:
--------------------------------------------------------------------------------
1 |
2 | - Consider adding services as a configuration option instead of being hardcoded (Currently mdns + ssdp)
3 | - Bipartite comms
4 |
--------------------------------------------------------------------------------
/packet_logs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04 AS builder
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update
4 | RUN apt-get install -y --no-install-recommends nftables iproute2 netcat-traditional inetutils-ping net-tools nano ca-certificates git curl wget
5 | RUN mkdir /code
6 | WORKDIR /code
7 | ARG TARGETARCH
8 | RUN wget https://dl.google.com/go/go1.24.2.linux-${TARGETARCH}.tar.gz
9 | RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.2.linux-${TARGETARCH}.tar.gz
10 | ENV PATH="/usr/local/go/bin:$PATH"
11 | COPY code/ /code/
12 | WORKDIR /code
13 | RUN go build -ldflags "-s -w" -o /packet_logs
14 | COPY stream-json-logs/ /code/stream-json-logs
15 | WORKDIR /code/stream-json-logs
16 | RUN go build -ldflags "-s -w" -o /stream-json-logs
17 |
18 | FROM ghcr.io/spr-networks/container_template:latest
19 | ENV DEBIAN_FRONTEND=noninteractive
20 | COPY scripts /scripts/
21 | COPY --from=builder /packet_logs /
22 | COPY --from=builder /stream-json-logs /
23 | ENTRYPOINT ["/scripts/startup.sh"]
24 |
--------------------------------------------------------------------------------
/packet_logs/client-influxdb/README.md:
--------------------------------------------------------------------------------
1 | # influx export
2 |
3 | this code will subscribe to netfilter events from eventbus and store the results in InfluxDB.
4 |
5 | buckets will have points called tcp and udp
6 |
7 | ## fetch and run influxdb
8 |
9 | ```sh
10 | docker pull influxdb:2.4.0
11 |
12 | DIR_INFLUXDB=$PWD
13 | docker run -d --name=influxdb \
14 | -p 8086:8086 \
15 | -v $DIR_INFLUXDB:/root/.influxdb2 \
16 | --net=influxdb-telegraf-net \
17 | influxdb:2.4.0
18 |
19 | # can run telegraf for other stats
20 | docker run --rm --name=telegraf \
21 | -v $PWD/telegraf.conf:/etc/telegraf/telegraf.conf \
22 | --net=influxdb-telegraf-net \
23 | telegraf
24 | ```
25 |
26 | ## setup influxdb settings in api.json
27 |
28 | ```json
29 | ...
30 | "InfluxDB": {
31 | "URL": "http://localhost:8086",
32 | "Org": "spr",
33 | "Bucket": "spr",
34 | "Token": "your-base64-token-here"
35 | },
36 | ...
37 | ```
38 |
--------------------------------------------------------------------------------
/packet_logs/client-test/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.2
6 |
7 | require (
8 | github.com/google/gopacket v1.1.19
9 | github.com/spr-networks/sprbus v0.1.7
10 | )
11 |
12 | require (
13 | github.com/golang/protobuf v1.5.4 // indirect
14 | github.com/moby/moby v23.0.1+incompatible // indirect
15 | github.com/moby/pubsub v1.0.0 // indirect
16 | github.com/sirupsen/logrus v1.9.3 // indirect
17 | golang.org/x/net v0.39.0 // indirect
18 | golang.org/x/sys v0.32.0 // indirect
19 | golang.org/x/text v0.24.0 // indirect
20 | google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect
21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
22 | google.golang.org/grpc v1.72.0 // indirect
23 | google.golang.org/protobuf v1.36.6 // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/packet_logs/code/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.2
6 |
7 | require (
8 | github.com/florianl/go-nflog/v2 v2.1.0
9 | github.com/google/gopacket v1.1.19
10 | github.com/spr-networks/sprbus v0.1.7
11 | github.com/vishvananda/netlink v1.3.0
12 | golang.org/x/sys v0.32.0
13 | )
14 |
15 | require (
16 | github.com/golang/protobuf v1.5.4 // indirect
17 | github.com/google/go-cmp v0.7.0 // indirect
18 | github.com/josharian/native v1.1.0 // indirect
19 | github.com/mdlayher/netlink v1.7.2 // indirect
20 | github.com/mdlayher/socket v0.5.1 // indirect
21 | github.com/moby/moby v23.0.3+incompatible // indirect
22 | github.com/moby/pubsub v1.0.0 // indirect
23 | github.com/sirupsen/logrus v1.9.3 // indirect
24 | github.com/vishvananda/netns v0.0.5 // indirect
25 | golang.org/x/net v0.39.0 // indirect
26 | golang.org/x/sync v0.13.0 // indirect
27 | golang.org/x/text v0.24.0 // indirect
28 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
29 | google.golang.org/grpc v1.72.0 // indirect
30 | google.golang.org/protobuf v1.36.6 // indirect
31 | )
32 |
--------------------------------------------------------------------------------
/packet_logs/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -a
3 | . /configs/base/config.sh
4 |
5 | /packet_logs
6 |
--------------------------------------------------------------------------------
/packet_logs/scripts/update-netfilter-rules.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # run to make sure netfilter rules have the correct prefix and include groups
4 |
5 | POS=$(sudo nft -a list ruleset | grep 'log prefix "DRP:MAC "'|sed 's/.*# handle //g')
6 | sudo nft replace rule inet filter DROP_MAC_SPOOF handle $POS log prefix \"drop:mac \" group 1
7 |
8 | POS=$(sudo nft -a list ruleset | grep -i 'log prefix "DRP:FWD "'|sed 's/.*# handle //g')
9 | sudo nft replace rule inet filter DROPLOGFWD handle $POS log prefix \"drop:forward \" group 1
10 |
11 | POS=$(sudo nft -a list ruleset | grep -i 'log prefix "DRP:INP "'|sed 's/.*# handle //g')
12 | sudo nft replace rule inet filter DROPLOGINP handle $POS log prefix \"drop:input \" group 1
13 |
14 | POS=$(sudo nft -a list ruleset | grep '@internet_access'|head -1|sed 's/.*# handle //g')
15 | sudo nft insert rule inet filter FORWARD position $POS counter oifname \"eth0\" log prefix \"wan:out \" group 0
16 |
17 | POS=$(sudo nft -a list ruleset | grep 'jump F_EST_RELATED'|tail -1|sed 's/.*# handle //g')
18 | sudo nft add rule inet filter INPUT position $POS log prefix \"lan:in \" group 0
19 |
--------------------------------------------------------------------------------
/packet_logs/stream-json-logs/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.2
6 |
7 | require (
8 | github.com/google/gopacket v1.1.19
9 | github.com/spr-networks/sprbus v0.1.7
10 | )
11 |
12 | require (
13 | github.com/golang/protobuf v1.5.4 // indirect
14 | github.com/moby/moby v23.0.1+incompatible // indirect
15 | github.com/moby/pubsub v1.0.0 // indirect
16 | github.com/sirupsen/logrus v1.9.3 // indirect
17 | golang.org/x/net v0.39.0 // indirect
18 | golang.org/x/sys v0.32.0 // indirect
19 | golang.org/x/text v0.24.0 // indirect
20 | google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect
21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
22 | google.golang.org/grpc v1.72.0 // indirect
23 | google.golang.org/protobuf v1.36.6 // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/plugin-lookup/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04 AS builder
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update
4 | RUN apt-get install -y --no-install-recommends git curl ca-certificates wget
5 | ARG TARGETARCH
6 | RUN wget https://dl.google.com/go/go1.24.2.linux-${TARGETARCH}.tar.gz
7 | RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.2.linux-${TARGETARCH}.tar.gz
8 | ENV PATH="/usr/local/go/bin:$PATH"
9 | RUN mkdir /code
10 | WORKDIR /code
11 | COPY code/ /code/
12 | ARG USE_TMPFS=true
13 | RUN --mount=type=tmpfs,target=/tmpfs \
14 | [ "$USE_TMPFS" = "true" ] && ln -s /tmpfs /root/go; \
15 | go build -ldflags "-s -w" -o /lookup_plugin /code/lookup_plugin.go
16 |
17 | FROM ghcr.io/spr-networks/container_template:latest
18 | ENV DEBIAN_FRONTEND=noninteractive
19 | COPY scripts /scripts/
20 | COPY data /data/
21 | COPY --from=builder /lookup_plugin /
22 | ENTRYPOINT ["/scripts/startup.sh"]
23 |
--------------------------------------------------------------------------------
/plugin-lookup/README.md:
--------------------------------------------------------------------------------
1 | # SPR plugin-lookup
2 |
3 | This plugin provides api endpoints to query for IP address ASN & MAC address OUI information.
4 |
5 | using the plugin:
6 | ```sh
7 | curl -u "admin:$PASS" 192.168.2.1/plugins/lookup/asn/1.1.1.1
8 | curl -u "admin:$PASS" 192.168.2.1/plugins/lookup/oui/00:11:22:33:44:55:66
9 | ```
10 |
11 | For ASN lookups its using this:
12 | https://iptoasn.com/data/ip2asn-v4.tsv.gz
13 |
14 | And the OUI vendor list from Wireshark:
15 | https://www.wireshark.org/download/automated/data/manuf
16 |
17 | Files are downloaded on startup of the container
18 |
--------------------------------------------------------------------------------
/plugin-lookup/code/go.mod:
--------------------------------------------------------------------------------
1 | module lookup_plugin
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/bradfitz/ip2asn v0.0.0-20220725205325-1069e332e707
7 | github.com/dutchcoders/go-ouitools v0.0.0-20150909074929-ac8139d3326a
8 | github.com/gorilla/mux v1.8.1
9 | )
10 |
11 | require go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
12 |
--------------------------------------------------------------------------------
/plugin-lookup/code/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bradfitz/ip2asn v0.0.0-20220725205325-1069e332e707 h1:XxXIhwtfPEVazb1dkFi6EA1vbSWw6XCBU/vR79BI2XI=
2 | github.com/bradfitz/ip2asn v0.0.0-20220725205325-1069e332e707/go.mod h1:JY/QtWIIARc1HYx53VnijIVPRgdEo1lBrtJfYt2DHMc=
3 | github.com/dutchcoders/go-ouitools v0.0.0-20150909074929-ac8139d3326a h1:k0tlcLlo0xiIQ+PsvXHcMw5lmrWJ2dgtplMk5SnvAuw=
4 | github.com/dutchcoders/go-ouitools v0.0.0-20150909074929-ac8139d3326a/go.mod h1:iw2+sjeXZHqxa3T+ufyw4bBRZyfp5B4prg1YPMHV4V0=
5 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
6 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
7 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
8 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
9 | go4.org/mem v0.0.0-20200601023850-d8ee1dfa5518/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k=
10 | go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
11 | go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
12 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
13 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
14 |
--------------------------------------------------------------------------------
/plugin-lookup/code/lookup_plugin_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestASN(t *testing.T) {
8 | err := initDb()
9 | if err != nil {
10 | t.Fatalf("initDb error: %v", err)
11 | }
12 |
13 | ip := "1.0.4.1"
14 | result, err := lookupASN(ip)
15 |
16 | if err != nil {
17 | t.Fatalf("newClient error: %v", err)
18 | }
19 |
20 | if result.Name != "WPL-AS-AP Wirefreebroadband Pty Ltd" {
21 | t.Fatalf("asn: empty result for ip")
22 | }
23 | }
24 |
25 | func TestOUI(t *testing.T) {
26 | err := initDb()
27 | if err != nil {
28 | t.Fatalf("initDb error: %v", err)
29 | }
30 |
31 | mac := "B8:27:EB:11:22:33"
32 | vendor, err := mOUI.VendorLookup(mac)
33 |
34 | //B8:27:EB RaspberryPiF # Raspberry Pi Foundation
35 | if err != nil {
36 | t.Fatalf("oui error: %v", err)
37 | }
38 |
39 | if vendor != "Raspberry Pi Foundation" {
40 | t.Fatalf("oui: empty result for mac: %v", vendor)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/plugin-lookup/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/plugin-lookup/scripts/download.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # ip2asn
4 | curl -s -O https://iptoasn.com/data/ip2asn-v4.tsv.gz
5 | gunzip -f ip2asn-v4.tsv.gz
6 |
7 | # oui
8 | #curl -O https://raw.githubusercontent.com/boundary/wireshark/master/manuf
9 | # more entries but have no short format for vendor, fix this for go pkg
10 | curl -s -O https://www.wireshark.org/download/automated/data/manuf
11 | grep -q '#\sApple, Inc.' manuf
12 | if [ $? -ne 0 ]; then
13 | cat manuf | grep -v '^#' | awk '{print $1 "\t" $2 "\t" "# " $3 " " $4 " " $5}' > manuf.new && mv manuf.new manuf
14 | fi
15 |
--------------------------------------------------------------------------------
/plugin-lookup/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # download lists on startup. if no internet access, sleep 1 min and try again
4 | # run plugin when done
5 | cd /data
6 |
7 | while true; do
8 |
9 | /scripts/download.sh
10 |
11 | if [ -f /data/ip2asn-v4.tsv ]; then
12 | break
13 | else
14 | sleep 60
15 | fi
16 |
17 | done
18 |
19 | /lookup_plugin
20 |
--------------------------------------------------------------------------------
/plugin-lookup/scripts/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # TODO test in container
4 |
5 | DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 | DIR_DATA=$DIR/../data
7 |
8 | rm -f $DIR_DATA/{ip2asn-v4.tsv,manuf}
9 |
10 | cd $DIR_DATA && $DIR/download.sh
11 |
12 | if ! [ -f $DIR_DATA/ip2asn-v4.tsv ]; then
13 | echo "- failed to download asn db"
14 | exit
15 | fi
16 |
17 | cd $DIR/../code && go test
18 |
--------------------------------------------------------------------------------
/ppp/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spr-networks/container_template:latest
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update && apt-get install -y --no-install-recommends jq ppp && rm -rf /var/lib/apt/lists/*
4 | COPY scripts /scripts
5 | ENTRYPOINT ["/scripts/startup.sh"]
6 |
--------------------------------------------------------------------------------
/ppp/docker-compose.yml:
--------------------------------------------------------------------------------
1 | x-logging:
2 | &default-logging
3 | driver: journald
4 |
5 | x-labels:
6 | &default-labels
7 | org.supernetworks.ci: ${CI:-false}
8 | org.supernetworks.version: ${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
9 |
10 | services:
11 | ppp:
12 | container_name: superppp
13 | image: ghcr.io/spr-networks/super_ppp:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
14 | build:
15 | context: .
16 | labels: *default-labels
17 | x-bake:
18 | tags:
19 | - ghcr.io/spr-networks/super_ppp:latest${RELEASE_CHANNEL:-}
20 | - ghcr.io/spr-networks/super_ppp:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
21 | network_mode: host
22 | privileged: true
23 | logging: *default-logging
24 | volumes:
25 | - "${SUPERDIR}./configs/ppp/:/etc/ppp/"
26 |
--------------------------------------------------------------------------------
/ppp/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | jq -r '.PPPs[] |
4 | "PPPIF=" + .Iface ,
5 | "PPPIFNAME=" + .PPPIface ,
6 | "PPP_VLANID=" + .VLAN ,
7 | "if [ \"$PPPIF\" ]; then" ,
8 | "if [ \"$PPP_VLANID\" ]; then",
9 | "ip link add link $PPPIF name $PPPIF.$PPP_VLANID type vlan id $PPP_VLANID",
10 | "ip link set up dev $PPPIF.$PPP_VLANID",
11 | "fi",
12 | "ip link set up dev $PPPIF",
13 | "/usr/sbin/pppd call provider_${PPPIF} ifname $PPPIFNAME 2>&1",
14 | "fi"' /etc/ppp/ppp.json | bash
15 |
16 | sleep inf
17 |
--------------------------------------------------------------------------------
/pull_containers.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 | shopt -s expand_aliases
3 | if ! which docker-compose > /dev/null 2>&1; then
4 | # Set an alias for docker-compose if it's missing
5 | alias docker-compose='docker compose'
6 | fi
7 | docker-compose pull
8 |
--------------------------------------------------------------------------------
/superd/code/go.mod:
--------------------------------------------------------------------------------
1 | module superd
2 |
3 | go 1.24
4 |
5 | toolchain go1.24.2
6 |
7 | require (
8 | github.com/gorilla/mux v1.8.1
9 | github.com/spr-networks/sprbus v0.1.7
10 | )
11 |
12 | require (
13 | github.com/golang/protobuf v1.5.4 // indirect
14 | github.com/moby/moby v23.0.1+incompatible // indirect
15 | github.com/moby/pubsub v1.0.0 // indirect
16 | github.com/sirupsen/logrus v1.9.3 // indirect
17 | golang.org/x/net v0.39.0 // indirect
18 | golang.org/x/sys v0.32.0 // indirect
19 | golang.org/x/text v0.24.0 // indirect
20 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
21 | google.golang.org/grpc v1.72.0 // indirect
22 | google.golang.org/protobuf v1.36.6 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/superd/scripts/docker-compose:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker compose $@
3 |
--------------------------------------------------------------------------------
/superd/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -a
3 | . /configs/base/config.sh
4 | /superd
5 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # SPR headless test with macsim
2 |
3 | ## Clients
4 |
5 | - sta1,sta2,sta3 connect to ap (TestLab)
6 | - sta4 runs nodejs tests in `runner/code/`, same password as sta3
7 |
8 | # Setup
9 |
10 | for hwsim and host deps on ubuntu:
11 |
12 | ```sh
13 | apt install -y linux-image-extra-virtual net-tools iw
14 | ```
15 |
16 | for latest docker compose:
17 | ```sh
18 | # remove if already installed (spr image)
19 | apt purge docker-compose docker
20 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
21 | chmod a+r /etc/apt/keyrings/docker.gpg
22 | echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
23 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
24 | apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
25 | service docker start
26 | docker --version && docker compose --version
27 | alias docker-compose="docker compose"
28 | ```
29 |
30 | ## Dependencies
31 |
32 | if running the tests on the host, nodejs is required for sta4:
33 |
34 | ```sh
35 | apt-get update && apt-get install -y \
36 | software-properties-common \
37 | npm
38 | npm install n -g && \
39 | n latest
40 | ```
41 |
--------------------------------------------------------------------------------
/tests/hostapd_wlan1.conf:
--------------------------------------------------------------------------------
1 | ctrl_interface=/state/wifi/control_wlan1
2 | country_code=US
3 | interface=wlan1
4 | ssid=SPR_wlan1
5 | hw_mode=a
6 | ieee80211d=1
7 | ieee80211n=1
8 | ieee80211ac=1
9 | ieee80211h=1
10 | #wifi 6
11 | ieee80211ax=0
12 | he_su_beamformer=0
13 | he_su_beamformee=0
14 | he_mu_beamformer=0
15 | #for mixed mode need w=1
16 | ieee80211w=1
17 | wmm_enabled=1
18 | preamble=1
19 | # awus036acm and netgear a6210 supported features
20 | ht_capab=[HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40]
21 | vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-A-MPDU-LEN-EXP3]
22 | vht_oper_chwidth=1
23 | channel=36
24 | vht_oper_centr_freq_seg0_idx=42
25 | auth_algs=1
26 | wpa=2
27 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
28 | rsn_pairwise=CCMP
29 |
30 | # Security parameters
31 |
32 | # Isolate stations and per-station group keys
33 | ap_isolate=1
34 | multicast_to_unicast=1
35 | tdls_prohibit=1
36 |
37 | # Mitigate krack attack
38 | wpa_disable_eapol_key_retries=1
39 |
40 | # For PMKID leaks in wpa2, any hardening to do here? Besides not using fast roaming?
41 | # no rsn_preauth, no 11r/ft-psk
42 |
43 | # VLAN
44 | per_sta_vif=1
45 |
46 | # Support H2E and hunting and pecking
47 | sae_pwe=2
48 |
49 | # Passwords
50 |
51 | wpa_psk_file=/configs/wifi/wpa2pskfile
52 | sae_psk_file=/configs/wifi/sae_passwords
53 |
--------------------------------------------------------------------------------
/tests/hwsim_setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # reset hwsim
4 | modprobe -r mac80211_hwsim
5 | modprobe mac80211_hwsim radios=8
6 | ip link set dev hwsim0 up
7 |
8 |
9 | move_iface_pid() {
10 | PID=$(docker inspect --format='{{.State.Pid}}' $2)
11 | PHY=phy$(iw $1 info | grep wiphy | awk '{print $2}')
12 | #echo move $1 is $PHY to $2 is $PID
13 | iw phy $PHY set netns $PID
14 | }
15 |
16 |
17 | # Move APs to superbase
18 | move_iface_pid "wlan0" "superbase"
19 | move_iface_pid "wlan1" "superbase"
20 |
21 | # Run wifid start script again
22 | docker exec -d superwifid /scripts/startup.sh
23 |
24 | # give a radio to the stations
25 | move_iface_pid "wlan2" "sta1"
26 | move_iface_pid "wlan3" "sta2"
27 | move_iface_pid "wlan4" "sta3"
28 | #move_iface_pid "wlan5" "sta4"
29 |
--------------------------------------------------------------------------------
/tests/run_sta.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker start sta1
3 |
--------------------------------------------------------------------------------
/tests/runner/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:23.10
2 |
3 | RUN apt-get update && apt-get -y install iproute2 wireless-tools iw nano tcpdump inetutils-ping netcat.traditional wpasupplicant curl hostapd isc-dhcp-client
4 |
5 | RUN apt-get update && apt-get install -y \
6 | software-properties-common \
7 | npm
8 |
9 | RUN npm install n -g && \
10 | n latest
11 |
12 | COPY code /code
13 | WORKDIR /code
14 | RUN npm install
15 |
16 | COPY go.sh /
17 | ENTRYPOINT ["/go.sh"]
18 |
--------------------------------------------------------------------------------
/tests/runner/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker build . -t runner
3 |
--------------------------------------------------------------------------------
/tests/runner/code/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/tests/runner/code/README.md:
--------------------------------------------------------------------------------
1 | # Run tests
2 |
3 | ## Running
4 |
5 | ```sh
6 | export API_URL="http://192.168.2.1" # default
7 | export TOKEN=SPR-API-TOKEN
8 | # or
9 | #export API_URL="http://127.0.0.1:8000"
10 | #export AUTH="admin:admin" # default
11 |
12 | yarn test
13 | yarn test:plus
14 | ```
15 |
16 | # Build
17 |
18 | ```sh
19 | yarn install
20 | ```
21 |
22 | ## Install recent node version on host
23 |
24 | ```
25 | apt-get update && apt-get install -y \
26 | software-properties-common \
27 | npm
28 | npm install n -g && \
29 | n latest
30 | ```
31 |
32 | ## Or use a Dockerfile for `sta_test`:
33 |
34 | **TODO**
35 |
36 | Same as sta1/
37 |
38 | ```yaml
39 | FROM ubuntu:23.10
40 |
41 | RUN apt-get update && apt-get -y install iproute2 wireless-tools iw nano tcpdump inetutils-ping netcat wpasupplicant curl hostapd isc-dhcp-client
42 |
43 | RUN apt-get update && apt-get install -y \
44 | software-properties-common \
45 | npm
46 | RUN npm install n -g && \
47 | n latest
48 |
49 | COPY w.conf /w.conf
50 | COPY go.sh /
51 | ENTRYPOINT ["/go.sh"]
52 | ```
53 |
54 | ## TODO
55 |
56 | run this code either on host or in a docker container similar to sta1,2,3
57 |
--------------------------------------------------------------------------------
/tests/runner/code/agent.js:
--------------------------------------------------------------------------------
1 | const API_URL = process.env.API_URL || 'http://192.168.2.1'
2 | const TOKEN = process.env.TOKEN || null
3 | const authType = TOKEN ? 'Bearer' : 'Basic'
4 | const authInfo =
5 | TOKEN || Buffer.from(process.env.AUTH || 'admin:admin').toString('base64')
6 | const AUTH = `${authType} ${authInfo}`
7 | const supertest = require('supertest')
8 |
9 | const hook =
10 | (method = 'get') =>
11 | (args) =>
12 | supertest(API_URL)[method](args).set('Authorization', AUTH)
13 |
14 | const request_auth = {
15 | post: hook('post'),
16 | get: hook('get'),
17 | put: hook('put'),
18 | delete: hook('delete')
19 | }
20 |
21 | const request = supertest(API_URL)
22 |
23 | module.exports = request_auth
24 | module.exports.request = request
25 | module.exports.request_auth = request_auth
26 |
--------------------------------------------------------------------------------
/tests/runner/code/e2e/README.md:
--------------------------------------------------------------------------------
1 | End to end tests.
2 |
3 | Assumptions:
4 |
5 | yarn test has already been run to establish the device
6 |
7 |
8 | These tests will evaluate network, firewall status,
9 | and they take longer to run.
10 |
11 |
12 | NOTES: requires running as a user with access to docker
13 |
--------------------------------------------------------------------------------
/tests/runner/code/host/stations.js:
--------------------------------------------------------------------------------
1 | const agent = require('../agent')
2 | const assert = require('assert')
3 |
4 | const { exec } = require('child_process');
5 | /*
6 | Note: these tests should run after 'device.js'
7 | */
8 |
9 | describe('stations', () => {
10 | it('should connect sta3', (done) => {
11 |
12 | exec('docker exec sta3 ip -br addr | grep 192.168', (err, stdout, stderr) => {
13 | assert(err == null || err.code == 0, "failed to get an ip")
14 | done()
15 | })
16 | })
17 |
18 | })
19 |
--------------------------------------------------------------------------------
/tests/runner/code/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/tests/runner/code/index.js
--------------------------------------------------------------------------------
/tests/runner/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-api",
3 | "version": "0.1.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "npx mocha",
8 | "test:host": "npx mocha host",
9 | "test:plus": "npx mocha plus"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "mocha": "^10.2.0",
15 | "supertest": "^6.3.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/runner/code/test/auth.js:
--------------------------------------------------------------------------------
1 | const { request, request_auth } = require('../agent')
2 |
3 | describe('auth', () => {
4 | it('should return 401', (done) => {
5 | request.get('/status').expect(401, (err, res) => {
6 | assert(res.status == 401)
7 | done()
8 | })
9 | })
10 |
11 | it('should accept token', (done) => {
12 | request_auth
13 | .get('/status')
14 | .expect(200)
15 | .expect(/Online/, done)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/tests/runner/code/test/device.js:
--------------------------------------------------------------------------------
1 | const agent = require('../agent')
2 | const assert = require('assert')
3 |
4 | describe('device', () => {
5 | it('should add a device', (done) => {
6 | let dev = {
7 | MAC: 'pending',
8 | Name: 'devName',
9 | Policies: ['dns', 'wan'],
10 | Groups: [],
11 | DeviceTags: [],
12 | PSKEntry: { Psk: 'password', Type: 'sae' }
13 | }
14 |
15 | agent
16 | .put('/device?identity=pending')
17 | .send(dev)
18 | .set('Accept', 'application/json')
19 | .expect('Content-Type', /json/)
20 | .end((err, res) => {
21 | assert(res.status == 200)
22 | assert.equal(res.body.Name, 'devName')
23 | assert.equal(res.body.PSKEntry.Psk, '**', 'password not masked')
24 |
25 | done()
26 | })
27 | })
28 |
29 | it('should list devices', (done) => {
30 | agent
31 | .get('/devices')
32 | .expect('Content-Type', /json/)
33 | .end((err, res) => {
34 | assert(res.status == 200)
35 | assert(Object.keys(res.body).length > 0, 'missing devices')
36 |
37 | done()
38 | })
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/tests/runner/code/test/features.js:
--------------------------------------------------------------------------------
1 | const agent = require('../agent')
2 | const assert = require('assert')
3 |
4 | describe('features', () => {
5 | it('list features', (done) => {
6 | agent
7 | .get('/features')
8 | .expect('Content-Type', /json/)
9 | .end((err, res) => {
10 | assert(res.status == 200)
11 | assert(res.body.includes('wifi'), 'no wifi')
12 | assert(res.body.includes('dns'), 'no dns')
13 | assert(res.body.includes('wireguard'), 'no wireguard')
14 |
15 | done()
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/tests/runner/code/test/ip.js:
--------------------------------------------------------------------------------
1 | const agent = require('../agent')
2 | const assert = require('assert')
3 |
4 | describe('ip', () => {
5 | it('addr', (done) => {
6 | agent
7 | .get('/ip/addr')
8 | .expect('Content-Type', /json/)
9 | .end((err, res) => {
10 | assert(res.status == 200)
11 | assert(res.body.length > 0, 'missing ifaces')
12 |
13 | done()
14 | })
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/tests/runner/go.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | DIR_TEST=/code
3 |
4 | export API_URL=${API_URL:-"http://localhost:8000"}
5 | export AUTH=${AUTH:-"admin:admin"}
6 | echo "+ API_URL= $API_URL"
7 |
8 | echo "+ RUNNING TESTS"
9 | cd $DIR_TEST && npm run test
10 |
11 | exit $?
12 |
--------------------------------------------------------------------------------
/tests/sta1/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:23.10
2 |
3 | RUN apt-get update && apt-get -y install iproute2 wireless-tools iw nano tcpdump inetutils-ping netcat.traditional wpasupplicant curl hostapd isc-dhcp-client
4 |
5 | COPY w.conf /w.conf
6 | COPY sta3.conf /sta3.conf
7 | COPY go.sh /
8 | COPY sta3.sh /
9 | COPY sta4.sh /
10 | COPY sta4.conf /
11 |
12 | ENTRYPOINT ["/go.sh"]
13 |
--------------------------------------------------------------------------------
/tests/sta1/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker build . -t sta1
3 |
--------------------------------------------------------------------------------
/tests/sta1/go.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | while true
4 | do
5 | IFACE=$(ip -br link | grep wlan | awk '{print $1}' | head -n 1)
6 | if [ ! -z $IFACE ]; then
7 | dhclient -r $IFACE
8 | dhclient -nw $IFACE
9 | wpa_supplicant -Dnl80211 -i${IFACE} -c /w.conf
10 | fi
11 | sleep 5
12 | done
13 |
14 |
--------------------------------------------------------------------------------
/tests/sta1/sta3.conf:
--------------------------------------------------------------------------------
1 | country=us
2 | ctrl_interface=/var/run/wpa_supplicant
3 |
4 | network={
5 | scan_ssid=1
6 | ssid="TestLab"
7 | psk="password"
8 | key_mgmt=SAE
9 | }
10 |
--------------------------------------------------------------------------------
/tests/sta1/sta3.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | while true
4 | do
5 | IFACE=$(ip -br link | grep wlan | awk '{print $1}' | head -n 1)
6 | if [ ! -z $IFACE ]; then
7 | dhclient -r $IFACE
8 | dhclient -nw $IFACE
9 | wpa_supplicant -Dnl80211 -i${IFACE} -c /sta3.conf
10 | fi
11 | sleep 5
12 | done
13 |
14 |
--------------------------------------------------------------------------------
/tests/sta1/sta4.conf:
--------------------------------------------------------------------------------
1 | country=us
2 | ctrl_interface=/var/run/wpa_supplicant
3 |
4 | network={
5 | scan_ssid=1
6 | ssid="TestLab"
7 | psk="password"
8 | key_mgmt=SAE
9 | }
10 |
--------------------------------------------------------------------------------
/tests/sta1/sta4.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DIR_TEST=/code
4 |
5 | while true
6 | do
7 | IFACE=$(ip -br link | grep wlan | awk '{print $1}' | head -n 1)
8 | if [ ! -z $IFACE ]; then
9 |
10 | echo "+ IFACE= $IFACE"
11 |
12 | dhclient -r $IFACE
13 | dhclient -nw $IFACE
14 | wpa_supplicant -B -Dnl80211 -i${IFACE} -c /sta4.conf
15 | fi
16 | sleep 5
17 | done
18 |
--------------------------------------------------------------------------------
/tests/sta1/w.conf:
--------------------------------------------------------------------------------
1 | country=us
2 | ctrl_interface=/var/run/wpa_supplicant
3 |
4 | network={
5 | scan_ssid=1
6 | ssid="TestLab"
7 | psk="asdfasdf"
8 | key_mgmt=SAE
9 | }
10 |
--------------------------------------------------------------------------------
/tests/test_configs/.gitignore:
--------------------------------------------------------------------------------
1 | sae_passwords
2 | wpa2pskfile
3 |
--------------------------------------------------------------------------------
/tests/test_configs/base/auth_tokens.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Name": "PLUS-API-Token",
4 | "Token": "djVLIc16cHzld-BoCpwZQ0XWTfp8IUBv0nn9bTcTaL4",
5 | "Expire": 0,
6 | "ScopedPaths": []
7 | }
8 | ]
--------------------------------------------------------------------------------
/tests/test_configs/base/auth_users.json:
--------------------------------------------------------------------------------
1 | {"admin": "admin"}
2 |
--------------------------------------------------------------------------------
/tests/test_configs/base/config.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | COMPOSE_FILE="docker-compose-test.yml"
3 | #VIRTUAL_SPR=1
4 | #comment below to DISABLE ssh, API from the Upstream Interface
5 | UPSTREAM_SERVICES_ENABLE=1
6 |
7 | SSID_NAME=TestLab
8 | SSID_INTERFACE=wlan0
9 | #PPPIF=eth0
10 | #WANIF=ppp0
11 | #PPP_VLANID=201
12 | #PPP_PROVIDER=provider-config
13 | #WANIF=wlp170s0
14 | WANIF=eth0 # for testing
15 | #RUN_WAN_DHCP=true
16 | #RUN_WAN_DHCP_IPV=4
17 | # Uncomment the next line if a second ethernet port goes to wired LAN
18 | #LANIF=eth1
19 | #VLANIF=wlan1
20 | #VLANSIF=$VLANIF.
21 |
22 | LANIP=192.168.100.1
23 | DNSIP=$LANIP
24 | TINYNETSTART=192.168.100.4
25 | TINYNETSTOP=192.168.100.255
26 | TINYNETMASK=255.255.255.252
27 | TINYSLASHMASK=30
28 | #DOCKERNET=172.0.0.0/8
29 | #DOCKERIF=docker0
30 |
31 | WIREGUARD_PORT=51280
32 |
33 |
--------------------------------------------------------------------------------
/tests/test_configs/base/dhcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "TinyNets": [
3 | "192.168.100.0/24",
4 | "192.168.101.0/24"
5 | ],
6 | "LeaseTime": "24h0m0s"
7 | }
8 |
--------------------------------------------------------------------------------
/tests/test_configs/base/firewall.json:
--------------------------------------------------------------------------------
1 | {
2 | "ForwardingRules": [],
3 | "BlockRules": [],
4 | "ForwardingBlockRules": [],
5 | "ServicePorts": [
6 | {
7 | "Protocol": "tcp",
8 | "Port": "22",
9 | "UpstreamEnabled": true
10 | },
11 | {
12 | "Protocol": "tcp",
13 | "Port": "80",
14 | "UpstreamEnabled": true
15 | },
16 | {
17 | "Protocol": "tcp",
18 | "Port": "443",
19 | "UpstreamEnabled": true
20 | },
21 | {
22 | "Protocol": "tcp",
23 | "Port": "5201",
24 | "UpstreamEnabled": true
25 | },
26 | {
27 | "Protocol": "tcp",
28 | "Port": "6060",
29 | "UpstreamEnabled": true
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/tests/test_configs/base/interfaces.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Name": "wlan0",
4 | "Type": "AP",
5 | "Enabled": false
6 | },
7 | {
8 | "Name": "eth0",
9 | "Type": "Uplink",
10 | "Enabled": false
11 | },
12 | {
13 | "Name": "wlan1",
14 | "Type": "AP",
15 | "Enabled": true
16 | }
17 | ]
--------------------------------------------------------------------------------
/tests/test_configs/base/release_channel:
--------------------------------------------------------------------------------
1 | -dev
--------------------------------------------------------------------------------
/tests/test_configs/base/release_version:
--------------------------------------------------------------------------------
1 | 0.1.29
--------------------------------------------------------------------------------
/tests/test_configs/devices/devices.json:
--------------------------------------------------------------------------------
1 | {
2 | "02:00:00:00:02:00": {
3 | "Name": "test-sta1",
4 | "MAC": "02:00:00:00:02:00",
5 | "WGPubKey": "",
6 | "VLANTag": "",
7 | "RecentIP": "192.168.100.10",
8 | "PSKEntry": {
9 | "Type": "sae",
10 | "Psk": "asdfasdf"
11 | },
12 | "Groups": [
13 |
14 | ],
15 | "Policies": [
16 | "dns",
17 | "wan",
18 | "lan",
19 | "lan_upstream"
20 | ],
21 | "DeviceTags": [
22 | ]
23 | },
24 | "02:00:00:00:03:00": {
25 | "Name": "test-sta2",
26 | "MAC": "02:00:00:00:03:00",
27 | "WGPubKey": "",
28 | "VLANTag": "",
29 | "RecentIP": "192.168.101.2",
30 | "PSKEntry": {
31 | "Type": "sae",
32 | "Psk": "asdfasdf"
33 | },
34 | "Groups": [
35 |
36 | ],
37 | "Policies": [
38 | "dns",
39 | "wan"
40 | ],
41 | "DeviceTags": []
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/test_configs/devices/groups.json:
--------------------------------------------------------------------------------
1 | [
2 | ]
3 |
--------------------------------------------------------------------------------
/tests/test_configs/dhcp/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/tests/test_configs/dhcp/.gitignore
--------------------------------------------------------------------------------
/tests/test_configs/dhcp/coredhcp.yml:
--------------------------------------------------------------------------------
1 | # Note, this is generated by gen_coredhcp_yaml.sh. Modify that to make changes
2 | server4:
3 | # Listens on all interfaces when none are configured. Note that iptables should block dhcp from $WANIF
4 | plugins:
5 | - tiny_subnets:
6 |
--------------------------------------------------------------------------------
/tests/test_configs/dns/Corefile:
--------------------------------------------------------------------------------
1 | . {
2 | block enable_superapi
3 | rebinding_protection
4 |
5 | hosts /state/dns/local_mappings {
6 | ttl 60
7 | reload 30s
8 | fallthrough
9 | }
10 |
11 | forward . tls://1.1.1.1 tls://1.0.0.1 {
12 | tls_servername cloudflare-dns.com
13 | }
14 |
15 | jsonlog {
16 | enable_superapi
17 | #influxdb http://test:8086/ test DNS credentials==
18 | }
19 | log
20 | errors
21 | cache 30
22 | }
23 |
--------------------------------------------------------------------------------
/tests/test_configs/dyndns/godyndns.json:
--------------------------------------------------------------------------------
1 | {"provider":"","email":"","password":"","login_token":"","domains":null,"ip_urls":[],"ip_url":"","ipv6_url":"","ip_type":"","interval":0,"socks5":"","resolver":"","run_once":true}
--------------------------------------------------------------------------------
/tests/test_configs/pfw/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "ForwardingRules": [],
3 | "BlockRules": [],
4 | "TagRules": [],
5 | "GroupRules": [],
6 | "Variables": {},
7 | "SiteVPNs": [],
8 | "APIToken": "djVLIc16cHzld-BoCpwZQ0XWTfp8IUBv0nn9bTcTaL4"
9 | }
--------------------------------------------------------------------------------
/tests/test_configs/ppp/chap-secrets:
--------------------------------------------------------------------------------
1 | # Secrets for authentication using CHAP
2 | # client server secret IP addresses
3 |
4 |
5 | "user@provider" * "password"
6 |
--------------------------------------------------------------------------------
/tests/test_configs/ppp/peers/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/tests/test_configs/ppp/peers/.gitignore
--------------------------------------------------------------------------------
/tests/test_configs/ppp/peers/peer_sample:
--------------------------------------------------------------------------------
1 | # Minimalistic default options file for DSL/PPPoE connections
2 |
3 | noipdefault
4 | defaultroute
5 | replacedefaultroute
6 | #hide-password
7 | #lcp-echo-interval 30
8 | #lcp-echo-failure 4
9 | noauth
10 | persist
11 | #mtu 1492
12 | #persist
13 | #maxfail 0
14 | #holdoff 20
15 | # Note, make sure to update this for the appropriate interface.
16 | # The below example assumes a tagged VLAN
17 | plugin rp-pppoe.so eth0.201
18 | user "user@provider"
19 |
--------------------------------------------------------------------------------
/tests/test_configs/scripts/gen_coredhcp_yaml.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | . configs/base/config.sh
3 |
4 | cat << END
5 | # Note, this is generated by gen_coredhcp_yaml.sh. Modify that to make changes
6 | server4:
7 | # Listens on all interfaces when none are configured. Note that iptables should block dhcp from $WANIF
8 | plugins:
9 | - server_id: $LANIP
10 | - dns: $DNSIP
11 | - router: $LANIP
12 | - netmask: $TINYNETMASK
13 | - tiny_subnets: /state/dhcp/leases.txt $TINYNETSTART $TINYNETSTOP 730h0m0s
14 | - execute: /scripts/dhcp_helper.sh
15 |
16 | END
17 |
18 |
--------------------------------------------------------------------------------
/tests/test_configs/scripts/gen_hostapd.sh:
--------------------------------------------------------------------------------
1 | . configs/base/config.sh
2 |
3 | cat << END
4 | ctrl_interface=/state/wifi/control
5 | country_code=US
6 | interface=$SSID_INTERFACE
7 | ssid=$SSID_NAME
8 | hw_mode=a
9 | ieee80211d=1
10 | ieee80211n=1
11 | ieee80211ac=1
12 | ieee80211h=1
13 | #for mixed mode need w=1
14 | ieee80211w=1
15 | wmm_enabled=1
16 | preamble=1
17 | # awus036acm and netgear a6210 supported features
18 | ht_capab=[LDPC][HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1]
19 | vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
20 | vht_oper_chwidth=1
21 | channel=36
22 | vht_oper_centr_freq_seg0_idx=42
23 | auth_algs=1
24 | wpa=2
25 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
26 | rsn_pairwise=CCMP
27 |
28 | # Security parameters
29 |
30 | # Isolate stations and per-station group keys
31 | ap_isolate=1
32 | multicast_to_unicast=1
33 |
34 | # Mitigate krack attack
35 | wpa_disable_eapol_key_retries=1
36 |
37 | # For PMKID leaks in wpa2, any hardening to do here? Besides not using fast roaming?
38 | # no rsn_preauth, no 11r/ft-psk
39 |
40 | # VLAN
41 | per_sta_vif=1
42 |
43 | # Support H2E and hunting and pecking
44 | sae_pwe=2
45 |
46 | # Passwords
47 |
48 | wpa_psk_file=/configs/wifi/wpa2pskfile
49 | sae_psk_file=/configs/wifi/sae_passwords
50 |
51 | END
52 |
--------------------------------------------------------------------------------
/tests/test_configs/scripts/gen_multicast_startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . ./configs/base/config.sh
4 |
5 | cat << END
6 | #!/bin/bash
7 | /code/multicastproxy $LANIF,$VLANSIF
8 | END
9 |
--------------------------------------------------------------------------------
/tests/test_configs/scripts/gen_watchdog.sh:
--------------------------------------------------------------------------------
1 | . configs/base/config.sh
2 |
3 | cat << EOF
4 | watchdog-device = /dev/watchdog
5 | watchdog-timeout = 300
6 | #interface = $VLANIF
7 |
8 | EOF
9 |
--------------------------------------------------------------------------------
/tests/test_configs/watchdog/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/tests/test_configs/watchdog/.gitignore
--------------------------------------------------------------------------------
/tests/test_configs/watchdog/watchdog.conf:
--------------------------------------------------------------------------------
1 | watchdog-device = /dev/watchdog
2 | watchdog-timeout = 300
3 | #interface =
4 |
5 |
--------------------------------------------------------------------------------
/tests/test_configs/wifi/.hostapd_wlan1.conf.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spr-networks/super/1161bfa1350749a3fe67c0ca0c8914216f7b3863/tests/test_configs/wifi/.hostapd_wlan1.conf.swp
--------------------------------------------------------------------------------
/tests/test_configs/wifi/hostapd.conf.bak:
--------------------------------------------------------------------------------
1 | ctrl_interface=/state/wifi/control
2 | country_code=US
3 | interface=
4 | ssid=
5 | hw_mode=a
6 | ieee80211d=1
7 | ieee80211n=1
8 | ieee80211ac=1
9 | ieee80211h=1
10 | #for mixed mode need w=1
11 | ieee80211w=1
12 | wmm_enabled=1
13 | preamble=1
14 | # awus036acm and netgear a6210 supported features
15 | ht_capab=[LDPC][HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1]
16 | vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
17 | vht_oper_chwidth=1
18 | channel=36
19 | vht_oper_centr_freq_seg0_idx=42
20 | auth_algs=1
21 | wpa=2
22 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
23 | rsn_pairwise=CCMP
24 |
25 | # Security parameters
26 |
27 | # Isolate stations and per-station group keys
28 | ap_isolate=1
29 | multicast_to_unicast=1
30 |
31 | # Mitigate krack attack
32 | wpa_disable_eapol_key_retries=1
33 |
34 | # For PMKID leaks in wpa2, any hardening to do here? Besides not using fast roaming?
35 | # no rsn_preauth, no 11r/ft-psk
36 |
37 | # VLAN
38 | per_sta_vif=1
39 |
40 | # Passwords
41 |
42 | wpa_psk_file=/configs/wifi/wpa2pskfile
43 | sae_psk_file=/configs/wifi/sae_passwords
44 |
45 |
--------------------------------------------------------------------------------
/tests/test_configs/wifi/hostapd_template.conf:
--------------------------------------------------------------------------------
1 | ctrl_interface=/state/wifi/control_
2 | country_code=US
3 | interface=
4 | ssid=
5 | hw_mode=a
6 | ieee80211d=1
7 | ieee80211n=1
8 | ieee80211ac=1
9 | ieee80211h=1
10 | #wifi 6
11 | ieee80211ax=0
12 | he_su_beamformer=0
13 | he_su_beamformee=0
14 | he_mu_beamformer=0
15 | #for mixed mode need w=1
16 | ieee80211w=1
17 | wmm_enabled=1
18 | preamble=1
19 | # awus036acm and netgear a6210 supported features
20 | ht_capab=[LDPC][HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1]
21 | vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
22 | vht_oper_chwidth=1
23 | channel=36
24 | vht_oper_centr_freq_seg0_idx=42
25 | auth_algs=1
26 | wpa=2
27 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
28 | rsn_pairwise=CCMP
29 |
30 | # Security parameters
31 |
32 | # Isolate stations and per-station group keys
33 | ap_isolate=1
34 | multicast_to_unicast=1
35 | tdls_prohibit=1
36 |
37 | # Mitigate krack attack
38 | wpa_disable_eapol_key_retries=1
39 |
40 | # For PMKID leaks in wpa2, any hardening to do here? Besides not using fast roaming?
41 | # no rsn_preauth, no 11r/ft-psk
42 |
43 | # VLAN
44 | per_sta_vif=1
45 |
46 | # Support H2E and hunting and pecking
47 | sae_pwe=2
48 |
49 | # Passwords
50 |
51 | wpa_psk_file=/configs/wifi/wpa2pskfile
52 | sae_psk_file=/configs/wifi/sae_passwords
53 |
--------------------------------------------------------------------------------
/tests/test_configs/wifi/hostapd_wlan1.conf:
--------------------------------------------------------------------------------
1 | sae_psk_file=/configs/wifi/sae_passwords
2 | ssid=TestLab
3 | interface=wlan1
4 | ieee80211ac=1
5 | country_code=US
6 | he_su_beamformer=0
7 | ieee80211ax=0
8 | wpa_psk_file=/configs/wifi/wpa2pskfile
9 | ieee80211n=1
10 | he_oper_chwidth=1
11 | ieee80211h=1
12 | ap_isolate=1
13 | vht_oper_chwidth=1
14 | multicast_to_unicast=1
15 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
16 | vht_capab=
17 | preamble=1
18 | channel=36
19 | he_mu_beamformer=0
20 | he_su_beamformee=0
21 | per_sta_vif=1
22 | ht_capab=[HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40]
23 | ieee80211w=1
24 | wmm_enabled=1
25 | rsn_pairwise=CCMP
26 | wpa_disable_eapol_key_retries=1
27 | auth_algs=1
28 | hw_mode=a
29 | wpa=2
30 | ieee80211d=1
31 | he_oper_centr_freq_seg0_idx=42
32 | tdls_prohibit=1
33 | ctrl_interface=/state/wifi/control_wlan1
34 | vht_oper_centr_freq_seg0_idx=42
35 | sae_pwe=2
36 |
--------------------------------------------------------------------------------
/tests/test_configs/wifi/hostapd_wlan1.conf.bak:
--------------------------------------------------------------------------------
1 | ctrl_interface=/state/wifi/control_wlan1
2 | country_code=US
3 | interface=wlan1
4 | ssid=SPR_wlan1
5 | hw_mode=a
6 | ieee80211d=1
7 | ieee80211n=1
8 | ieee80211ac=1
9 | ieee80211h=1
10 | #wifi 6
11 | ieee80211ax=0
12 | he_su_beamformer=0
13 | he_su_beamformee=0
14 | he_mu_beamformer=0
15 | #for mixed mode need w=1
16 | ieee80211w=1
17 | wmm_enabled=1
18 | preamble=1
19 | # awus036acm and netgear a6210 supported features
20 | ht_capab=[LDPC][HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1]
21 | vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
22 | vht_oper_chwidth=1
23 | channel=36
24 | vht_oper_centr_freq_seg0_idx=42
25 | auth_algs=1
26 | wpa=2
27 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
28 | rsn_pairwise=CCMP
29 |
30 | # Security parameters
31 |
32 | # Isolate stations and per-station group keys
33 | ap_isolate=1
34 | multicast_to_unicast=1
35 | tdls_prohibit=1
36 |
37 | # Mitigate krack attack
38 | wpa_disable_eapol_key_retries=1
39 |
40 | # For PMKID leaks in wpa2, any hardening to do here? Besides not using fast roaming?
41 | # no rsn_preauth, no 11r/ft-psk
42 |
43 | # VLAN
44 | per_sta_vif=1
45 |
46 | # Passwords
47 |
48 | wpa_psk_file=/configs/wifi/wpa2pskfile
49 | sae_psk_file=/configs/wifi/sae_passwords
50 |
--------------------------------------------------------------------------------
/tests/test_configs/wireguard/wg0.conf:
--------------------------------------------------------------------------------
1 | [Interface]
2 | PrivateKey = 6eegAO3hff6GmpvjZsrAL8qD1s4by3hjd1lRLiH4YlI=
3 | ListenPort = 51280
4 |
5 | #[Peer]
6 | #PublicKey = pubkey
7 | #PresharedKey = sharedkey
8 | #AllowedIPs = 192.168.3.3/32
9 |
--------------------------------------------------------------------------------
/tests/test_start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export RELEASE_CHANNEL=-dev
4 |
5 | docker build tests/sta1 -t sta1
6 |
7 | # Reset testing config
8 | rm -rf configs
9 | cp -R tests/test_configs configs
10 |
11 | docker compose -f docker-compose-test.yml up -d
12 |
13 |
14 |
15 | # start stations -- no docker network, they must rely on hwsim
16 | docker run --network none --privileged --rm -d --name sta1 sta1
17 | docker run --network none --privileged --rm -d --name sta2 sta1
18 | docker run --network none --privileged --rm -d --entrypoint=/sta3.sh --name sta3 sta1
19 | docker run --network none --privileged --rm -d --entrypoint=/sta4.sh --name sta4 sta1
20 |
21 | ./tests/hwsim_setup.sh
22 |
23 | if [ -n "$RUNTEST" ]; then
24 | ./tests/run-tests.sh
25 | fi
26 |
--------------------------------------------------------------------------------
/tests/test_stop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker compose -f docker-compose-test.yml stop
3 | docker kill sta{1,2,3,4}
4 | docker kill runner
5 |
--------------------------------------------------------------------------------
/watchdog/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update
4 | RUN apt-get install -y watchdog netbase
5 | COPY scripts /scripts
6 |
7 | ENTRYPOINT ["/scripts/startup.sh"]
8 |
--------------------------------------------------------------------------------
/watchdog/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | watchdog -F -c /configs/watchdog/watchdog.conf
3 |
--------------------------------------------------------------------------------
/wifi_uplink/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spr-networks/container_template:latest
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update && apt-get install -y --no-install-recommends isc-dhcp-client jq wpasupplicant wireless-regdb && rm -rf /var/lib/apt/lists/*
4 | COPY scripts /scripts
5 | ENTRYPOINT ["/scripts/startup.sh"]
6 |
--------------------------------------------------------------------------------
/wifi_uplink/docker-compose.yml:
--------------------------------------------------------------------------------
1 | x-logging:
2 | &default-logging
3 | driver: journald
4 |
5 | x-labels:
6 | &default-labels
7 | org.supernetworks.ci: ${CI:-false}
8 | org.supernetworks.version: ${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
9 |
10 | services:
11 | wifi_uplink:
12 | container_name: superwifiuplink
13 | image: ghcr.io/spr-networks/super_wifiuplink:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
14 | build:
15 | context: .
16 | labels: *default-labels
17 | x-bake:
18 | tags:
19 | - ghcr.io/spr-networks/super_wifiuplink:latest${RELEASE_CHANNEL:-}
20 | - ghcr.io/spr-networks/super_wifiuplink:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
21 | network_mode: host
22 | privileged: true
23 | logging: *default-logging
24 | volumes:
25 | - "${SUPERDIR}./configs/wifi_uplink/:/configs/wifi_uplink/"
26 |
--------------------------------------------------------------------------------
/wifi_uplink/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -f "/configs/wifi_uplink/wpa.json" ]; then
4 | while IFS= read -r iface; do
5 | wpa_supplicant -B -i "${iface}" -Dnl80211 -c "/configs/wifi_uplink/wpa_${iface}.conf"
6 | done < <(jq -r '.WPAs[] | select(.Enabled == true) | .Iface' /configs/wifi_uplink/wpa.json)
7 | fi
8 |
9 | sleep inf
10 |
--------------------------------------------------------------------------------
/wifid-setup/.gitignore:
--------------------------------------------------------------------------------
1 | sta_mac_iface_map/*
2 | configs/sae_passwords
3 | configs/hostapd.conf
4 | configs/wpa2pskfile
5 |
--------------------------------------------------------------------------------
/wifid-setup/docker-compose-test.yml:
--------------------------------------------------------------------------------
1 | x-labels:
2 | &default-labels
3 | org.supernetworks.ci: ${CI:-false}
4 | org.supernetworks.version: ${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
5 |
6 | x-logging:
7 | &default-logging
8 | driver: journald
9 |
10 | services:
11 | wifid-setup:
12 | container_name: superwifid-setup
13 | image: ghcr.io/spr-networks/super_wifid:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
14 | entrypoint: /scripts/setup.sh
15 | network_mode: service:base
16 | privileged: true
17 | logging: *default-logging
18 | volumes:
19 | - ${SUPERDIR}/configs/base/:/configs/base/:ro
20 | - ${SUPERDIR}/configs/wifi/:/configs/wifi/
21 | - ${SUPERDIR}/state/wifi/:/state/wifi/
22 |
--------------------------------------------------------------------------------
/wifid-setup/docker-compose.yml:
--------------------------------------------------------------------------------
1 | x-labels:
2 | &default-labels
3 | org.supernetworks.ci: ${CI:-false}
4 | org.supernetworks.version: ${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
5 |
6 | x-logging:
7 | &default-logging
8 | driver: journald
9 |
10 | services:
11 | wifid-setup:
12 | container_name: superwifid-setup
13 | image: ghcr.io/spr-networks/super_wifid:${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}
14 | entrypoint: /scripts/setup.sh
15 | network_mode: host
16 | privileged: true
17 | logging: *default-logging
18 | volumes:
19 | - ${SUPERDIR}/configs/base/:/configs/base/:ro
20 | - ${SUPERDIR}/configs/wifi/:/configs/wifi/
21 | - ${SUPERDIR}/state/wifi/:/state/wifi/
22 |
--------------------------------------------------------------------------------
/wifid/.gitignore:
--------------------------------------------------------------------------------
1 | sta_mac_iface_map/*
2 | configs/sae_passwords
3 | configs/hostapd.conf
4 | configs/wpa2pskfile
5 |
--------------------------------------------------------------------------------
/wifid/notes.txt:
--------------------------------------------------------------------------------
1 | - Virtual ESSIDs are possible. Need to set BSSIDs with specific prefixes though
2 | Ex:
3 | ..config
4 | #bssid=04:c0:ca:ae:d1:b0
5 |
6 | #virtual bssid for wpa2
7 | #bss=wlan1_0
8 | #bssid=06:c0:ca:ae:d1:b0
9 | #ssid=s5210wpa2
10 | #wpa=2
11 | #wpa_key_mgmt=WPA-PSK
12 | #rsn_pairwise=CCMP
13 | #wpa_psk_file=/configs/wpa2pskfile
14 | #ap_isolate=1
15 | #per_sta_vif=1
16 |
--------------------------------------------------------------------------------
/wifid/scripts/action-setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | IFACE=$1
4 | EVENT=$2
5 | MAC=$3
6 |
7 | if [ "$EVENT" = "AP-STA-CONNECTED" ]; then
8 | VLAN_IFACE=$IFACE
9 | /hostap_dhcp_helper add $VLAN_IFACE $MAC
10 | curl --unix-socket /state/wifi/apisock http://localhost/reportPSKAuthSuccess -X PUT -d "{\"Iface\": \"$VLAN_IFACE\", \"Event\": \"$EVENT\", \"Mac\": \"$MAC\"}"
11 | elif [ "$EVENT" = "AP-STA-DISCONNECTED" ]; then
12 | VLAN_IFACE=$IFACE
13 | /hostap_dhcp_helper remove $VLAN_IFACE $MAC
14 | curl --unix-socket /state/wifi/apisock http://localhost/reportDisconnect -X PUT -d "{\"Iface\": \"$VLAN_IFACE\", \"Event\": \"$EVENT\", \"Mac\": \"$MAC\"}"
15 | elif [ "$EVENT" = "AP-STA-POSSIBLE-PSK-MISMATCH" ]; then
16 | TYPE=$4
17 | REASON=$5
18 | curl --unix-socket /state/wifi/apisock http://localhost/reportPSKAuthFailure -X PUT -d "{\"Type\": \"$TYPE\", \"Mac\": \"$MAC\", \"Reason\": \"$REASON\"}"
19 | fi
20 |
--------------------------------------------------------------------------------
/wifid/scripts/action.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | IFACE=$1
4 | EVENT=$2
5 | MAC=$3
6 |
7 | if [ "$EVENT" = "AP-STA-CONNECTED" ]; then
8 | VLAN_ID=$(hostapd_cli -p /state/wifi/control_${IFACE} sta $MAC | grep vlan_id | cut -c 9-)
9 | VLAN_IFACE=$IFACE.$VLAN_ID
10 | /hostap_dhcp_helper add $VLAN_IFACE $MAC
11 | curl --unix-socket /state/wifi/apisock http://localhost/reportPSKAuthSuccess -X PUT -d "{\"Iface\": \"$VLAN_IFACE\", \"Event\": \"$EVENT\", \"Mac\": \"$MAC\"}"
12 | elif [ "$EVENT" = "AP-STA-DISCONNECTED" ]; then
13 | VLAN_ID=$(hostapd_cli -p /state/wifi/control_${IFACE} sta $MAC | grep vlan_id | cut -c 9-)
14 | VLAN_IFACE=$IFACE.$VLAN_ID
15 | /hostap_dhcp_helper remove $VLAN_IFACE $MAC
16 | curl --unix-socket /state/wifi/apisock http://localhost/reportDisconnect -X PUT -d "{\"Iface\": \"$VLAN_IFACE\", \"Event\": \"$EVENT\", \"Mac\": \"$MAC\"}"
17 | elif [ "$EVENT" = "AP-STA-POSSIBLE-PSK-MISMATCH" ]; then
18 | TYPE=$4
19 | REASON=$5
20 | curl --unix-socket /state/wifi/apisock http://localhost/reportPSKAuthFailure -X PUT -d "{\"Type\": \"$TYPE\", \"Mac\": \"$MAC\", \"Reason\": \"$REASON\"}"
21 | fi
22 |
--------------------------------------------------------------------------------
/wifid/scripts/hostapd_failsafe_band1.conf:
--------------------------------------------------------------------------------
1 | ap_isolate=1
2 | auth_algs=1
3 | bss_transition=0
4 | channel=1
5 | country_code=US
6 | ctrl_interface=/state/wifi/control_wlan0
7 | hw_mode=g
8 | ieee80211ac=1
9 | ieee80211d=1
10 | ieee80211h=1
11 | ieee80211n=1
12 | ieee80211w=1
13 | interface=wlan0
14 | multicast_to_unicast=1
15 | per_sta_vif=1
16 | preamble=1
17 | rsn_pairwise=CCMP CCMP-256
18 | sae_psk_file=/configs/wifi/sae_passwords
19 | sae_pwe=2
20 | ssid=failsafe_ssid
21 | tdls_prohibit=1
22 | time_advertisement=0
23 | vht_capab=
24 | vht_oper_chwidth=0
25 | wpa=2
26 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
27 | wpa_psk_file=/configs/wifi/wpa2pskfile
28 |
--------------------------------------------------------------------------------
/wifid/scripts/hostapd_failsafe_band2.conf:
--------------------------------------------------------------------------------
1 | ap_isolate=1
2 | auth_algs=1
3 | channel=36
4 | country_code=US
5 | ctrl_interface=/state/wifi/control_wlan0
6 | hw_mode=a
7 | ieee80211ac=1
8 | ieee80211ax=1
9 | ieee80211d=1
10 | ieee80211h=1
11 | ieee80211n=1
12 | ieee80211w=2
13 | interface=wlan0
14 | multicast_to_unicast=1
15 | per_sta_vif=1
16 | preamble=1
17 | rsn_pairwise=CCMP CCMP-256
18 | sae_psk_file=/configs/wifi/sae_passwords
19 | sae_pwe=2
20 | ssid=failsafe_ssid
21 | wpa=2
22 | wpa_disable_eapol_key_retries=1
23 | wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
24 | wpa_psk_file=/configs/wifi/wpa2pskfile
25 |
--------------------------------------------------------------------------------
/wifid/scripts/pi-setup.conf:
--------------------------------------------------------------------------------
1 | #This is used for setup mode on raspberry pis only.
2 | ctrl_interface=/state/wifi/control_wlan0
3 | country_code=US
4 | interface=wlan0
5 | ssid=spr-setup
6 |
7 | # radio
8 | hw_mode=a
9 | channel=36
10 | ieee80211d=1
11 | ieee80211n=1
12 | ieee80211ac=1
13 | ieee80211h=1
14 | ieee80211w=1
15 | wmm_enabled=1
16 | preamble=1
17 |
--------------------------------------------------------------------------------
/wifid/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | PI_WLAN=""
3 | FOUND_PI_BRCMFMAC=false
4 | PI_SETUP_PENDING=false
5 |
6 | # If on a pi and in setup mode, spin up a default setup AP
7 | if grep -q "Raspberry Pi" /proc/cpuinfo; then
8 |
9 | while read -r line; do
10 | if echo "$line" | grep -q 'wlan'; then
11 | PI_WLAN=$(echo "$line" | awk '{print $3}' | tr -d ':')
12 | fi
13 |
14 | if echo "$line" | grep -q 'driver=brcmfmac'; then
15 | FOUND_PI_BRCMFMAC=true
16 | fi
17 |
18 | if [[ -n $PI_WLAN && $FOUND_PI_BRCMFMAC = true ]]; then
19 | break
20 | fi
21 | done <<< "$(lshw -class network)"
22 |
23 | if [[ -n $PI_WLAN && $FOUND_PI_BRCMFMAC = true ]]; then
24 | # reset PI_WLAN state always, this makes sure setup ap is gone
25 | # if wifid was restarted
26 | ip link set dev $PI_WLAN down
27 | ip addr flush dev $PI_WLAN
28 | iw dev $PI_WLAN set type managed
29 | ip link set dev $PI_WLAN up
30 | # Check if /configs/base/.setup_done does not exist
31 | if [ ! -f /configs/base/.setup_done ]; then
32 | PI_SETUP_PENDING=true
33 | fi
34 | fi
35 |
36 | if $PI_SETUP_PENDING; then
37 | cp /scripts/pi-setup.conf /configs/wifi/pi-setup_${PI_WLAN}.conf
38 | sed -i "s/wlan0/$PI_WLAN/g" /configs/wifi/pi-setup_${PI_WLAN}.conf
39 | hostapd -B /configs/wifi/pi-setup_${PI_WLAN}.conf
40 | hostapd_cli -B -p /state/wifi/control_${PI_WLAN} -a /scripts/action-setup.sh
41 | sleep inf
42 | fi
43 | fi
44 |
--------------------------------------------------------------------------------
/wireguard/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04 AS builder
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt-get update
4 | RUN apt-get install -y --no-install-recommends nftables iproute2 netcat-traditional inetutils-ping net-tools nano ca-certificates git curl wget
5 | RUN mkdir /code
6 | WORKDIR /code
7 | ARG TARGETARCH
8 | RUN wget https://dl.google.com/go/go1.24.2.linux-${TARGETARCH}.tar.gz
9 | RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.2.linux-${TARGETARCH}.tar.gz
10 | ENV PATH="/usr/local/go/bin:$PATH"
11 | COPY code/ /code/
12 |
13 | ARG USE_TMPFS=true
14 | RUN --mount=type=tmpfs,target=/tmpfs \
15 | [ "$USE_TMPFS" = "true" ] && ln -s /tmpfs /root/go; \
16 | go build -ldflags "-s -w" -o /wireguard_plugin /code/wireguard_plugin.go
17 |
18 | FROM ghcr.io/spr-networks/container_template:latest
19 | ENV DEBIAN_FRONTEND=noninteractive
20 | RUN apt-get update && apt-get install -y --no-install-recommends wireguard-tools && rm -rf /var/lib/apt/lists/*
21 | COPY scripts /scripts/
22 | COPY --from=builder /wireguard_plugin /
23 | ENTRYPOINT ["/scripts/startup.sh"]
24 |
--------------------------------------------------------------------------------
/wireguard/README.md:
--------------------------------------------------------------------------------
1 | # plugin-wireguard
2 |
3 | ### Note:
4 | If using custom wireguard configurations with SPR groups, be sure to use "wg" as the prefix to the interface name,
5 | so that the API does not flush the devices from the groups.
6 |
7 |
8 | testing the setup:
9 |
10 | ```sh
11 | docker build -t plugin-wireguard --build-arg TARGETARCH=amd64 .
12 | docker run -v $PWD/../state/wireguard:/state/api -v $PWD/../configs:/configs plugin-wireguard
13 | ```
14 |
15 | verify plugin is working:
16 | ```sh
17 | export SOCK=$PWD/../state/wireguard/wireguard_plugin
18 | sudo chmod a+w $SOCK
19 | curl -s --unix-socket $SOCK http://localhost/peers
20 | ```
21 |
22 | if no PublicKey is specifed one will be generated:
23 | ```sh
24 | curl -s --unix-socket $SOCK http://localhost/peer -X PUT --data "{}"
25 | ```
26 |
27 | or specify PublicKey:
28 | ```sh
29 | KEY=$(wg genkey)
30 | PUBKEY=$(echo $KEY | wg pubkey)
31 | curl -s --unix-socket $SOCK http://localhost/peer -X PUT --data "{\"PublicKey\": \"${PUBKEY}\"}"
32 | ```
33 |
34 | use the ui for .conf and qrcode
35 |
36 |
--------------------------------------------------------------------------------
/wireguard/code/go.mod:
--------------------------------------------------------------------------------
1 | module sample_plugin
2 |
3 | go 1.17
4 |
5 | require github.com/gorilla/mux v1.8.1
6 |
--------------------------------------------------------------------------------
/wireguard/code/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
2 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
3 |
--------------------------------------------------------------------------------
/wireguard/code/run.sh:
--------------------------------------------------------------------------------
1 | go build -ldflags "-s -w" -o wireguard_plugin ./wireguard_plugin.go
2 | sudo -E ./wireguard_plugin
3 |
--------------------------------------------------------------------------------
/wireguard/scripts/down.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ip link del dev wg0
3 |
--------------------------------------------------------------------------------
/wireguard/scripts/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -a
3 | . /configs/base/config.sh
4 |
5 | export DNSIP=$(cat /configs/base/lanip)
6 | if [ -f state/plugins/mesh/enabled ]; then
7 | exit 0
8 | fi
9 |
10 | WIREGUARD_CONFIG=/configs/wireguard/wg0.conf
11 |
12 | grep 'PrivateKey = privkey' $WIREGUARD_CONFIG
13 | if [ $? -eq 0 ]; then
14 | echo "+ wg0.conf template found"
15 | echo "+ generating new keys for wiregurd"
16 | PRIVKEY=$(wg genkey)
17 | PUBKEY=$(echo $PRIVKEY | wg pubkey)
18 | # only interface
19 | ESCAPED_PUBKEY=$(printf '%s\n' "$PUBKEY" | sed -e 's/[\/&]/\\&/g')
20 | cat $WIREGUARD_CONFIG | sed "s/PrivateKey = privkey/PrivateKey = $ESCAPED_PUBKEY/g" | tee /tmp/wg0.conf
21 | mv /tmp/wg0.conf $WIREGUARD_CONFIG
22 | fi
23 |
24 | . /scripts/up.sh
25 |
26 | /wireguard_plugin
27 |
--------------------------------------------------------------------------------
/wireguard/scripts/up.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | . /configs/base/config.sh
3 |
4 | ip link add dev wg0 type wireguard
5 | ip addr flush dev wg0
6 | wg setconf wg0 $WIREGUARD_CONFIG
7 | ip link set dev wg0 multicast on
8 | ip link set dev wg0 up
9 |
--------------------------------------------------------------------------------