Skip to content

Commit e857462

Browse files
committed
dynamically populate localIPAddresses without requiring a reboot
1 parent b5f3e96 commit e857462

File tree

5 files changed

+71
-112
lines changed

5 files changed

+71
-112
lines changed

cmd/main.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"log"
5-
"time"
65

76
"github.com/doubleunion/accesscontrol/router"
87
rpio "github.com/stianeikeland/go-rpio/v4"
@@ -15,18 +14,5 @@ func main() {
1514
}
1615
defer rpio.Close()
1716

18-
// Run updateIPAndRestart every minute in a separate thread
19-
go func() {
20-
ticker := time.NewTicker(1 * time.Minute)
21-
defer ticker.Stop()
22-
23-
for range ticker.C {
24-
err := router.UpdateIPAndRestart()
25-
if err != nil {
26-
log.Printf("Error in updateIPAndRestart: %v", err)
27-
}
28-
}
29-
}()
30-
3117
router.RunRouter()
3218
}

door/door.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"bytes"
55
cryptoRand "crypto/rand"
66
"fmt"
7-
rpio "github.com/stianeikeland/go-rpio/v4"
87
"log"
98
"sync"
109
"time"
10+
11+
rpio "github.com/stianeikeland/go-rpio/v4"
1112
)
1213

1314
type Door struct {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/labstack/echo-jwt/v4 v4.2.0
88
github.com/labstack/echo/v4 v4.11.3
99
github.com/stianeikeland/go-rpio/v4 v4.6.0
10+
golang.org/x/crypto v0.14.0
1011
)
1112

