mirror of https://github.com/jetkvm/kvm.git
316 lines
10 KiB
Go
316 lines
10 KiB
Go
package dhclient
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
|
)
|
|
|
|
var (
|
|
defaultLeaseTime = time.Duration(30 * time.Minute)
|
|
defaultRenewalTime = time.Duration(15 * time.Minute)
|
|
)
|
|
|
|
// Lease is a network configuration obtained by DHCP.
|
|
type Lease struct {
|
|
// from https://udhcp.busybox.net/README.udhcpc
|
|
IPAddress net.IP `env:"ip" json:"ip"` // The obtained IP
|
|
Netmask net.IP `env:"subnet" json:"netmask"` // The assigned subnet mask
|
|
Broadcast net.IP `env:"broadcast" json:"broadcast"` // The broadcast address for this network
|
|
TTL int `env:"ipttl" json:"ttl,omitempty"` // The TTL to use for this network
|
|
MTU int `env:"mtu" json:"mtu,omitempty"` // The MTU to use for this network
|
|
HostName string `env:"hostname" json:"hostname,omitempty"` // The assigned hostname
|
|
Domain string `env:"domain" json:"domain,omitempty"` // The domain name of the network
|
|
SearchList []string `env:"search" json:"search_list,omitempty"` // The search list for the network
|
|
BootPNextServer net.IP `env:"siaddr" json:"bootp_next_server,omitempty"` // The bootp next server option
|
|
BootPServerName string `env:"sname" json:"bootp_server_name,omitempty"` // The bootp server name option
|
|
BootPFile string `env:"boot_file" json:"bootp_file,omitempty"` // The bootp boot file option
|
|
Timezone string `env:"timezone" json:"timezone,omitempty"` // Offset in seconds from UTC
|
|
Routers []net.IP `env:"router" json:"routers,omitempty"` // A list of routers
|
|
DNS []net.IP `env:"dns" json:"dns_servers,omitempty"` // A list of DNS servers
|
|
NTPServers []net.IP `env:"ntpsrv" json:"ntp_servers,omitempty"` // A list of NTP servers
|
|
LPRServers []net.IP `env:"lprsvr" json:"lpr_servers,omitempty"` // A list of LPR servers
|
|
TimeServers []net.IP `env:"timesvr" json:"_time_servers,omitempty"` // A list of time servers (obsolete)
|
|
IEN116NameServers []net.IP `env:"namesvr" json:"_name_servers,omitempty"` // A list of IEN 116 name servers (obsolete)
|
|
LogServers []net.IP `env:"logsvr" json:"_log_servers,omitempty"` // A list of MIT-LCS UDP log servers (obsolete)
|
|
CookieServers []net.IP `env:"cookiesvr" json:"_cookie_servers,omitempty"` // A list of RFC 865 cookie servers (obsolete)
|
|
WINSServers []net.IP `env:"wins" json:"_wins_servers,omitempty"` // A list of WINS servers
|
|
SwapServer net.IP `env:"swapsvr" json:"_swap_server,omitempty"` // The IP address of the client's swap server
|
|
BootSize int `env:"bootsize" json:"bootsize,omitempty"` // The length in 512 octect blocks of the bootfile
|
|
RootPath string `env:"rootpath" json:"root_path,omitempty"` // The path name of the client's root disk
|
|
LeaseTime time.Duration `env:"lease" json:"lease,omitempty"` // The lease time, in seconds
|
|
RenewalTime time.Duration `env:"renewal" json:"renewal,omitempty"` // The renewal time, in seconds
|
|
RebindingTime time.Duration `env:"rebinding" json:"rebinding,omitempty"` // The rebinding time, in seconds
|
|
DHCPType string `env:"dhcptype" json:"dhcp_type,omitempty"` // DHCP message type (safely ignored)
|
|
ServerID string `env:"serverid" json:"server_id,omitempty"` // The IP of the server
|
|
Message string `env:"message" json:"reason,omitempty"` // Reason for a DHCPNAK
|
|
TFTPServerName string `env:"tftp" json:"tftp,omitempty"` // The TFTP server name
|
|
BootFileName string `env:"bootfile" json:"bootfile,omitempty"` // The boot file name
|
|
Uptime time.Duration `env:"uptime" json:"uptime,omitempty"` // The uptime of the device when the lease was obtained, in seconds
|
|
ClassIdentifier string `env:"classid" json:"class_identifier,omitempty"` // The class identifier
|
|
LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease
|
|
|
|
InterfaceName string `json:"interface_name,omitempty"` // The name of the interface
|
|
|
|
p4 *nclient4.Lease
|
|
p6 *dhcpv6.Message
|
|
|
|
isEmpty map[string]bool
|
|
}
|
|
|
|
// fromNclient4Lease creates a lease from a nclient4.Lease.
|
|
func fromNclient4Lease(l *nclient4.Lease, iface string) *Lease {
|
|
lease := &Lease{}
|
|
|
|
lease.p4 = l
|
|
|
|
// only the fields that we need are set
|
|
lease.Routers = l.ACK.Router()
|
|
lease.IPAddress = l.ACK.YourIPAddr
|
|
lease.Netmask = net.IP(l.ACK.SubnetMask())
|
|
lease.Broadcast = l.ACK.BroadcastAddress()
|
|
// lease.MTU = int(resp.Options.Get(dhcpv4.OptionInterfaceMTU))
|
|
|
|
lease.NTPServers = l.ACK.NTPServers()
|
|
|
|
lease.HostName = l.ACK.HostName()
|
|
lease.Domain = l.ACK.DomainName()
|
|
|
|
searchList := l.ACK.DomainSearch()
|
|
if searchList != nil {
|
|
lease.SearchList = searchList.Labels
|
|
}
|
|
|
|
lease.DNS = l.ACK.DNS()
|
|
|
|
lease.ClassIdentifier = l.ACK.ClassIdentifier()
|
|
lease.ServerID = l.ACK.ServerIdentifier().String()
|
|
|
|
lease.Message = l.ACK.Message()
|
|
lease.LeaseTime = l.ACK.IPAddressLeaseTime(defaultLeaseTime)
|
|
lease.RenewalTime = l.ACK.IPAddressRenewalTime(defaultRenewalTime)
|
|
|
|
lease.InterfaceName = iface
|
|
|
|
return lease
|
|
}
|
|
|
|
// fromNclient6Lease creates a lease from a nclient6.Message.
|
|
func fromNclient6Lease(l *dhcpv6.Message, iface string) *Lease {
|
|
lease := &Lease{}
|
|
|
|
lease.p6 = l
|
|
|
|
iana := l.Options.OneIANA()
|
|
if iana == nil {
|
|
return nil
|
|
}
|
|
|
|
address := iana.Options.OneAddress()
|
|
if address == nil {
|
|
return nil
|
|
}
|
|
|
|
lease.IPAddress = address.IPv6Addr
|
|
lease.Netmask = net.IP(net.CIDRMask(128, 128))
|
|
lease.DNS = l.Options.DNS()
|
|
// lease.LeaseTime = iana.Options.OnePreferredLifetime()
|
|
// lease.RenewalTime = iana.Options.OneValidLifetime()
|
|
// lease.RebindingTime = iana.Options.OneRebindingTime()
|
|
|
|
lease.InterfaceName = iface
|
|
|
|
return lease
|
|
}
|
|
|
|
func (l *Lease) setIsEmpty(m map[string]bool) {
|
|
l.isEmpty = m
|
|
}
|
|
|
|
// IsEmpty returns true if the lease is empty for the given key.
|
|
func (l *Lease) IsEmpty(key string) bool {
|
|
return l.isEmpty[key]
|
|
}
|
|
|
|
// ToJSON returns the lease as a JSON string.
|
|
func (l *Lease) ToJSON() string {
|
|
json, err := json.Marshal(l)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(json)
|
|
}
|
|
|
|
// SetLeaseExpiry sets the lease expiry time.
|
|
func (l *Lease) SetLeaseExpiry() (time.Time, error) {
|
|
if l.Uptime == 0 || l.LeaseTime == 0 {
|
|
return time.Time{}, fmt.Errorf("uptime or lease time isn't set")
|
|
}
|
|
|
|
// get the uptime of the device
|
|
file, err := os.Open("/proc/uptime")
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("failed to open uptime file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var uptime time.Duration
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
text := scanner.Text()
|
|
parts := strings.Split(text, " ")
|
|
uptime, err = time.ParseDuration(parts[0] + "s")
|
|
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("failed to parse uptime: %w", err)
|
|
}
|
|
}
|
|
|
|
relativeLeaseRemaining := (l.Uptime + l.LeaseTime) - uptime
|
|
leaseExpiry := time.Now().Add(relativeLeaseRemaining)
|
|
|
|
l.LeaseExpiry = &leaseExpiry
|
|
|
|
return leaseExpiry, nil
|
|
}
|
|
|
|
// UnmarshalDHCPCLease unmarshals a lease from a string.
|
|
func UnmarshalDHCPCLease(lease *Lease, str string) error {
|
|
// parse the lease file as a map
|
|
data := make(map[string]string)
|
|
for _, line := range strings.Split(str, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
// skip empty lines and comments
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
data[key] = value
|
|
}
|
|
|
|
// now iterate over the lease struct and set the values
|
|
leaseType := reflect.TypeOf(lease).Elem()
|
|
leaseValue := reflect.ValueOf(lease).Elem()
|
|
|
|
valuesParsed := make(map[string]bool)
|
|
|
|
for i := 0; i < leaseType.NumField(); i++ {
|
|
field := leaseValue.Field(i)
|
|
|
|
// get the env tag
|
|
key := leaseType.Field(i).Tag.Get("env")
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
valuesParsed[key] = false
|
|
|
|
// get the value from the data map
|
|
value, ok := data[key]
|
|
if !ok || value == "" {
|
|
continue
|
|
}
|
|
|
|
switch field.Interface().(type) {
|
|
case string:
|
|
field.SetString(value)
|
|
case int:
|
|
val, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
field.SetInt(int64(val))
|
|
case time.Duration:
|
|
val, err := time.ParseDuration(value + "s")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
field.Set(reflect.ValueOf(val))
|
|
case net.IP:
|
|
ip := net.ParseIP(value)
|
|
if ip == nil {
|
|
continue
|
|
}
|
|
field.Set(reflect.ValueOf(ip))
|
|
case []net.IP:
|
|
val := make([]net.IP, 0)
|
|
for _, ipStr := range strings.Fields(value) {
|
|
ip := net.ParseIP(ipStr)
|
|
if ip == nil {
|
|
continue
|
|
}
|
|
val = append(val, ip)
|
|
}
|
|
field.Set(reflect.ValueOf(val))
|
|
default:
|
|
return fmt.Errorf("unsupported field `%s` type: %s", key, field.Type().String())
|
|
}
|
|
|
|
valuesParsed[key] = true
|
|
}
|
|
|
|
lease.setIsEmpty(valuesParsed)
|
|
|
|
return nil
|
|
}
|
|
|
|
// MarshalDHCPCLease marshals a lease to a string.
|
|
func MarshalDHCPCLease(lease *Lease) (string, error) {
|
|
leaseType := reflect.TypeOf(lease).Elem()
|
|
leaseValue := reflect.ValueOf(lease).Elem()
|
|
|
|
leaseFile := ""
|
|
|
|
for i := 0; i < leaseType.NumField(); i++ {
|
|
field := leaseValue.Field(i)
|
|
key := leaseType.Field(i).Tag.Get("env")
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
outValue := ""
|
|
|
|
switch field.Interface().(type) {
|
|
case string:
|
|
outValue = field.String()
|
|
case int:
|
|
outValue = strconv.Itoa(int(field.Int()))
|
|
case time.Duration:
|
|
outValue = strconv.Itoa(int(field.Int()))
|
|
case net.IP:
|
|
outValue = field.String()
|
|
case []net.IP:
|
|
ips := field.Interface().([]net.IP)
|
|
ipStrings := make([]string, len(ips))
|
|
for i, ip := range ips {
|
|
ipStrings[i] = ip.String()
|
|
}
|
|
outValue = strings.Join(ipStrings, " ")
|
|
default:
|
|
return "", fmt.Errorf("unsupported field `%s` type: %s", key, field.Type().String())
|
|
}
|
|
|
|
leaseFile += fmt.Sprintf("%s=%s\n", key, outValue)
|
|
}
|
|
|
|
return leaseFile, nil
|
|
}
|