Skip to content
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
39 changes: 39 additions & 0 deletions cmd/limactl/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"maps"
"net"
"os"
"path/filepath"
"slices"
"strings"
"text/tabwriter"
Expand All @@ -18,12 +19,17 @@ import (
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/networks"
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
"github.com/lima-vm/lima/v2/pkg/networks/usernet/filter"
"github.com/lima-vm/lima/v2/pkg/yqutil"
)

const networkCreateExample = ` Create a network:
$ limactl network create foo --gateway 192.168.42.1/24

Create a network with policy filtering:
$ limactl network create secure --gateway 192.168.42.1/24 --policy ~/policy.yaml

Connect VM instances to the newly created network:
$ limactl create --network lima:foo --name vm1
$ limactl create --network lima:foo --name vm2
Expand Down Expand Up @@ -144,6 +150,7 @@ func newNetworkCreateCommand() *cobra.Command {
flags.String("gateway", "", "gateway, e.g., \"192.168.42.1/24\"")
flags.String("interface", "", "interface for bridged mode")
_ = cmd.RegisterFlagCompletionFunc("interface", bashFlagCompleteNetworkInterfaceNames)
flags.String("policy", "", "path to policy file (YAML or JSON, user-v2 mode only)")
return cmd
}

Expand Down Expand Up @@ -174,6 +181,38 @@ func networkCreateAction(cmd *cobra.Command, args []string) error {
return err
}

policyPath, err := flags.GetString("policy")
if err != nil {
return err
}

// Handle policy file if provided
if policyPath != "" {
// Only user-v2 mode supports filtering
if mode != networks.ModeUserV2 {
logrus.Warnf("Policy filtering is only supported for mode 'user-v2', ignoring --policy flag")
} else {
// Load the policy to validate it
pol, err := filter.LoadPolicy(policyPath)
if err != nil {
return fmt.Errorf("failed to load policy: %w", err)
}

// Save as JSON in the network directory (~/.lima/_networks/<name>/policy.json)
policyJSONPath, err := usernet.PolicyFile(name)
if err != nil {
return fmt.Errorf("failed to get policy path: %w", err)
}
// Ensure network directory exists (follows usernet convention)
if err := os.MkdirAll(filepath.Dir(policyJSONPath), 0o755); err != nil {
return fmt.Errorf("failed to create network directory: %w", err)
}
if err := filter.SavePolicyJSON(pol, policyJSONPath); err != nil {
return fmt.Errorf("failed to save policy: %w", err)
}
}
}

switch mode {
case networks.ModeBridged:
if gateway != "" {
Expand Down
20 changes: 20 additions & 0 deletions cmd/limactl/usernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
"strconv"
"syscall"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/networks/usernet"
"github.com/lima-vm/lima/v2/pkg/networks/usernet/filter"
)

func newUsernetCommand() *cobra.Command {
Expand All @@ -31,6 +33,7 @@ func newUsernetCommand() *cobra.Command {
hostagentCommand.Flags().String("subnet", "192.168.5.0/24", "Sets subnet value for the usernet network")
hostagentCommand.Flags().Int("mtu", 1500, "mtu")
hostagentCommand.Flags().StringToString("leases", nil, "Pass default static leases for startup. Eg: '192.168.104.1=52:55:55:b3:bc:d9,192.168.104.2=5a:94:ef:e4:0c:df' ")
hostagentCommand.Flags().String("policy", "", "Path to policy JSON file")
return hostagentCommand
}

Expand Down Expand Up @@ -75,6 +78,22 @@ func usernetAction(cmd *cobra.Command, _ []string) error {
return err
}

policyPath, err := cmd.Flags().GetString("policy")
if err != nil {
return err
}

// Parse the policy at the CLI boundary (fail fast on invalid policy)
var policy *filter.Policy
if policyPath != "" {
logrus.Debugf("Loading policy from: %s", policyPath)
policy, err = filter.LoadPolicy(policyPath)
if err != nil {
return fmt.Errorf("failed to load policy: %w", err)
}
logrus.Debugf("Loaded policy with %d rules", len(policy.Rules))
}

os.RemoveAll(endpoint)
os.RemoveAll(qemuSocket)
os.RemoveAll(fdSocket)
Expand All @@ -92,5 +111,6 @@ func usernetAction(cmd *cobra.Command, _ []string) error {
FdSocket: fdSocket,
Subnet: subnet,
DefaultLeases: leases,
Policy: policy,
})
}
10 changes: 10 additions & 0 deletions pkg/networks/usernet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ func Leases(name string) (string, error) {
return sockPath, nil
}

// PolicyFile returns the path to the policy JSON file for the given network name.
// For usernet, this is stored in ~/.lima/_networks/<name>/policy.json (not VarRun).
func PolicyFile(name string) (string, error) {
dir, err := dirnames.LimaNetworksDir()
if err != nil {
return "", err
}
return filepath.Join(dir, name, "policy.json"), nil
}

func netmaskToCidr(baseIP, netMask net.IP) (net.IP, *net.IPNet, error) {
size, _ := net.IPMask(netMask.To4()).Size()
return net.ParseCIDR(fmt.Sprintf("%s/%d", baseIP.String(), size))
Expand Down
Loading
Loading