diff --git a/docs/config.md b/docs/config.md index 1f74b4ae..51aace18 100644 --- a/docs/config.md +++ b/docs/config.md @@ -187,8 +187,10 @@ Attribute | Description | Default `dhcp` | Optionally enable/disable DHCP per access configuration `dhcpv6` | Optionally enable/disable DHCPv6 per access configuration `stream-group-id` | Optional stream group identifier - -**WARNING**: DHCP (IPv4) is currently not supported! +`cfm-cc` | Optionally enable/disable EOAM CFM CC (IPoE only) | false +`cfm-level` | Set EOAM CFM maintenance domain level | 0 +`cfm-ma-id` | Set EOAM CFM maintenance association identifier | 0 +`cfm-ma-name` | Set EOAM CFM maintenance association short name For all modes it is possible to configure between zero and three VLAN tags on the access interface as shown below. diff --git a/docs/ctrl.md b/docs/ctrl.md index 34c10e82..8448bb77 100644 --- a/docs/ctrl.md +++ b/docs/ctrl.md @@ -127,15 +127,19 @@ Attribute | Description `interfaces` | List all interfaces with index `session-counters` | Return session counters `terminate` | Terminate all sessions similar to sending SIGINT (ctr+c) -`session-traffic` | Display session traffic statistics | | +`session-traffic` | Display session traffic statistics `session-traffic-enabled` | Enable session traffic for all sessions `session-traffic-disabled` | Disable session traffic for all sessions -`stream-traffic-enabled` | Enable stream traffic for all sessions | | -`stream-traffic-disabled` | Disable stream traffic for all sessions | | +`stream-traffic-enabled` | Enable stream traffic for all sessions +`stream-traffic-disabled` | Disable stream traffic for all sessions `multicast-traffic-start` | Start sending multicast traffic from network interface `multicast-traffic-stop` | Stop sending multicast traffic from network interface `li-flows` | List all LI flows with detailed statistics `sessions-pending` | List all sessions not established +`cfm-cc-start` | Start EOAM CFM CC +`cfm-cc-stop` | Stop EOAM CFM CC +`cfm-cc-rdi-on` | Set EOAM CFM CC RDI +`cfm-cc-rdi-off` | Unset EOAM CFM CC RDI ### Session Commands @@ -174,6 +178,10 @@ Attribute | Description | Mandatory Arguments | Optional Arguments `igmp-join` | Join group | `group` | `source1`, `source2`, `source3` `igmp-leave` | Leave group | `group` | `igmp-info` | IGMP information | | +`cfm-cc-start` | Start EOAM CFM CC +`cfm-cc-stop` | Stop EOAM CFM CC +`cfm-cc-rdi-on` | Set EOAM CFM CC RDI +`cfm-cc-rdi-off` | Unset EOAM CFM CC RDI The `session-id` is the same as used for `{session-global}` in the configuration section. This number starts with 1 and is increased diff --git a/src/bbl.h b/src/bbl.h index d8a2d605..ecba89b7 100644 --- a/src/bbl.h +++ b/src/bbl.h @@ -81,6 +81,7 @@ #define BBL_SEND_ICMPV6_REPLY 0x00080000 #define BBL_SEND_ICMPV6_NS 0x00100000 #define BBL_SEND_ICMPV6_NA 0x00200000 +#define BBL_SEND_CFM_CC 0x00400000 /* Network Interface */ #define BBL_IF_SEND_ARP_REQUEST 0x00000001 @@ -252,6 +253,8 @@ typedef struct bbl_interface_ /* Packet Stats */ uint32_t arp_tx; uint32_t arp_rx; + uint32_t cfm_cc_tx; + uint32_t cfm_cc_rx; uint32_t padi_tx; uint32_t pado_rx; uint32_t padr_tx; @@ -342,55 +345,61 @@ typedef struct bbl_interface_ typedef struct bbl_access_config_ { - bool exhausted; - uint32_t sessions; /* per access config session counter */ - struct bbl_interface_ *access_if; + bool exhausted; + uint32_t sessions; /* per access config session counter */ + struct bbl_interface_ *access_if; - char *interface; + char *interface; - bbl_access_type_t access_type; /* pppoe or ipoe */ - bbl_vlan_mode_t vlan_mode; /* 1:1 (default) or N:1 */ + bbl_access_type_t access_type; /* pppoe or ipoe */ + bbl_vlan_mode_t vlan_mode; /* 1:1 (default) or N:1 */ - uint16_t stream_group_id; + uint16_t stream_group_id; - uint16_t access_outer_vlan; - uint16_t access_outer_vlan_min; - uint16_t access_outer_vlan_max; - uint16_t access_inner_vlan; - uint16_t access_inner_vlan_min; - uint16_t access_inner_vlan_max; - uint16_t access_third_vlan; + uint16_t access_outer_vlan; + uint16_t access_outer_vlan_min; + uint16_t access_outer_vlan_max; + uint16_t access_inner_vlan; + uint16_t access_inner_vlan_min; + uint16_t access_inner_vlan_max; + uint16_t access_third_vlan; - /* Static */ - uint32_t static_ip; - uint32_t static_ip_iter; - uint32_t static_gateway; - uint32_t static_gateway_iter; + /* Static */ + uint32_t static_ip; + uint32_t static_ip_iter; + uint32_t static_gateway; + uint32_t static_gateway_iter; - /* Authentication */ - char *username; - char *password; - uint16_t authentication_protocol; + /* Authentication */ + char *username; + char *password; + uint16_t authentication_protocol; - /* Access Line */ - char *agent_remote_id; - char *agent_circuit_id; - uint32_t rate_up; - uint32_t rate_down; - uint32_t dsl_type; + /* Access Line */ + char *agent_remote_id; + char *agent_circuit_id; + uint32_t rate_up; + uint32_t rate_down; + uint32_t dsl_type; - /* Protocols */ - bool ipcp_enable; - bool ip6cp_enable; - bool ipv4_enable; - bool ipv6_enable; - bool dhcp_enable; - bool dhcpv6_enable; - bool igmp_autostart; - uint8_t igmp_version; - bool session_traffic_autostart; + /* Protocols */ + bool ipcp_enable; + bool ip6cp_enable; + bool ipv4_enable; + bool ipv6_enable; + bool dhcp_enable; + bool dhcpv6_enable; + bool igmp_autostart; + uint8_t igmp_version; + bool session_traffic_autostart; - void *next; /* pointer to next access config element */ + /* CFM CC */ + bool cfm_cc; + uint8_t cfm_level; + uint16_t cfm_ma_id; + char *cfm_ma_name; + + void *next; /* pointer to next access config element */ } bbl_access_config_s; /* @@ -745,6 +754,7 @@ typedef struct bbl_session_ struct timer_ *timer_session_traffic_ipv6; struct timer_ *timer_session_traffic_ipv6pd; struct timer_ *timer_rate; + struct timer_ *timer_cfm_cc; bbl_access_type_t access_type; @@ -782,6 +792,14 @@ typedef struct bbl_session_ uint8_t server_mac[ETH_ADDR_LEN]; uint8_t client_mac[ETH_ADDR_LEN]; + /* CFM */ + bool cfm_cc; + bool cfm_rdi; + uint32_t cfm_seq; + uint8_t cfm_level; + uint16_t cfm_ma_id; + char *cfm_ma_name; + /* PPPoE */ uint16_t pppoe_session_id; uint8_t *pppoe_ac_cookie; diff --git a/src/bbl_config.c b/src/bbl_config.c index 0feb44d8..2893ed2c 100644 --- a/src/bbl_config.c +++ b/src/bbl_config.c @@ -321,6 +321,30 @@ json_parse_access_interface (bbl_ctx_s *ctx, json_t *access_interface, bbl_acces if (value) { access_config->stream_group_id = json_number_value(value); } + + value = json_object_get(access_interface, "cfm-cc"); + if (json_is_boolean(value)) { + access_config->cfm_cc = json_boolean_value(value); + } + value = json_object_get(access_interface, "cfm-level"); + if (value) { + access_config->cfm_level = json_number_value(value); + if(access_config->cfm_level > 7) { + fprintf(stderr, "JSON config error: Invalid value for access->cfm-level\n"); + return false; + } + } + value = json_object_get(access_interface, "cfm-ma-id"); + if (value) { + access_config->cfm_ma_id = json_number_value(value); + } + if (json_unpack(access_interface, "{s:s}", "cfm-ma-name", &s) == 0) { + access_config->cfm_ma_name = strdup(s); + } else if (access_config->cfm_cc) { + fprintf(stderr, "JSON config error: Missing access->cfm-ma-name\n"); + return false; + } + return true; } diff --git a/src/bbl_ctrl.c b/src/bbl_ctrl.c index 78ae6513..38627a45 100644 --- a/src/bbl_ctrl.c +++ b/src/bbl_ctrl.c @@ -1208,6 +1208,75 @@ bbl_ctrl_sessions_pending(int fd, bbl_ctx_s *ctx, uint32_t session_id __attribut return result; } +ssize_t +bbl_ctrl_cfm_cc_start_stop(int fd, bbl_ctx_s *ctx, uint32_t session_id, bool status) { + bbl_session_s *session; + uint32_t i; + if(session_id) { + session = bbl_session_get(ctx, session_id); + if(session) { + session->cfm_cc = status; + return bbl_ctrl_status(fd, "ok", 200, NULL); + } else { + return bbl_ctrl_status(fd, "warning", 404, "session not found"); + } + } else { + /* Iterate over all sessions */ + for(i = 0; i < ctx->sessions; i++) { + session = ctx->session_list[i]; + if(session) { + session->cfm_cc = status; + } + } + return bbl_ctrl_status(fd, "ok", 200, NULL); + } +} + +ssize_t +bbl_ctrl_cfm_cc_start(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arguments __attribute__((unused))) { + return bbl_ctrl_cfm_cc_start_stop(fd, ctx, session_id, true); +} + +ssize_t +bbl_ctrl_cfm_cc_stop(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arguments __attribute__((unused))) { + return bbl_ctrl_cfm_cc_start_stop(fd, ctx, session_id, false); +} + +ssize_t +bbl_ctrl_cfm_cc_rdi(int fd, bbl_ctx_s *ctx, uint32_t session_id, bool status) { + bbl_session_s *session; + uint32_t i; + if(session_id) { + session = bbl_session_get(ctx, session_id); + if(session) { + session->cfm_rdi = status; + return bbl_ctrl_status(fd, "ok", 200, NULL); + } else { + return bbl_ctrl_status(fd, "warning", 404, "session not found"); + } + } else { + /* Iterate over all sessions */ + for(i = 0; i < ctx->sessions; i++) { + session = ctx->session_list[i]; + if(session) { + session->cfm_rdi = status; + } + } + return bbl_ctrl_status(fd, "ok", 200, NULL); + } +} + +ssize_t +bbl_ctrl_cfm_cc_rdi_on(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arguments __attribute__((unused))) { + return bbl_ctrl_cfm_cc_rdi(fd, ctx, session_id, true); +} + +ssize_t +bbl_ctrl_cfm_cc_rdi_off(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arguments __attribute__((unused))) { + return bbl_ctrl_cfm_cc_rdi(fd, ctx, session_id, false); +} + + struct action { char *name; callback_function *fn; @@ -1242,6 +1311,10 @@ struct action actions[] = { {"stream-traffic-disabled", bbl_ctrl_stream_traffic_stop}, {"stream-traffic-stop", bbl_ctrl_stream_traffic_stop}, {"sessions-pending", bbl_ctrl_sessions_pending}, + {"cfm-cc-start", bbl_ctrl_cfm_cc_start}, + {"cfm-cc-stop", bbl_ctrl_cfm_cc_stop}, + {"cfm-cc-rdi-on", bbl_ctrl_cfm_cc_rdi_on}, + {"cfm-cc-rdi-off", bbl_ctrl_cfm_cc_rdi_off}, {NULL, NULL}, }; diff --git a/src/bbl_protocols.c b/src/bbl_protocols.c index c6fccbc9..f64c07b3 100644 --- a/src/bbl_protocols.c +++ b/src/bbl_protocols.c @@ -1423,6 +1423,101 @@ encode_pppoe_session(uint8_t *buf, uint16_t *len, return result; } +/* + * encode_cfm + */ +protocol_error_t +encode_cfm(uint8_t *buf, uint16_t *len, bbl_cfm_t *cfm) { + + uint8_t max_ma_str_len = 45; + + if(cfm->type != CFM_TYPE_CCM) { + /* Currently only CFM CC is supported */ + return ENCODE_ERROR; + } + + *buf = cfm->md_level << 5; /* CFM MD level and version */ + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + *buf = cfm->type; /* CFM OpCode */ + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + /* Set CFM CC interval fixed to 1s (4) */ + *buf = 4; + if(cfm->rdi) { + /* Set RDI bit */ + *buf |= 128; + } + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + /* Remember first TLV offset position */ + *buf = 70; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + memset(buf, 0x0, 70); + + /* Sequence Number */ + *(uint32_t*)buf = htobe32(cfm->seq); + BUMP_WRITE_BUFFER(buf, len, sizeof(uint32_t)); + + /* MA Identifier */ + *(uint16_t*)buf = htobe16(cfm->ma_id); + BUMP_WRITE_BUFFER(buf, len, sizeof(uint16_t)); + + /* MD Name */ + *buf = cfm->md_name_format; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + if(cfm->md_name_format != CMF_MD_NAME_FORMAT_NONE) { + if(cfm->md_name_len > 32) cfm->md_name_len = 32; + *buf = cfm->md_name_len; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + memcpy(buf, cfm->md_name, cfm->md_name_len); + BUMP_WRITE_BUFFER(buf, len, cfm->md_name_len); + max_ma_str_len -= cfm->md_name_len + 1; + } + /* MA Name */ + *buf = cfm->ma_name_format; + if(cfm->ma_name_len > max_ma_str_len) cfm->ma_name_len = max_ma_str_len; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + *buf = cfm->ma_name_len; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + memcpy(buf, cfm->ma_name, cfm->ma_name_len); + BUMP_WRITE_BUFFER(buf, len, max_ma_str_len); + + /* Y.1731 */ + BUMP_WRITE_BUFFER(buf, len, 16); + + /* CFM TLVs */ + + /* Sender ID TLV */ + *buf = 1; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + *(uint16_t*)buf = htobe16(1); + BUMP_WRITE_BUFFER(buf, len, sizeof(uint16_t)); + *buf = 0; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + /* Port Status TLV */ + *buf = 2; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + *(uint16_t*)buf = htobe16(1); + BUMP_WRITE_BUFFER(buf, len, sizeof(uint16_t)); + *buf = 2; /* UP */ + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + /* Interface Status TLV */ + *buf = 4; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + *(uint16_t*)buf = htobe16(1); + BUMP_WRITE_BUFFER(buf, len, sizeof(uint16_t)); + *buf = 1; /* UP */ + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + /* End TLV */ + *buf = 0; + BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); + + return PROTOCOL_SUCCESS; +} + /* * encode_ethernet */ @@ -1478,6 +1573,8 @@ encode_ethernet(uint8_t *buf, uint16_t *len, return encode_ipv4(buf, len, (bbl_ipv4_t*)eth->next); case ETH_TYPE_IPV6: return encode_ipv6(buf, len, (bbl_ipv6_t*)eth->next); + case ETH_TYPE_CFM: + return encode_cfm(buf, len, (bbl_cfm_t*)eth->next); default: return UNKNOWN_PROTOCOL; } diff --git a/src/bbl_protocols.h b/src/bbl_protocols.h index afac325b..d9fec6b9 100644 --- a/src/bbl_protocols.h +++ b/src/bbl_protocols.h @@ -42,6 +42,7 @@ #define ETH_TYPE_ARP 0x0806 #define ETH_TYPE_IPV4 0x0800 #define ETH_TYPE_IPV6 0x86dd +#define ETH_TYPE_CFM 0x8902 #define ETH_ADDR_LEN 6 #define ETH_VLAN_ID_MAX 4095 @@ -204,6 +205,11 @@ #define QMX_LI_UDP_PORT 49152 +#define CFM_TYPE_CCM 1 +#define CMF_MD_NAME_FORMAT_NONE 1 +#define CMF_MD_NAME_FORMAT_STRING 4 +#define CMF_MA_NAME_FORMAT_STRING 2 + #define MAX_VLANS 3 #define BUMP_BUFFER(_buf, _len, _size) \ @@ -798,6 +804,20 @@ typedef struct bbl_qmx_li_ { uint16_t payload_len; // LI payload length } bbl_qmx_li_t; +typedef struct bbl_cfm_ { + uint8_t type; + uint32_t seq; + bool rdi; + uint8_t md_level; + uint8_t md_name_format; + uint8_t md_name_len; + uint8_t *md_name; + uint16_t ma_id; + uint8_t ma_name_format; + uint8_t ma_name_len; + uint8_t *ma_name; +} bbl_cfm_t; + /* * decode_ethernet */ diff --git a/src/bbl_rx.c b/src/bbl_rx.c index 643e622d..20f73ce1 100644 --- a/src/bbl_rx.c +++ b/src/bbl_rx.c @@ -389,6 +389,15 @@ bbl_rx_udp_ipv6(bbl_ethernet_header_t *eth, bbl_ipv6_t *ipv6, bbl_interface_s *i } } +void +bbl_cfm_cc(timer_s *timer) { + bbl_session_s *session = timer->data; + if(session->session_state == BBL_ESTABLISHED && session->cfm_cc) { + session->send_requests |= BBL_SEND_CFM_CC; + bbl_session_network_tx_qnode_insert(session); + } +} + void bbl_rx_established_ipoe(bbl_ethernet_header_t *eth, bbl_interface_s *interface, bbl_session_s *session) { @@ -428,6 +437,10 @@ bbl_rx_established_ipoe(bbl_ethernet_header_t *eth, bbl_interface_s *interface, bbl_session_traffic_start_ipv6(ctx, session); bbl_session_traffic_start_ipv6pd(ctx, session); } + if(session->cfm_cc) { + /* Start CFM CC (currently fixed set to 1s) */ + timer_add_periodic(&ctx->timer_root, &session->timer_cfm_cc, "CFM-CC", 1, 0, session, &bbl_cfm_cc); + } } } } diff --git a/src/bbl_session.c b/src/bbl_session.c index 9348578c..7d01ccf4 100644 --- a/src/bbl_session.c +++ b/src/bbl_session.c @@ -473,6 +473,19 @@ bbl_sessions_init(bbl_ctx_s *ctx) session->agent_remote_id = strdup(s); } + /* Update CFM */ + if(access_config->cfm_cc) { + session->cfm_cc = true; + session->cfm_level = access_config->cfm_level; + session->cfm_ma_id = access_config->cfm_ma_id; + if(access_config->cfm_ma_name) { + s = replace_substring(access_config->cfm_ma_name, "{session-global}", snum1); + session->cfm_ma_name = s; + s = replace_substring(session->cfm_ma_name, "{session}", snum2); + session->cfm_ma_name = strdup(s); + } + } + /* Update access rates ... */ session->rate_up = access_config->rate_up; session->rate_down = access_config->rate_down; diff --git a/src/bbl_tx.c b/src/bbl_tx.c index 5884b13e..870608e7 100644 --- a/src/bbl_tx.c +++ b/src/bbl_tx.c @@ -1488,6 +1488,31 @@ bbl_encode_packet_arp_reply (bbl_session_s *session) return encode_ethernet(session->write_buf, &session->write_idx, ð); } +protocol_error_t +bbl_encode_packet_cfm_cc (bbl_session_s *session) +{ + bbl_ethernet_header_t eth = {0}; + bbl_cfm_t cfm = {0}; + eth.dst = session->server_mac; + eth.src = session->client_mac; + eth.vlan_outer = session->vlan_key.outer_vlan_id; + eth.vlan_inner = session->vlan_key.inner_vlan_id; + eth.vlan_three = session->access_third_vlan; + eth.type = ETH_TYPE_CFM; + eth.next = &cfm; + cfm.type = CFM_TYPE_CCM; + cfm.seq = session->cfm_seq++; + cfm.rdi = session->cfm_rdi; + cfm.md_level = session->cfm_level; + cfm.md_name_format = CMF_MD_NAME_FORMAT_NONE; + cfm.ma_id = session->cfm_ma_id; + cfm.ma_name_format = CMF_MA_NAME_FORMAT_STRING; + cfm.ma_name_len = strlen(session->cfm_ma_name); + cfm.ma_name = (uint8_t*)session->cfm_ma_name; + session->interface->stats.cfm_cc_tx++; + return encode_ethernet(session->write_buf, &session->write_idx, ð); +} + protocol_error_t bbl_encode_packet (bbl_session_s *session, uint8_t *buf, uint16_t *len, bool *accounting) { @@ -1561,6 +1586,9 @@ bbl_encode_packet (bbl_session_s *session, uint8_t *buf, uint16_t *len, bool *ac } else if (session->send_requests & BBL_SEND_DHCP_REQUEST) { result = bbl_encode_packet_dhcp(session); session->send_requests &= ~BBL_SEND_DHCP_REQUEST; + } else if (session->send_requests & BBL_SEND_CFM_CC) { + result = bbl_encode_packet_cfm_cc(session); + session->send_requests &= ~BBL_SEND_CFM_CC; } else { session->send_requests = 0; }