+ install: check static ip
* move code to network_utils + add tests 'dhcp' package has its own network_utils for now + doc: add static ip configuration algorithm on Ubuntu
This commit is contained in:
parent
207e0236f7
commit
5c385521c4
|
@ -138,10 +138,13 @@ Request:
|
||||||
{
|
{
|
||||||
"web":{"port":80,"ip":"192.168.11.33"},
|
"web":{"port":80,"ip":"192.168.11.33"},
|
||||||
"dns":{"port":53,"ip":"127.0.0.1","autofix":false},
|
"dns":{"port":53,"ip":"127.0.0.1","autofix":false},
|
||||||
|
"set_static_ip": true | false
|
||||||
}
|
}
|
||||||
|
|
||||||
Server should check whether a port is available only in case it itself isn't already listening on that port.
|
Server should check whether a port is available only in case it itself isn't already listening on that port.
|
||||||
|
|
||||||
|
If `set_static_ip` is `true`, Server attempts to set a static IP for the network interface chosen by `dns.ip` setting. If the operation is successful, `static_ip.static` setting will be `yes`. If it fails, `static_ip.static` setting will be set to `error` and `static_ip.error` will contain the error message.
|
||||||
|
|
||||||
Server replies on success:
|
Server replies on success:
|
||||||
|
|
||||||
200 OK
|
200 OK
|
||||||
|
@ -149,7 +152,14 @@ Server replies on success:
|
||||||
{
|
{
|
||||||
"web":{"status":""},
|
"web":{"status":""},
|
||||||
"dns":{"status":""},
|
"dns":{"status":""},
|
||||||
|
"static_ip": {
|
||||||
|
"static": "yes|no|error",
|
||||||
|
"ip": "<Current dynamic IP address>", // set if static=no
|
||||||
|
"error": "..." // set if static=error
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
If `static_ip.static` is `no`, Server has detected that the system uses a dynamic address and it can automatically set a static address if `set_static_ip` in request is `true`. See section `Static IP check/set` for detailed process.
|
||||||
|
|
||||||
Server replies on error:
|
Server replies on error:
|
||||||
|
|
||||||
|
@ -172,7 +182,11 @@ Request:
|
||||||
POST /control/install/check_config
|
POST /control/install/check_config
|
||||||
|
|
||||||
{
|
{
|
||||||
"dns":{"port":53,"ip":"127.0.0.1","autofix":false}
|
"dns":{
|
||||||
|
"port":53,
|
||||||
|
"ip":"127.0.0.1",
|
||||||
|
"autofix":false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Check if DNSStubListener is enabled:
|
Check if DNSStubListener is enabled:
|
||||||
|
@ -499,13 +513,7 @@ which will print:
|
||||||
default via 192.168.0.1 proto dhcp metric 100
|
default via 192.168.0.1 proto dhcp metric 100
|
||||||
|
|
||||||
|
|
||||||
#### Phase 2
|
#### Phase 2 (Raspbian)
|
||||||
|
|
||||||
This method only works on Raspbian.
|
|
||||||
|
|
||||||
On Ubuntu DHCP for a network interface can't be disabled via `dhcpcd.conf`. This must be configured in `/etc/netplan/01-netcfg.yaml`.
|
|
||||||
|
|
||||||
Fedora doesn't use `dhcpcd.conf` configuration at all.
|
|
||||||
|
|
||||||
Step 1.
|
Step 1.
|
||||||
|
|
||||||
|
@ -526,6 +534,44 @@ If we would set a different IP address, we'd need to replace the IP address for
|
||||||
ip addr replace dev eth0 192.168.0.1/24
|
ip addr replace dev eth0 192.168.0.1/24
|
||||||
|
|
||||||
|
|
||||||
|
#### Phase 2 (Ubuntu)
|
||||||
|
|
||||||
|
`/etc/netplan/01-netcfg.yaml` or `/etc/netplan/01-network-manager-all.yaml`
|
||||||
|
|
||||||
|
This configuration example has a static IP set for `enp0s3` interface:
|
||||||
|
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
renderer: networkd
|
||||||
|
ethernets:
|
||||||
|
enp0s3:
|
||||||
|
dhcp4: no
|
||||||
|
addresses: [192.168.0.2/24]
|
||||||
|
gateway: 192.168.0.1
|
||||||
|
nameservers:
|
||||||
|
addresses: [192.168.0.1,8.8.8.8]
|
||||||
|
|
||||||
|
For dynamic configuration `dhcp4: yes` is set.
|
||||||
|
|
||||||
|
Make a backup copy to `/etc/netplan/01-netcfg.yaml.backup`.
|
||||||
|
|
||||||
|
Apply:
|
||||||
|
|
||||||
|
netplan apply
|
||||||
|
|
||||||
|
Restart network:
|
||||||
|
|
||||||
|
systemctl restart networking
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
systemctl restart network-manager
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
systemctl restart system-networkd
|
||||||
|
|
||||||
|
|
||||||
### Add a static lease
|
### Add a static lease
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
|
@ -2,18 +2,14 @@ package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/file"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -115,7 +111,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type netInterface struct {
|
type netInterfaceJSON struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MTU int `json:"mtu"`
|
MTU int `json:"mtu"`
|
||||||
HardwareAddr string `json:"hardware_address"`
|
HardwareAddr string `json:"hardware_address"`
|
||||||
|
@ -123,29 +119,6 @@ type netInterface struct {
|
||||||
Flags string `json:"flags"`
|
Flags string `json:"flags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
|
||||||
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
|
||||||
func getValidNetInterfaces() ([]net.Interface, error) {
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
netIfaces := []net.Interface{}
|
|
||||||
|
|
||||||
for i := range ifaces {
|
|
||||||
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
|
||||||
// this interface is ppp, we're not interested in this one
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface := ifaces[i]
|
|
||||||
netIfaces = append(netIfaces, iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
return netIfaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
response := map[string]interface{}{}
|
response := map[string]interface{}{}
|
||||||
|
|
||||||
|
@ -170,7 +143,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonIface := netInterface{
|
jsonIface := netInterfaceJSON{
|
||||||
Name: iface.Name,
|
Name: iface.Name,
|
||||||
MTU: iface.MTU,
|
MTU: iface.MTU,
|
||||||
HardwareAddr: iface.HardwareAddr.String(),
|
HardwareAddr: iface.HardwareAddr.String(),
|
||||||
|
@ -263,137 +236,6 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if network interface has a static IP configured
|
|
||||||
func hasStaticIP(ifaceName string) (bool, error) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return false, errors.New("Can't detect static IP: not supported on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(body), "\n")
|
|
||||||
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
|
||||||
withinInterfaceCtx := false
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
if withinInterfaceCtx && len(line) == 0 {
|
|
||||||
// an empty line resets our state
|
|
||||||
withinInterfaceCtx = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) == 0 || line[0] == '#' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
if !withinInterfaceCtx {
|
|
||||||
if line == nameLine {
|
|
||||||
// we found our interface
|
|
||||||
withinInterfaceCtx = true
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(line, "interface ") {
|
|
||||||
// we found another interface - reset our state
|
|
||||||
withinInterfaceCtx = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "static ip_address=") {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get IP address with netmask
|
|
||||||
func getFullIP(ifaceName string) string {
|
|
||||||
cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName)
|
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
|
||||||
d, err := cmd.Output()
|
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(string(d))
|
|
||||||
if len(fields) < 4 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
_, _, err = net.ParseCIDR(fields[3])
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get gateway IP address
|
|
||||||
func getGatewayIP(ifaceName string) string {
|
|
||||||
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
|
||||||
d, err := cmd.Output()
|
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(string(d))
|
|
||||||
if len(fields) < 3 || fields[0] != "default" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(fields[2])
|
|
||||||
if ip == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a static IP for network interface
|
|
||||||
func setStaticIP(ifaceName string) error {
|
|
||||||
ip := getFullIP(ifaceName)
|
|
||||||
if len(ip) == 0 {
|
|
||||||
return errors.New("Can't get IP address")
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip4, _, err := net.ParseCIDR(ip)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
|
||||||
ifaceName, ip)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
|
|
||||||
gatewayIP := getGatewayIP(ifaceName)
|
|
||||||
if len(gatewayIP) != 0 {
|
|
||||||
add = fmt.Sprintf("static routers=%s\n",
|
|
||||||
gatewayIP)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
|
||||||
ip4)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
|
|
||||||
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
lj := staticLeaseJSON{}
|
lj := staticLeaseJSON{}
|
||||||
|
|
|
@ -0,0 +1,317 @@
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/file"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/joomcode/errorx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netInterface struct {
|
||||||
|
Name string
|
||||||
|
MTU int
|
||||||
|
HardwareAddr string
|
||||||
|
Addresses []string
|
||||||
|
Flags string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
||||||
|
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
||||||
|
func getValidNetInterfaces() ([]net.Interface, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIfaces := []net.Interface{}
|
||||||
|
|
||||||
|
for i := range ifaces {
|
||||||
|
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
||||||
|
// this interface is ppp, we're not interested in this one
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := ifaces[i]
|
||||||
|
netIfaces = append(netIfaces, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netIfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getValidNetInterfacesMap returns interfaces that are eligible for DNS and WEB only
|
||||||
|
// we do not return link-local addresses here
|
||||||
|
func getValidNetInterfacesForWeb() ([]netInterface, error) {
|
||||||
|
ifaces, err := getValidNetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.Decorate(err, "Couldn't get interfaces")
|
||||||
|
}
|
||||||
|
if len(ifaces) == 0 {
|
||||||
|
return nil, errors.New("couldn't find any legible interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
var netInterfaces []netInterface
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
addrs, e := iface.Addrs()
|
||||||
|
if e != nil {
|
||||||
|
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIface := netInterface{
|
||||||
|
Name: iface.Name,
|
||||||
|
MTU: iface.MTU,
|
||||||
|
HardwareAddr: iface.HardwareAddr.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if iface.Flags != 0 {
|
||||||
|
netIface.Flags = iface.Flags.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want link-local addresses in json, so skip them
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
// not an IPNet, should not happen
|
||||||
|
return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
||||||
|
}
|
||||||
|
// ignore link-local
|
||||||
|
if ipnet.IP.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
netIface.Addresses = append(netIface.Addresses, ipnet.IP.String())
|
||||||
|
}
|
||||||
|
if len(netIface.Addresses) != 0 {
|
||||||
|
netInterfaces = append(netInterfaces, netIface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netInterfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if network interface has a static IP configured
|
||||||
|
// Supports: Raspbian.
|
||||||
|
func hasStaticIP(ifaceName string) (bool, error) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return false, errors.New("Can't detect static IP: not supported on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasStaticIPDhcpcdConf(string(body), ifaceName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for dhcpcd.conf
|
||||||
|
func hasStaticIPDhcpcdConf(data, ifaceName string) bool {
|
||||||
|
lines := strings.Split(data, "\n")
|
||||||
|
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
||||||
|
withinInterfaceCtx := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if withinInterfaceCtx && len(line) == 0 {
|
||||||
|
// an empty line resets our state
|
||||||
|
withinInterfaceCtx = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if !withinInterfaceCtx {
|
||||||
|
if line == nameLine {
|
||||||
|
// we found our interface
|
||||||
|
withinInterfaceCtx = true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(line, "interface ") {
|
||||||
|
// we found another interface - reset our state
|
||||||
|
withinInterfaceCtx = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "static ip_address=") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IP address with netmask
|
||||||
|
func getFullIP(ifaceName string) string {
|
||||||
|
cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName)
|
||||||
|
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||||
|
d, err := cmd.Output()
|
||||||
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(string(d))
|
||||||
|
if len(fields) < 4 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
_, _, err = net.ParseCIDR(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get interface name by its IP address.
|
||||||
|
func getInterfaceByIP(ip string) string {
|
||||||
|
ifaces, err := getValidNetInterfacesForWeb()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
for _, addr := range iface.Addresses {
|
||||||
|
if ip == addr {
|
||||||
|
return iface.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gateway IP address
|
||||||
|
func getGatewayIP(ifaceName string) string {
|
||||||
|
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
||||||
|
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||||
|
d, err := cmd.Output()
|
||||||
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(string(d))
|
||||||
|
if len(fields) < 3 || fields[0] != "default" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(fields[2])
|
||||||
|
if ip == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a static IP for network interface
|
||||||
|
// Supports: Raspbian.
|
||||||
|
func setStaticIP(ifaceName string) error {
|
||||||
|
ip := getFullIP(ifaceName)
|
||||||
|
if len(ip) == 0 {
|
||||||
|
return errors.New("Can't get IP address")
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4, _, err := net.ParseCIDR(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gatewayIP := getGatewayIP(ifaceName)
|
||||||
|
add := setStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
|
||||||
|
|
||||||
|
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for dhcpcd.conf
|
||||||
|
func setStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
|
||||||
|
var body []byte
|
||||||
|
|
||||||
|
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
||||||
|
ifaceName, ip)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
|
||||||
|
if len(gatewayIP) != 0 {
|
||||||
|
add = fmt.Sprintf("static routers=%s\n",
|
||||||
|
gatewayIP)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
||||||
|
dnsIP)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
|
||||||
|
func checkPortAvailable(host string, port int) error {
|
||||||
|
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
|
// We wait for some time and hope that this fd will be closed.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPacketPortAvailable(host string, port int) error {
|
||||||
|
ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
|
// We wait for some time and hope that this fd will be closed.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if error is "address already in use"
|
||||||
|
func errorIsAddrInUse(err error) bool {
|
||||||
|
errOpError, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errErrno, ok := errSyscallError.Err.(syscall.Errno)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
const WSAEADDRINUSE = 10048
|
||||||
|
return errErrno == WSAEADDRINUSE
|
||||||
|
}
|
||||||
|
|
||||||
|
return errErrno == syscall.EADDRINUSE
|
||||||
|
}
|
|
@ -22,6 +22,14 @@ type firstRunData struct {
|
||||||
Interfaces map[string]interface{} `json:"interfaces"`
|
Interfaces map[string]interface{} `json:"interfaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type netInterfaceJSON struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
HardwareAddr string `json:"hardware_address"`
|
||||||
|
Addresses []string `json:"ip_addresses"`
|
||||||
|
Flags string `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
// Get initial installation settings
|
// Get initial installation settings
|
||||||
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
data := firstRunData{}
|
data := firstRunData{}
|
||||||
|
@ -36,7 +44,14 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data.Interfaces = make(map[string]interface{})
|
data.Interfaces = make(map[string]interface{})
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
data.Interfaces[iface.Name] = iface
|
ifaceJSON := netInterfaceJSON{
|
||||||
|
Name: iface.Name,
|
||||||
|
MTU: iface.MTU,
|
||||||
|
HardwareAddr: iface.HardwareAddr,
|
||||||
|
Addresses: iface.Addresses,
|
||||||
|
Flags: iface.Flags,
|
||||||
|
}
|
||||||
|
data.Interfaces[iface.Name] = ifaceJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -48,9 +63,10 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkConfigReqEnt struct {
|
type checkConfigReqEnt struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Autofix bool `json:"autofix"`
|
Autofix bool `json:"autofix"`
|
||||||
|
SetStaticIP bool `json:"set_static_ip"`
|
||||||
}
|
}
|
||||||
type checkConfigReq struct {
|
type checkConfigReq struct {
|
||||||
Web checkConfigReqEnt `json:"web"`
|
Web checkConfigReqEnt `json:"web"`
|
||||||
|
@ -61,9 +77,15 @@ type checkConfigRespEnt struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CanAutofix bool `json:"can_autofix"`
|
CanAutofix bool `json:"can_autofix"`
|
||||||
}
|
}
|
||||||
|
type staticIPJSON struct {
|
||||||
|
Static string `json:"static"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
type checkConfigResp struct {
|
type checkConfigResp struct {
|
||||||
Web checkConfigRespEnt `json:"web"`
|
Web checkConfigRespEnt `json:"web"`
|
||||||
DNS checkConfigRespEnt `json:"dns"`
|
DNS checkConfigRespEnt `json:"dns"`
|
||||||
|
StaticIP staticIPJSON `json:"static_ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if ports are available, respond with results
|
// Check if ports are available, respond with results
|
||||||
|
@ -108,6 +130,33 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respData.DNS.Status = fmt.Sprintf("%v", err)
|
respData.DNS.Status = fmt.Sprintf("%v", err)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
interfaceName := getInterfaceByIP(reqData.DNS.IP)
|
||||||
|
staticIPStatus := "yes"
|
||||||
|
|
||||||
|
if len(interfaceName) == 0 {
|
||||||
|
staticIPStatus = "error"
|
||||||
|
respData.StaticIP.Error = fmt.Sprintf("Couldn't find network interface by IP %s", reqData.DNS.IP)
|
||||||
|
|
||||||
|
} else if reqData.DNS.SetStaticIP {
|
||||||
|
err = setStaticIP(interfaceName)
|
||||||
|
staticIPStatus = "error"
|
||||||
|
respData.StaticIP.Error = err.Error()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// check if we have a static IP
|
||||||
|
isStaticIP, err := hasStaticIP(interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
staticIPStatus = "error"
|
||||||
|
respData.StaticIP.Error = err.Error()
|
||||||
|
} else if !isStaticIP {
|
||||||
|
staticIPStatus = "no"
|
||||||
|
respData.StaticIP.IP = getFullIP(interfaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
respData.StaticIP.Static = staticIPStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
138
home/helpers.go
138
home/helpers.go
|
@ -2,7 +2,6 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,7 +12,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -151,117 +149,6 @@ func postInstallHandler(handler http.Handler) http.Handler {
|
||||||
return &postInstallHandlerStruct{handler}
|
return &postInstallHandlerStruct{handler}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
|
||||||
// network interfaces
|
|
||||||
// ------------------
|
|
||||||
type netInterface struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
HardwareAddr string `json:"hardware_address"`
|
|
||||||
Addresses []string `json:"ip_addresses"`
|
|
||||||
Flags string `json:"flags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
|
||||||
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
|
||||||
func getValidNetInterfaces() ([]net.Interface, error) {
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
netIfaces := []net.Interface{}
|
|
||||||
|
|
||||||
for i := range ifaces {
|
|
||||||
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
|
||||||
// this interface is ppp, we're not interested in this one
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface := ifaces[i]
|
|
||||||
netIfaces = append(netIfaces, iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
return netIfaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getValidNetInterfacesMap returns interfaces that are eligible for DNS and WEB only
|
|
||||||
// we do not return link-local addresses here
|
|
||||||
func getValidNetInterfacesForWeb() ([]netInterface, error) {
|
|
||||||
ifaces, err := getValidNetInterfaces()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errorx.Decorate(err, "Couldn't get interfaces")
|
|
||||||
}
|
|
||||||
if len(ifaces) == 0 {
|
|
||||||
return nil, errors.New("couldn't find any legible interface")
|
|
||||||
}
|
|
||||||
|
|
||||||
var netInterfaces []netInterface
|
|
||||||
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
addrs, e := iface.Addrs()
|
|
||||||
if e != nil {
|
|
||||||
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
netIface := netInterface{
|
|
||||||
Name: iface.Name,
|
|
||||||
MTU: iface.MTU,
|
|
||||||
HardwareAddr: iface.HardwareAddr.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if iface.Flags != 0 {
|
|
||||||
netIface.Flags = iface.Flags.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't want link-local addresses in json, so skip them
|
|
||||||
for _, addr := range addrs {
|
|
||||||
ipnet, ok := addr.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
// not an IPNet, should not happen
|
|
||||||
return nil, fmt.Errorf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
|
||||||
}
|
|
||||||
// ignore link-local
|
|
||||||
if ipnet.IP.IsLinkLocalUnicast() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
netIface.Addresses = append(netIface.Addresses, ipnet.IP.String())
|
|
||||||
}
|
|
||||||
if len(netIface.Addresses) != 0 {
|
|
||||||
netInterfaces = append(netInterfaces, netIface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return netInterfaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
|
|
||||||
func checkPortAvailable(host string, port int) error {
|
|
||||||
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ln.Close()
|
|
||||||
|
|
||||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
|
||||||
// We wait for some time and hope that this fd will be closed.
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPacketPortAvailable(host string, port int) error {
|
|
||||||
ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ln.Close()
|
|
||||||
|
|
||||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
|
||||||
// We wait for some time and hope that this fd will be closed.
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to a remote server resolving hostname using our own DNS server
|
// Connect to a remote server resolving hostname using our own DNS server
|
||||||
func customDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
func customDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
log.Tracef("network:%v addr:%v", network, addr)
|
log.Tracef("network:%v addr:%v", network, addr)
|
||||||
|
@ -303,31 +190,6 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err
|
||||||
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if error is "address already in use"
|
|
||||||
func errorIsAddrInUse(err error) bool {
|
|
||||||
errOpError, ok := err.(*net.OpError)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
errErrno, ok := errSyscallError.Err.(syscall.Errno)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
const WSAEADDRINUSE = 10048
|
|
||||||
return errErrno == WSAEADDRINUSE
|
|
||||||
}
|
|
||||||
|
|
||||||
return errErrno == syscall.EADDRINUSE
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
// debug logging helpers
|
// debug logging helpers
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
|
|
@ -0,0 +1,317 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/file"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/joomcode/errorx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netInterface struct {
|
||||||
|
Name string
|
||||||
|
MTU int
|
||||||
|
HardwareAddr string
|
||||||
|
Addresses []string
|
||||||
|
Flags string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
||||||
|
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
||||||
|
func getValidNetInterfaces() ([]net.Interface, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIfaces := []net.Interface{}
|
||||||
|
|
||||||
|
for i := range ifaces {
|
||||||
|
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
||||||
|
// this interface is ppp, we're not interested in this one
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := ifaces[i]
|
||||||
|
netIfaces = append(netIfaces, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netIfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getValidNetInterfacesMap returns interfaces that are eligible for DNS and WEB only
|
||||||
|
// we do not return link-local addresses here
|
||||||
|
func getValidNetInterfacesForWeb() ([]netInterface, error) {
|
||||||
|
ifaces, err := getValidNetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.Decorate(err, "Couldn't get interfaces")
|
||||||
|
}
|
||||||
|
if len(ifaces) == 0 {
|
||||||
|
return nil, errors.New("couldn't find any legible interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
var netInterfaces []netInterface
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
addrs, e := iface.Addrs()
|
||||||
|
if e != nil {
|
||||||
|
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIface := netInterface{
|
||||||
|
Name: iface.Name,
|
||||||
|
MTU: iface.MTU,
|
||||||
|
HardwareAddr: iface.HardwareAddr.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if iface.Flags != 0 {
|
||||||
|
netIface.Flags = iface.Flags.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want link-local addresses in json, so skip them
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
// not an IPNet, should not happen
|
||||||
|
return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
||||||
|
}
|
||||||
|
// ignore link-local
|
||||||
|
if ipnet.IP.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
netIface.Addresses = append(netIface.Addresses, ipnet.IP.String())
|
||||||
|
}
|
||||||
|
if len(netIface.Addresses) != 0 {
|
||||||
|
netInterfaces = append(netInterfaces, netIface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netInterfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if network interface has a static IP configured
|
||||||
|
// Supports: Raspbian.
|
||||||
|
func hasStaticIP(ifaceName string) (bool, error) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return false, errors.New("Can't detect static IP: not supported on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasStaticIPDhcpcdConf(string(body), ifaceName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for dhcpcd.conf
|
||||||
|
func hasStaticIPDhcpcdConf(data, ifaceName string) bool {
|
||||||
|
lines := strings.Split(data, "\n")
|
||||||
|
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
||||||
|
withinInterfaceCtx := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if withinInterfaceCtx && len(line) == 0 {
|
||||||
|
// an empty line resets our state
|
||||||
|
withinInterfaceCtx = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if !withinInterfaceCtx {
|
||||||
|
if line == nameLine {
|
||||||
|
// we found our interface
|
||||||
|
withinInterfaceCtx = true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(line, "interface ") {
|
||||||
|
// we found another interface - reset our state
|
||||||
|
withinInterfaceCtx = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "static ip_address=") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IP address with netmask
|
||||||
|
func getFullIP(ifaceName string) string {
|
||||||
|
cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName)
|
||||||
|
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||||
|
d, err := cmd.Output()
|
||||||
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(string(d))
|
||||||
|
if len(fields) < 4 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
_, _, err = net.ParseCIDR(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get interface name by its IP address.
|
||||||
|
func getInterfaceByIP(ip string) string {
|
||||||
|
ifaces, err := getValidNetInterfacesForWeb()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
for _, addr := range iface.Addresses {
|
||||||
|
if ip == addr {
|
||||||
|
return iface.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gateway IP address
|
||||||
|
func getGatewayIP(ifaceName string) string {
|
||||||
|
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
||||||
|
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||||
|
d, err := cmd.Output()
|
||||||
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(string(d))
|
||||||
|
if len(fields) < 3 || fields[0] != "default" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(fields[2])
|
||||||
|
if ip == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a static IP for network interface
|
||||||
|
// Supports: Raspbian.
|
||||||
|
func setStaticIP(ifaceName string) error {
|
||||||
|
ip := getFullIP(ifaceName)
|
||||||
|
if len(ip) == 0 {
|
||||||
|
return errors.New("Can't get IP address")
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4, _, err := net.ParseCIDR(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gatewayIP := getGatewayIP(ifaceName)
|
||||||
|
add := setStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
|
||||||
|
|
||||||
|
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for dhcpcd.conf
|
||||||
|
func setStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
|
||||||
|
var body []byte
|
||||||
|
|
||||||
|
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
||||||
|
ifaceName, ip)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
|
||||||
|
if len(gatewayIP) != 0 {
|
||||||
|
add = fmt.Sprintf("static routers=%s\n",
|
||||||
|
gatewayIP)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
||||||
|
dnsIP)
|
||||||
|
body = append(body, []byte(add)...)
|
||||||
|
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
|
||||||
|
func checkPortAvailable(host string, port int) error {
|
||||||
|
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
|
// We wait for some time and hope that this fd will be closed.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPacketPortAvailable(host string, port int) error {
|
||||||
|
ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
|
// We wait for some time and hope that this fd will be closed.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if error is "address already in use"
|
||||||
|
func errorIsAddrInUse(err error) bool {
|
||||||
|
errOpError, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errErrno, ok := errSyscallError.Err.(syscall.Errno)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
const WSAEADDRINUSE = 10048
|
||||||
|
return errErrno == WSAEADDRINUSE
|
||||||
|
}
|
||||||
|
|
||||||
|
return errErrno == syscall.EADDRINUSE
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHasStaticIPDhcpcdConf(t *testing.T) {
|
||||||
|
dhcpdConf := `#comment
|
||||||
|
# comment
|
||||||
|
|
||||||
|
interface eth0
|
||||||
|
static ip_address=192.168.0.1/24
|
||||||
|
|
||||||
|
# interface wlan0
|
||||||
|
static ip_address=192.168.1.1/24
|
||||||
|
|
||||||
|
# comment
|
||||||
|
`
|
||||||
|
assert.True(t, !hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
||||||
|
|
||||||
|
dhcpdConf = `#comment
|
||||||
|
# comment
|
||||||
|
|
||||||
|
interface eth0
|
||||||
|
static ip_address=192.168.0.1/24
|
||||||
|
|
||||||
|
# interface wlan0
|
||||||
|
static ip_address=192.168.1.1/24
|
||||||
|
|
||||||
|
# comment
|
||||||
|
|
||||||
|
interface wlan0
|
||||||
|
# comment
|
||||||
|
static ip_address=192.168.2.1/24
|
||||||
|
`
|
||||||
|
assert.True(t, hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetStaticIPDhcpcdConf(t *testing.T) {
|
||||||
|
dhcpcdConf := `
|
||||||
|
interface wlan0
|
||||||
|
static ip_address=192.168.0.2/24
|
||||||
|
static routers=192.168.0.1
|
||||||
|
static domain_name_servers=192.168.0.2
|
||||||
|
|
||||||
|
`
|
||||||
|
s := setStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2")
|
||||||
|
assert.Equal(t, dhcpcdConf, s)
|
||||||
|
|
||||||
|
// without gateway
|
||||||
|
dhcpcdConf = `
|
||||||
|
interface wlan0
|
||||||
|
static ip_address=192.168.0.2/24
|
||||||
|
static domain_name_servers=192.168.0.2
|
||||||
|
|
||||||
|
`
|
||||||
|
s = setStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2")
|
||||||
|
assert.Equal(t, dhcpcdConf, s)
|
||||||
|
}
|
Loading…
Reference in New Issue