Getting Started with the NFSClient component
Introduction
The NFSClient component implements an NFS 4.0 client, providing a simple way to connect to and interact with existing NFS shares. The NFSClient component is available for all supported platforms.
Getting Started
To begin, the RemoteHost and RemotePort properties should first be set to the host and port of the NFS server. By default, the RemotePort property is set to 2049. The LocalHost and LocalPort properties may also be set to specify the name of the local host or user-assigned IP interface through which connections are initiated. NFS exports may require that requests originate from a port less than 1024 (IPPORT_RESERVED). In this case, LocalPort should be manually specified.
The SecurityMechanism property (and other related properties) may also be set beforehand. Please refer to the Security section for additional details. Once set, the Connect method should be called to initiate a connection to the server. For example:
component.RemoteHost = "10.0.1.123";
component.RemotePort = 2049;
component.Connect();
Once the connection is successful (or fails), the Connected event will fire accordingly, with details regarding the connection status. Assuming the connection was successful, the Connected property will be set to True.
After successfully connecting to the NFS server, the RemotePath property will always be set to the root from the component's perspective, /.
If the server exports a specific directory (e.g., /mnt/mynfs), the component may need to navigate to this export manually by calling the ChangeRemotePath method with the relevant export path.
In some server-side configurations, the client may already start in the exported directory upon a connection, allowing immediate access without additional navigation.
Security
Before connecting to the server, the SecurityMechanism property should be set accordingly. This property may be set to one of the following mechanisms:
Mechanism | Description |
---|---|
none | No authentication is required to establish a connection to the server (AUTH_NONE or AUTH_NULL). |
sys | System authentication is required to establish a connection to the server (AUTH_SYS or AUTH_UNIX). |
krb5 | Kerberos v5 with client authentication only. |
krb5i | Kerberos v5 with client authentication and integrity protection. |
krb5p | Kerberos v5 with client authentication, integrity protection, and encryption. |
System Authentication
By default, SecurityMechanism is set to sys, enabling remote procedure call (RPC) system authentication (AUTH_SYS). In this case, the RPCUid and RPCGid properties are used to specify the UID and GID the component should perform the request as. These properties are set to 0 by default, indicating root access.
Note: This mechanism is not secure. Network packets are still transferred in plaintext. This mechanism (in addition to none) is recommended only for systems operating over a local network.
Kerberos Authentication
For additional security, the SecurityMechanism may be set to krb5, krb5i, or krb5p to enable RPC Kerberos authentication (RPCSEC_GSS).
It is important to note the differences among the listed Kerberos security mechanisms. If krb5 is utilized, Kerberos will be used only to perform client authentication.
If krb5i is utilized, Kerberos will be used to perform client authentication and integrity protection, ensuring that incoming and outgoing packets are untampered. Packets remain unencrypted, however, making sensitive data potentially visible to anyone monitoring the network.
If krb5p is utilized, Kerberos will be used to perform client authentication, integrity protection, and packet encryption, making this the most secure option.
If the security mechanism is set to any of the Kerberos security mechanisms, the following properties must also be set accordingly:
Property | Description |
---|---|
KDCHost | This property must be set to the IP address of the KDC host. |
KDCPort | This property must be set to the port the KDCHost is listening on. This is set to 88 by default. |
SPN | This property must be set to the SPN (Service Principal Name) of the service the user is attempting to access. |
User | This property must be set to the user attempting access to the service. |
Password | This property must be set to the password of the set user. |
KeytabFile | An alternative to Password, this property can be set to the path to a keytab file containing credentials for the specified user. |
For example, connecting with a Kerberos security mechanism may look like this:
// Configure general settings
component.RemoteHost = "10.0.1.223";
component.RemotePort = 2049;
component.SecurityMechanism = "krb5p";
// Configure Kerberos settings
component.KDCHost = "10.0.1.226";
component.User = "nfs/nfs-client.example.com@EXAMPLE.COM";
component.SPN = "nfs/nfs-server.example.com";
//component.Password = "SecurePassword123!";
component.KeytabFile = "C:\nfsclient.keytab"; // alternative to 'Password'
component.Connect();
For more information on configuring the component to work with Kerberos, please refer to the following article here.
Navigating the Server
As mentioned previously, upon successfully connecting to the NFS server, the RemotePath property will always be set to the root from the component's perspective, /. The ChangeRemotePath method may or may not need to be called to navigate to the relevant server-side export.
Regardless, many methods operate based on the current working directory of the component as identified by RemotePath. For example, when calling the ListDirectory method, the directory contents of the current RemotePath will be listed.
As such, ChangeRemotePath can be used to change the current working directory of the component to some specified path. Both relative and absolute paths are supported with this method.
Absolute Paths
If the path begins with a /, it is considered to be an absolute path and must specify the entire path from the root of the server. For example:
component.ChangeRemotePath("/home/testuser/myfolder");
Relative Paths
If the path does not begin with a /, it is considered a relative path and is resolved in relation to the current directory. For instance, a value of myfolder will indicate a subfolder of the current directory. The special value .. refers to the parent directory of the current path. For example:
//Change to the 'myfolder' sub-directory
component.ChangeRemotePath("myfolder");
//Navigate up two levels and then into the 'another/folder' path.
component.ChangeRemotePath("../../another/folder");
Note: Absolute and relative paths are supported for almost every component method that takes a file name and path as a parameter. Additionally, absolute and relative paths are supported by the RemoteFile property, which many methods make use of. For more information, please refer to the section below and the individual method descriptions.
Managing Files and Directories
This section describes many different operations that can be performed on the NFS server. Before going into additional details, note that the behavior of many of these methods operate depending on whether the specified RemoteFile or method parameter is an absolute or relative path.
If the specified RemoteFile or method parameter is an absolute path, the current RemotePath will be ignored in favor of the absolute path. If, however, the specified RemoteFile or method parameter is a relative path, the operation will be performed relative to the current RemotePath.
File Lookup
To check whether a file or directory exists in the NFS server, the CheckFileExists method may be used. This method will search for the object specified by RemoteFile. For example:
// Relative Path
component.ChangeRemotePath("/home/testuser/myfolder");
component.RemoteFile = "test.txt";
// Checks for test.txt in /home/testuser/myfolder
if(component.CheckFileExists()) {
component.Download();
}
// Absolute Path
component.RemoteFile = "/home/test/test.txt";
// Checks for test.txt in /home/test
if(component.CheckFileExists()) {
component.Download();
}
Listing Directories
To list the contents of a directory, the ListDirectory method should be called to retrieve the entries of the current working directory (as indicated by the current RemotePath).
The DirList event will fire for each entry in the current directory. The RemoteFile property may be used to specify a file mask for filtering the entries returned through DirList. For example:
// List all text files in /home/test
component.OnDirList += (o, e) => {
Console.WriteLine("Dir Entry: " + e.FileName);
};
component.ChangeRemotePath("/home/test");
component.RemoteFile = "*.txt";
component.ListDirectory();
Note: Because RemoteFile acts as a file mask, to retrieve a complete directory listing, RemoteFile should be set to an empty string (or a mask like "*").
The following special characters are supported for pattern matching through the file mask indicated by RemoteFile (if applicable):
Character | Description |
---|---|
? | Any single character |
* | Any characters or no characters (e.g., C*t matches Cat, Cot, Coast, Ct). |
[,-] | A range of characters (e.g., [a-z], [a], [0-9], [0-9,a-d,f,r-z]). |
\ | The slash is ignored and exact matching is performed on the next character. |
If these characters need to be used as a literal in a pattern, then they must be escaped by surrounding them with brackets [].
Note: "]" and "-" do not need to be escaped. See the following escape sequences:
Character | Escape Sequence |
---|---|
? | [?] |
* | [*] |
[ | [[] |
\ | [] |
For example, to match the value [Something].txt, specify the pattern [[]Something].txt.
Create and Delete Objects
Files and directories may be created using the CreateFile and MakeDirectory methods, respectively. CreateFile is used to create a new file containing no data. To create a new file with content, please refer to the Upload method. For example:
// Create directory
component.MakeDirectory("testdir");
// Create empty file in new directory
component.ChangeRemotePath("testdir");
component.CreateFile("test.txt");
Files and directories may be deleted using DeleteFile and RemoteDirectory methods, respectively. For example (based on the previous directory structure in the last example):
// Remove file created previously
component.ChangeRemotePath("testdir");
component.DeleteFile("test.txt");
// Navigate out of directory
component.ChangeRemotePath("../");
component.RemoveDirectory("testdir");
Uploading Files
As mentioned, CreateFile may be used to create a new, empty file. To upload a file with content, the Upload method should be used. The RemoteFile property will specify the remote location to upload the local data to. If the RemoteFile exists and the Overwrite property is set to False, an error will occur while uploading.
The component can either upload a local file by setting the LocalFile property, or upload the contents of a stream, which can be set using the SetUploadStream method. When uploading via a stream, the CloseStreamAfterTransfer configuration setting may be set to determine whether or not to close the stream after the transfer (enabled by default). When setting the stream, the value of the specified LocalFile will automatically reset.
Uploaded data are also made available in the Transfer event, where you can track the progress of the upload. For example:
component.OnTransfer += (o, e) => {
if (e.Direction == 0) {
Console.WriteLine("Uploading File. Progress: " + e.PercentDone);
}
}
// Using LocalFile
component.LocalFile = "C:\nfsdir\test.txt";
component.RemoteFile = "/home/test/test.txt";
component.Upload();
// Using SetDownloadStream
FileStream fs = new FileStream("C:\nfsdir\test.txt", FileMode.Open);
component.SetUploadStream(fs);
component.RemoteFile = "/home/test/test.txt";
component.Upload();
As an alternative to Upload, the UploadRange method may be used to upload a specified number of bytes to a given position in the RemoteFile. This method can also be used to append data to the end of a file; however, the Append method may be also be used for this purpose. For example:
// E.g., appending data to EOF
component.RemoteFile = "/home/test/test.txt";
component.QueryFileAttributes();
int startByte = component.FileAttributes.Size;
byte[] buffer = Encoding.Default.GetBytes("Upload these bytes");
component.UploadRange("/home/test", "test.txt", startByte, buffer.Length, buffer);
Downloading Files
To download the content of a file hosted on the server, the Download method should be used. The RemoteFile property should be set to the remote file to download. The LocalFile property can be used to specify a local file to download the specified remote file to. If the LocalFile already exists and the Overwrite property is False, an error will occur.
The component can either download the remote file contents to the specified LocalFile, or download the contents to a stream, which can be set using the SetDownloadStream method. When downloading via a stream, the CloseStreamAfterTransfer configuration setting may be set to determine whether or not to close the stream after the transfer (enabled by default).
Downloaded file contents are also made available in the Transfer event, regardless of whether you are downloading to a local file or stream. For example:
component.OnTransfer += (o, e) => {
if (e.Direction == 1) {
Console.WriteLine("Downloading File. Progress: " + e.PercentDone);
Console.WriteLine("Text: " + e.Text);
}
}
// Using LocalFile
component.RemoteFile = "/home/test/test.txt";
component.LocalFile = "C:\nfsdir\test.txt";
component.Download();
// Using SetDownloadStream
FileStream fs = new FileStream("C:\nfsdir\test.txt", FileMode.Open);
component.RemoteFile = "/home/test/test.txt";
component.SetDownloadStream(fs);
component.Download();
As an alternative to Download, the DownloadRange method may be used to download a specified number of bytes to some local buffer. This method is particularly useful for efficient management of large files and targeted data retrieval within them. This method will return the total number of bytes read into the buffer. For example:
// E.g., downloading first 5 bytes of file
byte[] downloadBuffer = new byte[5];
int bytesDownloaded = component.DownloadRange("/home/test", "test.txt", 0, downloadBuffer.Length, downloadBuffer);
Console.WriteLine(bytesDownloaded + " bytes downloaded.");
Creating and Reading Links
The component supports creating hard links and symbolic links using the CreateLink method.
CreateLink takes three parameters: the name of the link to be created, the target of the link, and the type of link (hard link or symbolic link). For example:
nfs.ChangeRemotePath("/nfs");
// Create symlink.txt in /nfs pointing to existing_file.txt in the same directory
nfs.CreateLink("symlink.txt", "existing_file.txt", 0);
// Create hardlink.txt in /nfs pointing to existing_file.txt in the same directory
nfs.CreateLink("hardlink.txt", "existing_file.txt", 1);
To read a hard link, you can set the RemoteFile property to the name of the hard link and then call Download (with the relevant local file or stream set).
Reading a symbolic link requires some additional steps. First, the ReadLink method should be called taking the name of the symbolic link as a parameter, which will return a byte array containing the contents of the symbolic link as stored on the server. Typically, the contents are a file name that the link points to. Before going into detail, see the following example:
nfs.ChangeRemotePath("/nfs");
// Create symlink.txt in /nfs pointing to existing_file.txt in the same directory
nfs.CreateLink("symlink.txt", "existing_file.txt", 0);
// Read link
byte[] linkData = nfs.ReadLink("symlink.txt");
string file = Encoding.UTF8.GetString(linkData);
nfs.RemoteFile = file;
nfs.LocalFile = "temp.txt";
nfs.Download();
In NFS v4.0, the contents of a symbolic link are treated as opaque (binary data) at the protocol level. The server stores this opaque data and returns this to the client when requested, meaning it is up to the client to interpret the link data.
Most of the time, the opaque data will represent the file the symbolic link is pointing to relative to the position of the symbolic link, meaning it can be converted to a UTF-8 string representing this file. The contents of the file should then be downloaded by setting RemoteFile (and possibly RemotePath) accordingly and calling Download.
Handling File Attributes
The component provides a couple of ways to handle file attributes. As mentioned previously, when calling ListDirectory, the DirList event will fire for each directory entry. During this event, the FileAttributes collection will be populated with the attributes of the associated directory entry. These attributes are meant to be retrieved only during this event, rather than set.
Outside of DirList, the QueryFileAttributes method must be called to retrieve a file's attributes. After doing so, the FileAttributes property will be populated accordingly.
To update relevant file attributes, the FileAttributes property can be modified, and the UpdateFileAttributes method can be called to make those changes remotely. Not all attributes can be modified. For example:
// First, query attributes
component.RemoteFile = "/home/test/test.txt";
component.QueryFileAttributes();
// Update OwnerId and OwnerGroupId of file
component.FileAttributes.OwnerId = "1000";
component.FileAttributes.OwnerGroupId = "1000";
component.UpdateFileAttributes();
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.