diff --git a/pkg/filesystem/virtual/configuration/nfsv4_mount_darwin.go b/pkg/filesystem/virtual/configuration/nfsv4_mount_darwin.go index 35f012bd..7125270d 100644 --- a/pkg/filesystem/virtual/configuration/nfsv4_mount_darwin.go +++ b/pkg/filesystem/virtual/configuration/nfsv4_mount_darwin.go @@ -108,16 +108,48 @@ func (m *nfsv4Mount) mount(terminationGroup program.Group, rpcServer *rpcserver. if !ok { return status.Error(codes.InvalidArgument, "Darwin specific NFSv4 server configuration options not provided") } - - // Expose the NFSv4 server on a UNIX socket. osConfiguration := darwinConfiguration.Darwin - if err := os.Remove(osConfiguration.SocketPath); err != nil && !os.IsNotExist(err) { - return util.StatusWrapf(err, "Could not remove stale socket for NFSv4 server %#v", osConfiguration.SocketPath) - } - sock, err := net.Listen("unix", osConfiguration.SocketPath) - if err != nil { - return util.StatusWrap(err, "Failed to create listening socket for NFSv4 server") + + // Expose the NFSv4 server. + var sock net.Listener + var socketType string + var serverAddress string + var tcpAddr *net.TCPAddr + + switch transport := osConfiguration.Transport.(type) { + case *pb.NFSv4DarwinMountConfiguration_SocketPath: + if err := os.Remove(transport.SocketPath); err != nil && !os.IsNotExist(err) { + return util.StatusWrapf(err, "Could not remove stale socket for NFSv4 server %#v", transport.SocketPath) + } + var err error + sock, err = net.Listen("unix", transport.SocketPath) + if err != nil { + return util.StatusWrap(err, "Failed to create listening socket for NFSv4 server") + } + // "ticotsord" is the X/Open Transport Interface (XTI) + // equivalent of AF_LOCAL with SOCK_STREAM. + socketType = "ticotsord" + serverAddress = transport.SocketPath + + case *pb.NFSv4DarwinMountConfiguration_TcpAddress: + var err error + sock, err = net.Listen("tcp", transport.TcpAddress) + if err != nil { + return util.StatusWrapf(err, "Failed to create TCP listener for NFSv4 server on %#v", transport.TcpAddress) + } + socketType = "tcp" + tcpAddr = sock.Addr().(*net.TCPAddr) + if tcpAddr.IP.IsUnspecified() { + // Binding to all interfaces; use loopback for the kernel to connect. + serverAddress = "127.0.0.1" + } else { + serverAddress = tcpAddr.IP.String() + } + + default: + return status.Error(codes.InvalidArgument, "No transport configuration provided") } + // TODO: Run this as part of the program.Group, so that it gets // cleaned up upon shutdown. go func() { @@ -181,24 +213,30 @@ func (m *nfsv4Mount) mount(terminationGroup program.Group, rpcServer *rpcserver. attrMask[0] |= (1 << nfs_sys_prot.NFS_MATTR_ATTRCACHE_DIR_MIN) | (1 << nfs_sys_prot.NFS_MATTR_ATTRCACHE_DIR_MAX) writeAttributeCachingDuration(&m.childDirectoriesAttributeCaching, &attrVals) - // "ticotsord" is the X/Open Transport Interface (XTI) - // equivalent of AF_LOCAL with SOCK_STREAM. attrMask[0] |= 1 << nfs_sys_prot.NFS_MATTR_SOCKET_TYPE - nfs_sys_prot.WriteNfsMattrSocketType(&attrVals, "ticotsord") + nfs_sys_prot.WriteNfsMattrSocketType(&attrVals, socketType) + + // For TCP transport, set NFS_PORT before FS_LOCATIONS. + if tcpAddr != nil { + attrMask[0] |= 1 << nfs_sys_prot.NFS_MATTR_NFS_PORT + nfs_sys_prot.WriteNfsMattrNfsPort(&attrVals, uint32(tcpAddr.Port)) + } attrMask[0] |= 1 << nfs_sys_prot.NFS_MATTR_FS_LOCATIONS fsLocations := nfs_sys_prot.NfsFsLocations{ NfslLocation: []nfs_sys_prot.NfsFsLocation{{ NfslServer: []nfs_sys_prot.NfsFsServer{{ NfssName: m.fsName, - NfssAddress: []string{osConfiguration.SocketPath}, + NfssAddress: []string{serverAddress}, }}, }}, } fsLocations.WriteTo(&attrVals) - attrMask[0] |= 1 << nfs_sys_prot.NFS_MATTR_LOCAL_NFS_PORT - nfs_sys_prot.WriteNfsMattrLocalNfsPort(&attrVals, osConfiguration.SocketPath) + if tcpAddr == nil { + attrMask[0] |= 1 << nfs_sys_prot.NFS_MATTR_LOCAL_NFS_PORT + nfs_sys_prot.WriteNfsMattrLocalNfsPort(&attrVals, serverAddress) + } if m.leavesAttributeCaching == NoAttributeCaching { attrMask[1] |= 1 << (nfs_sys_prot.NFS_MATTR_READLINK_NOCACHE - 32) diff --git a/pkg/proto/configuration/filesystem/virtual/virtual.pb.go b/pkg/proto/configuration/filesystem/virtual/virtual.pb.go index 0ce707e5..bbbca675 100644 --- a/pkg/proto/configuration/filesystem/virtual/virtual.pb.go +++ b/pkg/proto/configuration/filesystem/virtual/virtual.pb.go @@ -372,10 +372,14 @@ func (*NFSv4MountConfiguration_Darwin) isNFSv4MountConfiguration_OperatingSystem func (*NFSv4MountConfiguration_Linux) isNFSv4MountConfiguration_OperatingSystem() {} type NFSv4DarwinMountConfiguration struct { - state protoimpl.MessageState `protogen:"open.v1"` - SocketPath string `protobuf:"bytes,1,opt,name=socket_path,json=socketPath,proto3" json:"socket_path,omitempty"` - AccessCacheSize uint32 `protobuf:"varint,4,opt,name=access_cache_size,json=accessCacheSize,proto3" json:"access_cache_size,omitempty"` - MinorVersion *wrapperspb.UInt32Value `protobuf:"bytes,5,opt,name=minor_version,json=minorVersion,proto3" json:"minor_version,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Transport: + // + // *NFSv4DarwinMountConfiguration_SocketPath + // *NFSv4DarwinMountConfiguration_TcpAddress + Transport isNFSv4DarwinMountConfiguration_Transport `protobuf_oneof:"transport"` + AccessCacheSize uint32 `protobuf:"varint,4,opt,name=access_cache_size,json=accessCacheSize,proto3" json:"access_cache_size,omitempty"` + MinorVersion *wrapperspb.UInt32Value `protobuf:"bytes,5,opt,name=minor_version,json=minorVersion,proto3" json:"minor_version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -410,9 +414,27 @@ func (*NFSv4DarwinMountConfiguration) Descriptor() ([]byte, []int) { return file_github_com_buildbarn_bb_remote_execution_pkg_proto_configuration_filesystem_virtual_virtual_proto_rawDescGZIP(), []int{3} } +func (x *NFSv4DarwinMountConfiguration) GetTransport() isNFSv4DarwinMountConfiguration_Transport { + if x != nil { + return x.Transport + } + return nil +} + func (x *NFSv4DarwinMountConfiguration) GetSocketPath() string { if x != nil { - return x.SocketPath + if x, ok := x.Transport.(*NFSv4DarwinMountConfiguration_SocketPath); ok { + return x.SocketPath + } + } + return "" +} + +func (x *NFSv4DarwinMountConfiguration) GetTcpAddress() string { + if x != nil { + if x, ok := x.Transport.(*NFSv4DarwinMountConfiguration_TcpAddress); ok { + return x.TcpAddress + } } return "" } @@ -431,6 +453,22 @@ func (x *NFSv4DarwinMountConfiguration) GetMinorVersion() *wrapperspb.UInt32Valu return nil } +type isNFSv4DarwinMountConfiguration_Transport interface { + isNFSv4DarwinMountConfiguration_Transport() +} + +type NFSv4DarwinMountConfiguration_SocketPath struct { + SocketPath string `protobuf:"bytes,1,opt,name=socket_path,json=socketPath,proto3,oneof"` +} + +type NFSv4DarwinMountConfiguration_TcpAddress struct { + TcpAddress string `protobuf:"bytes,6,opt,name=tcp_address,json=tcpAddress,proto3,oneof"` +} + +func (*NFSv4DarwinMountConfiguration_SocketPath) isNFSv4DarwinMountConfiguration_Transport() {} + +func (*NFSv4DarwinMountConfiguration_TcpAddress) isNFSv4DarwinMountConfiguration_Transport() {} + type NFSv4LinuxMountConfiguration struct { state protoimpl.MessageState `protogen:"open.v1"` MountOptions []string `protobuf:"bytes,1,rep,name=mount_options,json=mountOptions,proto3" json:"mount_options,omitempty"` @@ -571,12 +609,15 @@ const file_github_com_buildbarn_bb_remote_execution_pkg_proto_configuration_file "\x13enforced_lease_time\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x11enforcedLeaseTime\x12K\n" + "\x14announced_lease_time\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x12announcedLeaseTime\x12\x87\x01\n" + "\x15system_authentication\x18\x04 \x01(\v2R.buildbarn.configuration.filesystem.virtual.RPCv2SystemAuthenticationConfigurationR\x14systemAuthenticationB\x12\n" + - "\x10operating_system\"\xbb\x01\n" + - "\x1dNFSv4DarwinMountConfiguration\x12\x1f\n" + - "\vsocket_path\x18\x01 \x01(\tR\n" + - "socketPath\x12*\n" + + "\x10operating_system\"\xed\x01\n" + + "\x1dNFSv4DarwinMountConfiguration\x12!\n" + + "\vsocket_path\x18\x01 \x01(\tH\x00R\n" + + "socketPath\x12!\n" + + "\vtcp_address\x18\x06 \x01(\tH\x00R\n" + + "tcpAddress\x12*\n" + "\x11access_cache_size\x18\x04 \x01(\rR\x0faccessCacheSize\x12A\n" + - "\rminor_version\x18\x05 \x01(\v2\x1c.google.protobuf.UInt32ValueR\fminorVersionJ\x04\b\x02\x10\x03J\x04\b\x03\x10\x04\"C\n" + + "\rminor_version\x18\x05 \x01(\v2\x1c.google.protobuf.UInt32ValueR\fminorVersionB\v\n" + + "\ttransportJ\x04\b\x02\x10\x03J\x04\b\x03\x10\x04\"C\n" + "\x1cNFSv4LinuxMountConfiguration\x12#\n" + "\rmount_options\x18\x01 \x03(\tR\fmountOptions\"\xba\x02\n" + "&RPCv2SystemAuthenticationConfiguration\x12n\n" + @@ -653,6 +694,10 @@ func file_github_com_buildbarn_bb_remote_execution_pkg_proto_configuration_files (*NFSv4MountConfiguration_Darwin)(nil), (*NFSv4MountConfiguration_Linux)(nil), } + file_github_com_buildbarn_bb_remote_execution_pkg_proto_configuration_filesystem_virtual_virtual_proto_msgTypes[3].OneofWrappers = []any{ + (*NFSv4DarwinMountConfiguration_SocketPath)(nil), + (*NFSv4DarwinMountConfiguration_TcpAddress)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pkg/proto/configuration/filesystem/virtual/virtual.proto b/pkg/proto/configuration/filesystem/virtual/virtual.proto index 144fe24c..3a70e8b3 100644 --- a/pkg/proto/configuration/filesystem/virtual/virtual.proto +++ b/pkg/proto/configuration/filesystem/virtual/virtual.proto @@ -214,19 +214,30 @@ message NFSv4MountConfiguration { } message NFSv4DarwinMountConfiguration { - // Path on which to bind the UNIX socket of the NFSv4 server. The - // kernel will connect to this socket when mounting. - // - // NOTE: No facilities are provided to set the ownership or - // permissions on the socket file. On most operating systems, the - // socket file will have mode 0777. How the mode is interpreted when - // changed is inconsistent between operating systems. Some require the - // socket to be writable in order to connect, while others ignore the - // permissions altogether. - // - // It is therefore strongly advised that socket files are placed - // inside directories that have access controls set up properly. - string socket_path = 1; + // Transport configuration for the NFSv4 server. + oneof transport { + // Path on which to bind the UNIX socket of the NFSv4 server. The + // kernel will connect to this socket when mounting. + // + // NOTE: No facilities are provided to set the ownership or + // permissions on the socket file. On most operating systems, the + // socket file will have mode 0777. How the mode is interpreted when + // changed is inconsistent between operating systems. Some require the + // socket to be writable in order to connect, while others ignore the + // permissions altogether. + // + // It is therefore strongly advised that socket files are placed + // inside directories that have access controls set up properly. + string socket_path = 1; + + // TCP address on which to bind the NFSv4 server (e.g., + // "localhost:2049", "127.0.0.1:2049", or ":2049"). The kernel will + // connect to this address when mounting. + // + // If a wildcard address is specified (e.g., ":2049" or + // "0.0.0.0:2049"), the kernel will connect via loopback. + string tcp_address = 6; + } // Was 'minimum_directories_attribute_cache_timeout' and // 'maximum_directories_attribute_cache_timeout'. These options are