summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--docs/sources/flowspec.md34
-rw-r--r--gobgp/cmd/global.go2
-rw-r--r--packet/bgp/bgp.go173
-rw-r--r--packet/bgp/bgp_test.go6
-rw-r--r--packet/bgp/constant.go55
-rw-r--r--test/scenario_test/flow_spec_test.py4
6 files changed, 215 insertions, 59 deletions
diff --git a/docs/sources/flowspec.md b/docs/sources/flowspec.md
index e2b02fcb..bb498a48 100644
--- a/docs/sources/flowspec.md
+++ b/docs/sources/flowspec.md
@@ -50,7 +50,7 @@ CLI syntax to add ipv4/ipv6 flowspec rule is
<PROTO> : ospf, pim, igp, udp, igmp, tcp, egp, rsvp, gre, ipip, unknown, icmp, sctp, <VALUE>
<FRAGMENT_TYPE> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment
<TCPFLAG> : U, C, E, F, S, R, P, A
- <ITEM> : &?{<|>|=}<value>
+ <ITEM> : &?{<|>|>=|<=|==|!=}<value>
<THEN_EXPR> : { accept | discard | rate-limit <value> | redirect <RT> | mark <value> | action { sample | terminal | sample-terminal } | rt <RT>... }...
<RT> : xxx:yyy, xx.xx.xx.xx:yyy, xxx.xxx:yyy
```
@@ -61,11 +61,29 @@ that for l2vpn flowspec rule is
% global rib add match <MATCH_EXPR> then <THEN_EXPR> -a [l2vpn-flowspec]
<MATCH_EXPR> : { { destination-mac | source-mac } <MAC> | ether-type <ETHER_TYPE> | { llc-dsap | llc-ssap | llc-control | snap | vid | cos | inner-vid | inner-cos } <ITEM>... }...
<ETHER_TYPE> : arp, vmtp, ipx, snmp, net-bios, xtp, pppoe-discovery, ipv4, rarp, ipv6, pppoe-session, loopback, apple-talk, aarp
- <ITEM> : &?{<|>|=}<value>
+ <ITEM> : &?{<|>|>=|<=|==|!=}<value>
<THEN_EXPR> : { accept | discard | rate-limit <value> | redirect <RT> | mark <value> | action { sample | terminal | sample-terminal } | rt <RT>... }...
<RT> : xxx:yyy, xx.xx.xx.xx:yyy, xxx.xxx:yyy
```
+### Decimal values and Operators
+Gobgp comply with the following draft: https://tools.ietf.org/html/draft-hr-idr-rfc5575bis-03
+
+All decimal values like ports, destination port, source port, procotol number can be married with the following operators:
+```
+ +----+----+----+----------------------------------+
+ | lt | gt | eq | Resulting operation |
+ +----+----+----+----------------------------------+
+ | 0 | 0 | 0 | true (independent of the value) |
+ | 0 | 0 | 1 | == (equal) |
+ | 0 | 1 | 0 | > (greater than) |
+ | 0 | 1 | 1 | >= (greater than or equal) |
+ | 1 | 0 | 0 | < (less than) |
+ | 1 | 0 | 1 | <= (less than or equal) |
+ | 1 | 1 | 0 | != (not equal value) |
+ | 1 | 1 | 1 | false (independent of the value) |
+ +----+----+----+----------------------------------+
+```
### Examples
```shell
@@ -73,7 +91,7 @@ that for l2vpn flowspec rule is
% gobgp global rib -a ipv4-flowspec add match destination 10.0.0.0/24 source 20.0.0.0/24 then redirect 10:10
# add a flowspec rule wich discard flows with dst 2001::2/128 and port equals 80 and with TCP flags not match SA (SYN/ACK) and not match U (URG)
-% gobgp global rib -a ipv6-flowspec add match destination 2001::2/128 port '=80' tcp-flags '=!SA&=!U' then discard
+% gobgp global rib -a ipv6-flowspec add match destination 2001::2/128 port '==80' tcp-flags '=!SA&=!U' then discard
# show flowspec table
% gobgp global rib -a ipv4-flowspec
@@ -81,17 +99,21 @@ that for l2vpn flowspec rule is
*> [destination:10.0.0.0/24][source:20.0.0.0/24] 0.0.0.0 00:00:04 [{Origin: i} {Extcomms: [redirect: 10:10]}]
# add another flowspec rule which discard flows whose ip protocol is tcp and destination port is 80 or greater than or equal to 8080 and lesser than or equal to 8888
-% gobgp global rib -a ipv4-flowspec add match protocol tcp destination-port '=80' '>=8080&<=8888' then discard
+% gobgp global rib -a ipv4-flowspec add match protocol tcp destination-port '==80' '>=8080&<=8888' then discard
+
+# add flowspec rule to drop traffic not going to destination port 80, 443 or 22
+
+gobgp global rib -a ipv4-flowspec add match destination 2.2.2.2/32 dest-port '!=80&!=443&!=22' then discard
% gobgp global rib -a ipv4-flowspec
Network Next Hop AS_PATH Age Attrs
*> [destination:10.0.0.0/24][source:20.0.0.0/24] 0.0.0.0 00:03:19 [{Origin: i} {Extcomms: [redirect: 10:10]}]
-*> [protocol: tcp][destination-port: =80 >=8080&<=8888] 0.0.0.0 00:00:03 [{Origin: i} {Extcomms: [discard]}]
+*> [protocol: tcp][destination-port: ==80 >=8080&<=8888] 0.0.0.0 00:00:03 [{Origin: i} {Extcomms: [discard]}]
# delete a flowspec rule
% gobgp global rib -a ipv4-flowspec del match destination 10.0.0.0/24 source 20.0.0.0/24 then redirect 10:10
% gobgp global rib -a ipv4-flowspec
Network Next Hop AS_PATH Age Attrs
-*> [protocol: tcp][destination-port: =80 >=8080&<=8888] 0.0.0.0 00:00:03 [{Origin: i} {Extcomms: [discard]}]
+*> [protocol: tcp][destination-port: ==80 >=8080&<=8888] 0.0.0.0 00:00:03 [{Origin: i} {Extcomms: [discard]}]
```
diff --git a/gobgp/cmd/global.go b/gobgp/cmd/global.go
index 72295e65..0c771080 100644
--- a/gobgp/cmd/global.go
+++ b/gobgp/cmd/global.go
@@ -868,7 +868,7 @@ usage: %s rib %s%%smatch <MATCH_EXPR> then <THEN_EXPR> -a %%s
<PROTO> : %s
<FRAGMENT_TYPE> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment
<TCPFLAG> : %s
- <ITEM> : &?{<|>|=}<value>`,
+ <ITEM> : & {<|<=|>|>=|==|!=}<value>`,
bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_PREFIX],
bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_PREFIX],
bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_IP_PROTO],
diff --git a/packet/bgp/bgp.go b/packet/bgp/bgp.go
index ece8692e..a911c411 100644
--- a/packet/bgp/bgp.go
+++ b/packet/bgp/bgp.go
@@ -2591,60 +2591,124 @@ func doFlowSpecNumericParser(rf RouteFamily, args []string, validationFunc func(
if afi, _ := RouteFamilyToAfiSafi(rf); afi == AFI_IP && FlowSpecValueMap[args[0]] == FLOW_SPEC_TYPE_LABEL {
return nil, fmt.Errorf("flow label spec is only allowed for ipv6")
}
- exp := regexp.MustCompile("^((<=|>=|[<>=])(\\d+)&)?(<=|>=|[<>=])?(\\d+)$")
+ cmdType := args[0]
+ args = append(args[:0], args[1:]...) // removing command string
+ fullCmd := strings.Join(args, " ") // rebuiling tcp filters
+ opsFlags, err := parseDecValuesCmd(fullCmd, validationFunc)
+ if err != nil {
+ return nil, err
+ }
items := make([]*FlowSpecComponentItem, 0)
-
- f := func(and bool, o, v string) (*FlowSpecComponentItem, error) {
- op := 0
- if and {
- op |= 0x40
- }
- if len(o) == 0 {
- op |= 0x1
- }
- for _, oo := range o {
- switch oo {
- case '>':
- op |= 0x2
- case '<':
- op |= 0x4
- case '=':
- op |= 0x1
- }
- }
- value, err := strconv.Atoi(v)
- if err != nil {
- return nil, err
- }
- err = validationFunc(value)
- if err != nil {
- return nil, err
- }
- return NewFlowSpecComponentItem(op, value), nil
+ for _, opFlag := range opsFlags {
+ items = append(items, NewFlowSpecComponentItem(opFlag[0], opFlag[1]))
}
+ return NewFlowSpecComponent(FlowSpecValueMap[cmdType], items), nil
+}
- for _, arg := range args[1:] {
- var and bool
- elems := exp.FindStringSubmatch(arg)
- if len(elems) == 0 {
- return nil, fmt.Errorf("invalid flowspec numeric item")
- }
- if elems[1] != "" {
- and = true
- item, err := f(false, elems[2], elems[3])
+func parseDecValuesCmd(myCmd string, validationFunc func(int) error) ([][2]int, error) {
+ var index int = 0
+ var decOperatorsAndValues [][2]int
+ var operatorValue [2]int
+ var errorNum error
+ for index < len(myCmd) {
+ myCmdChar := myCmd[index : index+1]
+ switch myCmdChar {
+ case DECNumOpNameMap[DEC_NUM_OP_GT], DECNumOpNameMap[DEC_NUM_OP_LT]:
+ // We found a < or > let's check if we face >= or <=
+ if myCmd[index+1:index+2] == "=" {
+ myCmdChar = myCmd[index : index+2]
+ index++
+ }
+ if bit := DECNumOpValueMap[myCmdChar]; bit&DECNumOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ index++
+ } else {
+ err := fmt.Errorf("Operator > < or >= <= appears multiple times")
+ return nil, err
+ }
+ case "!", "=":
+ // we found the beginning of a not let's check secong character
+ if myCmd[index+1:index+2] == "=" {
+ myCmdChar = myCmd[index : index+2]
+ if bit := DECNumOpValueMap[myCmdChar]; bit&DECNumOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ index += 2
+ } else {
+ err := fmt.Errorf("Not or Equal operator appears multiple time")
+ return nil, err
+ }
+ } else {
+ err := fmt.Errorf("Malformated not or equal operator")
+ return nil, err
+ }
+ case "t", "f": // we could be facing true or false, let's check
+ if myCmd == DECNumOpNameMap[DEC_NUM_OP_FALSE] || myCmd == DECNumOpNameMap[DEC_NUM_OP_TRUE] {
+ if bit := DECNumOpValueMap[myCmd]; bit&DECNumOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ index = index + len(myCmd)
+ } else {
+ err := fmt.Errorf("Boolean operator appears multiple times")
+ return nil, err
+ }
+ } else {
+ err := fmt.Errorf("Boolean operator %s badly formatted", myCmd)
+ return nil, err
+ }
+ case DECLogicOpNameMap[DEC_LOGIC_OP_AND], DECLogicOpNameMap[DEC_LOGIC_OP_OR]:
+ if bit := DECLogicOpValueMap[myCmdChar]; bit&DECLogicOp(operatorValue[0]) == 0 {
+ if myCmdChar == DECLogicOpNameMap[DEC_LOGIC_OP_AND] {
+ operatorValue[0] |= int(bit)
+ }
+ decOperatorsAndValues = append(decOperatorsAndValues, operatorValue)
+ operatorValue[0] = 0
+ operatorValue[1] = 0
+ index++
+ } else {
+ err := fmt.Errorf("AND or OR (space) operator appears multiple time")
+ return nil, err
+ }
+ case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
+ myLoopChar := myCmdChar
+ loopIndex := index
+ // we loop till we reach the end of decimal value
+ // exit conditions : we reach the end of decimal value (we found & or ' ') or we reach the end of the line
+ for loopIndex < len(myCmd) &&
+ (myLoopChar != DECLogicOpNameMap[DEC_LOGIC_OP_AND] && myLoopChar != DECLogicOpNameMap[DEC_LOGIC_OP_OR]) {
+ // we check if inspected charater is a number
+ if _, err := strconv.Atoi(myLoopChar); err == nil {
+ // we move to next character
+ loopIndex++
+ if loopIndex < len(myCmd) {
+ myLoopChar = myCmd[loopIndex : loopIndex+1] // we move to the next character only if we didn't reach the end of cmd
+ }
+ } else {
+ err := fmt.Errorf("Decimal value badly formatted: %s", myLoopChar)
+ return nil, err
+ }
+ }
+ decimalValueString := myCmd[index:loopIndex]
+ operatorValue[1], errorNum = strconv.Atoi(decimalValueString)
+ if errorNum != nil {
+ return nil, errorNum
+ }
+ err := validationFunc(operatorValue[1])
if err != nil {
return nil, err
}
- items = append(items, item)
- }
- item, err := f(and, elems[4], elems[5])
- if err != nil {
+ // we check if we found any operator, if not we set default as ==
+ if operatorValue[0] == 0 {
+ operatorValue[0] = DEC_NUM_OP_EQ
+ }
+ // we are done with decimal value, we give back the next cooming charater to the main loop
+ index = loopIndex
+ default:
+ err := fmt.Errorf("%s not part of flowspec decimal value or operators", myCmdChar)
return nil, err
}
- items = append(items, item)
}
-
- return NewFlowSpecComponent(FlowSpecValueMap[args[0]], items), nil
+ operatorValue[0] |= int(DECLogicOpValueMap["E"])
+ decOperatorsAndValues = append(decOperatorsAndValues, operatorValue)
+ return decOperatorsAndValues, nil
}
func flowSpecNumericParser(rf RouteFamily, args []string) (FlowSpecComponentInterface, error) {
@@ -3127,12 +3191,27 @@ func formatNumericOp(op int) string {
return opstr
}
+func formatNumericOpFrontQty(op int) string {
+ gtlteqOnly := op & 0x07
+ return fmt.Sprintf("%s", DECNumOpNameMap[DECNumOp(gtlteqOnly)])
+}
+
+func formatNumericOpBackLogic(op int) string {
+ andOrOnly := op & 0x40 // let's ignore the END bit to avoid having an E at the end of the string
+ return fmt.Sprintf("%s", DECLogicOpNameMap[DECLogicOp(andOrOnly)])
+}
+
func formatNumeric(op int, value int) string {
- return fmt.Sprintf("%s%d", formatNumericOp(op), value)
+ gtlteqOnly := op & 0x07
+ if DECNumOp(gtlteqOnly) == DECNumOpValueMap[DECNumOpNameMap[DEC_NUM_OP_FALSE]] || DECNumOp(gtlteqOnly) == DECNumOpValueMap[DECNumOpNameMap[DEC_NUM_OP_TRUE]] {
+ return fmt.Sprintf("%s%s", formatNumericOpFrontQty(op), formatNumericOpBackLogic(op))
+ } else {
+ return fmt.Sprintf("%s%d%s", formatNumericOpFrontQty(op), value, formatNumericOpBackLogic(op))
+ }
}
func formatProto(op int, value int) string {
- return fmt.Sprintf("%s%s", formatNumericOp(op), Protocol(value).String())
+ return fmt.Sprintf("%s%s%s", formatNumericOpFrontQty(op), Protocol(value).String(), formatNumericOpBackLogic(op))
}
func formatFlag(op int, value int) string {
diff --git a/packet/bgp/bgp_test.go b/packet/bgp/bgp_test.go
index cee2b3ab..e0f1a5ca 100644
--- a/packet/bgp/bgp_test.go
+++ b/packet/bgp/bgp_test.go
@@ -519,13 +519,13 @@ func Test_EVPNIPPrefixRoute(t *testing.T) {
func Test_CompareFlowSpecNLRI(t *testing.T) {
assert := assert.New(t)
- cmp, err := ParseFlowSpecComponents(RF_FS_IPv4_UC, "destination 10.0.0.2/32 source 10.0.0.1/32 destination-port =3128 protocol tcp")
+ cmp, err := ParseFlowSpecComponents(RF_FS_IPv4_UC, "destination 10.0.0.2/32 source 10.0.0.1/32 destination-port ==3128 protocol tcp")
assert.Nil(err)
n1 := &FlowSpecNLRI{Value: cmp, rf: RF_FS_IPv4_UC}
- cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.0/24 destination-port =3128 protocol tcp")
+ cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.0/24 destination-port ==3128 protocol tcp")
assert.Nil(err)
n2 := &FlowSpecNLRI{Value: cmp, rf: RF_FS_IPv4_UC}
- cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.9/32 port =80 =8080 destination-port >8080&<8080 =3128 source-port >1024 protocol udp tcp")
+ cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.9/32 port ==80 ==8080 destination-port >8080&<8080 ==3128 source-port >1024 protocol ==udp ==tcp")
n3 := &FlowSpecNLRI{Value: cmp, rf: RF_FS_IPv4_UC}
assert.Nil(err)
cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "destination 192.168.0.2/32")
diff --git a/packet/bgp/constant.go b/packet/bgp/constant.go
index 4888df7f..faa156e1 100644
--- a/packet/bgp/constant.go
+++ b/packet/bgp/constant.go
@@ -154,6 +154,61 @@ var TCPFlagOpValueMap = map[string]TCPFlagOp{
TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]: TCP_FLAG_OP_MATCH,
}
+type DECNumOp int
+
+const (
+ DEC_NUM_OP_TRUE = 0x00 // true always with END bit set
+ DEC_NUM_OP_EQ = 0x01
+ DEC_NUM_OP_GT = 0x02
+ DEC_NUM_OP_GT_EQ = 0x03
+ DEC_NUM_OP_LT = 0x04
+ DEC_NUM_OP_LT_EQ = 0x05
+ DEC_NUM_OP_NOT_EQ = 0x06
+ DEC_NUM_OP_FALSE = 0x07 // true always with END bit set
+)
+
+var DECNumOpNameMap = map[DECNumOp]string{
+ DEC_NUM_OP_TRUE: "true",
+ DEC_NUM_OP_EQ: "==",
+ DEC_NUM_OP_GT: ">",
+ DEC_NUM_OP_GT_EQ: ">=",
+ DEC_NUM_OP_LT: "<",
+ DEC_NUM_OP_LT_EQ: "<=",
+ DEC_NUM_OP_NOT_EQ: "!=",
+ DEC_NUM_OP_FALSE: "false",
+}
+
+var DECNumOpValueMap = map[string]DECNumOp{
+ DECNumOpNameMap[DEC_NUM_OP_TRUE]: DEC_NUM_OP_TRUE,
+ DECNumOpNameMap[DEC_NUM_OP_EQ]: DEC_NUM_OP_EQ,
+ DECNumOpNameMap[DEC_NUM_OP_GT]: DEC_NUM_OP_GT,
+ DECNumOpNameMap[DEC_NUM_OP_GT_EQ]: DEC_NUM_OP_GT_EQ,
+ DECNumOpNameMap[DEC_NUM_OP_LT]: DEC_NUM_OP_LT,
+ DECNumOpNameMap[DEC_NUM_OP_LT_EQ]: DEC_NUM_OP_LT_EQ,
+ DECNumOpNameMap[DEC_NUM_OP_NOT_EQ]: DEC_NUM_OP_NOT_EQ,
+ DECNumOpNameMap[DEC_NUM_OP_FALSE]: DEC_NUM_OP_FALSE,
+}
+
+type DECLogicOp int
+
+const (
+ DEC_LOGIC_OP_END = 0x80
+ DEC_LOGIC_OP_OR = 0x00
+ DEC_LOGIC_OP_AND = 0x40
+)
+
+var DECLogicOpNameMap = map[DECLogicOp]string{
+ DEC_LOGIC_OP_OR: " ",
+ DEC_LOGIC_OP_AND: "&",
+ DEC_LOGIC_OP_END: "E",
+}
+
+var DECLogicOpValueMap = map[string]DECLogicOp{
+ DECLogicOpNameMap[DEC_LOGIC_OP_OR]: DEC_LOGIC_OP_OR,
+ DECLogicOpNameMap[DEC_LOGIC_OP_AND]: DEC_LOGIC_OP_AND,
+ DECLogicOpNameMap[DEC_LOGIC_OP_END]: DEC_LOGIC_OP_END,
+}
+
func (f TCPFlag) String() string {
ss := make([]string, 0, 6)
for _, v := range []TCPFlag{TCP_FLAG_FIN, TCP_FLAG_SYN, TCP_FLAG_RST, TCP_FLAG_PUSH, TCP_FLAG_ACK, TCP_FLAG_URGENT, TCP_FLAG_CWR, TCP_FLAG_ECE} {
diff --git a/test/scenario_test/flow_spec_test.py b/test/scenario_test/flow_spec_test.py
index c8fa9068..656498d8 100644
--- a/test/scenario_test/flow_spec_test.py
+++ b/test/scenario_test/flow_spec_test.py
@@ -44,13 +44,13 @@ class GoBGPTestBase(unittest.TestCase):
matchs = ['destination 10.0.0.0/24', 'source 20.0.0.0/24']
thens = ['discard']
e1.add_route(route='flow1', rf='ipv4-flowspec', matchs=matchs, thens=thens)
- matchs2 = ['tcp-flags S', 'protocol tcp udp', "packet-length '>1000&<2000'"]
+ matchs2 = ['tcp-flags S', 'protocol ==tcp ==udp', "packet-length '>1000&<2000'"]
thens2 = ['rate-limit 9600', 'redirect 0.10:100', 'mark 20', 'action sample']
g1.add_route(route='flow1', rf='ipv4-flowspec', matchs=matchs2, thens=thens2)
matchs3 = ['destination 2001::/24/10', 'source 2002::/24/15']
thens3 = ['discard']
e1.add_route(route='flow2', rf='ipv6-flowspec', matchs=matchs3, thens=thens3)
- matchs4 = ['destination 2001::/24 10', "label '=100'"]
+ matchs4 = ['destination 2001::/24 10', "label '==100'"]
thens4 = ['discard']
g1.add_route(route='flow2', rf='ipv6-flowspec', matchs=matchs4, thens=thens4)