Skip to content

Commit ca9a702

Browse files
committed
Add network policy filtering for user-v2 networking
Implements egress traffic filtering with: - Protocol, port, IP/CIDR, and domain-based rules - DNS packet snooping for domain-to-IP tracking - ICMP support (ICMPv4/ICMPv6) - partial - awaiting gvisor fix - Policy validation with strict error checking - DNS tracker with 10k domain limit and TTL expiration Usage: limactl network create NAME --policy policy.yaml Signed-off-by: Simon Kaegi <simon.kaegi@gmail.com>
1 parent e21b634 commit ca9a702

File tree

16 files changed

+2914
-1
lines changed

16 files changed

+2914
-1
lines changed

cmd/limactl/network.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"maps"
1111
"net"
1212
"os"
13+
"path/filepath"
1314
"slices"
1415
"strings"
1516
"text/tabwriter"
@@ -18,12 +19,17 @@ import (
1819
"github.com/spf13/cobra"
1920

2021
"github.com/lima-vm/lima/v2/pkg/networks"
22+
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
23+
"github.com/lima-vm/lima/v2/pkg/networks/usernet/filter"
2124
"github.com/lima-vm/lima/v2/pkg/yqutil"
2225
)
2326

2427
const networkCreateExample = ` Create a network:
2528
$ limactl network create foo --gateway 192.168.42.1/24
2629
30+
Create a network with policy filtering:
31+
$ limactl network create secure --gateway 192.168.42.1/24 --policy ~/policy.yaml
32+
2733
Connect VM instances to the newly created network:
2834
$ limactl create --network lima:foo --name vm1
2935
$ limactl create --network lima:foo --name vm2
@@ -144,6 +150,7 @@ func newNetworkCreateCommand() *cobra.Command {
144150
flags.String("gateway", "", "gateway, e.g., \"192.168.42.1/24\"")
145151
flags.String("interface", "", "interface for bridged mode")
146152
_ = cmd.RegisterFlagCompletionFunc("interface", bashFlagCompleteNetworkInterfaceNames)
153+
flags.String("policy", "", "path to policy file (YAML or JSON, user-v2 mode only)")
147154
return cmd
148155
}
149156

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

184+
policyPath, err := flags.GetString("policy")
185+
if err != nil {
186+
return err
187+
}
188+
189+
// Handle policy file if provided
190+
if policyPath != "" {
191+
// Only user-v2 mode supports filtering
192+
if mode != networks.ModeUserV2 {
193+
logrus.Warnf("Policy filtering is only supported for mode 'user-v2', ignoring --policy flag")
194+
} else {
195+
// Load the policy to validate it
196+
pol, err := filter.LoadPolicy(policyPath)
197+
if err != nil {
198+
return fmt.Errorf("failed to load policy: %w", err)
199+
}
200+
201+
// Save as JSON in the network directory (~/.lima/_networks/<name>/policy.json)
202+
policyJSONPath, err := usernet.PolicyFile(name)
203+
if err != nil {
204+
return fmt.Errorf("failed to get policy path: %w", err)
205+
}
206+
// Ensure network directory exists (follows usernet convention)
207+
if err := os.MkdirAll(filepath.Dir(policyJSONPath), 0o755); err != nil {
208+
return fmt.Errorf("failed to create network directory: %w", err)
209+
}
210+
if err := filter.SavePolicyJSON(pol, policyJSONPath); err != nil {
211+
return fmt.Errorf("failed to save policy: %w", err)
212+
}
213+
}
214+
}
215+
177216
switch mode {
178217
case networks.ModeBridged:
179218
if gateway != "" {

cmd/limactl/usernet.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import (
1111
"strconv"
1212
"syscall"
1313

14+
"github.com/sirupsen/logrus"
1415
"github.com/spf13/cobra"
1516

1617
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
18+
"github.com/lima-vm/lima/v2/pkg/networks/usernet/filter"
1719
)
1820

1921
func newUsernetCommand() *cobra.Command {
@@ -31,6 +33,7 @@ func newUsernetCommand() *cobra.Command {
3133
hostagentCommand.Flags().String("subnet", "192.168.5.0/24", "Sets subnet value for the usernet network")
3234
hostagentCommand.Flags().Int("mtu", 1500, "mtu")
3335
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' ")
36+
hostagentCommand.Flags().String("policy", "", "Path to policy JSON file")
3437
return hostagentCommand
3538
}
3639

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

81+
policyPath, err := cmd.Flags().GetString("policy")
82+
if err != nil {
83+
return err
84+
}
85+
86+
// Parse the policy at the CLI boundary (fail fast on invalid policy)
87+
var policy *filter.Policy
88+
if policyPath != "" {
89+
logrus.Debugf("Loading policy from: %s", policyPath)
90+
policy, err = filter.LoadPolicy(policyPath)
91+
if err != nil {
92+
return fmt.Errorf("failed to load policy: %w", err)
93+
}
94+
logrus.Debugf("Loaded policy with %d rules", len(policy.Rules))
95+
}
96+
7897
os.RemoveAll(endpoint)
7998
os.RemoveAll(qemuSocket)
8099
os.RemoveAll(fdSocket)
@@ -92,5 +111,6 @@ func usernetAction(cmd *cobra.Command, _ []string) error {
92111
FdSocket: fdSocket,
93112
Subnet: subnet,
94113
DefaultLeases: leases,
114+
Policy: policy,
95115
})
96116
}

pkg/networks/usernet/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ func Leases(name string) (string, error) {
112112
return sockPath, nil
113113
}
114114

115+
// PolicyFile returns the path to the policy JSON file for the given network name.
116+
// For usernet, this is stored in ~/.lima/_networks/<name>/policy.json (not VarRun).
117+
func PolicyFile(name string) (string, error) {
118+
dir, err := dirnames.LimaNetworksDir()
119+
if err != nil {
120+
return "", err
121+
}
122+
return filepath.Join(dir, name, "policy.json"), nil
123+
}
124+
115125
func netmaskToCidr(baseIP, netMask net.IP) (net.IP, *net.IPNet, error) {
116126
size, _ := net.IPMask(netMask.To4()).Size()
117127
return net.ParseCIDR(fmt.Sprintf("%s/%d", baseIP.String(), size))

0 commit comments

Comments
 (0)