nfs3: support NFS over UDP

pull/2787/head
Victor Julien 8 years ago
parent d9f87cec3d
commit c7e10c73f9

@ -289,6 +289,8 @@ pub struct NFS3State {
ts_gap: bool, // last TS update was gap
tc_gap: bool, // last TC update was gap
is_udp: bool,
/// tx counter for assigning incrementing id's to tx's
tx_id: u64,
@ -318,6 +320,7 @@ impl NFS3State {
tc_ssn_gap:false,
ts_gap:false,
tc_gap:false,
is_udp:false,
tx_id:0,
de_state_count:0,
//ts_txs_updated:false,
@ -686,9 +689,11 @@ impl NFS3State {
tx.request_done = true;
}
}
self.ts_chunk_xid = r.hdr.xid;
let file_data_len = w.file_data.len() as u32 - fill_bytes as u32;
self.ts_chunk_left = w.file_len as u32 - file_data_len as u32;
if !self.is_udp {
self.ts_chunk_xid = r.hdr.xid;
let file_data_len = w.file_data.len() as u32 - fill_bytes as u32;
self.ts_chunk_left = w.file_len as u32 - file_data_len as u32;
}
0
}
@ -994,8 +999,10 @@ impl NFS3State {
//if is_last {
// self.tc_txs_updated = true;
//}
self.tc_chunk_xid = r.hdr.xid;
self.tc_chunk_left = reply.count as u32 - reply.data.len() as u32;
if !self.is_udp {
self.tc_chunk_xid = r.hdr.xid;
self.tc_chunk_left = reply.count as u32 - reply.data.len() as u32;
}
SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {}",
r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left);
@ -1321,6 +1328,43 @@ impl NFS3State {
};
status
}
/// Parsing function
pub fn parse_udp_ts<'b>(&mut self, input: &'b[u8]) -> u32 {
let mut status = 0;
SCLogDebug!("parse_udp_ts ({})", input.len());
if input.len() > 0 {
match parse_rpc_udp_request(input) {
IResult::Done(_, ref rpc_record) => {
self.is_udp = true;
status |= self.process_request_record(rpc_record);
},
IResult::Incomplete(_) => {
},
IResult::Error(e) => { panic!("Parsing failed: {:?}",e); //break
},
}
}
status
}
/// Parsing function
pub fn parse_udp_tc<'b>(&mut self, input: &'b[u8]) -> u32 {
let mut status = 0;
SCLogDebug!("parse_udp_tc ({})", input.len());
if input.len() > 0 {
match parse_rpc_udp_reply(input) {
IResult::Done(_, ref rpc_record) => {
self.is_udp = true;
status |= self.process_reply_record(rpc_record);
},
IResult::Incomplete(_) => {
},
IResult::Error(e) => { panic!("Parsing failed: {:?}",e); //break
},
}
};
status
}
fn getfiles(&mut self, direction: u8) -> * mut FileContainer {
//SCLogDebug!("direction: {}", direction);
if direction == STREAM_TOCLIENT {
@ -1411,6 +1455,45 @@ pub extern "C" fn rs_nfs3_parse_response(_flow: *mut Flow,
}
}
/// C binding parse a DNS request. Returns 1 on success, -1 on failure.
#[no_mangle]
pub extern "C" fn rs_nfs3_parse_request_udp(_flow: *mut Flow,
state: &mut NFS3State,
_pstate: *mut libc::c_void,
input: *mut libc::uint8_t,
input_len: libc::uint32_t,
_data: *mut libc::c_void)
-> libc::int8_t
{
let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
SCLogDebug!("parsing {} bytes of request data", input_len);
if state.parse_udp_ts(buf) == 0 {
1
} else {
-1
}
}
#[no_mangle]
pub extern "C" fn rs_nfs3_parse_response_udp(_flow: *mut Flow,
state: &mut NFS3State,
_pstate: *mut libc::c_void,
input: *mut libc::uint8_t,
input_len: libc::uint32_t,
_data: *mut libc::c_void)
-> libc::int8_t
{
SCLogDebug!("parsing {} bytes of response data", input_len);
let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
if state.parse_udp_tc(buf) == 0 {
1
} else {
-1
}
}
#[no_mangle]
pub extern "C" fn rs_nfs3_state_get_tx_count(state: &mut NFS3State)
-> libc::uint64_t
@ -1643,6 +1726,43 @@ pub fn nfs3_probe(i: &[u8], direction: u8) -> i8 {
}
}
pub fn nfs3_probe_udp(i: &[u8], direction: u8) -> i8 {
if direction == STREAM_TOCLIENT {
match parse_rpc_udp_reply(i) {
IResult::Done(_, ref rpc) => {
if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 {
SCLogNotice!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
return 1;
} else {
return -1;
}
},
IResult::Incomplete(_) => {
return -1;
},
IResult::Error(_) => {
return -1;
},
}
} else {
match parse_rpc_udp_request(i) {
IResult::Done(_, ref rpc) => {
if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 {
return 1;
} else {
return -1;
}
},
IResult::Incomplete(_) => {
return -1;
},
IResult::Error(_) => {
return -1;
},
}
}
}
/// TOSERVER probe function
#[no_mangle]
pub extern "C" fn rs_nfs_probe_ts(input: *const libc::uint8_t, len: libc::uint32_t)
@ -1664,6 +1784,26 @@ pub extern "C" fn rs_nfs_probe_tc(input: *const libc::uint8_t, len: libc::uint32
return nfs3_probe(slice, STREAM_TOCLIENT);
}
/// TOSERVER probe function
#[no_mangle]
pub extern "C" fn rs_nfs_probe_udp_ts(input: *const libc::uint8_t, len: libc::uint32_t)
-> libc::int8_t
{
let slice: &[u8] = unsafe {
std::slice::from_raw_parts(input as *mut u8, len as usize)
};
return nfs3_probe_udp(slice, STREAM_TOSERVER);
}
/// TOCLIENT probe function
#[no_mangle]
pub extern "C" fn rs_nfs_probe_udp_tc(input: *const libc::uint8_t, len: libc::uint32_t)
-> libc::int8_t
{
let slice: &[u8] = unsafe {
std::slice::from_raw_parts(input as *mut u8, len as usize)
};
return nfs3_probe_udp(slice, STREAM_TOCLIENT);
}
#[no_mangle]
pub extern "C" fn rs_nfs3_getfiles(direction: u8, ptr: *mut NFS3State) -> * mut FileContainer {

@ -635,3 +635,111 @@ named!(pub parse_rpc_reply<RpcReplyPacket>,
}
))
);
named!(pub parse_rpc_udp_packet_header<RpcPacketHeader>,
do_parse!(
xid: be_u32
>> msgtype: be_u32
>> (
RpcPacketHeader {
frag_is_last:false,
frag_len:0,
xid:xid,
msgtype:msgtype,
}
))
);
#[derive(Debug,PartialEq)]
pub struct RpcUdpRequestPacket<'a> {
pub hdr: RpcPacketHeader<>,
pub rpcver: u32,
pub program: u32,
pub progver: u32,
pub procedure: u32,
pub creds_flavor: u32,
pub creds_len: u32,
pub creds: Option<&'a[u8]>,
pub creds_unix:Option<RpcRequestCredsUnix<'a>>,
pub verifier_flavor: u32,
pub verifier_len: u32,
pub verifier: Option<&'a[u8]>,
pub prog_data: &'a[u8],
}
named!(pub parse_rpc_udp_request<RpcPacket>,
do_parse!(
hdr: parse_rpc_udp_packet_header
>> rpcver: be_u32
>> program: be_u32
>> progver: be_u32
>> procedure: be_u32
>> creds_flavor: be_u32
>> creds_len: be_u32
>> creds: cond!(creds_flavor != 1 && creds_len > 0, take!(creds_len as usize))
>> creds_unix: cond!(creds_len > 0 && creds_flavor == 1, flat_map!(take!((creds_len) as usize),parse_rfc_request_creds_unix))
>> verifier_flavor: be_u32
>> verifier_len: be_u32
>> verifier: cond!(verifier_len > 0, take!(verifier_len as usize))
>> pl: rest
>> (
RpcPacket {
hdr:hdr,
rpcver:rpcver,
program:program,
progver:progver,
procedure:procedure,
creds_flavor:creds_flavor,
creds_len:creds_len,
creds:creds,
creds_unix:creds_unix,
verifier_flavor:verifier_flavor,
verifier_len:verifier_len,
verifier:verifier,
prog_data:pl,
}
))
);
named!(pub parse_rpc_udp_reply<RpcReplyPacket>,
do_parse!(
hdr: parse_rpc_udp_packet_header
>> verifier_flavor: be_u32
>> verifier_len: be_u32
>> verifier: cond!(verifier_len > 0, take!(verifier_len as usize))
>> reply_state: be_u32
>> accept_state: be_u32
>> pl: rest
>> (
RpcReplyPacket {
hdr:hdr,
verifier_flavor:verifier_flavor,
verifier_len:verifier_len,
verifier:verifier,
reply_state:reply_state,
accept_state:accept_state,
prog_data:pl,
}
))
);

