/* * (C) 2005-2012 by Pablo Neira Ayuso * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This code has been sponsored by Vyatta Inc. */ #include "internal/internal.h" #include #include static int nfct_build_tuple_ip(struct nlmsghdr *nlh, const struct __nfct_tuple *t) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_TUPLE_IP); if (nest == NULL) return -1; switch(t->l3protonum) { case AF_INET: mnl_attr_put_u32(nlh, CTA_IP_V4_SRC, t->src.v4); mnl_attr_put_u32(nlh, CTA_IP_V4_DST, t->dst.v4); break; case AF_INET6: mnl_attr_put(nlh, CTA_IP_V6_SRC, sizeof(struct in6_addr), &t->src.v6); mnl_attr_put(nlh, CTA_IP_V6_DST, sizeof(struct in6_addr), &t->dst.v6); break; default: mnl_attr_nest_cancel(nlh, nest); return -1; } mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_tuple_proto(struct nlmsghdr *nlh, const struct __nfct_tuple *t) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_TUPLE_PROTO); if (nest == NULL) return -1; mnl_attr_put_u8(nlh, CTA_PROTO_NUM, t->protonum); switch(t->protonum) { case IPPROTO_UDP: case IPPROTO_TCP: case IPPROTO_SCTP: case IPPROTO_DCCP: case IPPROTO_GRE: case IPPROTO_UDPLITE: mnl_attr_put_u16(nlh, CTA_PROTO_SRC_PORT, t->l4src.tcp.port); mnl_attr_put_u16(nlh, CTA_PROTO_DST_PORT, t->l4dst.tcp.port); break; case IPPROTO_ICMP: mnl_attr_put_u8(nlh, CTA_PROTO_ICMP_CODE, t->l4dst.icmp.code); mnl_attr_put_u8(nlh, CTA_PROTO_ICMP_TYPE, t->l4dst.icmp.type); mnl_attr_put_u16(nlh, CTA_PROTO_ICMP_ID, t->l4src.icmp.id); break; case IPPROTO_ICMPV6: mnl_attr_put_u8(nlh, CTA_PROTO_ICMPV6_CODE, t->l4dst.icmp.code); mnl_attr_put_u8(nlh, CTA_PROTO_ICMPV6_TYPE, t->l4dst.icmp.type); mnl_attr_put_u16(nlh, CTA_PROTO_ICMPV6_ID, t->l4src.icmp.id); break; default: mnl_attr_nest_cancel(nlh, nest); return -1; } mnl_attr_nest_end(nlh, nest); return 0; } int nfct_build_tuple_raw(struct nlmsghdr *nlh, const struct __nfct_tuple *t) { if (nfct_build_tuple_ip(nlh, t) < 0) return -1; if (nfct_build_tuple_proto(nlh, t) < 0) return -1; return 0; } int nfct_build_tuple(struct nlmsghdr *nlh, const struct __nfct_tuple *t, int type) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, type); if (nest == NULL) return -1; if (nfct_build_tuple_raw(nlh, t) < 0) goto err; mnl_attr_nest_end(nlh, nest); return 0; err: mnl_attr_nest_cancel(nlh, nest); return -1; } static int nfct_build_protoinfo(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest, *nest_proto; switch(ct->head.orig.protonum) { case IPPROTO_TCP: /* Preliminary attribute check to avoid sending an empty * CTA_PROTOINFO_TCP nest, which results in EINVAL in * Linux kernel <= 2.6.25. */ if (!(test_bit(ATTR_TCP_STATE, ct->head.set) || test_bit(ATTR_TCP_FLAGS_ORIG, ct->head.set) || test_bit(ATTR_TCP_FLAGS_REPL, ct->head.set) || test_bit(ATTR_TCP_MASK_ORIG, ct->head.set) || test_bit(ATTR_TCP_MASK_REPL, ct->head.set) || test_bit(ATTR_TCP_WSCALE_ORIG, ct->head.set) || test_bit(ATTR_TCP_WSCALE_REPL, ct->head.set))) { break; } nest = mnl_attr_nest_start(nlh, CTA_PROTOINFO); nest_proto = mnl_attr_nest_start(nlh, CTA_PROTOINFO_TCP); if (test_bit(ATTR_TCP_STATE, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_TCP_STATE, ct->protoinfo.tcp.state); } if (test_bit(ATTR_TCP_FLAGS_ORIG, ct->head.set) && test_bit(ATTR_TCP_MASK_ORIG, ct->head.set)) { mnl_attr_put(nlh, CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, sizeof(struct nf_ct_tcp_flags), &ct->protoinfo.tcp.flags[0]); } if (test_bit(ATTR_TCP_FLAGS_REPL, ct->head.set) && test_bit(ATTR_TCP_MASK_REPL, ct->head.set)) { mnl_attr_put(nlh, CTA_PROTOINFO_TCP_FLAGS_REPLY, sizeof(struct nf_ct_tcp_flags), &ct->protoinfo.tcp.flags[1]); } if (test_bit(ATTR_TCP_WSCALE_ORIG, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_TCP_WSCALE_ORIGINAL, ct->protoinfo.tcp.wscale[__DIR_ORIG]); } if (test_bit(ATTR_TCP_WSCALE_REPL, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_TCP_WSCALE_REPLY, ct->protoinfo.tcp.wscale[__DIR_REPL]); } mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); break; case IPPROTO_SCTP: /* See comment above on TCP. */ if (!(test_bit(ATTR_SCTP_STATE, ct->head.set) || test_bit(ATTR_SCTP_VTAG_ORIG, ct->head.set) || test_bit(ATTR_SCTP_VTAG_REPL, ct->head.set))) { break; } nest = mnl_attr_nest_start(nlh, CTA_PROTOINFO); nest_proto = mnl_attr_nest_start(nlh, CTA_PROTOINFO_SCTP); if (test_bit(ATTR_SCTP_STATE, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_SCTP_STATE, ct->protoinfo.sctp.state); } if (test_bit(ATTR_SCTP_VTAG_ORIG, ct->head.set)) { mnl_attr_put_u32(nlh, CTA_PROTOINFO_SCTP_VTAG_ORIGINAL, htonl(ct->protoinfo.sctp.vtag[__DIR_ORIG])); } if (test_bit(ATTR_SCTP_VTAG_REPL, ct->head.set)) { mnl_attr_put_u32(nlh, CTA_PROTOINFO_SCTP_VTAG_REPLY, htonl(ct->protoinfo.sctp.vtag[__DIR_REPL])); } mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); break; case IPPROTO_DCCP: /* See comment above on TCP. */ if (!(test_bit(ATTR_DCCP_STATE, ct->head.set) || test_bit(ATTR_DCCP_ROLE, ct->head.set) || test_bit(ATTR_DCCP_HANDSHAKE_SEQ, ct->head.set))) { break; } nest = mnl_attr_nest_start(nlh, CTA_PROTOINFO); nest_proto = mnl_attr_nest_start(nlh, CTA_PROTOINFO_DCCP); if (test_bit(ATTR_DCCP_STATE, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_DCCP_STATE, ct->protoinfo.dccp.state); } if (test_bit(ATTR_DCCP_ROLE, ct->head.set)) { mnl_attr_put_u8(nlh, CTA_PROTOINFO_DCCP_ROLE, ct->protoinfo.dccp.role); } if (test_bit(ATTR_DCCP_HANDSHAKE_SEQ, ct->head.set)) { uint64_t handshake_seq = be64toh(ct->protoinfo.dccp.handshake_seq); mnl_attr_put_u64(nlh, CTA_PROTOINFO_DCCP_HANDSHAKE_SEQ, handshake_seq); } mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); default: break; } return 0; } static int nfct_nat_seq_adj(struct nlmsghdr *nlh, const struct nf_conntrack *ct, int dir) { mnl_attr_put_u32(nlh, CTA_NAT_SEQ_CORRECTION_POS, htonl(ct->natseq[dir].correction_pos)); mnl_attr_put_u32(nlh, CTA_NAT_SEQ_OFFSET_BEFORE, htonl(ct->natseq[dir].offset_before)); mnl_attr_put_u32(nlh, CTA_NAT_SEQ_OFFSET_AFTER, htonl(ct->natseq[dir].offset_after)); return 0; } static int nfct_build_nat_seq_adj(struct nlmsghdr *nlh, const struct nf_conntrack *ct, int dir) { int type = (dir == __DIR_ORIG) ? CTA_NAT_SEQ_ADJ_ORIG : CTA_NAT_SEQ_ADJ_REPLY; struct nlattr *nest; nest = mnl_attr_nest_start(nlh, type); nfct_nat_seq_adj(nlh, ct, dir); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_protonat(struct nlmsghdr *nlh, const struct nf_conntrack *ct, const struct __nfct_nat *nat) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_PROTO); switch (ct->head.orig.protonum) { case IPPROTO_TCP: case IPPROTO_UDP: mnl_attr_put_u16(nlh, CTA_PROTONAT_PORT_MIN, nat->l4min.tcp.port); mnl_attr_put_u16(nlh, CTA_PROTONAT_PORT_MAX, nat->l4max.tcp.port); break; } mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_nat(struct nlmsghdr *nlh, const struct __nfct_nat *nat, uint8_t l3protonum) { switch (l3protonum) { case AF_INET: mnl_attr_put_u32(nlh, CTA_NAT_MINIP, nat->min_ip.v4); break; case AF_INET6: mnl_attr_put(nlh, CTA_NAT_V6_MINIP, sizeof(struct in6_addr), &nat->min_ip.v6); break; default: break; } return 0; } static int nfct_build_snat(struct nlmsghdr *nlh, const struct nf_conntrack *ct, uint8_t l3protonum) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_SRC); nfct_build_nat(nlh, &ct->snat, l3protonum); nfct_build_protonat(nlh, ct, &ct->snat); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_snat_ipv4(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_SRC); nfct_build_nat(nlh, &ct->snat, AF_INET); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_snat_ipv6(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_SRC); nfct_build_nat(nlh, &ct->snat, AF_INET6); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_snat_port(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_SRC); nfct_build_protonat(nlh, ct, &ct->snat); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_dnat(struct nlmsghdr *nlh, const struct nf_conntrack *ct, uint8_t l3protonum) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_DST); nfct_build_nat(nlh, &ct->dnat, l3protonum); nfct_build_protonat(nlh, ct, &ct->dnat); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_dnat_ipv4(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_DST); nfct_build_nat(nlh, &ct->dnat, AF_INET); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_dnat_ipv6(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_DST); nfct_build_nat(nlh, &ct->dnat, AF_INET6); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_dnat_port(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_NAT_DST); nfct_build_protonat(nlh, ct, &ct->dnat); mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_status(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { mnl_attr_put_u32(nlh, CTA_STATUS, htonl(ct->status | IPS_CONFIRMED)); return 0; } static int nfct_build_timeout(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { mnl_attr_put_u32(nlh, CTA_TIMEOUT, htonl(ct->timeout)); return 0; } static int nfct_build_mark(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { mnl_attr_put_u32(nlh, CTA_MARK, htonl(ct->mark)); return 0; } static int nfct_build_secmark(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { mnl_attr_put_u32(nlh, CTA_SECMARK, htonl(ct->secmark)); return 0; } static int nfct_build_helper_name(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_HELP); mnl_attr_put_strz(nlh, CTA_HELP_NAME, ct->helper_name); if (ct->helper_info != NULL) { mnl_attr_put(nlh, CTA_HELP_INFO, ct->helper_info_len, ct->helper_info); } mnl_attr_nest_end(nlh, nest); return 0; } static int nfct_build_zone(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { mnl_attr_put_u16(nlh, CTA_ZONE, htons(ct->zone)); return 0; } static void nfct_build_labels(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { struct nfct_bitmask *b = ct->connlabels; unsigned int size = b->words * sizeof(b->bits[0]); mnl_attr_put(nlh, CTA_LABELS, size, b->bits); if (test_bit(ATTR_CONNLABELS_MASK, ct->head.set)) { b = ct->connlabels_mask; if (size == (b->words * sizeof(b->bits[0]))) mnl_attr_put(nlh, CTA_LABELS_MASK, size, b->bits); } } int nfct_nlmsg_build(struct nlmsghdr *nlh, const struct nf_conntrack *ct) { if (!test_bit(ATTR_ORIG_L3PROTO, ct->head.set)) { errno = EINVAL; return -1; } if (test_bit(ATTR_ORIG_IPV4_SRC, ct->head.set) || test_bit(ATTR_ORIG_IPV4_DST, ct->head.set) || test_bit(ATTR_ORIG_IPV6_SRC, ct->head.set) || test_bit(ATTR_ORIG_IPV6_DST, ct->head.set) || test_bit(ATTR_ORIG_PORT_SRC, ct->head.set) || test_bit(ATTR_ORIG_PORT_DST, ct->head.set) || test_bit(ATTR_ORIG_L3PROTO, ct->head.set) || test_bit(ATTR_ORIG_L4PROTO, ct->head.set) || test_bit(ATTR_ORIG_ZONE, ct->head.set) || test_bit(ATTR_ICMP_TYPE, ct->head.set) || test_bit(ATTR_ICMP_CODE, ct->head.set) || test_bit(ATTR_ICMP_ID, ct->head.set)) { const struct __nfct_tuple *t = &ct->head.orig; struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_TUPLE_ORIG); if (nest == NULL) return -1; if (nfct_build_tuple_raw(nlh, t) < 0) { mnl_attr_nest_cancel(nlh, nest); return -1; } if (test_bit(ATTR_ORIG_ZONE, ct->head.set)) mnl_attr_put_u16(nlh, CTA_TUPLE_ZONE, htons(t->zone)); mnl_attr_nest_end(nlh, nest); } if (test_bit(ATTR_REPL_IPV4_SRC, ct->head.set) || test_bit(ATTR_REPL_IPV4_DST, ct->head.set) || test_bit(ATTR_REPL_IPV6_SRC, ct->head.set) || test_bit(ATTR_REPL_IPV6_DST, ct->head.set) || test_bit(ATTR_REPL_PORT_SRC, ct->head.set) || test_bit(ATTR_REPL_PORT_DST, ct->head.set) || test_bit(ATTR_REPL_L3PROTO, ct->head.set) || test_bit(ATTR_REPL_L4PROTO, ct->head.set) || test_bit(ATTR_REPL_ZONE, ct->head.set) || test_bit(ATTR_ICMP_TYPE, ct->head.set) || test_bit(ATTR_ICMP_CODE, ct->head.set) || test_bit(ATTR_ICMP_ID, ct->head.set)) { const struct __nfct_tuple *t = &ct->repl; struct nlattr *nest; nest = mnl_attr_nest_start(nlh, CTA_TUPLE_REPLY); if (nest == NULL) return -1; if (nfct_build_tuple_raw(nlh, t) < 0) { mnl_attr_nest_cancel(nlh, nest); return -1; } if (test_bit(ATTR_REPL_ZONE, ct->head.set)) mnl_attr_put_u16(nlh, CTA_TUPLE_ZONE, htons(t->zone)); mnl_attr_nest_end(nlh, nest); } if (test_bit(ATTR_MASTER_IPV4_SRC, ct->head.set) || test_bit(ATTR_MASTER_IPV4_DST, ct->head.set) || test_bit(ATTR_MASTER_IPV6_SRC, ct->head.set) || test_bit(ATTR_MASTER_IPV6_DST, ct->head.set) || test_bit(ATTR_MASTER_PORT_SRC, ct->head.set) || test_bit(ATTR_MASTER_PORT_DST, ct->head.set) || test_bit(ATTR_MASTER_L3PROTO, ct->head.set) || test_bit(ATTR_MASTER_L4PROTO, ct->head.set)) { nfct_build_tuple(nlh, &ct->master, CTA_TUPLE_MASTER); } if (test_bit(ATTR_STATUS, ct->head.set)) nfct_build_status(nlh, ct); if (test_bit(ATTR_TIMEOUT, ct->head.set)) nfct_build_timeout(nlh, ct); if (test_bit(ATTR_MARK, ct->head.set)) nfct_build_mark(nlh, ct); if (test_bit(ATTR_SECMARK, ct->head.set)) nfct_build_secmark(nlh, ct); nfct_build_protoinfo(nlh, ct); if (test_bit(ATTR_SNAT_IPV4, ct->head.set) && test_bit(ATTR_SNAT_PORT, ct->head.set)) { nfct_build_snat(nlh, ct, AF_INET); } else if (test_bit(ATTR_SNAT_IPV6, ct->head.set) && test_bit(ATTR_SNAT_PORT, ct->head.set)) { nfct_build_snat(nlh, ct, AF_INET6); } else if (test_bit(ATTR_SNAT_IPV4, ct->head.set)) { nfct_build_snat_ipv4(nlh, ct); } else if (test_bit(ATTR_SNAT_IPV6, ct->head.set)) { nfct_build_snat_ipv6(nlh, ct); } else if (test_bit(ATTR_SNAT_PORT, ct->head.set)) { nfct_build_snat_port(nlh, ct); } if (test_bit(ATTR_DNAT_IPV4, ct->head.set) && test_bit(ATTR_DNAT_PORT, ct->head.set)) { nfct_build_dnat(nlh, ct, AF_INET); } else if (test_bit(ATTR_DNAT_IPV6, ct->head.set) && test_bit(ATTR_DNAT_PORT, ct->head.set)) { nfct_build_dnat(nlh, ct, AF_INET6); } else if (test_bit(ATTR_DNAT_IPV4, ct->head.set)) { nfct_build_dnat_ipv4(nlh, ct); } else if (test_bit(ATTR_DNAT_IPV6, ct->head.set)) { nfct_build_dnat_ipv6(nlh, ct); } else if (test_bit(ATTR_DNAT_PORT, ct->head.set)) { nfct_build_dnat_port(nlh, ct); } if (test_bit(ATTR_ORIG_NAT_SEQ_CORRECTION_POS, ct->head.set) && test_bit(ATTR_ORIG_NAT_SEQ_OFFSET_BEFORE, ct->head.set) && test_bit(ATTR_ORIG_NAT_SEQ_OFFSET_AFTER, ct->head.set)) { nfct_build_nat_seq_adj(nlh, ct, __DIR_ORIG); } if (test_bit(ATTR_REPL_NAT_SEQ_CORRECTION_POS, ct->head.set) && test_bit(ATTR_REPL_NAT_SEQ_OFFSET_BEFORE, ct->head.set) && test_bit(ATTR_REPL_NAT_SEQ_OFFSET_AFTER, ct->head.set)) { nfct_build_nat_seq_adj(nlh, ct, __DIR_REPL); } if (test_bit(ATTR_HELPER_NAME, ct->head.set)) nfct_build_helper_name(nlh, ct); if (test_bit(ATTR_ZONE, ct->head.set)) nfct_build_zone(nlh, ct); if (test_bit(ATTR_CONNLABELS, ct->head.set)) nfct_build_labels(nlh, ct); return 0; }