Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-AMF
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenXG
OpenXG-AMF
Commits
31c75155
Commit
31c75155
authored
Oct 19, 2021
by
Tien-Thinh Nguyen
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'nrf-selection' into 'develop'
NRF Selection See merge request oai/cn5g/oai-cn5g-amf!64
parents
c0f30330
7b0cdfb1
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
248 additions
and
9 deletions
+248
-9
ci-scripts/dsTesterDockerCompose/docker-compose.tplt
ci-scripts/dsTesterDockerCompose/docker-compose.tplt
+4
-0
etc/amf.conf
etc/amf.conf
+9
-1
scripts/entrypoint.sh
scripts/entrypoint.sh
+7
-0
src/amf-app/amf_config.cpp
src/amf-app/amf_config.cpp
+63
-0
src/amf-app/amf_config.hpp
src/amf-app/amf_config.hpp
+10
-0
src/amf-app/amf_n11.cpp
src/amf-app/amf_n11.cpp
+142
-7
src/amf-app/amf_n11.hpp
src/amf-app/amf_n11.hpp
+13
-1
No files found.
ci-scripts/dsTesterDockerCompose/docker-compose.tplt
View file @
31c75155
...
...
@@ -85,6 +85,10 @@ services:
- AUSF_PORT=80
- AUSF_API_VERSION=v1
- AUSF_FQDN=localhost
- NSSF_IPV4_ADDRESS=0.0.0.0
- NSSF_PORT=80
- NSSF_API_VERSION=v1
- NSSF_FQDN=localhost
depends_on:
- cicd_mysql
networks:
...
...
etc/amf.conf
View file @
31c75155
...
...
@@ -96,13 +96,21 @@ AMF =
API_VERSION
=
"@AUSF_API_VERSION@"
;
# YOUR AUSF API VERSION FOR SBI CONFIG HERE
FQDN
=
"@AUSF_FQDN@"
# YOUR AUSF FQDN CONFIG HERE
};
NSSF
:
{
IPV4_ADDRESS
=
"@NSSF_IPV4_ADDRESS@"
;
# YOUR NSSF CONFIG HERE
PORT
= @
NSSF_PORT
@;
# YOUR NSSF CONFIG HERE (default: 80)
API_VERSION
=
"@NSSF_API_VERSION@"
;
# YOUR NSSF API VERSION FOR SBI CONFIG HERE
FQDN
=
"@NSSF_FQDN@"
# YOUR NSSF FQDN CONFIG HERE
};
};
SUPPORT_FEATURES
:
{
# STRING, {"yes", "no"},
NF_REGISTRATION
=
"@NF_REGISTRATION@"
;
# Set to yes if AMF resgisters to an NRF
NRF_SELECTION
=
"@NRF_SELECTION@"
;
# Set to yes to enable NRF discovery and selection
SMF_SELECTION
=
"@SMF_SELECTION@"
;
# Set to yes to enable SMF discovery and selection
EXTERNAL_AUSF
=
"@EXTERNAL_AUSF@"
;
# Set to yes if AMF works with an external AUSF
EXTERNAL_UDM
=
"@EXTERNAL_UDM@"
;
# Set to yes if AMF works with an external UDM
...
...
scripts/entrypoint.sh
View file @
31c75155
...
...
@@ -7,7 +7,14 @@ CONFIG_DIR="/openair-amf/etc"
# Default values
EXTERNAL_AUSF
=
${
EXTERNAL_AUSF
:-
no
}
EXTERNAL_UDM
=
${
EXTERNAL_UDM
:-
no
}
NRF_SELECTION
=
${
NRF_SELECTION
:-
no
}
NSSF_IPV4_ADDRESS
=
${
NSSF_IPV4_ADDRESS
:-
0
.0.0.0
}
NSSF_PORT
=
${
NSSF_PORT
:-
80
}
NSSF_API_VERSION
=
${
NSSF_API_VERSION
:-
v2
}
NSSF_FQDN
=
${
NSSF_FQDN
:-
oai
-nssf
}
if
[[
${
USE_FQDN_DNS
}
==
"yes"
]]
;
then
NSSF_IPV4_ADDR
=
${
NSSF_IPV4_ADDR_0
:-
0
.0.0.0
}
SMF_IPV4_ADDR_0
=
${
SMF_IPV4_ADDR_0
:-
0
.0.0.0
}
SMF_IPV4_ADDR_1
=
${
SMF_IPV4_ADDR_1
:-
0
.0.0.0
}
NRF_IPV4_ADDRESS
=
${
NRF_IPV4_ADDRESS
:-
0
.0.0.0
}
...
...
src/amf-app/amf_config.cpp
View file @
31c75155
...
...
@@ -78,6 +78,7 @@ amf_config::amf_config() {
nas_cfg
=
{};
smf_pool
=
{};
support_features
.
enable_nf_registration
=
false
;
support_features
.
enable_nrf_selection
=
false
;
support_features
.
enable_smf_selection
=
false
;
support_features
.
enable_external_ausf
=
false
;
support_features
.
enable_external_udm
=
false
;
...
...
@@ -237,6 +238,14 @@ int amf_config::load(const std::string& config_file) {
support_features
.
enable_nf_registration
=
false
;
}
support_features_cfg
.
lookupValue
(
AMF_CONFIG_STRING_SUPPORT_FEATURES_NRF_SELECTION
,
opt
);
if
(
boost
::
iequals
(
opt
,
"yes"
))
{
support_features
.
enable_nrf_selection
=
true
;
}
else
{
support_features
.
enable_nrf_selection
=
false
;
}
support_features_cfg
.
lookupValue
(
AMF_CONFIG_STRING_SUPPORT_FEATURES_SMF_SELECTION
,
opt
);
if
(
boost
::
iequals
(
opt
,
"yes"
))
{
...
...
@@ -456,6 +465,48 @@ int amf_config::load(const std::string& config_file) {
}
}
// NSSF
if
(
support_features
.
enable_nrf_selection
)
{
const
Setting
&
nssf_cfg
=
new_if_cfg
[
AMF_CONFIG_STRING_NSSF
];
struct
in_addr
nssf_ipv4_addr
=
{};
unsigned
int
nssf_port
=
{};
std
::
string
nssf_api_version
=
{};
if
(
!
support_features
.
use_fqdn_dns
)
{
nssf_cfg
.
lookupValue
(
AMF_CONFIG_STRING_IPV4_ADDRESS
,
address
);
IPV4_STR_ADDR_TO_INADDR
(
util
::
trim
(
address
).
c_str
(),
nssf_ipv4_addr
,
"BAD IPv4 ADDRESS FORMAT FOR NSSF !"
);
nssf_addr
.
ipv4_addr
=
nssf_ipv4_addr
;
if
(
!
(
nssf_cfg
.
lookupValue
(
AMF_CONFIG_STRING_PORT
,
nssf_port
)))
{
Logger
::
amf_app
().
error
(
AMF_CONFIG_STRING_PORT
"failed"
);
throw
(
AMF_CONFIG_STRING_PORT
"failed"
);
}
nssf_addr
.
port
=
nssf_port
;
if
(
!
(
nssf_cfg
.
lookupValue
(
AMF_CONFIG_STRING_API_VERSION
,
nssf_api_version
)))
{
Logger
::
amf_app
().
error
(
AMF_CONFIG_STRING_API_VERSION
"failed"
);
throw
(
AMF_CONFIG_STRING_API_VERSION
"failed"
);
}
nssf_addr
.
api_version
=
nssf_api_version
;
}
else
{
std
::
string
nssf_fqdn
=
{};
nssf_cfg
.
lookupValue
(
AMF_CONFIG_STRING_FQDN_DNS
,
nssf_fqdn
);
uint8_t
addr_type
=
{};
fqdn
::
resolve
(
nssf_fqdn
,
address
,
nssf_port
,
addr_type
);
if
(
addr_type
!=
0
)
{
// IPv6: TODO
throw
(
"DO NOT SUPPORT IPV6 ADDR FOR NSSF!"
);
}
else
{
// IPv4
IPV4_STR_ADDR_TO_INADDR
(
util
::
trim
(
address
).
c_str
(),
nssf_ipv4_addr
,
"BAD IPv4 ADDRESS FORMAT FOR NSSF !"
);
nssf_addr
.
ipv4_addr
=
nssf_ipv4_addr
;
nssf_addr
.
port
=
nssf_port
;
nssf_addr
.
api_version
=
"v1"
;
// TODO: get API version
}
}
}
}
catch
(
const
SettingNotFoundException
&
nfex
)
{
Logger
::
amf_app
().
error
(
"%s : %s, using defaults"
,
nfex
.
what
(),
nfex
.
getPath
());
...
...
@@ -607,6 +658,15 @@ void amf_config::display() {
" API version ...........: %s"
,
nrf_addr
.
api_version
.
c_str
());
}
if
(
support_features
.
enable_nrf_selection
)
{
Logger
::
config
().
info
(
"- NSSF:"
);
Logger
::
config
().
info
(
" IP Addr ...............: %s"
,
inet_ntoa
(
nssf_addr
.
ipv4_addr
));
Logger
::
config
().
info
(
" Port ..................: %d"
,
nssf_addr
.
port
);
Logger
::
config
().
info
(
" API version ...........: %s"
,
nssf_addr
.
api_version
.
c_str
());
}
if
(
support_features
.
enable_external_ausf
)
{
Logger
::
config
().
info
(
"- AUSF:"
);
Logger
::
config
().
info
(
...
...
@@ -634,6 +694,9 @@ void amf_config::display() {
Logger
::
config
().
info
(
" NF Registration .......: %s"
,
support_features
.
enable_nf_registration
?
"Yes"
:
"No"
);
Logger
::
config
().
info
(
" NRF Selection .........: %s"
,
support_features
.
enable_nrf_selection
?
"Yes"
:
"No"
);
Logger
::
config
().
info
(
" SMF Selection .........: %s"
,
support_features
.
enable_smf_selection
?
"Yes"
:
"No"
);
...
...
src/amf-app/amf_config.hpp
View file @
31c75155
...
...
@@ -67,6 +67,8 @@
#define AMF_CONFIG_STRING_AUSF "AUSF"
#define AMF_CONFIG_STRING_NSSF "NSSF"
#define AMF_CONFIG_STRING_SCHED_PARAMS "SCHED_PARAMS"
#define AMF_CONFIG_STRING_THREAD_RD_CPU_ID "CPU_ID"
#define AMF_CONFIG_STRING_THREAD_RD_SCHED_POLICY "SCHED_POLICY"
...
...
@@ -102,6 +104,7 @@
"ORDERED_SUPPORTED_CIPHERING_ALGORITHM_LIST"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES "SUPPORT_FEATURES"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES_NF_REGISTRATION "NF_REGISTRATION"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES_NRF_SELECTION "NRF_SELECTION"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES_SMF_SELECTION "SMF_SELECTION"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES_EXTERNAL_AUSF "EXTERNAL_AUSF"
#define AMF_CONFIG_STRING_SUPPORT_FEATURES_EXTERNAL_UDM "EXTERNAL_UDM"
...
...
@@ -204,6 +207,7 @@ class amf_config {
struct
{
bool
enable_nf_registration
;
bool
enable_nrf_selection
;
bool
enable_smf_selection
;
bool
enable_external_ausf
;
bool
enable_external_udm
;
...
...
@@ -222,6 +226,12 @@ class amf_config {
unsigned
int
port
;
std
::
string
api_version
;
}
ausf_addr
;
struct
{
struct
in_addr
ipv4_addr
;
unsigned
int
port
;
std
::
string
api_version
;
}
nssf_addr
;
}
;
}
// namespace config
...
...
src/amf-app/amf_n11.cpp
View file @
31c75155
...
...
@@ -330,7 +330,14 @@ void amf_n11::handle_itti_message(itti_nsmf_pdusession_create_sm_context& smf) {
std
::
string
smf_addr
=
{};
std
::
string
smf_api_version
=
{};
if
(
!
psc
.
get
()
->
smf_available
)
{
if
(
amf_cfg
.
support_features
.
enable_smf_selection
)
{
if
(
amf_cfg
.
support_features
.
enable_nrf_selection
)
{
if
(
!
discover_smf_from_nsi_info
(
smf_addr
,
smf_api_version
,
psc
.
get
()
->
snssai
,
psc
.
get
()
->
plmn
,
psc
.
get
()
->
dnn
))
{
Logger
::
amf_n11
().
error
(
"NRF Selection, no NRF candidate is available"
);
return
;
}
}
else
if
(
amf_cfg
.
support_features
.
enable_smf_selection
)
{
// use NRF to find suitable SMF based on snssai, plmn and dnn
if
(
!
discover_smf
(
smf_addr
,
smf_api_version
,
psc
.
get
()
->
snssai
,
psc
.
get
()
->
plmn
,
...
...
@@ -892,11 +899,134 @@ void amf_n11::curl_http_client(
curl_global_cleanup
();
free_wrapper
((
void
**
)
&
body_data
);
}
//-----------------------------------------------------------------------------------------------------
bool
amf_n11
::
discover_smf_from_nsi_info
(
std
::
string
&
smf_addr
,
std
::
string
&
smf_api_version
,
const
snssai_t
snssai
,
const
plmn_t
plmn
,
const
std
::
string
dnn
)
{
Logger
::
amf_n11
().
debug
(
"Send NS Selection to NSSF to discover the appropriate NRF"
);
bool
result
=
true
;
std
::
string
nrf_addr
=
{};
std
::
string
nrf_port
=
{};
std
::
string
nrf_api_version
=
{};
uint8_t
http_version
=
1
;
if
(
amf_cfg
.
support_features
.
use_http2
)
http_version
=
2
;
// Get NSI information from NSSF
nlohmann
::
json
slice_info
=
{};
nlohmann
::
json
snssai_info
=
{};
snssai_info
[
"sst"
]
=
snssai
.
sST
;
if
(
!
snssai
.
sD
.
empty
())
snssai_info
[
"sd"
]
=
snssai
.
sD
;
slice_info
[
"sNssai"
]
=
snssai_info
;
slice_info
[
"roamingIndication"
]
=
"NON_ROAMING"
;
// ToDo Add TAI
std
::
string
url
=
std
::
string
(
inet_ntoa
(
*
((
struct
in_addr
*
)
&
amf_cfg
.
nssf_addr
.
ipv4_addr
)))
+
":"
+
std
::
to_string
(
amf_cfg
.
nssf_addr
.
port
)
+
"/nnssf-nsselection/"
+
amf_cfg
.
nssf_addr
.
api_version
+
"/network-slice-information?nf-type=AMF&nf-id=abc&slice-"
"info-request-for-pdu-session="
+
slice_info
.
dump
();
curl_global_init
(
CURL_GLOBAL_ALL
);
CURL
*
curl
=
curl
=
curl_easy_init
();
if
(
curl
)
{
CURLcode
res
=
{};
struct
curl_slist
*
headers
=
nullptr
;
// headers = curl_slist_append(headers, "charsets: utf-8");
headers
=
curl_slist_append
(
headers
,
"content-type: application/json"
);
curl_easy_setopt
(
curl
,
CURLOPT_HTTPHEADER
,
headers
);
curl_easy_setopt
(
curl
,
CURLOPT_URL
,
url
.
c_str
());
curl_easy_setopt
(
curl
,
CURLOPT_HTTPGET
,
1
);
curl_easy_setopt
(
curl
,
CURLOPT_TIMEOUT_MS
,
CURL_TIMEOUT_MS
);
curl_easy_setopt
(
curl
,
CURLOPT_INTERFACE
,
amf_cfg
.
n11
.
if_name
.
c_str
());
if
(
http_version
==
2
)
{
curl_easy_setopt
(
curl
,
CURLOPT_VERBOSE
,
1L
);
// we use a self-signed test server, skip verification during debugging
curl_easy_setopt
(
curl
,
CURLOPT_SSL_VERIFYPEER
,
0L
);
curl_easy_setopt
(
curl
,
CURLOPT_SSL_VERIFYHOST
,
0L
);
curl_easy_setopt
(
curl
,
CURLOPT_HTTP_VERSION
,
CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE
);
}
// Response information
long
httpCode
=
{
0
};
std
::
unique_ptr
<
std
::
string
>
httpData
(
new
std
::
string
());
curl_easy_setopt
(
curl
,
CURLOPT_WRITEFUNCTION
,
&
callback
);
curl_easy_setopt
(
curl
,
CURLOPT_WRITEDATA
,
httpData
.
get
());
// curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length());
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
res
=
curl_easy_perform
(
curl
);
curl_easy_getinfo
(
curl
,
CURLINFO_RESPONSE_CODE
,
&
httpCode
);
Logger
::
amf_n11
().
debug
(
"NS Selection, response from NSSF, HTTP Code: %d"
,
httpCode
);
if
(
httpCode
==
200
)
{
Logger
::
amf_n11
().
debug
(
"NS Selection, got successful response from NSSF"
);
nlohmann
::
json
response_data
=
{};
try
{
response_data
=
nlohmann
::
json
::
parse
(
*
httpData
.
get
());
}
catch
(
nlohmann
::
json
::
exception
&
e
)
{
Logger
::
amf_n11
().
warn
(
"NS Selection, could not parse json from the NRF "
"response"
);
}
Logger
::
amf_n11
().
debug
(
"NS Selection, response from NSSF, json data:
\n
%s"
,
response_data
.
dump
().
c_str
());
// Process data to obtain NRF info
if
(
response_data
.
find
(
"nsiInformation"
)
!=
response_data
.
end
())
{
std
::
string
nrf_id
=
response_data
[
"nsiInformation"
][
"nrfId"
];
std
::
string
nsi_id
=
response_data
[
"nsiInformation"
][
"nsiId"
];
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
nrf_id
,
boost
::
is_any_of
(
"/"
));
nrf_api_version
=
split_result
[
split_result
.
size
()
-
2
].
c_str
();
std
::
string
nrf_endpoint
=
split_result
[
split_result
.
size
()
-
4
].
c_str
();
std
::
vector
<
std
::
string
>
split_nrf_endpoint
;
boost
::
split
(
split_nrf_endpoint
,
nrf_endpoint
,
boost
::
is_any_of
(
":"
));
nrf_port
=
split_nrf_endpoint
[
split_nrf_endpoint
.
size
()
-
1
].
c_str
();
nrf_addr
=
split_nrf_endpoint
[
split_nrf_endpoint
.
size
()
-
2
].
c_str
();
}
}
else
{
Logger
::
amf_n11
().
warn
(
"NS Selection, could not get response from NSSF"
);
result
=
false
;
}
Logger
::
amf_n11
().
debug
(
"NS Selection, NRF Addr: %s, NRF Port: %s, NRF Api Version: %s"
,
nrf_addr
.
c_str
(),
nrf_port
.
c_str
(),
nrf_api_version
.
c_str
());
curl_slist_free_all
(
headers
);
curl_easy_cleanup
(
curl
);
}
curl_global_cleanup
();
if
(
!
result
)
return
result
;
Logger
::
amf_n11
().
debug
(
"NSI Inforation is successfully retrieved from NSSF"
);
if
(
!
discover_smf
(
smf_addr
,
smf_api_version
,
snssai
,
plmn
,
dnn
,
nrf_addr
,
nrf_port
,
nrf_api_version
))
return
false
;
return
true
;
}
//-----------------------------------------------------------------------------------------------------
bool
amf_n11
::
discover_smf
(
std
::
string
&
smf_addr
,
std
::
string
&
smf_api_version
,
const
snssai_t
snssai
,
const
plmn_t
plmn
,
const
std
::
string
dnn
)
{
const
plmn_t
plmn
,
const
std
::
string
dnn
,
const
std
::
string
&
nrf_addr
,
const
std
::
string
&
nrf_port
,
const
std
::
string
&
nrf_api_version
)
{
Logger
::
amf_n11
().
debug
(
"Send NFDiscovery to NRF to discover the available SMFs"
);
bool
result
=
true
;
...
...
@@ -905,12 +1035,17 @@ bool amf_n11::discover_smf(
if
(
amf_cfg
.
support_features
.
use_http2
)
http_version
=
2
;
nlohmann
::
json
json_data
=
{};
std
::
string
url
=
{};
// TODO: remove hardcoded values
std
::
string
url
=
std
::
string
(
inet_ntoa
(
*
((
struct
in_addr
*
)
&
amf_cfg
.
nrf_addr
.
ipv4_addr
)))
+
":"
+
std
::
to_string
(
amf_cfg
.
nrf_addr
.
port
)
+
"/nnrf-disc/"
+
amf_cfg
.
nrf_addr
.
api_version
+
"/nf-instances?target-nf-type=SMF&requester-nf-type=AMF"
;
if
(
!
nrf_addr
.
empty
()
&
!
nrf_port
.
empty
()
&
!
nrf_api_version
.
empty
())
{
url
=
nrf_addr
+
":"
+
nrf_port
+
"/nnrf-disc/"
+
nrf_api_version
+
"/nf-instances?target-nf-type=SMF&requester-nf-type=AMF"
;
}
else
url
=
std
::
string
(
inet_ntoa
(
*
((
struct
in_addr
*
)
&
amf_cfg
.
nrf_addr
.
ipv4_addr
)))
+
":"
+
std
::
to_string
(
amf_cfg
.
nrf_addr
.
port
)
+
"/nnrf-disc/"
+
amf_cfg
.
nrf_addr
.
api_version
+
"/nf-instances?target-nf-type=SMF&requester-nf-type=AMF"
;
curl_global_init
(
CURL_GLOBAL_ALL
);
CURL
*
curl
=
curl
=
curl_easy_init
();
...
...
src/amf-app/amf_n11.hpp
View file @
31c75155
...
...
@@ -39,6 +39,10 @@
#include "itti_msg_sbi.hpp"
#include "pdu_session_context.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
namespace
amf_application
{
class
amf_n11
{
...
...
@@ -76,11 +80,19 @@ class amf_n11 {
std
::
string
remoteUri
,
std
::
string
Method
,
std
::
string
msgBody
,
std
::
string
&
response
,
uint8_t
http_version
=
1
);
bool
discover_smf
(
bool
discover_smf
_from_nsi_info
(
std
::
string
&
smf_addr
,
std
::
string
&
smf_api_version
,
const
snssai_t
snssai
,
const
plmn_t
plmn
,
const
std
::
string
dnn
);
bool
discover_smf
(
std
::
string
&
smf_addr
,
std
::
string
&
smf_api_version
,
const
snssai_t
snssai
,
const
plmn_t
plmn
,
const
std
::
string
dnn
,
const
std
::
string
&
nrf_addr
=
{},
const
std
::
string
&
nrf_port
=
{},
const
std
::
string
&
nrf_api_version
=
{});
void
register_nf_instance
(
std
::
shared_ptr
<
itti_n11_register_nf_instance_request
>
msg
);
bool
send_ue_authentication_request
(
oai
::
amf
::
model
::
AuthenticationInfo
&
auth_info
,
oai
::
amf
::
model
::
UEAuthenticationCtx
&
ue_auth_ctx
,
uint8_t
http_version
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment