Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Add IP mask field to Network Panel #897

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/console/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
askNetworkMethodPanel = "askNetworkMethod"
hostnamePanel = "hostname"
addressPanel = "address"
addrMaskPanel = "mask"
gatewayPanel = "gateway"
mtuPanel = "mtu"
dnsServersPanel = "dnsServers"
Expand Down Expand Up @@ -61,6 +62,7 @@ const (
askNetworkMethodLabel = "IPv4 Method"
hostNameLabel = "HostName"
addressLabel = "IPv4 Address"
addrMaskLabel = "IPv4 Mask"
gatewayLabel = "Gateway"
mtuLabel = "MTU (optional)"
dnsServersLabel = "DNS Servers"
Expand Down
122 changes: 116 additions & 6 deletions pkg/console/install_panels.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ type UserInputData struct {
Password string
PasswordConfirm string
Address string
AddrMask string
DNSServers string
NTPServers string
HasCheckedNTPServers bool
}

type IPMask = net.IPMask
type ParseError = net.ParseError

const (
NICStateNotFound = iota
NICStateDown
Expand Down Expand Up @@ -1188,7 +1192,7 @@ func showNetworkPage(c *Console) error {
if mgmtNetwork.Method != config.NetworkMethodStatic {
return showNext(c, askVlanIDPanel, askBondModePanel, askNetworkMethodPanel, askInterfacePanel)
}
return showNext(c, askVlanIDPanel, askBondModePanel, askNetworkMethodPanel, addressPanel, gatewayPanel, mtuPanel, askInterfacePanel)
return showNext(c, askVlanIDPanel, askBondModePanel, askNetworkMethodPanel, addressPanel, addrMaskPanel, gatewayPanel, mtuPanel, askInterfacePanel)
}

func showHostnamePage(c *Console) error {
Expand Down Expand Up @@ -1285,6 +1289,69 @@ func addHostnamePanel(c *Console) error {
return nil
}

// ParseMask parses s as an IP mask, returning the result.
// The string s must be in IPv4 dotted decimal form ("255.255.255.0").
// Each octet must be a valid number between 0 and 255, and the bits must be
// continuous (all 1s followed by all 0s when viewed as a 32-bit number).
// If s is not a valid textual representation of an IP mask,
// ParseMask returns an error describing why the parsing failed.
func ParseMask(mask string) (IPMask, error) {
// Split the mask string by dots
parts := strings.Split(mask, ".")

// Validate number of parts
if len(parts) != 4 {
return nil, &ParseError{Type: "invalid mask format: must be x.x.x.x", Text: mask}
}

result := make(IPMask, 4)

// Parse and validate each octet
for i, part := range parts {
// Convert string to integer
num, err := strconv.Atoi(part)
if err != nil {
return nil, &ParseError{Type: fmt.Sprintf("invalid number in position %d: %s", i+1, part), Text: mask}
}
// Validate range (0-255)
if num < 0 || num > 255 {
return nil, &ParseError{Type: fmt.Sprintf("number out of range in position %d: %d", i+1, num), Text: mask}
}

result[i] = byte(num)
}

// Validate mask format (must be continuous 1s followed by continuous 0s)
if !isValidMaskFormat(result) {
return nil, &ParseError{Type: "invalid subnet mask: not continuous", Text: mask}
}

return result, nil
}

// Helper function to validate mask format
func isValidMaskFormat(mask IPMask) bool {
// Convert mask to 32-bit integer
n := uint32(mask[0])<<24 | uint32(mask[1])<<16 | uint32(mask[2])<<8 | uint32(mask[3])

// Check if zeros start after ones
// Valid masks in binary should be continuous 1s followed by continuous 0s
// Shift right until we find first 1
for n&1 == 0 {
n = n >> 1
}

// Now we should only see 1s; if we see a 0, it's invalid
for n != 0 {
if n&1 == 0 {
return false
}
n = n >> 1
}

return true
}

func addNetworkPanel(c *Console) error {
setLocation := createVerticalLocator(c)

Expand Down Expand Up @@ -1313,6 +1380,11 @@ func addNetworkPanel(c *Console) error {
return err
}

addrMaskV, err := widgets.NewInput(c.Gui, addrMaskPanel, addrMaskLabel, false)
if err != nil {
return err
}

gatewayV, err := widgets.NewInput(c.Gui, gatewayPanel, gatewayLabel, false)
if err != nil {
return err
Expand Down Expand Up @@ -1380,6 +1452,7 @@ func addNetworkPanel(c *Console) error {
askBondModePanel,
askNetworkMethodPanel,
addressPanel,
addrMaskPanel,
gatewayPanel,
mtuPanel,
networkValidatorPanel,
Expand Down Expand Up @@ -1601,10 +1674,10 @@ func addNetworkPanel(c *Console) error {
}
mgmtNetwork.Method = selected
if selected == config.NetworkMethodStatic {
return showNext(c, mtuPanel, gatewayPanel, addressPanel)
return showNext(c, mtuPanel, gatewayPanel, addrMaskPanel, addressPanel)
}

c.CloseElements(mtuPanel, gatewayPanel, addressPanel)
c.CloseElements(mtuPanel, gatewayPanel, addrMaskPanel, addressPanel)
return gotoNextPage(askNetworkMethodPanel)
}
askNetworkMethodV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{
Expand Down Expand Up @@ -1632,15 +1705,17 @@ func addNetworkPanel(c *Console) error {
}
ip, ipNet, err := net.ParseCIDR(address)
if err != nil {
return err.Error(), nil
userInputData.Address = address
return "", nil
}
mask := ipNet.Mask
userInputData.Address = address
userInputData.AddrMask = ipNet.Mask.String()
mgmtNetwork.IP = ip.String()
mgmtNetwork.SubnetMask = fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
return "", nil
}
addressVConfirm := gotoNextPanel(c, []string{gatewayPanel}, validateAddress)
addressVConfirm := gotoNextPanel(c, []string{addrMaskPanel}, validateAddress)
addressV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{
gocui.KeyArrowUp: gotoNextPanel(c, []string{askNetworkMethodPanel}, func() (string, error) {
userInputData.Address, err = addressV.GetData()
Expand All @@ -1653,6 +1728,41 @@ func addNetworkPanel(c *Console) error {
setLocation(addressV.Panel, 3)
c.AddElement(addressPanel, addressV)

//AddressMaskV
addrMaskV.PreShow = func() error {
c.Gui.Cursor = true
addrMaskV.Value = mgmtNetwork.SubnetMask
return nil
}
validateAddrMask := func() (string, error) {
addrMask, err := addrMaskV.GetData()
if err != nil {
return "", err
}
if err = checkStaticRequiredString("mask", addrMask); err != nil {
return err.Error(), nil
}
ipMask, err := ParseMask(addrMask)
if err != nil {
return err.Error(), nil
}
userInputData.AddrMask = ipMask.String()
mgmtNetwork.SubnetMask = fmt.Sprintf("%d.%d.%d.%d", ipMask[0], ipMask[1], ipMask[2], ipMask[3])
return "", nil
}
addrMaskVConfirm := gotoNextPanel(c, []string{gatewayPanel}, validateAddrMask)
addrMaskV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{
gocui.KeyArrowUp: gotoNextPanel(c, []string{addressPanel}, func() (string, error) {
userInputData.AddrMask, err = addrMaskV.GetData()
return "", err
}),
gocui.KeyArrowDown: addrMaskVConfirm,
gocui.KeyEnter: addrMaskVConfirm,
gocui.KeyEsc: gotoPrevPage,
}
setLocation(addrMaskV.Panel, 3)
c.AddElement(addrMaskPanel, addrMaskV)

// gatewayV
gatewayV.PreShow = func() error {
c.Gui.Cursor = true
Expand All @@ -1675,7 +1785,7 @@ func addNetworkPanel(c *Console) error {
}
gatewayVConfirm := gotoNextPanel(c, []string{mtuPanel}, validateGateway)
gatewayV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{
gocui.KeyArrowUp: gotoNextPanel(c, []string{addressPanel}, func() (string, error) {
gocui.KeyArrowUp: gotoNextPanel(c, []string{addrMaskPanel}, func() (string, error) {
mgmtNetwork.Gateway, err = gatewayV.GetData()
return "", err
}),
Expand Down
Loading