1213
require (
@@ -16,7 +17,6 @@ require (
1617
github.com/mattn/go-isatty v0.0.19 // indirect
1718
github.com/valyala/bytebufferpool v1.0.0 // indirect
1819
github.com/valyala/fasttemplate v1.2.2 // indirect
19-
golang.org/x/crypto v0.14.0 // indirect
2020
golang.org/x/net v0.17.0 // indirect
2121
golang.org/x/sys v0.13.0 // indirect
2222
golang.org/x/text v0.13.0 // indirect

router/dynamic_ip.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package router
2+
3+
import (
4+
"log"
5+
"net"
6+
"net/http"
7+
"sync"
8+
"time"
9+
10+
"github.com/labstack/echo/v4"
11+
)
12+
13+
var (
14+
localInternetAddresses []net.IP
15+
localInternetAddressesMutex sync.RWMutex
16+
)
17+
18+
func beginLocalIPMonitoring() {
19+
updateIPAddressAndSet()
20+
21+
go func() {
22+
ticker := time.NewTicker(30 * time.Second)
23+
defer ticker.Stop()
24+
25+
for range ticker.C {
26+
updateIPAddressAndSet()
27+
}
28+
}()
29+
}
30+
31+
func updateIPAddressAndSet() {
32+
ips, err := net.LookupIP("doorcontrol.doubleunion.org")
33+
if err != nil {
34+
log.Printf("Error in updateIPAddressAndSet: %v", err)
35+
}
36+
37+
localInternetAddressesMutex.Lock()
38+
defer localInternetAddressesMutex.Unlock()
39+
localInternetAddresses = ips
40+
41+
log.Printf("Updated local IP addresses to: %v", ips)
42+
}
43+
44+
func requireLocalNetworkMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
45+
return func(c echo.Context) error {
46+
// Get the remote address from the request
47+
remoteAddr := c.Request().RemoteAddr
48+
49+
// Parse the IP address
50+
ip, _, err := net.SplitHostPort(remoteAddr)
51+
if err != nil {
52+
return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse IP address")
53+
}
54+
55+
localInternetAddressesMutex.RLock()
56+
defer localInternetAddressesMutex.RUnlock()
57+
for _, allowedIP := range localInternetAddresses {
58+
if ip == allowedIP.String() {
59+
// Continue to the next middleware or route handler
60+
return next(c)
61+
}
62+
}
63+
64+
// Fail the request, there was no matching IP
65+
return jsonResponse(c, http.StatusForbidden, "requests not allowed from remote hosts")
66+
}
67+
}

router/router.go

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import (
55
"fmt"
66
"io"
77
"log"
8-
"net"
98
"net/http"
109
"os"
11-
"os/exec"
12-
"strings"
1310
"time"
1411

1512
"github.com/doubleunion/accesscontrol/door"
@@ -21,20 +18,13 @@ import (
2118
"golang.org/x/crypto/acme/autocert"
2219
)
2320

24-
const serviceFilePath = "/etc/systemd/system/accesscontrol.service"
25-
const ipQueryURL = "https://wtfismyip.com/text"
26-
27-
var localInternetAddress = os.Getenv("LOCAL_INTERNET_ADDRESS")
28-
2921
func RunRouter() {
3022
signingKey := os.Getenv("ACCESS_CONTROL_SIGNING_KEY")
3123
if signingKey == "" {
3224
log.Fatal("signing key is missing")
3325
}
3426

35-
if localInternetAddress == "" {
36-
log.Fatal("local internet address is missing")
37-
}
27+
beginLocalIPMonitoring()
3828

3929
door := door.New()
4030

@@ -96,88 +86,3 @@ func RunRouter() {
9686
func jsonResponse(c echo.Context, code int, message string) error {
9787
return c.JSON(code, map[string]string{"message": message})
9888
}
99-
100-
func requireLocalNetworkMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
101-
return func(c echo.Context) error {
102-
// Get the remote address from the request
103-
remoteAddr := c.Request().RemoteAddr
104-
105-
// Parse the IP address
106-
ip, _, err := net.SplitHostPort(remoteAddr)
107-
if err != nil {
108-
return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse IP address")
109-
}
110-
111-
if ip != localInternetAddress {
112-
return jsonResponse(c, http.StatusForbidden, "requests not allowed from remote hosts")
113-
}
114-
115-
// Continue to the next middleware or route handler
116-
return next(c)
117-
}
118-
}
119-
120-
func UpdateIPAndRestart() error {
121-
// Step 1: Query current IP address
122-
resp, err := http.Get(ipQueryURL)
123-
if err != nil {
124-
return err
125-
}
126-
defer resp.Body.Close()
127-
128-
ipBytes, err := io.ReadAll(resp.Body)
129-
if err != nil {
130-
return err
131-
}
132-
currentIP := strings.TrimSpace(string(ipBytes))
133-
134-
// Step 2: Read the service file
135-
content, err := os.ReadFile(serviceFilePath)
136-
if err != nil {
137-
return err
138-
}
139-
140-
// Step 3: Check if the IP matches
141-
lines := strings.Split(string(content), "\n")
142-
var updatedContent []string
143-
ipUpdated := false
144-
for _, line := range lines {
145-
if strings.HasPrefix(line, "Environment=LOCAL_INTERNET_ADDRESS=") {
146-
fileIP := strings.TrimPrefix(line, "Environment=LOCAL_INTERNET_ADDRESS=")
147-
if fileIP != currentIP {
148-
line = "Environment=LOCAL_INTERNET_ADDRESS=" + currentIP
149-
ipUpdated = true
150-
}
151-
}
152-
updatedContent = append(updatedContent, line)
153-
}
154-
155-
// Step 4: Update the file if necessary
156-
if ipUpdated {
157-
// first we have to output the new contents to a temporary file
158-
// because we don't have access to the service file directly
159-
tempFilePath := "/tmp/accesscontrol.service"
160-
err = os.WriteFile(tempFilePath, []byte(strings.Join(updatedContent, "\n")), 0644)
161-
if err != nil {
162-
return err
163-
}
164-
165-
// then we copy the temporary file to the service file path
166-
// the path is owned by the process user so this is allowed by the OS without sudo
167-
cmd := exec.Command("cp", tempFilePath, serviceFilePath)
168-
err = cmd.Run()
169-
if err != nil {
170-
return err
171-
}
172-
173-
// Step 5: Restart the Raspberry Pi
174-
cmd = exec.Command("sudo", "shutdown", "-r", "now")
175-
//log.Printf("Error in updateIPAndRestart: %v", err)
176-
err = cmd.Run()
177-
if err != nil {
178-
return err
179-
}
180-
}
181-
182-
return nil
183-
}

0 commit comments

Comments
 (0)