]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
fix undef warning when apparmor changeprofile fails
[pve-container.git] / src / PVE / LXC.pm
index 9642f2e508eb0895a5f8f1ccf9565cfab246cba2..88a9d6f17fbdde89a1cf80a0e261ec6e58e4d64e 100644 (file)
@@ -4,8 +4,8 @@ use strict;
 use warnings;
 
 use Cwd qw();
-use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED ENOSYS EEXIST);
-use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY);
+use Errno qw(ELOOP ENOENT ENOTDIR EROFS ECONNREFUSED EEXIST);
+use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY :mode);
 use File::Path;
 use File::Spec;
 use IO::Poll qw(POLLIN POLLHUP);
@@ -46,6 +46,7 @@ use PVE::LXC::Tools;
 my $have_sdn;
 eval {
     require PVE::Network::SDN::Zones;
+    require PVE::Network::SDN::Vnets;
     $have_sdn = 1;
 };
 
@@ -639,6 +640,29 @@ sub update_lxc_config {
        $raw .= "lxc.mount.auto = sys:mixed\n";
     }
 
+    PVE::LXC::Config->foreach_passthrough_device($conf, sub {
+       my ($key, $device) = @_;
+
+       die "Path is not defined for passthrough device $key"
+           unless (defined($device->{path}));
+
+       my $absolute_path = $device->{path};
+       my ($mode, $rdev) = (stat($absolute_path))[2, 6];
+
+       die "Device $absolute_path does not exist\n" if $! == ENOENT;
+
+       die "Error accessing device $absolute_path\n"
+           if (!defined($mode) || !defined($rdev));
+
+       die "$absolute_path is not a device\n"
+           if (!S_ISBLK($mode) && !S_ISCHR($mode));
+
+       my $major = PVE::Tools::dev_t_major($rdev);
+       my $minor = PVE::Tools::dev_t_minor($rdev);
+       my $device_type_char = S_ISBLK($mode) ? 'b' : 'c';
+       $raw .= "lxc.cgroup2.devices.allow = $device_type_char $major:$minor rw\n";
+    });
+
     # WARNING: DO NOT REMOVE this without making sure that loop device nodes
     # cannot be exposed to the container with r/w access (cgroup perms).
     # When this is enabled mounts will still remain in the monitor's namespace
@@ -898,6 +922,8 @@ sub destroy_lxc_container {
        });
     }
 
+    delete_ifaces_ipams_ips($conf, $vmid);
+
     rmdir "/var/lib/lxc/$vmid/rootfs";
     unlink "/var/lib/lxc/$vmid/config";
     rmdir "/var/lib/lxc/$vmid";
@@ -935,7 +961,7 @@ sub net_tap_plug : prototype($$) {
 
     if ($have_sdn) {
        PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);
-       PVE::Network::SDN::Zones::add_bridge_fdb($iface, $hwaddr, $bridge, $firewall);
+       PVE::Network::SDN::Zones::add_bridge_fdb($iface, $hwaddr, $bridge);
     } else {
        PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, { mac => $hwaddr });
     }
@@ -961,27 +987,47 @@ sub update_net {
            safe_string_ne($oldnet->{name}, $newnet->{name})) {
 
            PVE::Network::veth_delete($veth);
+
+           if ($have_sdn && safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr})) {
+               eval { PVE::Network::SDN::Vnets::del_ips_from_mac($oldnet->{bridge}, $oldnet->{hwaddr}, $conf->{hostname}) };
+               warn $@ if $@;
+
+               PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
+               PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr}, $vmid, $conf->{hostname});
+           }
+
            delete $conf->{$opt};
            PVE::LXC::Config->write_config($vmid, $conf);
 
            hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
        } else {
-           if (safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+           my $bridge_changed = safe_string_ne($oldnet->{bridge}, $newnet->{bridge});
+
+           if ($bridge_changed ||
                safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
                safe_num_ne($oldnet->{firewall}, $newnet->{firewall}) ||
                safe_boolean_ne($oldnet->{link_down}, $newnet->{link_down})
            ) {
-
                if ($oldnet->{bridge}) {
+                   my $oldbridge = $oldnet->{bridge};
+
                    PVE::Network::tap_unplug($veth);
                    foreach (qw(bridge tag firewall)) {
                        delete $oldnet->{$_};
                    }
                    $conf->{$opt} = PVE::LXC::Config->print_lxc_network($oldnet);
                    PVE::LXC::Config->write_config($vmid, $conf);
+
+                   if ($have_sdn && $bridge_changed) {
+                       eval { PVE::Network::SDN::Vnets::del_ips_from_mac($oldbridge, $oldnet->{hwaddr}, $conf->{hostname}) };
+                       warn $@ if $@;
+                   }
                }
 
+               if ($have_sdn && $bridge_changed) {
+                   PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
+               }
                PVE::LXC::net_tap_plug($veth, $newnet);
 
                # This includes the rate:
@@ -998,6 +1044,11 @@ sub update_net {
            PVE::LXC::Config->write_config($vmid, $conf);
        }
     } else {
+       if ($have_sdn) {
+           PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
+           PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr}, $vmid, $conf->{hostname});
+       }
+
        hotplug_net($vmid, $conf, $opt, $newnet, $netid);
     }
 
@@ -1036,6 +1087,32 @@ sub hotplug_net {
     PVE::LXC::Config->write_config($vmid, $conf);
 }
 
+sub get_interfaces {
+    my ($vmid) = @_;
+
+    my $pid = eval { find_lxc_pid($vmid); };
+    return if $@;
+
+    my $output;
+    # enters the network namespace of the container and executes 'ip a'
+    run_command(['nsenter', '-t', $pid, '--net', '--', 'ip', '--json', 'a'],
+       outfunc => sub { $output .= shift; });
+
+    my $config = JSON::decode_json($output);
+
+    my $res;
+    for my $interface ($config->@*) {
+       my $obj = { name => $interface->{ifname} };
+       for my $ip ($interface->{addr_info}->@*) {
+           $obj->{$ip->{family}} = $ip->{local} . "/" . $ip->{prefixlen};
+       }
+       $obj->{hwaddr} = $interface->{address};
+       push @$res, $obj
+    }
+
+    return $res;
+}
+
 sub update_ipconfig {
     my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
 
@@ -1314,10 +1391,14 @@ sub check_ct_modify_config_perm {
            }
        } elsif ($opt eq 'memory' || $opt eq 'swap') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
-       } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
-                $opt eq 'searchdomain' || $opt eq 'hostname') {
+       } elsif ($opt =~ m/^net\d+$/) {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
+           check_bridge_access($rpcenv, $authuser, $oldconf->{$opt}) if $oldconf->{$opt};
+           check_bridge_access($rpcenv, $authuser, $newconf->{$opt}) if $newconf->{$opt};
+       } elsif ($opt =~ m/^dev\d+$/) {
+           raise_perm_exc("configuring device passthrough is only allowed for root\@pam");
+       } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' || $opt eq 'hostname') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
-           PVE::LXC::check_bridge_access($rpcenv, $authuser, $newconf->{$opt});
        } elsif ($opt eq 'features') {
            raise_perm_exc("changing feature flags for privileged container is only allowed for root\@pam")
                if !$unprivileged;
@@ -1390,9 +1471,7 @@ sub check_bridge_access {
     return 1 if $authuser eq 'root@pam';
 
     my $net = PVE::LXC::Config->parse_lxc_network($raw);
-    my $bridge = $net->{bridge};
-    my $tag = $net->{tag};
-    my $trunks = $net->{trunks};
+    my ($bridge, $tag, $trunks) = $net->@{'bridge', 'tag', 'trunks'};
     check_vnet_access($rpcenv, $authuser, $bridge, $tag, $trunks);
 
     return 1;
@@ -1681,7 +1760,6 @@ sub mountpoint_stage {
        __mountpoint_mount($mountpoint, $stage_dir, $storage_cfg, $snapname, $rootuid, $rootgid, 1);
 
     if (!defined($path)) {
-       return undef if $! == ENOSYS;
        die "failed to mount subvolume: $!\n";
     }
 
@@ -1716,16 +1794,9 @@ sub mountpoint_insert_staged {
 
 # Use $stage_mount, $rootdir is treated as a temporary path to "stage" the file system. The user
 #   can then open a file descriptor to it which can be used with the `move_mount` syscall.
-#   Note that if the kernel does not support the new mount API, this will not perform any action
-#   and return `undef` with $! = ENOSYS.
 sub __mountpoint_mount {
     my ($mountpoint, $rootdir, $storage_cfg, $snapname, $rootuid, $rootgid, $stage_mount) = @_;
 
-    if (defined($stage_mount) && !PVE::LXC::Tools::can_use_new_mount_api()) {
-       $! = ENOSYS;
-       return undef;
-    }
-
     # When staging mount points we always mount to $rootdir directly (iow. as if `mp=/`).
     # This is required since __mount_prepare_rootdir() will return handles to the parent directory
     # which we use in __bindmount_verify()!
@@ -1903,8 +1974,9 @@ sub mountpoint_hotplug :prototype($$$$$) {
        my $dir = get_staging_mount_path($opt);
 
        # Now switch our apparmor profile before mounting:
-       my $data = 'changeprofile /usr/bin/lxc-start';
-       if (syswrite($aa_fd, $data, length($data)) != length($data)) {
+       my $data = 'changeprofile pve-container-mounthotplug';
+       my $data_written = syswrite($aa_fd, $data, length($data));
+       if (!defined($data_written) || $data_written != length($data)) {
            die "failed to change apparmor profile: $!\n";
        }
        # Check errors on close as well:
@@ -1933,7 +2005,7 @@ sub get_staging_mount_path($) {
     return $target;
 }
 
-# Mount /run/pve/mountpoints as tmpfs
+# Mount tmpfs for mount point staging and return the path.
 sub get_staging_tempfs() {
     # We choose a path in /var/lib/lxc/ here because the lxc-start apparmor profile restricts most
     # mounts to that.
@@ -2375,6 +2447,34 @@ sub validate_id_maps {
     }
 }
 
+sub map_ct_id_to_host {
+    my ($id, $id_map, $id_type) = @_;
+
+    for my $mapping (@$id_map) {
+       my ($type, $ct, $host, $length) = @$mapping;
+
+       next if ($type ne $id_type);
+
+       if ($id >= $ct && $id < ($ct + $length)) {
+           return $host - $ct + $id;
+       }
+    }
+
+    return $id;
+}
+
+sub map_ct_uid_to_host {
+    my ($uid, $id_map) = @_;
+
+    return map_ct_id_to_host($uid, $id_map, 'u');
+}
+
+sub map_ct_gid_to_host {
+    my ($gid, $id_map) = @_;
+
+    return map_ct_id_to_host($gid, $id_map, 'g');
+}
+
 sub userns_command {
     my ($id_map) = @_;
     if (@$id_map) {
@@ -2720,4 +2820,30 @@ sub thaw($) {
     }
 }
 
+sub create_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    for my $opt (keys %$conf) {
+       next if $opt !~ m/^net(\d+)$/;
+       my $net = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
+       next if $net->{type} ne 'veth';
+       PVE::Network::SDN::Vnets::add_next_free_cidr($net->{bridge}, $conf->{hostname}, $net->{hwaddr}, $vmid, undef, 1);
+    }
+}
+
+sub delete_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    for my $opt (keys %$conf) {
+       next if $opt !~ m/^net(\d+)$/;
+       my $net = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
+       eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{hwaddr}, $conf->{hostname}) };
+       warn $@ if $@;
+    }
+}
+
 1;