#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "elvs_kernel_vxlan.h"
#define MIN_VXLAN_HEAD_ROOM (sizeof(struct lb_vxlan_hdr) + sizeof(struct udphdr) + \
sizeof(struct iphdr) + 18)
//cat /sys/module/elvs_kernel_vxlan/parameters/m_pkt_cnt
static long unsigned int m_pkt_cnt = 0;
module_param(m_pkt_cnt, ulong, 0644);
MODULE_PARM_DESC(m_pkt_cnt, "all packets sent");
static int m_ip_num = 0;
module_param(m_ip_num, int, 0644);
MODULE_PARM_DESC(m_ip_num, "exist ip numbers");
uint16_t m_free_list = INVALID_RSIP_POS;
uint16_t m_bucket_list[MAX_BUCKET_NUM];
struct elvs_ip_mgr m_ip_list[MAX_RSIP_NUM];
static int elvs_init_ip(void){
int i=0;
memset(m_bucket_list,0xff,sizeof(m_bucket_list));
memset(m_ip_list,0,sizeof(m_ip_list));
m_free_list = 0;
for (i=0; i < (MAX_RSIP_NUM - 1); i++){
m_ip_list[i].next = i+1;
}
m_ip_list[MAX_RSIP_NUM - 1].next = INVALID_RSIP_POS;
return RET_SUCCESS;
}
static struct elvs_ip_mgr* elvs_get_ip(uint32_t overlay_ip){
int bucket_pos = (overlay_ip >> 24)%MAX_BUCKET_NUM;
int ip_pos = m_bucket_list[bucket_pos];
while (ip_pos < MAX_RSIP_NUM){
if (m_ip_list[ip_pos].overlay_ip == overlay_ip){
return &m_ip_list[ip_pos];
}
ip_pos = m_ip_list[ip_pos].next;
}
return NULL;
}
static int elvs_add_ip(struct elvs_nl_cmd *ip_cmd){
int bucket_pos = (ip_cmd->overlay_ip >> 24)%MAX_BUCKET_NUM;
int ip_pos = m_free_list;
if (NULL != elvs_get_ip(ip_cmd->overlay_ip)){
return RET_EXIST;
}
if (ip_pos >= MAX_RSIP_NUM ){
return RET_NORESOURCE;
}
m_free_list = m_ip_list[ip_pos].next;
m_ip_list[ip_pos].overlay_ip = ip_cmd->overlay_ip;
m_ip_list[ip_pos].underlay_ip = ip_cmd->underlay_ip;
m_ip_list[ip_pos].vni_id = ip_cmd->vni_id;
m_ip_list[ip_pos].next = m_bucket_list[bucket_pos];
m_bucket_list[bucket_pos] = ip_pos;
m_ip_num++;
return RET_SUCCESS;
}
static int elvs_mod_ip(struct elvs_nl_cmd *ip_cmd){
struct elvs_ip_mgr* ip_mgr = elvs_get_ip(ip_cmd->overlay_ip);
if (NULL == ip_mgr){
return RET_NOTEXIST;
}
ip_mgr->underlay_ip = ip_cmd->underlay_ip;
ip_mgr->vni_id = ip_cmd->vni_id;
return RET_SUCCESS;
}
static int elvs_del_ip(struct elvs_nl_cmd *ip_cmd){
uint32_t overlay_ip = ip_cmd->overlay_ip;
int bucket_pos = (overlay_ip >> 24)%MAX_BUCKET_NUM;
int ip_pos = m_bucket_list[bucket_pos];
int last_pos = ip_pos;
while (ip_pos < MAX_RSIP_NUM){
if (m_ip_list[ip_pos].overlay_ip == overlay_ip){
if (ip_pos == last_pos){//head
m_bucket_list[bucket_pos] = m_ip_list[ip_pos].next;
}else{
m_ip_list[last_pos].next = m_ip_list[ip_pos].next;;
}
ip_cmd->underlay_ip = m_ip_list[ip_pos].underlay_ip;
m_ip_list[ip_pos].next = m_free_list;
m_free_list = ip_pos;
if (m_ip_num > 0){
m_ip_num--;
}
return RET_SUCCESS;
}
last_pos = ip_pos;
ip_pos = m_ip_list[ip_pos].next;
}
return RET_NOTEXIST;
}
static struct sock *elvs_netlink_sock = NULL;
static void elvs_check_ip_empty_full(int cmd){
int i = 0, free_list_num = 0;
uint16_t free_list;
if ((NETLINK_CMD_ADD != cmd) && (NETLINK_CMD_DEL != cmd)){
return;
}
if (0 == m_ip_num){
for (i = 0; i < MAX_BUCKET_NUM; i++){
if (INVALID_RSIP_POS != m_bucket_list[i]){
printk(KERN_INFO"%s: m_bucket_list[%d]=%u is not empty!\n",__func__, i,m_bucket_list[i]);
return;
}
}
free_list = m_free_list;
while (INVALID_RSIP_POS != free_list){
free_list_num++;
free_list = m_ip_list[free_list].next;
}
if (free_list_num != MAX_RSIP_NUM){
printk("KERN_INFO%s: free_list_num=%d %d\n",__func__, free_list_num,MAX_RSIP_NUM);
return;
}
printk(KERN_INFO"%s: empty success!\n",__func__);
}else if(MAX_RSIP_NUM == m_ip_num){
if (INVALID_RSIP_POS != m_free_list){
printk(KERN_INFO"%s: not empty m_free_list=%u %d\n",__func__, m_free_list, INVALID_RSIP_POS);
return;
}
printk(KERN_INFO"%s: full success!\n",__func__);
}
return;
}
//audit_receive_msg
static int elvs_netlink_receive_msg(struct elvs_nl_cmd *cmd){
int ret = RET_INVALID_CMD;
char *cmd_str = "unknown";
if (NETLINK_CMD_ADD == cmd->cmd){
cmd_str = "add";
ret = elvs_add_ip(cmd);
}else if (NETLINK_CMD_MOD == cmd->cmd){
cmd_str = "mod";
ret = elvs_mod_ip(cmd);
}else if (NETLINK_CMD_DEL == cmd->cmd){
cmd_str = "del";
ret = elvs_del_ip(cmd);
}else if (NETLINK_CMD_GET == cmd->cmd){
struct elvs_nl_cmd cmd_tmp;
struct elvs_ip_mgr* mgr = elvs_get_ip(cmd->overlay_ip);
if (NULL == mgr){
ret = RET_NOTEXIST;
}else{
cmd_tmp.cmd = cmd->cmd;
cmd_tmp.overlay_ip = mgr->overlay_ip;
cmd_tmp.underlay_ip = mgr->underlay_ip;
cmd = &cmd_tmp;
}
cmd_str = "get";
}
elvs_check_ip_empty_full(cmd->cmd);
printk(KERN_DEBUG"elvs_netlink cmd[%s-%d] overlay_ip=0x%x underlay_ip=0x%x ret=%d ip_num=%d\n",
cmd_str,cmd->cmd, ntohl(cmd->overlay_ip),ntohl(cmd->underlay_ip), ret,m_ip_num);
return ret;
}
/* Receive messages from netlink socket. */
static void elvs_netlink_receive(struct sk_buff *skb){
struct nlmsghdr *nlh;
struct elvs_nl_cmd *elvs_cmd;
int len, err = RET_SUCCESS;
if(!skb){
return;
}
if(skb->len < NLMSG_SPACE(sizeof(struct elvs_nl_cmd))) {
return;
}
nlh = nlmsg_hdr(skb);
len = skb->len;
//printk(KERN_DEBUG "elvs_netlink len=%u %u %u seq=%u pid=%u\n",
// nlh->nlmsg_len,NLMSG_ALIGN(nlh->nlmsg_len),len,
// nlh->nlmsg_seq,nlh->nlmsg_pid);
while (nlmsg_ok(nlh, len)) {
elvs_cmd = nlmsg_data(nlh);
err = elvs_netlink_receive_msg(elvs_cmd);
netlink_ack(skb, nlh, err);
nlh = nlmsg_next(nlh, &len);
}
}
static int elvs_netlink_init(void){
struct netlink_kernel_cfg cfg;
memset(&cfg,0,sizeof(cfg));
cfg.input = elvs_netlink_receive;
elvs_netlink_sock = netlink_kernel_create(&init_net, ELVS_NETLINK_ID, &cfg);
if (!elvs_netlink_sock){
printk("netlink_kernel_create fail!\n");
return -1;
}
printk("netlink_kernel_create success!\n");
return 0;
}
static int elvs_netlink_exit(void){
if (elvs_netlink_sock){
netlink_kernel_release(elvs_netlink_sock);
}
return 0;
}
static int elvs_ip_route_outer(struct sk_buff *skb, struct iphdr *ip,struct iphdr *ip_outer){
struct dst_entry *dst_old = skb_dst(skb);
struct rtable *rt;
struct net *net_out;
if (NULL == dst_old){
printk(KERN_INFO "dst_old is NULL!\n");
return -1;
}
if (NULL == dst_old->dev){
printk(KERN_INFO "dst_old->dev is NULL!\n");
return -1;
}
net_out = dev_net(dst_old->dev);
if (NULL == net_out){
printk(KERN_INFO "net_out is NULL!\n");
return -1;
}
rt = ip_route_output(net_out, ip_outer->daddr, ip->saddr,0,0);
if (IS_ERR(rt)){
printk(KERN_INFO "ip_route_output fail! rt=%p\n",rt);
return -1;
}
/* Drop old route. */
skb_dst_drop(skb);
skb_dst_set(skb, &rt->dst);
if (skb_dst(skb)->error){
printk(KERN_INFO "skb_dst(skb)->error=%d!\n",skb_dst(skb)->error);
return -1;
}
return 0;
}
static unsigned int nf_elvs_vxlan_fn(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state){
//return nf_nat_ipv6_local_fn(ops, skb, state, ip6table_nat_do_chain);
struct iphdr *ip, *outer_ip;
struct udphdr *udp;
struct lb_vxlan_hdr *vxlan;
struct elvs_ip_mgr* ip_mgr;
bool sysctl_vxlan_hdr_csum = false;
uint8_t tos = 0;
ip = ip_hdr(skb);
if ((skb->len < sizeof(struct iphdr) || ip_hdrlen(skb) < sizeof(struct iphdr))){
return NF_ACCEPT;
}
//skb->dev is null!
//state->out->namet=Ethernet1
//printk(KERN_INFO "state->out->namet=%s\n",state->out->name);
//daddr=0x11010102 saddr=0x11010101 head_room=244
//printk(KERN_INFO "daddr=0x%x saddr=0x%x head_room=%d\n",ntohl(ip->daddr),ntohl(ip->saddr),head_room);
//protocol=6 m_pkt_cnt=196 skb->csum=1048840
//printk(KERN_INFO "protocol=%u m_pkt_cnt=%lu skb->csum=%u\n",ip->protocol,m_pkt_cnt,skb->csum);
ip_mgr = elvs_get_ip(ip->daddr);
if (NULL == ip_mgr){
return NF_ACCEPT;
}
skb_cow_head(skb,MIN_VXLAN_HEAD_ROOM);
if (skb_headroom(skb) < MIN_VXLAN_HEAD_ROOM){
return NF_ACCEPT;//use old dst
}
printk(KERN_INFO "\nprotocol=%u daddr=0x%x saddr=0x%x head_room=%d %lu oif=%s\n",ip->protocol, ntohl(ip->daddr),ntohl(ip->saddr),
skb_headroom(skb),MIN_VXLAN_HEAD_ROOM,state->out->name);
vxlan = (struct lb_vxlan_hdr *)skb_push(skb, sizeof(struct udphdr));
vxlan->version = 1;
vxlan->tunnel_id = ip_mgr->vni_id;
udp = (struct udphdr *)skb_push(skb, sizeof(struct udphdr));
skb_reset_transport_header(skb);
udp->check = 0;
udp->dest = ntohs(10240);
udp->source = ntohs(10240);
udp->len = ntohs(skb->len);
outer_ip = (struct iphdr *)skb_push(skb, sizeof(struct iphdr));
skb_reset_network_header(skb);
*((uint16_t *) outer_ip) = htons((4 << 12) | (5 << 8) | (tos & 0xff));
outer_ip->tot_len = htons(skb->len);
outer_ip->frag_off = htons(IP_DF);
outer_ip->ttl = IPDEFTTL;
outer_ip->protocol = IPPROTO_UDP;
outer_ip->saddr = ip->saddr;
outer_ip->daddr = ip_mgr->underlay_ip;
ip_send_check(outer_ip);
udp->check = 0;
if (sysctl_vxlan_hdr_csum) {
skb->csum = skb_checksum(skb, sizeof(struct iphdr), skb->len - sizeof(struct iphdr), 0);
udp->check = csum_tcpudp_magic(outer_ip->saddr, outer_ip->daddr,skb->len - sizeof(struct iphdr),
IPPROTO_UDP, skb->csum);
}
//elvs_ip_route_outer(skb,ip,outer_ip);
//printk(KERN_INFO "ip daddr=0x%x saddr=0x%x head_room=%d\n",ntohl(ip->daddr),ntohl(ip->saddr),head_room);
printk(KERN_INFO "outer_ip daddr=0x%x saddr=0x%x\n",ntohl(outer_ip->daddr),ntohl(outer_ip->saddr));
printk(KERN_INFO "protocol=%u m_pkt_cnt=%lu skb->csum=%u\n",ip->protocol,m_pkt_cnt,skb->csum);
m_pkt_cnt++;
//return dst_output_sk(skb->sk, skb);
return NF_ACCEPT;
}
static struct nf_hook_ops nf_elvs_vxlan_ops[] __read_mostly = {
{
.hook = nf_elvs_vxlan_fn,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = 101,
// 101 (after NF_IP_PRI_NAT_SRC)will route first, state->out->namet=
},
};
static int __init elvs_vxlan_register(void){
int err;
elvs_init_ip();
err = nf_register_hooks(nf_elvs_vxlan_ops, ARRAY_SIZE(nf_elvs_vxlan_ops));
if (err < 0){
printk("nf_register_hooks fail! err=%d\n",err);
return err;
}
err = elvs_netlink_init();
if (err < 0){
printk("elvs_netlink_init fail! err=%d\n",err);
return err;
}
printk("elvs_vxlan_register success!\n");
return 0;
}
static void __exit elvs_vxlan_unregister(void){
elvs_netlink_exit();
nf_unregister_hooks(nf_elvs_vxlan_ops, ARRAY_SIZE(nf_elvs_vxlan_ops));
printk("elvs_vxlan_unregister success\n");
}
module_init(elvs_vxlan_register);
module_exit(elvs_vxlan_unregister);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ELVS KERNEL VXLAN OPERATIONS");
================================
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "elvs_kernel_vxlan.h"
//#include "elvs_kernel_tmp.c" for test
struct elvs_nl_sk{
int sock;
struct sockaddr_nl src_snl;
struct sockaddr_nl dst_snl;
};
struct elvs_nl_msg{
struct nlmsghdr nlhdr;
struct elvs_nl_cmd cmd;
};
struct elvs_nl_sk m_elvs_nl_sk;
struct elvs_nl_msg m_elvs_nl_msg;
struct iovec m_elvs_nl_iov;
struct msghdr m_elvs_nl_sndmsg;
static uint32_t m_nlmsg_seq = 0;
int netlink_send(struct elvs_nl_cmd *cmd){
int status;
memcpy(&m_elvs_nl_msg.cmd,cmd,sizeof(struct elvs_nl_cmd));
m_elvs_nl_msg.nlhdr.nlmsg_seq++;
status = sendmsg (m_elvs_nl_sk.sock, &m_elvs_nl_sndmsg, MSG_DONTWAIT);
if (status < 0){
printf("%s: sendmsg fail! status=%d %s\n",__func__,status, strerror(errno));
return -1;
}
printf("%s: sendmsg success! status=%d %s\n",__func__,status, strerror(errno));
return 0;
}
void netlink_receive(struct elvs_nl_sk *nl_sk){
char buf[1024];
struct iovec iov = {.iov_base = buf, .iov_len = sizeof buf};
struct sockaddr_nl snl;
struct msghdr msg = {.msg_name = (void *)&snl,
.msg_namelen = sizeof snl,
.msg_iov = &iov,
.msg_iovlen = 1};
struct nlmsghdr *h;
int status = recvmsg(nl_sk->sock, &msg, 0);
if (status < 0) {
printf("netlink_receive recvmsg! error status=%d errno=%d-%s\n",status,errno,strerror(errno));
return;
}
h = (struct nlmsghdr *)buf;
for (; NLMSG_OK(h, (unsigned int)status);h = NLMSG_NEXT(h, status)) {
if (h->nlmsg_type == NLMSG_DONE)/* Finish of reading. */{
printf("netlink_receive NLMSG_DONE=%d!\n",NLMSG_DONE);
return;
}
if (h->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
int errnum = err->error;
printf("netlink_receive response ret=%d!\n",err->error);
if (RET_SUCCESS == err->error) {
if (!(h->nlmsg_flags & NLM_F_MULTI))
return;
continue;
}
}
}
}
static int elvs_nl_init(void){
int ret;
struct timeval tv = {2,0};
memset(&m_elvs_nl_sk,0,sizeof(m_elvs_nl_sk));
m_elvs_nl_sk.src_snl.nl_family = AF_NETLINK;
m_elvs_nl_sk.src_snl.nl_pid = getpid();
m_elvs_nl_sk.src_snl.nl_groups = 0;
m_elvs_nl_sk.dst_snl.nl_family = AF_NETLINK;
m_elvs_nl_sk.dst_snl.nl_pid = 0; //to kernel
m_elvs_nl_sk.dst_snl.nl_groups = 0;
m_elvs_nl_sk.sock = socket(AF_NETLINK, SOCK_RAW, ELVS_NETLINK_ID);
if (m_elvs_nl_sk.sock < 0) {
printf("%s: open socket fail!: %s", __func__, strerror(errno));
return -1;
}
ret = bind(m_elvs_nl_sk.sock, (struct sockaddr *)(&m_elvs_nl_sk.src_snl), sizeof(struct sockaddr_nl));
if (ret < 0) {
printf("%s:bind fail ret=%d %s", __func__,ret, strerror(errno));
close(m_elvs_nl_sk.sock);
return -1;
}
ret = setsockopt(m_elvs_nl_sk.sock, SOL_SOCKET,SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));
if (ret < 0) {
printf("%s:setsockopt fail ret=%d %s", __func__,ret, strerror(errno));
close(m_elvs_nl_sk.sock);
return -1;
}
memset(&m_elvs_nl_msg,0,sizeof(m_elvs_nl_msg));
m_elvs_nl_msg.nlhdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct elvs_nl_cmd));
m_elvs_nl_msg.nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
m_elvs_nl_msg.nlhdr.nlmsg_pid = getpid();
memset(&m_elvs_nl_iov,0,sizeof(m_elvs_nl_iov));
m_elvs_nl_iov.iov_base = (void *)(&m_elvs_nl_msg);
m_elvs_nl_iov.iov_len = m_elvs_nl_msg.nlhdr.nlmsg_len;
memset(&m_elvs_nl_sndmsg,0,sizeof(m_elvs_nl_sndmsg));
m_elvs_nl_sndmsg.msg_name = (void *)&(m_elvs_nl_sk.dst_snl);
m_elvs_nl_sndmsg.msg_namelen = sizeof(m_elvs_nl_sk.dst_snl);
m_elvs_nl_sndmsg.msg_iov = &m_elvs_nl_iov;
m_elvs_nl_sndmsg.msg_iovlen = 1;
return RET_SUCCESS;
}
int elvs_nl_oper(int cmd, uint32_t overlay_ip, uint32_t underlay_ip){
int ret;
struct elvs_nl_cmd elvs_cmd;
elvs_cmd.cmd = cmd;
elvs_cmd.overlay_ip = overlay_ip;
elvs_cmd.underlay_ip = underlay_ip;
//elvs_netlink_receive_msg(&elvs_cmd); for test
netlink_send(&elvs_cmd);
netlink_receive(&m_elvs_nl_sk);
printf("\n");
return 0;
}
int dpvs_laddr_main(int argc, char **argv){
char *pro_name = argv[0];
int opt = 0;
char *const optstring = "-";
uint32_t ip_out;
uint16_t port_out = 0;
uint16_t proto = IPPROTO_TCP;
static struct option long_options[] = {
{"print", no_argument, NULL, CMD_DPVS_LADDR_PRINT},
{"svc-print", required_argument, NULL, CMD_DPVS_LADDR_SVC_PRINT},
{"free-print", no_argument, NULL, CMD_DPVS_LADDR_FREE_PRINT},
{"load", no_argument, NULL, CMD_DPVS_LADDR_LOAD},
{"force-clear", no_argument, NULL, CMD_DPVS_LADDR_FORCE_CLEAR},
{"tcp", no_argument, NULL, CMD_DPVS_LADDR_SVC_TCP},
{"udp", no_argument, NULL, CMD_DPVS_LADDR_SVC_UDP},
{"help", no_argument, NULL, CMD_DPVS_LADDR_HELP},
{0, 0, 0, 0},
};
optind = 1;
while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1){
switch(opt) {
case CMD_DPVS_LADDR_HELP:
dpvs_laddr_usage(argv[0]);
return 0;
case CMD_DPVS_LADDR_PRINT:
dpvs_laddr_dpvs_cmd(SOCKOPT_LADDR_MGR_GET);
return 0;
case CMD_DPVS_LADDR_LOAD:
dpvs_laddr_dpvs_cmd(SOCKOPT_LADDR_MGR_LOAD);
return 0;
case CMD_DPVS_LADDR_FORCE_CLEAR:
dpvs_laddr_dpvs_cmd(SOCKOPT_LADDR_MGR_FORCE_CLEAR);
return 0;
case CMD_DPVS_LADDR_FREE_PRINT:
dpvs_laddr_svc_get(0,0,0);
return 0;
case CMD_DPVS_LADDR_SVC_PRINT:
if (!dpvs_admin_utils_iport_get(optarg,&ip_out,&port_out)){
fprintf(stderr, "invalid %s\n",optarg);
return 0;
}
if ((0 == port_out) || (0 == ip_out)){
fprintf(stderr, "invalid %s\n",optarg);
return 0;
}
break;
case CMD_DPVS_LADDR_SVC_TCP:
proto = IPPROTO_TCP;
break;
case CMD_DPVS_LADDR_SVC_UDP:
proto = IPPROTO_UDP;
break;
default:
fprintf(stderr, "[ERR]: not support option=%d!\n",opt);
dpvs_laddr_usage(pro_name);
return -1;
}
}
if ((0 == port_out) || (0 == ip_out)){
fprintf(stderr, "please input ip:port\n");
dpvs_laddr_usage(pro_name);
return 0;
}
dpvs_laddr_svc_get(ip_out,port_out,proto);
return 0;
}
int main(int argc, char **argv){
int ret;
uint32_t overlay_ip = ntohl(0x11010102);
uint32_t underlay_ip = ntohl(0x11010202);
//tmp_main(); for test
ret = elvs_nl_init();
if (ret < 0){
return -1;
}
elvs_nl_oper(NETLINK_CMD_ADD, overlay_ip, underlay_ip);
#if 0
ret = ret;
for (overlay_ip=0; overlay_ip <= MAX_RSIP_NUM; overlay_ip++){
elvs_nl_oper(NETLINK_CMD_ADD, ntohl(overlay_ip), ntohl(overlay_ip + 0xf00000));
}
for (overlay_ip=1; overlay_ip <= MAX_RSIP_NUM; overlay_ip+=3){
elvs_nl_oper(NETLINK_CMD_DEL, ntohl(overlay_ip),0);
elvs_nl_oper(NETLINK_CMD_DEL, ntohl(overlay_ip-1),0);
elvs_nl_oper(NETLINK_CMD_DEL, ntohl(overlay_ip+1),0);
}
for (overlay_ip=0; overlay_ip <= MAX_RSIP_NUM; overlay_ip++){
elvs_nl_oper(NETLINK_CMD_GET, ntohl(overlay_ip), 0);
}
#endif
return 0;
}