From aaf88790719314821135da8361b4451fe60e3bbe Mon Sep 17 00:00:00 2001 From: buty4649 Date: Thu, 21 Mar 2024 01:08:27 +0900 Subject: [PATCH 1/5] Refactor iproute2 to use command runner interface --- iproute2/base.go | 62 ++++++++------------------------- iproute2/iproute2.go | 22 ++++++------ iproute2/runner.go | 81 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 59 deletions(-) create mode 100644 iproute2/runner.go diff --git a/iproute2/base.go b/iproute2/base.go index 4121959..ccd7e88 100644 --- a/iproute2/base.go +++ b/iproute2/base.go @@ -23,60 +23,28 @@ package iproute2 import ( "encoding/json" - "fmt" - "os/exec" - "strings" - "syscall" ) -type BaseCommand struct { - path string - prependArgs []string +type ipCmdRunner interface { + runIpCommand(args ...string) (string, error) } -type Error struct { - ExitStatus int - Message string +type BaseCommand struct { + runner ipCmdRunner } -func (b *BaseCommand) execute(args ...string) error { - _, err := b.executeWithOutput(args...) +func (b *BaseCommand) run(args ...string) error { + _, err := b.runner.runIpCommand(args...) return err } -func (e *Error) Error() string { - msg := strings.TrimRight(e.Message, "\n") - return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) -} - -func (b *BaseCommand) executeWithOutput(args ...string) (string, error) { - cmdArgs := append(b.prependArgs, args...) - - if logger != nil { - logger.Debug("exec", "cmd", b.path, "args", cmdArgs) - } - - cmd := exec.Command(b.path, cmdArgs...) - stdout, err := cmd.Output() - if err != nil { - exitErr, _ := err.(*exec.ExitError) - status, _ := exitErr.Sys().(syscall.WaitStatus) - stderr := string(exitErr.Stderr) - return "", &Error{ - ExitStatus: status.ExitStatus(), - Message: stderr, - } - } - return string(stdout), nil -} - func (b *BaseCommand) AddLink(name string, linkType string, options ...string) error { args := append([]string{"link", "add", name, "type", linkType}, options...) - return b.execute(args...) + return b.run(args...) } func (b *BaseCommand) DelLink(name string) error { - return b.execute("link", "del", name) + return b.run("link", "del", name) } func (b *BaseCommand) AddDummyDevice(name string) error { @@ -88,23 +56,23 @@ func (b *BaseCommand) AddVethDevice(name string, peerName string) error { } func (b *BaseCommand) SetLinkUp(name string) error { - return b.execute("link", "set", "dev", name, "up") + return b.run("link", "set", "dev", name, "up") } func (b *BaseCommand) AddAddress(name string, address string) error { - return b.execute("address", "add", address, "dev", name) + return b.run("address", "add", address, "dev", name) } func (b *BaseCommand) DelAddress(name string, address string) error { - return b.execute("address", "del", address, "dev", name) + return b.run("address", "del", address, "dev", name) } func (b *BaseCommand) AddRoute(name string, to string, via string) error { - return b.execute("route", "add", to, "via", via, "dev", name) + return b.run("route", "add", to, "via", via, "dev", name) } func (b *BaseCommand) DelRoute(name string, to string, via string) error { - return b.execute("route", "del", to, "via", via, "dev", name) + return b.run("route", "del", to, "via", via, "dev", name) } func (i *IpCmd) InNetns() bool { @@ -143,7 +111,7 @@ type InterfaceInfo struct { type Addresses []InterfaceInfo func (b *BaseCommand) ListAddresses() (*Addresses, error) { - data, err := b.executeWithOutput("-json", "address", "show") + data, err := b.runner.runIpCommand("-json", "address", "show") if err != nil { return nil, err } @@ -175,7 +143,7 @@ type Link struct { type Links []Link func (b *BaseCommand) ListLinks() (*Links, error) { - data, err := b.executeWithOutput("-json", "link", "show") + data, err := b.runner.runIpCommand("-json", "link", "show") if err != nil { return nil, err } diff --git a/iproute2/iproute2.go b/iproute2/iproute2.go index 236204f..0b71118 100644 --- a/iproute2/iproute2.go +++ b/iproute2/iproute2.go @@ -41,12 +41,12 @@ type IpCmd struct { func New(path string) *IpCmd { return &IpCmd{ - BaseCommand: BaseCommand{path: path}, + BaseCommand: BaseCommand{runner: &ipCmd{path: path}}, } } func (i *IpCmd) AddNetns(name string) error { - return i.execute("netns", "add", name) + return i.run("netns", "add", name) } func (i *IpCmd) DelNetns(name string) error { @@ -63,11 +63,11 @@ func (i *IpCmd) DelNetns(name string) error { return fmt.Errorf("netns %s has running processes: %s", name, strings.Join(pidStrs, ", ")) } - return i.execute("netns", "del", name) + return i.run("netns", "del", name) } func (i *IpCmd) ListNetns() []string { - data, _ := i.executeWithOutput("netns", "list") + data, _ := i.runner.runIpCommand("netns", "list") var netns []string for _, line := range strings.Split(data, "\n") { @@ -79,11 +79,11 @@ func (i *IpCmd) ListNetns() []string { } func (i *IpCmd) SetNetns(name string, netns string) error { - return i.execute("link", "set", name, "netns", netns) + return i.run("link", "set", name, "netns", netns) } func (i *IpCmd) ListNetnsProcesses(netns string) ([]int, error) { - out, err := i.executeWithOutput("netns", "pids", netns) + out, err := i.runner.runIpCommand("netns", "pids", netns) if err != nil { return nil, err } @@ -116,8 +116,10 @@ func (i *IpCmd) IntoNetns(netns string) *IpCmdWithNetns { ip := IpCmdWithNetns{ netns: netns, BaseCommand: BaseCommand{ - path: i.path, - prependArgs: []string{"netns", "exec", netns, i.path}, + runner: &ipCmdWithNetns{ + path: i.runner.(*ipCmd).path, + netns: netns, + }, }, } return &ip @@ -135,7 +137,3 @@ func (i *IpCmdWithNetns) InNetns() bool { func (i *IpCmdWithNetns) Netns() string { return i.netns } - -func (i *IpCmdWithNetns) ExecuteCommand(args ...string) (string, error) { - return i.executeWithOutput(args...) -} diff --git a/iproute2/runner.go b/iproute2/runner.go new file mode 100644 index 0000000..327c3fd --- /dev/null +++ b/iproute2/runner.go @@ -0,0 +1,81 @@ +/* +Copyright © 2024 buty4649 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package iproute2 + +import ( + "fmt" + "os/exec" + "strings" + "syscall" +) + +type ipCmd struct { + path string +} + +func (i *ipCmd) runIpCommand(args ...string) (string, error) { + return runCommand(i.path, args...) +} + +type ipCmdWithNetns struct { + path string + netns string +} + +func (i *ipCmdWithNetns) runIpCommand(args ...string) (string, error) { + cmd := append([]string{i.path}, args...) + return i.runWithNetns(cmd...) +} + +func (i *ipCmdWithNetns) runWithNetns(cmd ...string) (string, error) { + cmdArgs := append([]string{"netns", "exec", i.netns}, cmd...) + return runCommand(i.path, cmdArgs...) +} + +type Error struct { + ExitStatus int + Message string +} + +func (e *Error) Error() string { + msg := strings.TrimRight(e.Message, "\n") + return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) +} + +func runCommand(path string, args ...string) (string, error) { + if logger != nil { + logger.Debug("exec", "cmd", path, "args", args) + } + + cmd := exec.Command(path, args...) + stdout, err := cmd.Output() + if err != nil { + exitErr, _ := err.(*exec.ExitError) + status, _ := exitErr.Sys().(syscall.WaitStatus) + stderr := string(exitErr.Stderr) + return "", &Error{ + ExitStatus: status.ExitStatus(), + Message: stderr, + } + } + return string(stdout), nil +} From 65cac1eebf32ac1fd6734095721eb55211644f56 Mon Sep 17 00:00:00 2001 From: buty4649 Date: Thu, 21 Mar 2024 01:33:30 +0900 Subject: [PATCH 2/5] Add support for post script execution in netns --- cmd/apply.go | 21 +++++++++++++++++++++ config/config.go | 1 + iproute2/iproute2.go | 5 +++++ iproute2/runner.go | 28 +++++++++++++++++++++------- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index 9eb0e19..d8e9cf2 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -64,6 +64,11 @@ var applyCmd = &cobra.Command{ if err != nil { return err } + + err = RunPostScript(netns, values.PostScript) + if err != nil { + return err + } } return nil }, @@ -206,3 +211,19 @@ func SetupVethDevices(netns string, devices map[string]config.VethDevice) error func init() { rootCmd.AddCommand(applyCmd) } + +func RunPostScript(netns string, script string) error { + if script == "" { + return nil + } + + n := ip.IntoNetns(netns) + slog.Info("run post script", "netns", netns, "script", script) + out, err := n.ExecuteCommand(script) + if err != nil { + return err + } + slog.Debug("post script output", "netns", netns, "script", script, "output", out) + + return nil +} diff --git a/config/config.go b/config/config.go index 6d52a21..a61103c 100644 --- a/config/config.go +++ b/config/config.go @@ -35,6 +35,7 @@ type Netns struct { Ethernets map[string]Ethernet `yaml:"ethernets,omitempty"` DummyDevices map[string]Ethernet `yaml:"dummy-devices,omitempty"` VethDevices map[string]VethDevice `yaml:"veth-devices,omitempty"` + PostScript string `yaml:"post-script,omitempty"` } type Ethernet struct { diff --git a/iproute2/iproute2.go b/iproute2/iproute2.go index 0b71118..557d3d1 100644 --- a/iproute2/iproute2.go +++ b/iproute2/iproute2.go @@ -137,3 +137,8 @@ func (i *IpCmdWithNetns) InNetns() bool { func (i *IpCmdWithNetns) Netns() string { return i.netns } + +func (i *IpCmdWithNetns) ExecuteCommand(cmd string) (string, error) { + shell := []string{"/bin/bash"} + return i.runner.(*ipCmdWithNetns).runWithNetns(shell, &cmd) +} diff --git a/iproute2/runner.go b/iproute2/runner.go index 327c3fd..2c1d7ef 100644 --- a/iproute2/runner.go +++ b/iproute2/runner.go @@ -23,6 +23,7 @@ package iproute2 import ( "fmt" + "io" "os/exec" "strings" "syscall" @@ -33,7 +34,8 @@ type ipCmd struct { } func (i *ipCmd) runIpCommand(args ...string) (string, error) { - return runCommand(i.path, args...) + cmd := exec.Command(i.path, args...) + return runCommand(cmd) } type ipCmdWithNetns struct { @@ -43,12 +45,25 @@ type ipCmdWithNetns struct { func (i *ipCmdWithNetns) runIpCommand(args ...string) (string, error) { cmd := append([]string{i.path}, args...) - return i.runWithNetns(cmd...) + return i.runWithNetns(cmd, nil) } -func (i *ipCmdWithNetns) runWithNetns(cmd ...string) (string, error) { +func (i *ipCmdWithNetns) runWithNetns(cmd []string, input *string) (string, error) { cmdArgs := append([]string{"netns", "exec", i.netns}, cmd...) - return runCommand(i.path, cmdArgs...) + + c := exec.Command(i.path, cmdArgs...) + if input != nil { + stdin, err := c.StdinPipe() + if err != nil { + return "", err + } + go func() { + defer stdin.Close() + io.WriteString(stdin, *input) + }() + } + + return runCommand(c) } type Error struct { @@ -61,12 +76,11 @@ func (e *Error) Error() string { return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) } -func runCommand(path string, args ...string) (string, error) { +func runCommand(cmd *exec.Cmd) (string, error) { if logger != nil { - logger.Debug("exec", "cmd", path, "args", args) + logger.Debug("exec", "cmd", cmd.Path, "args", cmd.Args) } - cmd := exec.Command(path, args...) stdout, err := cmd.Output() if err != nil { exitErr, _ := err.(*exec.ExitError) From 90c8f6589d08801d6e2bca517f0e2b37fe5704ff Mon Sep 17 00:00:00 2001 From: buty4649 Date: Thu, 21 Mar 2024 01:37:30 +0900 Subject: [PATCH 3/5] Add cmdPath method to ipCmdRunner interface --- iproute2/base.go | 1 + iproute2/iproute2.go | 2 +- iproute2/runner.go | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/iproute2/base.go b/iproute2/base.go index ccd7e88..b89ab76 100644 --- a/iproute2/base.go +++ b/iproute2/base.go @@ -26,6 +26,7 @@ import ( ) type ipCmdRunner interface { + cmdPath() string runIpCommand(args ...string) (string, error) } diff --git a/iproute2/iproute2.go b/iproute2/iproute2.go index 557d3d1..c1028d1 100644 --- a/iproute2/iproute2.go +++ b/iproute2/iproute2.go @@ -117,7 +117,7 @@ func (i *IpCmd) IntoNetns(netns string) *IpCmdWithNetns { netns: netns, BaseCommand: BaseCommand{ runner: &ipCmdWithNetns{ - path: i.runner.(*ipCmd).path, + path: i.runner.cmdPath(), netns: netns, }, }, diff --git a/iproute2/runner.go b/iproute2/runner.go index 2c1d7ef..131a391 100644 --- a/iproute2/runner.go +++ b/iproute2/runner.go @@ -33,6 +33,10 @@ type ipCmd struct { path string } +func (i *ipCmd) cmdPath() string { + return i.path +} + func (i *ipCmd) runIpCommand(args ...string) (string, error) { cmd := exec.Command(i.path, args...) return runCommand(cmd) @@ -43,6 +47,10 @@ type ipCmdWithNetns struct { netns string } +func (i *ipCmdWithNetns) cmdPath() string { + return i.path +} + func (i *ipCmdWithNetns) runIpCommand(args ...string) (string, error) { cmd := append([]string{i.path}, args...) return i.runWithNetns(cmd, nil) From d6dc1374bd7e7badd13a6a35b7d77d0cac9bf0fb Mon Sep 17 00:00:00 2001 From: buty4649 Date: Thu, 21 Mar 2024 02:05:03 +0900 Subject: [PATCH 4/5] Refactor iproute2 to improve command execution --- iproute2/base.go | 70 +++++++++++++++++++++++++---- iproute2/iproute2.go | 14 +++--- iproute2/runner.go | 103 ------------------------------------------- 3 files changed, 67 insertions(+), 120 deletions(-) delete mode 100644 iproute2/runner.go diff --git a/iproute2/base.go b/iproute2/base.go index b89ab76..56bc01e 100644 --- a/iproute2/base.go +++ b/iproute2/base.go @@ -23,22 +23,74 @@ package iproute2 import ( "encoding/json" + "fmt" + "io" + "os/exec" + "strings" + "syscall" ) -type ipCmdRunner interface { - cmdPath() string - runIpCommand(args ...string) (string, error) -} - type BaseCommand struct { - runner ipCmdRunner + path string + prepend []string } func (b *BaseCommand) run(args ...string) error { - _, err := b.runner.runIpCommand(args...) + _, err := b.runIpCommand(args...) return err } +func (b *BaseCommand) runIpCommand(args ...string) (string, error) { + cmd := append([]string{b.path}, args...) + return b.runCommand(cmd, nil) +} + +type Error struct { + ExitStatus int + Message string +} + +func (e *Error) Error() string { + msg := strings.TrimRight(e.Message, "\n") + return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) +} + +func (b *BaseCommand) runCommand(cmd []string, input *string) (string, error) { + if b.prepend != nil { + cmd = append(b.prepend, cmd...) + } + path := cmd[0] + args := cmd[1:] + + if logger != nil { + logger.Debug("exec", "cmd", path, "args", args) + } + + c := exec.Command(path, args...) + if input != nil { + stdin, err := c.StdinPipe() + if err != nil { + return "", err + } + go func() { + defer stdin.Close() + io.WriteString(stdin, *input) + }() + } + + stdout, err := c.Output() + if err != nil { + exitErr, _ := err.(*exec.ExitError) + status, _ := exitErr.Sys().(syscall.WaitStatus) + stderr := string(exitErr.Stderr) + return "", &Error{ + ExitStatus: status.ExitStatus(), + Message: stderr, + } + } + return string(stdout), nil +} + func (b *BaseCommand) AddLink(name string, linkType string, options ...string) error { args := append([]string{"link", "add", name, "type", linkType}, options...) return b.run(args...) @@ -112,7 +164,7 @@ type InterfaceInfo struct { type Addresses []InterfaceInfo func (b *BaseCommand) ListAddresses() (*Addresses, error) { - data, err := b.runner.runIpCommand("-json", "address", "show") + data, err := b.runIpCommand("-json", "address", "show") if err != nil { return nil, err } @@ -144,7 +196,7 @@ type Link struct { type Links []Link func (b *BaseCommand) ListLinks() (*Links, error) { - data, err := b.runner.runIpCommand("-json", "link", "show") + data, err := b.runIpCommand("-json", "link", "show") if err != nil { return nil, err } diff --git a/iproute2/iproute2.go b/iproute2/iproute2.go index c1028d1..e812b75 100644 --- a/iproute2/iproute2.go +++ b/iproute2/iproute2.go @@ -41,7 +41,7 @@ type IpCmd struct { func New(path string) *IpCmd { return &IpCmd{ - BaseCommand: BaseCommand{runner: &ipCmd{path: path}}, + BaseCommand: BaseCommand{path: path}, } } @@ -67,7 +67,7 @@ func (i *IpCmd) DelNetns(name string) error { } func (i *IpCmd) ListNetns() []string { - data, _ := i.runner.runIpCommand("netns", "list") + data, _ := i.runIpCommand("netns", "list") var netns []string for _, line := range strings.Split(data, "\n") { @@ -83,7 +83,7 @@ func (i *IpCmd) SetNetns(name string, netns string) error { } func (i *IpCmd) ListNetnsProcesses(netns string) ([]int, error) { - out, err := i.runner.runIpCommand("netns", "pids", netns) + out, err := i.runIpCommand("netns", "pids", netns) if err != nil { return nil, err } @@ -116,10 +116,8 @@ func (i *IpCmd) IntoNetns(netns string) *IpCmdWithNetns { ip := IpCmdWithNetns{ netns: netns, BaseCommand: BaseCommand{ - runner: &ipCmdWithNetns{ - path: i.runner.cmdPath(), - netns: netns, - }, + path: i.path, + prepend: []string{i.path, "netns", "exec", netns}, }, } return &ip @@ -140,5 +138,5 @@ func (i *IpCmdWithNetns) Netns() string { func (i *IpCmdWithNetns) ExecuteCommand(cmd string) (string, error) { shell := []string{"/bin/bash"} - return i.runner.(*ipCmdWithNetns).runWithNetns(shell, &cmd) + return i.runCommand(shell, &cmd) } diff --git a/iproute2/runner.go b/iproute2/runner.go deleted file mode 100644 index 131a391..0000000 --- a/iproute2/runner.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright © 2024 buty4649 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -package iproute2 - -import ( - "fmt" - "io" - "os/exec" - "strings" - "syscall" -) - -type ipCmd struct { - path string -} - -func (i *ipCmd) cmdPath() string { - return i.path -} - -func (i *ipCmd) runIpCommand(args ...string) (string, error) { - cmd := exec.Command(i.path, args...) - return runCommand(cmd) -} - -type ipCmdWithNetns struct { - path string - netns string -} - -func (i *ipCmdWithNetns) cmdPath() string { - return i.path -} - -func (i *ipCmdWithNetns) runIpCommand(args ...string) (string, error) { - cmd := append([]string{i.path}, args...) - return i.runWithNetns(cmd, nil) -} - -func (i *ipCmdWithNetns) runWithNetns(cmd []string, input *string) (string, error) { - cmdArgs := append([]string{"netns", "exec", i.netns}, cmd...) - - c := exec.Command(i.path, cmdArgs...) - if input != nil { - stdin, err := c.StdinPipe() - if err != nil { - return "", err - } - go func() { - defer stdin.Close() - io.WriteString(stdin, *input) - }() - } - - return runCommand(c) -} - -type Error struct { - ExitStatus int - Message string -} - -func (e *Error) Error() string { - msg := strings.TrimRight(e.Message, "\n") - return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) -} - -func runCommand(cmd *exec.Cmd) (string, error) { - if logger != nil { - logger.Debug("exec", "cmd", cmd.Path, "args", cmd.Args) - } - - stdout, err := cmd.Output() - if err != nil { - exitErr, _ := err.(*exec.ExitError) - status, _ := exitErr.Sys().(syscall.WaitStatus) - stderr := string(exitErr.Stderr) - return "", &Error{ - ExitStatus: status.ExitStatus(), - Message: stderr, - } - } - return string(stdout), nil -} From 05dda85b2bb0d3197cf1669c90bad2c6bdaf82d0 Mon Sep 17 00:00:00 2001 From: buty4649 Date: Thu, 21 Mar 2024 02:07:16 +0900 Subject: [PATCH 5/5] Update netnsplan.yaml with routing and post-script --- sample/netnsplan.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sample/netnsplan.yaml b/sample/netnsplan.yaml index deb2ca2..819f3f3 100644 --- a/sample/netnsplan.yaml +++ b/sample/netnsplan.yaml @@ -1,15 +1,18 @@ netns: sample1: ethernets: - eth0: - addresses: - - 192.168.10.1 eth1: addresses: - 192.168.20.1 + route: + - to: default + via: 192.168.20.254 + post-script: | + sysctl --system + iptables-restore /etc/iptables/rules.v4 sample2: ethernets: - eth0: + eth2: addresses: - 172.16.10.1 dummy-devices: