lol

nixos/qemu-vm: add option for named network interfaces

Adds a new option to the virtualisation modules that enables specifying explicitly named network interfaces in QEMU VMs.
The existing `virtualisation.vlans` option is still supported for cases where the name of the network interface is irrelevant.

+140 -96
+1 -1
nixos/doc/manual/release-notes/rl-2311.section.md
··· 16 16 17 17 ## Other Notable Changes {#sec-release-23.11-notable-changes} 18 18 19 - - Create the first release note entry in this section! 19 + - A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.
+3 -1
nixos/lib/testing/driver.nix
··· 12 12 }; 13 13 14 14 15 - vlans = map (m: m.virtualisation.vlans) (lib.attrValues config.nodes); 15 + vlans = map (m: ( 16 + m.virtualisation.vlans ++ 17 + (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))) (lib.attrValues config.nodes); 16 18 vms = map (m: m.system.build.vm) (lib.attrValues config.nodes); 17 19 18 20 nodeHostNames =
+29 -15
nixos/lib/testing/network.nix
··· 4 4 inherit (lib) 5 5 attrNames concatMap concatMapStrings flip forEach head 6 6 listToAttrs mkDefault mkOption nameValuePair optionalString 7 - range types zipListsWith zipLists 7 + range toLower types zipListsWith zipLists 8 8 mdDoc 9 9 ; 10 10 ··· 18 18 19 19 networkModule = { config, nodes, pkgs, ... }: 20 20 let 21 - interfacesNumbered = zipLists config.virtualisation.vlans (range 1 255); 22 - interfaces = forEach interfacesNumbered ({ fst, snd }: 23 - nameValuePair "eth${toString snd}" { 24 - ipv4.addresses = 25 - [{ 26 - address = "192.168.${toString fst}.${toString config.virtualisation.test.nodeNumber}"; 21 + qemu-common = import ../qemu-common.nix { inherit lib pkgs; }; 22 + 23 + # Convert legacy VLANs to named interfaces and merge with explicit interfaces. 24 + vlansNumbered = forEach (zipLists config.virtualisation.vlans (range 1 255)) (v: { 25 + name = "eth${toString v.snd}"; 26 + vlan = v.fst; 27 + assignIP = true; 28 + }); 29 + explicitInterfaces = lib.mapAttrsToList (n: v: v // { name = n; }) config.virtualisation.interfaces; 30 + interfaces = vlansNumbered ++ explicitInterfaces; 31 + interfacesNumbered = zipLists interfaces (range 1 255); 32 + 33 + # Automatically assign IP addresses to requested interfaces. 34 + assignIPs = lib.filter (i: i.assignIP) interfaces; 35 + ipInterfaces = forEach assignIPs (i: 36 + nameValuePair i.name { ipv4.addresses = 37 + [ { address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}"; 27 38 prefixLength = 24; 28 39 }]; 29 40 }); 30 41 42 + qemuOptions = lib.flatten (forEach interfacesNumbered ({ fst, snd }: 43 + qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber)); 44 + udevRules = forEach interfacesNumbered ({ fst, snd }: 45 + # MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive. 46 + "SUBSYSTEM==\"net\",ACTION==\"add\",ATTR{address}==\"${toLower(qemu-common.qemuNicMac fst.vlan config.virtualisation.test.nodeNumber)}\",NAME=\"${fst.name}\""); 47 + 31 48 networkConfig = 32 49 { 33 50 networking.hostName = mkDefault config.virtualisation.test.nodeName; 34 51 35 - networking.interfaces = listToAttrs interfaces; 52 + networking.interfaces = listToAttrs ipInterfaces; 36 53 37 54 networking.primaryIPAddress = 38 - optionalString (interfaces != [ ]) (head (head interfaces).value.ipv4.addresses).address; 55 + optionalString (ipInterfaces != [ ]) (head (head ipInterfaces).value.ipv4.addresses).address; 39 56 40 57 # Put the IP addresses of all VMs in this machine's 41 58 # /etc/hosts file. If a machine has multiple ··· 51 68 "${config.networking.hostName}.${config.networking.domain} " + 52 69 "${config.networking.hostName}\n")); 53 70 54 - virtualisation.qemu.options = 55 - let qemu-common = import ../qemu-common.nix { inherit lib pkgs; }; 56 - in 57 - flip concatMap interfacesNumbered 58 - ({ fst, snd }: qemu-common.qemuNICFlags snd fst config.virtualisation.test.nodeNumber); 71 + virtualisation.qemu.options = qemuOptions; 72 + boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules; 59 73 }; 60 74 61 75 in 62 76 { 63 - key = "ip-address"; 77 + key = "network-interfaces"; 64 78 config = networkConfig // { 65 79 # Expose the networkConfig items for tests like nixops 66 80 # that need to recreate the network config.
+31 -1
nixos/modules/virtualisation/qemu-vm.nix
··· 564 564 virtualisation.vlans = 565 565 mkOption { 566 566 type = types.listOf types.ints.unsigned; 567 - default = [ 1 ]; 567 + default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; 568 + defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; 568 569 example = [ 1 2 ]; 569 570 description = 570 571 lib.mdDoc '' ··· 578 579 in the list of VMs. 579 580 ''; 580 581 }; 582 + 583 + virtualisation.interfaces = mkOption { 584 + default = {}; 585 + example = { 586 + enp1s0.vlan = 1; 587 + }; 588 + description = lib.mdDoc '' 589 + Network interfaces to add to the VM. 590 + ''; 591 + type = with types; attrsOf (submodule { 592 + options = { 593 + vlan = mkOption { 594 + type = types.ints.unsigned; 595 + description = lib.mdDoc '' 596 + VLAN to which the network interface is connected. 597 + ''; 598 + }; 599 + 600 + assignIP = mkOption { 601 + type = types.bool; 602 + default = false; 603 + description = lib.mdDoc '' 604 + Automatically assign an IP address to the network interface using the same scheme as 605 + virtualisation.vlans. 606 + ''; 607 + }; 608 + }; 609 + }); 610 + }; 581 611 582 612 virtualisation.writableStore = 583 613 mkOption {
+76 -78
nixos/tests/networking.nix
··· 93 93 name = "Static"; 94 94 nodes.router = router; 95 95 nodes.client = { pkgs, ... }: with pkgs.lib; { 96 - virtualisation.vlans = [ 1 2 ]; 96 + virtualisation.interfaces.enp1s0.vlan = 1; 97 + virtualisation.interfaces.enp2s0.vlan = 2; 97 98 networking = { 98 99 useNetworkd = networkd; 99 100 useDHCP = false; 100 101 defaultGateway = "192.168.1.1"; 101 102 defaultGateway6 = "fd00:1234:5678:1::1"; 102 - interfaces.eth1.ipv4.addresses = mkOverride 0 [ 103 + interfaces.enp1s0.ipv4.addresses = [ 103 104 { address = "192.168.1.2"; prefixLength = 24; } 104 105 { address = "192.168.1.3"; prefixLength = 32; } 105 106 { address = "192.168.1.10"; prefixLength = 32; } 106 107 ]; 107 - interfaces.eth2.ipv4.addresses = mkOverride 0 [ 108 + interfaces.enp2s0.ipv4.addresses = [ 108 109 { address = "192.168.2.2"; prefixLength = 24; } 109 110 ]; 110 111 }; ··· 170 171 # Disable test driver default config 171 172 networking.interfaces = lib.mkForce {}; 172 173 networking.useNetworkd = networkd; 173 - virtualisation.vlans = [ 1 ]; 174 + virtualisation.interfaces.enp1s0.vlan = 1; 174 175 }; 175 176 testScript = '' 176 177 start_all() 177 178 client.wait_for_unit("multi-user.target") 178 - client.wait_until_succeeds("ip addr show dev eth1 | grep '192.168.1'") 179 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'") 179 180 client.shell_interact() 180 181 client.succeed("ping -c 1 192.168.1.1") 181 182 router.succeed("ping -c 1 192.168.1.1") ··· 187 188 name = "SimpleDHCP"; 188 189 nodes.router = router; 189 190 nodes.client = { pkgs, ... }: with pkgs.lib; { 190 - virtualisation.vlans = [ 1 2 ]; 191 + virtualisation.interfaces.enp1s0.vlan = 1; 192 + virtualisation.interfaces.enp2s0.vlan = 2; 191 193 networking = { 192 194 useNetworkd = networkd; 193 195 useDHCP = false; 194 - interfaces.eth1 = { 195 - ipv4.addresses = mkOverride 0 [ ]; 196 - ipv6.addresses = mkOverride 0 [ ]; 197 - useDHCP = true; 198 - }; 199 - interfaces.eth2 = { 200 - ipv4.addresses = mkOverride 0 [ ]; 201 - ipv6.addresses = mkOverride 0 [ ]; 202 - useDHCP = true; 203 - }; 196 + interfaces.enp1s0.useDHCP = true; 197 + interfaces.enp2s0.useDHCP = true; 204 198 }; 205 199 }; 206 200 testScript = { ... }: ··· 211 205 router.wait_for_unit("network-online.target") 212 206 213 207 with subtest("Wait until we have an ip address on each interface"): 214 - client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") 215 - client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'") 216 - client.wait_until_succeeds("ip addr show dev eth2 | grep -q '192.168.2'") 217 - client.wait_until_succeeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'") 208 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'") 209 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'") 210 + client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'") 211 + client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'") 218 212 219 213 with subtest("Test vlan 1"): 220 214 client.wait_until_succeeds("ping -c 1 192.168.1.1") ··· 243 237 name = "OneInterfaceDHCP"; 244 238 nodes.router = router; 245 239 nodes.client = { pkgs, ... }: with pkgs.lib; { 246 - virtualisation.vlans = [ 1 2 ]; 240 + virtualisation.interfaces.enp1s0.vlan = 1; 241 + virtualisation.interfaces.enp2s0.vlan = 2; 247 242 networking = { 248 243 useNetworkd = networkd; 249 244 useDHCP = false; 250 - interfaces.eth1 = { 251 - ipv4.addresses = mkOverride 0 [ ]; 245 + interfaces.enp1s0 = { 252 246 mtu = 1343; 253 247 useDHCP = true; 254 248 }; 255 - interfaces.eth2.ipv4.addresses = mkOverride 0 [ ]; 256 249 }; 257 250 }; 258 251 testScript = { ... }: ··· 264 257 router.wait_for_unit("network.target") 265 258 266 259 with subtest("Wait until we have an ip address on each interface"): 267 - client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") 260 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'") 268 261 269 262 with subtest("ensure MTU is set"): 270 - assert "mtu 1343" in client.succeed("ip link show dev eth1") 263 + assert "mtu 1343" in client.succeed("ip link show dev enp1s0") 271 264 272 265 with subtest("Test vlan 1"): 273 266 client.wait_until_succeeds("ping -c 1 192.168.1.1") ··· 286 279 }; 287 280 bond = let 288 281 node = address: { pkgs, ... }: with pkgs.lib; { 289 - virtualisation.vlans = [ 1 2 ]; 282 + virtualisation.interfaces.enp1s0.vlan = 1; 283 + virtualisation.interfaces.enp2s0.vlan = 2; 290 284 networking = { 291 285 useNetworkd = networkd; 292 286 useDHCP = false; 293 287 bonds.bond0 = { 294 - interfaces = [ "eth1" "eth2" ]; 288 + interfaces = [ "enp1s0" "enp2s0" ]; 295 289 driverOptions.mode = "802.3ad"; 296 290 }; 297 - interfaces.eth1.ipv4.addresses = mkOverride 0 [ ]; 298 - interfaces.eth2.ipv4.addresses = mkOverride 0 [ ]; 299 291 interfaces.bond0.ipv4.addresses = mkOverride 0 300 292 [ { inherit address; prefixLength = 30; } ]; 301 293 }; ··· 326 318 }; 327 319 bridge = let 328 320 node = { address, vlan }: { pkgs, ... }: with pkgs.lib; { 329 - virtualisation.vlans = [ vlan ]; 321 + virtualisation.interfaces.enp1s0.vlan = vlan; 330 322 networking = { 331 323 useNetworkd = networkd; 332 324 useDHCP = false; 333 - interfaces.eth1.ipv4.addresses = mkOverride 0 334 - [ { inherit address; prefixLength = 24; } ]; 325 + interfaces.enp1s0.ipv4.addresses = [ { inherit address; prefixLength = 24; } ]; 335 326 }; 336 327 }; 337 328 in { ··· 339 330 nodes.client1 = node { address = "192.168.1.2"; vlan = 1; }; 340 331 nodes.client2 = node { address = "192.168.1.3"; vlan = 2; }; 341 332 nodes.router = { pkgs, ... }: with pkgs.lib; { 342 - virtualisation.vlans = [ 1 2 ]; 333 + virtualisation.interfaces.enp1s0.vlan = 1; 334 + virtualisation.interfaces.enp2s0.vlan = 2; 343 335 networking = { 344 336 useNetworkd = networkd; 345 337 useDHCP = false; 346 - bridges.bridge.interfaces = [ "eth1" "eth2" ]; 338 + bridges.bridge.interfaces = [ "enp1s0" "enp2s0" ]; 347 339 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ]; 348 340 interfaces.eth2.ipv4.addresses = mkOverride 0 [ ]; 349 341 interfaces.bridge.ipv4.addresses = mkOverride 0 ··· 377 369 nodes.router = router; 378 370 nodes.client = { pkgs, ... }: with pkgs.lib; { 379 371 environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules 380 - virtualisation.vlans = [ 1 ]; 372 + virtualisation.interfaces.enp1s0.vlan = 1; 381 373 networking = { 382 374 useNetworkd = networkd; 383 375 useDHCP = false; ··· 385 377 # reverse path filtering rules for the macvlan interface seem 386 378 # to be incorrect, causing the test to fail. Disable temporarily. 387 379 firewall.checkReversePath = false; 388 - macvlans.macvlan.interface = "eth1"; 389 - interfaces.eth1 = { 390 - ipv4.addresses = mkOverride 0 [ ]; 391 - useDHCP = true; 392 - }; 393 - interfaces.macvlan = { 394 - useDHCP = true; 395 - }; 380 + macvlans.macvlan.interface = "enp1s0"; 381 + interfaces.enp1s0.useDHCP = true; 382 + interfaces.macvlan.useDHCP = true; 396 383 }; 397 384 }; 398 385 testScript = { ... }: ··· 404 391 router.wait_for_unit("network.target") 405 392 406 393 with subtest("Wait until we have an ip address on each interface"): 407 - client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") 394 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'") 408 395 client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'") 409 396 410 397 with subtest("Print lots of diagnostic information"): ··· 431 418 fou = { 432 419 name = "foo-over-udp"; 433 420 nodes.machine = { ... }: { 434 - virtualisation.vlans = [ 1 ]; 421 + virtualisation.interfaces.enp1s0.vlan = 1; 435 422 networking = { 436 423 useNetworkd = networkd; 437 424 useDHCP = false; 438 - interfaces.eth1.ipv4.addresses = mkOverride 0 439 - [ { address = "192.168.1.1"; prefixLength = 24; } ]; 425 + interfaces.enp1s0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ]; 440 426 fooOverUDP = { 441 427 fou1 = { port = 9001; }; 442 428 fou2 = { port = 9002; protocol = 41; }; 443 429 fou3 = mkIf (!networkd) 444 430 { port = 9003; local.address = "192.168.1.1"; }; 445 431 fou4 = mkIf (!networkd) 446 - { port = 9004; local = { address = "192.168.1.1"; dev = "eth1"; }; }; 432 + { port = 9004; local = { address = "192.168.1.1"; dev = "enp1s0"; }; }; 447 433 }; 448 434 }; 449 435 systemd.services = { 450 - fou3-fou-encap.after = optional (!networkd) "network-addresses-eth1.service"; 436 + fou3-fou-encap.after = optional (!networkd) "network-addresses-enp1s0.service"; 451 437 }; 452 438 }; 453 439 testScript = { ... }: ··· 470 456 "gue": None, 471 457 "family": "inet", 472 458 "local": "192.168.1.1", 473 - "dev": "eth1", 459 + "dev": "enp1s0", 474 460 } in fous, "fou4 exists" 475 461 ''; 476 462 }; 477 463 sit = let 478 464 node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; { 479 - virtualisation.vlans = [ 1 ]; 465 + virtualisation.interfaces.enp1s0.vlan = 1; 480 466 networking = { 481 467 useNetworkd = networkd; 482 468 useDHCP = false; 483 469 sits.sit = { 484 470 inherit remote; 485 471 local = address4; 486 - dev = "eth1"; 472 + dev = "enp1s0"; 487 473 }; 488 - interfaces.eth1.ipv4.addresses = mkOverride 0 474 + interfaces.enp1s0.ipv4.addresses = mkOverride 0 489 475 [ { address = address4; prefixLength = 24; } ]; 490 476 interfaces.sit.ipv6.addresses = mkOverride 0 491 477 [ { address = address6; prefixLength = 64; } ]; ··· 685 671 vlan-ping = let 686 672 baseIP = number: "10.10.10.${number}"; 687 673 vlanIP = number: "10.1.1.${number}"; 688 - baseInterface = "eth1"; 674 + baseInterface = "enp1s0"; 689 675 vlanInterface = "vlan42"; 690 676 node = number: {pkgs, ... }: with pkgs.lib; { 691 - virtualisation.vlans = [ 1 ]; 677 + virtualisation.interfaces.enp1s0.vlan = 1; 692 678 networking = { 693 679 #useNetworkd = networkd; 694 680 useDHCP = false; ··· 785 771 privacy = { 786 772 name = "Privacy"; 787 773 nodes.router = { ... }: { 788 - virtualisation.vlans = [ 1 ]; 774 + virtualisation.interfaces.enp1s0.vlan = 1; 789 775 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true; 790 776 networking = { 791 777 useNetworkd = networkd; 792 778 useDHCP = false; 793 - interfaces.eth1.ipv6.addresses = singleton { 779 + interfaces.enp1s0.ipv6.addresses = singleton { 794 780 address = "fd00:1234:5678:1::1"; 795 781 prefixLength = 64; 796 782 }; ··· 798 784 services.radvd = { 799 785 enable = true; 800 786 config = '' 801 - interface eth1 { 787 + interface enp1s0 { 802 788 AdvSendAdvert on; 803 789 AdvManagedFlag on; 804 790 AdvOtherConfigFlag on; ··· 812 798 }; 813 799 }; 814 800 nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; { 815 - virtualisation.vlans = [ 1 ]; 801 + virtualisation.interfaces.enp1s0.vlan = 1; 816 802 networking = { 817 803 useNetworkd = networkd; 818 804 useDHCP = false; 819 - interfaces.eth1 = { 805 + interfaces.enp1s0 = { 820 806 tempAddress = "default"; 821 807 ipv4.addresses = mkOverride 0 [ ]; 822 808 ipv6.addresses = mkOverride 0 [ ]; ··· 825 811 }; 826 812 }; 827 813 nodes.client = { pkgs, ... }: with pkgs.lib; { 828 - virtualisation.vlans = [ 1 ]; 814 + virtualisation.interfaces.enp1s0.vlan = 1; 829 815 networking = { 830 816 useNetworkd = networkd; 831 817 useDHCP = false; 832 - interfaces.eth1 = { 818 + interfaces.enp1s0 = { 833 819 tempAddress = "enabled"; 834 820 ipv4.addresses = mkOverride 0 [ ]; 835 821 ipv6.addresses = mkOverride 0 [ ]; ··· 847 833 848 834 with subtest("Wait until we have an ip address"): 849 835 client_with_privacy.wait_until_succeeds( 850 - "ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'" 836 + "ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'" 851 837 ) 852 - client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'") 838 + client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'") 853 839 854 840 with subtest("Test vlan 1"): 855 841 client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") ··· 947 933 ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue) 948 934 ''; 949 935 }; 950 - rename = { 936 + rename = if networkd then { 951 937 name = "RenameInterface"; 952 938 nodes.machine = { pkgs, ... }: { 953 939 virtualisation.vlans = [ 1 ]; ··· 955 941 useNetworkd = networkd; 956 942 useDHCP = false; 957 943 }; 958 - } // 959 - (if networkd 960 - then { systemd.network.links."10-custom_name" = { 961 - matchConfig.MACAddress = "52:54:00:12:01:01"; 962 - linkConfig.Name = "custom_name"; 963 - }; 964 - } 965 - else { boot.initrd.services.udev.rules = '' 966 - SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="52:54:00:12:01:01", KERNEL=="eth*", NAME="custom_name" 967 - ''; 968 - }); 944 + systemd.network.links."10-custom_name" = { 945 + matchConfig.MACAddress = "52:54:00:12:01:01"; 946 + linkConfig.Name = "custom_name"; 947 + }; 948 + }; 969 949 testScript = '' 970 950 machine.succeed("udevadm settle") 971 951 print(machine.succeed("ip link show dev custom_name")) 972 952 ''; 953 + } else { 954 + name = "RenameInterface"; 955 + nodes = { }; 956 + testScript = ""; 973 957 }; 974 - nodes = { }; 975 958 # even with disabled networkd, systemd.network.links should work 976 959 # (as it's handled by udev, not networkd) 977 960 link = { ··· 1013 996 machine.wait_for_unit("network.target") 1014 997 machine.wait_until_succeeds("ip address show wap0 | grep -q ${testMac}") 1015 998 machine.fail("ip address show wlan0 | grep -q ${testMac}") 999 + ''; 1000 + }; 1001 + caseSensitiveRenaming = { 1002 + name = "CaseSensitiveRenaming"; 1003 + nodes.machine = { pkgs, ... }: { 1004 + virtualisation.interfaces.enCustom.vlan = 11; 1005 + networking = { 1006 + useNetworkd = networkd; 1007 + useDHCP = false; 1008 + }; 1009 + }; 1010 + testScript = '' 1011 + machine.succeed("udevadm settle") 1012 + print(machine.succeed("ip link show dev enCustom")) 1013 + machine.wait_until_succeeds("ip link show dev enCustom | grep -q '52:54:00:12:0b:01") 1016 1014 ''; 1017 1015 }; 1018 1016 };