@ -40,6 +40,7 @@ app-layer-smb2.c app-layer-smb2.h \
app-layer-smb.c app-layer-smb.h \
app-layer-smtp.c app-layer-smtp.h \
app-layer-nfs3.c app-layer-nfs3.h \
app-layer-nfs3-udp.c app-layer-nfs3-udp.h \
app-layer-template.c app-layer-template.h \
app-layer-ssh.c app-layer-ssh.h \
app-layer-ssl.c app-layer-ssl.h \

@ -0,0 +1,395 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Victor Julien <victor@inliniac.net>
*
* NFS3 application layer detector and parser for learning and
* nfs3 pruposes.
*
* This nfs3 implements a simple application layer for something
* like the NFS3 protocol running on port 2049.
*/
#include "suricata-common.h"
#include "stream.h"
#include "conf.h"
#include "util-unittest.h"
#include "app-layer-detect-proto.h"
#include "app-layer-parser.h"
#include "app-layer-nfs3-udp.h"
#ifndef HAVE_RUST
void RegisterNFS3UDPParsers(void)
{
}
#else
#include "rust.h"
#include "rust-nfs-nfs3-gen.h"
/* The default port to probe for echo traffic if not provided in the
* configuration file. */
#define NFS3_DEFAULT_PORT "2049"
/* The minimum size for a RFC message. For some protocols this might
* be the size of a header. TODO actual min size is likely larger */
#define NFS3_MIN_FRAME_LEN 32
/* Enum of app-layer events for an echo protocol. Normally you might
* have events for errors in parsing data, like unexpected data being
* received. For echo we'll make something up, and log an app-layer
* level alert if an empty message is received.
*
* Example rule:
*
* alert nfs3 any any -> any any (msg:"SURICATA NFS3 empty message"; \
* app-layer-event:nfs3.empty_message; sid:X; rev:Y;)
*/
enum {
NFS3_DECODER_EVENT_EMPTY_MESSAGE,
};
SCEnumCharMap nfs3_udp_decoder_event_table[] = {
{"EMPTY_MESSAGE", NFS3_DECODER_EVENT_EMPTY_MESSAGE},
{ NULL, 0 }
};
static void *NFS3StateAlloc(void)
{
return rs_nfs3_state_new();
}
static void NFS3StateFree(void *state)
{
rs_nfs3_state_free(state);
}
/**
* \brief Callback from the application layer to have a transaction freed.
*
* \param state a void pointer to the NFS3State object.
* \param tx_id the transaction ID to free.
*/
static void NFS3StateTxFree(void *state, uint64_t tx_id)
{
rs_nfs3_state_tx_free(state, tx_id);
}
#if 0
static int NFS3StateGetEventInfo(const char *event_name, int *event_id,
AppLayerEventType *event_type)
{
*event_id = SCMapEnumNameToValue(event_name, nfs3_decoder_event_table);
if (*event_id == -1) {
SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
"nfs3 enum map table.", event_name);
/* This should be treated as fatal. */
return -1;
}
*event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
return 0;
}
static AppLayerDecoderEvents *NFS3GetEvents(void *state, uint64_t tx_id)
{
NFS3State *nfs3_state = state;
NFS3Transaction *tx;
TAILQ_FOREACH(tx, &nfs3_state->tx_list, next) {
if (tx->tx_id == tx_id) {
return tx->decoder_events;
}
}
return NULL;
}
static int NFS3HasEvents(void *state)
{
NFS3State *echo = state;
return echo->events;
}
#endif
/**
* \brief Probe the input to see if it looks like echo.
*
* \retval ALPROTO_NFS3 if it looks like echo, otherwise
* ALPROTO_UNKNOWN.
*/
static AppProto NFS3ProbingParserTS(uint8_t *input, uint32_t input_len,
uint32_t *offset)
{
SCLogNotice("probing");
if (input_len < NFS3_MIN_FRAME_LEN) {
SCLogNotice("unknown");
return ALPROTO_UNKNOWN;
}
int8_t r = rs_nfs_probe_udp_ts(input, input_len);
if (r == 1) {
SCLogNotice("nfs3");
return ALPROTO_NFS3;
} else if (r == -1) {
SCLogNotice("failed");
return ALPROTO_FAILED;
}
SCLogNotice("Protocol not detected as ALPROTO_NFS3.");
return ALPROTO_UNKNOWN;
}
static AppProto NFS3ProbingParserTC(uint8_t *input, uint32_t input_len,
uint32_t *offset)
{
SCLogNotice("probing");
if (input_len < NFS3_MIN_FRAME_LEN) {
SCLogNotice("unknown");
return ALPROTO_UNKNOWN;
}
int8_t r = rs_nfs_probe_tc(input, input_len);
if (r == 1) {
SCLogNotice("nfs3");
return ALPROTO_NFS3;
} else if (r == -1) {
SCLogNotice("failed");
return ALPROTO_FAILED;
}
SCLogNotice("Protocol not detected as ALPROTO_NFS3.");
return ALPROTO_UNKNOWN;
}
static int NFS3ParseRequest(Flow *f, void *state,
AppLayerParserState *pstate, uint8_t *input, uint32_t input_len,
void *local_data)
{
uint16_t file_flags = FileFlowToFlags(f, STREAM_TOSERVER);
rs_nfs3_setfileflags(0, state, file_flags);
return rs_nfs3_parse_request_udp(f, state, pstate, input, input_len, local_data);
}
static int NFS3ParseResponse(Flow *f, void *state, AppLayerParserState *pstate,
uint8_t *input, uint32_t input_len, void *local_data)
{
uint16_t file_flags = FileFlowToFlags(f, STREAM_TOCLIENT);
rs_nfs3_setfileflags(1, state, file_flags);
return rs_nfs3_parse_response_udp(f, state, pstate, input, input_len, local_data);
}
static uint64_t NFS3GetTxCnt(void *state)
{
return rs_nfs3_state_get_tx_count(state);
}
static void *NFS3GetTx(void *state, uint64_t tx_id)
{
return rs_nfs3_state_get_tx(state, tx_id);
}
static void NFS3SetTxLogged(void *state, void *vtx, uint32_t logger)
{
rs_nfs3_tx_set_logged(state, vtx, logger);
}
static int NFS3GetTxLogged(void *state, void *vtx, uint32_t logger)
{
return rs_nfs3_tx_get_logged(state, vtx, logger);
}
/**
* \brief Called by the application layer.
*
* In most cases 1 can be returned here.
*/
static int NFS3GetAlstateProgressCompletionStatus(uint8_t direction) {
return rs_nfs3_state_progress_completion_status(direction);
}
/**
* \brief Return the state of a transaction in a given direction.
*
* In the case of the echo protocol, the existence of a transaction
* means that the request is done. However, some protocols that may
* need multiple chunks of data to complete the request may need more
* than just the existence of a transaction for the request to be
* considered complete.
*
* For the response to be considered done, the response for a request
* needs to be seen. The response_done flag is set on response for
* checking here.
*/
static int NFS3GetStateProgress(void *tx, uint8_t direction)
{
return rs_nfs3_tx_get_alstate_progress(tx, direction);
}
/**
* \brief get stored tx detect state
*/
static DetectEngineState *NFS3GetTxDetectState(void *vtx)
{
return rs_nfs3_state_get_tx_detect_state(vtx);
}
/**
* \brief set store tx detect state
*/
static int NFS3SetTxDetectState(void *state, void *vtx,
DetectEngineState *s)
{
rs_nfs3_state_set_tx_detect_state(state, vtx, s);
return 0;
}
static FileContainer *NFS3GetFiles(void *state, uint8_t direction)
{
return rs_nfs3_getfiles(direction, state);
}
static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
static SuricataFileContext sfc = { &sbcfg };
void RegisterNFS3UDPParsers(void)
{
const char *proto_name = "nfs3";
/* Check if NFS3 TCP detection is enabled. If it does not exist in
* the configuration file then it will be enabled by default. */
if (AppLayerProtoDetectConfProtoDetectionEnabled("udp", proto_name)) {
rs_nfs3_init(&sfc);
SCLogNotice("NFS3 UDP protocol detection enabled.");
AppLayerProtoDetectRegisterProtocol(ALPROTO_NFS3, proto_name);
if (RunmodeIsUnittests()) {
SCLogNotice("Unittest mode, registering default configuration.");
AppLayerProtoDetectPPRegister(IPPROTO_UDP, NFS3_DEFAULT_PORT,
ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN, STREAM_TOSERVER,
NFS3ProbingParserTS, NFS3ProbingParserTC);
}
else {
if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP,
proto_name, ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN,
NFS3ProbingParserTS, NFS3ProbingParserTC)) {
SCLogNotice("No NFS3 app-layer configuration, enabling NFS3"
" detection TCP detection on port %s.",
NFS3_DEFAULT_PORT);
AppLayerProtoDetectPPRegister(IPPROTO_UDP,
NFS3_DEFAULT_PORT, ALPROTO_NFS3, 0,
NFS3_MIN_FRAME_LEN, STREAM_TOSERVER,
NFS3ProbingParserTS, NFS3ProbingParserTC);
}
}
}
else {
SCLogNotice("Protocol detecter and parser disabled for NFS3.");
return;
}
if (AppLayerParserConfParserEnabled("udp", proto_name))
{
SCLogNotice("Registering NFS3 protocol parser.");
/* Register functions for state allocation and freeing. A
* state is allocated for every new NFS3 flow. */
AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_NFS3,
NFS3StateAlloc, NFS3StateFree);
/* Register request parser for parsing frame from server to client. */
AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_NFS3,
STREAM_TOSERVER, NFS3ParseRequest);
/* Register response parser for parsing frames from server to client. */
AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_NFS3,
STREAM_TOCLIENT, NFS3ParseResponse);
/* Register a function to be called by the application layer
* when a transaction is to be freed. */
AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_NFS3,
NFS3StateTxFree);
AppLayerParserRegisterLoggerFuncs(IPPROTO_UDP, ALPROTO_NFS3,
NFS3GetTxLogged, NFS3SetTxLogged);
/* Register a function to return the current transaction count. */
AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_NFS3,
NFS3GetTxCnt);
/* Transaction handling. */
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_NFS3,
NFS3GetAlstateProgressCompletionStatus);
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP,
ALPROTO_NFS3, NFS3GetStateProgress);
AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_NFS3,
NFS3GetTx);
AppLayerParserRegisterGetFilesFunc(IPPROTO_UDP, ALPROTO_NFS3, NFS3GetFiles);
/* Application layer event handling. */
// AppLayerParserRegisterHasEventsFunc(IPPROTO_UDP, ALPROTO_NFS3,
// NFS3HasEvents);
/* What is this being registered for? */
AppLayerParserRegisterDetectStateFuncs(IPPROTO_UDP, ALPROTO_NFS3,
NULL, NFS3GetTxDetectState, NFS3SetTxDetectState);
// AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_NFS3,
// NFS3StateGetEventInfo);
// AppLayerParserRegisterGetEventsFunc(IPPROTO_UDP, ALPROTO_NFS3,
// NFS3GetEvents);
}
else {
SCLogNotice("NFS3 protocol parsing disabled.");
}
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_NFS3,
NFS3UDPParserRegisterTests);
#endif
}
#ifdef UNITTESTS
#endif
void NFS3UDPParserRegisterTests(void)
{
#ifdef UNITTESTS
#endif
}
#endif /* HAVE_RUST */

@ -0,0 +1,34 @@
/* Copyright (C) 2017 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Victor Julien <victor@inliniac.net>
*/
#ifndef __APP_LAYER_NFS3_UDP_H__
#define __APP_LAYER_NFS3_UDP_H__
void RegisterNFS3UDPParsers(void);
void NFS3UDPParserRegisterTests(void);
/** Opaque Rust types. */
typedef struct NFS3tate_ NFS3State;
typedef struct NFS3Transaction_ NFS3Transaction;
#endif /* __APP_LAYER_NFS3_H__ */

@ -61,6 +61,7 @@
#include "app-layer-enip.h"
#include "app-layer-dnp3.h"
#include "app-layer-nfs3.h"
#include "app-layer-nfs3-udp.h"
#include "app-layer-template.h"
#include "conf.h"
@ -1384,6 +1385,7 @@ void AppLayerParserRegisterProtocolParsers(void)
RegisterENIPTCPParsers();
RegisterDNP3Parsers();
RegisterNFS3Parsers();
RegisterNFS3UDPParsers();
RegisterTemplateParsers();
/** IMAP */

@ -130,6 +130,7 @@ static OutputCtx *OutputNFS3LogInitSub(ConfNode *conf,
SCLogDebug("NFS3 log sub-module initialized.");
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_NFS3);
AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_NFS3);
return output_ctx;
}

Loading…
Cancel
Save