summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthieu Texier <matthieu@texier.tv>2017-02-17 21:20:59 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2017-02-17 21:20:59 +0900
commit6de16dc885ebfeffb9ff3470805980f6fac579c5 (patch)
tree887b2deea8f346802688b296746d9d7c8cb316c9
parenta1aa21844ee456cead80e8ef88fc373bc95b7b04 (diff)
flowspec: comply with RFC 5575 about TCP flags rules
This patch proposes a new way to configure BGP flowspec TCP flags rules It allows to comply with RFC 5575 by defining flags like this =SA =A / '!SA' / '=SA&=!U' = means match, ! means not, & means and, all TCP flags are identified by their first charater S for SYN A for Ack ...
-rw-r--r--docs/sources/flowspec.md7
-rw-r--r--gobgp/cmd/global.go2
-rw-r--r--packet/bgp/bgp.go119
-rw-r--r--packet/bgp/constant.go46
-rw-r--r--test/scenario_test/flow_spec_test.py2
5 files changed, 133 insertions, 43 deletions
diff --git a/docs/sources/flowspec.md b/docs/sources/flowspec.md
index 8fddcccc..e2b02fcb 100644
--- a/docs/sources/flowspec.md
+++ b/docs/sources/flowspec.md
@@ -45,11 +45,11 @@ CLI syntax to add ipv4/ipv6 flowspec rule is
```shell
% global rib add match <MATCH_EXPR> then <THEN_EXPR> -a [ipv4-flowspec|ipv6-flowspec]
<MATCH_EXPR> : { destination <PREFIX> [<OFFSET>] | source <PREFIX> [<OFFSET>] |
- protocol <PROTO>... | fragment <FRAGMENT_TYPE> | tcp-flags [not] [match] <TCPFLAG>... |
+ protocol <PROTO>... | fragment <FRAGMENT_TYPE> | tcp-flags [!] [=] <TCPFLAG>... |
{ port | destination-port | source-port | icmp-type | icmp-code | packet-length | dscp | label } <ITEM>... }...
<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> : rst, push, ack, urgent, fin, syn
+ <TCPFLAG> : U, C, E, F, S, R, P, A
<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
@@ -72,6 +72,9 @@ that for l2vpn flowspec rule is
# add a flowspec rule which redirect flows with dst 10.0.0.0/24 and src 20.0.0.0/24 to VRF with RT 10:10
% 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
+
# show flowspec table
% gobgp global rib -a ipv4-flowspec
Network Next Hop AS_PATH Age Attrs
diff --git a/gobgp/cmd/global.go b/gobgp/cmd/global.go
index 097847e8..72295e65 100644
--- a/gobgp/cmd/global.go
+++ b/gobgp/cmd/global.go
@@ -863,7 +863,7 @@ usage: %s rib %s%%smatch <MATCH_EXPR> then <THEN_EXPR> -a %%s
ExtCommNameMap[RATE], ExtCommNameMap[REDIRECT],
ExtCommNameMap[MARK], ExtCommNameMap[ACTION], ExtCommNameMap[RT])
ipFsMatchExpr := fmt.Sprintf(` <MATCH_EXPR> : { %s <PREFIX> [<OFFSET>] | %s <PREFIX> [<OFFSET>] |
- %s <PROTO>... | %s <FRAGMENT_TYPE> | %s [not] [match] <TCPFLAG>... |
+ %s <PROTO>... | %s <FRAGMENT_TYPE> | %s [!] [=] <TCPFLAG>... |
{ %s | %s | %s | %s | %s | %s | %s | %s } <ITEM>... }...
<PROTO> : %s
<FRAGMENT_TYPE> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment
diff --git a/packet/bgp/bgp.go b/packet/bgp/bgp.go
index b66e197f..ece8692e 100644
--- a/packet/bgp/bgp.go
+++ b/packet/bgp/bgp.go
@@ -2489,32 +2489,84 @@ func flowSpecIpProtoParser(rf RouteFamily, args []string) (FlowSpecComponentInte
}
func flowSpecTcpFlagParser(rf RouteFamily, args []string) (FlowSpecComponentInterface, error) {
- ss := make([]string, 0, len(TCPFlagNameMap))
- for _, v := range TCPFlagNameMap {
- ss = append(ss, v)
- }
- protos := strings.Join(ss, "|")
- exp := regexp.MustCompile(fmt.Sprintf("^%s (not )?(match )?((((%s)\\&)*(%s) )*(((%s)\\&)*(%s)))$", FlowSpecNameMap[FLOW_SPEC_TYPE_TCP_FLAG], protos, protos, protos, protos))
- elems := exp.FindStringSubmatch(strings.Join(args, " "))
- if len(elems) < 1 {
- return nil, fmt.Errorf("invalid flag format")
+ args = append(args[:0], args[1:]...) // removing tcp-flags string
+ fullCmd := strings.Join(args, " ") // rebuiling tcp filters
+ opsFlags, err := parseTcpFlagCmd(fullCmd)
+ if err != nil {
+ return nil, err
}
items := make([]*FlowSpecComponentItem, 0)
- op := 0
- if elems[2] != "" {
- op |= 0x1
- }
- if elems[1] != "" {
- op |= 0x2
+ for _, opFlag := range opsFlags {
+ items = append(items, NewFlowSpecComponentItem(opFlag[0], opFlag[1]))
}
- for _, v := range strings.Split(elems[3], " ") {
- flag := 0
- for _, e := range strings.Split(v, "&") {
- flag |= int(TCPFlagValueMap[e])
+ return NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, items), nil
+}
+
+func parseTcpFlagCmd(myCmd string) ([][2]int, error) {
+ var index int = 0
+ var tcpOperatorsFlagsValues [][2]int
+ var operatorValue [2]int
+ for index < len(myCmd) {
+ myCmdChar := myCmd[index : index+1]
+ switch myCmdChar {
+ case TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]:
+ if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ index++
+ } else {
+ err := fmt.Errorf("Match flag appears multiple time")
+ return nil, err
+ }
+ case TCPFlagOpNameMap[TCP_FLAG_OP_NOT]:
+ if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ index++
+ } else {
+ err := fmt.Errorf("Not flag appears multiple time")
+ return nil, err
+ }
+ case TCPFlagOpNameMap[TCP_FLAG_OP_AND], TCPFlagOpNameMap[TCP_FLAG_OP_OR]:
+ if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 {
+ operatorValue[0] |= int(bit)
+ tcpOperatorsFlagsValues = append(tcpOperatorsFlagsValues, operatorValue)
+ operatorValue[0] = 0
+ operatorValue[1] = 0
+ index++
+ } else {
+ err := fmt.Errorf("AND or OR (space) operator appears multiple time")
+ return nil, err
+ }
+ case TCPFlagNameMap[TCP_FLAG_ACK], TCPFlagNameMap[TCP_FLAG_SYN], TCPFlagNameMap[TCP_FLAG_FIN],
+ TCPFlagNameMap[TCP_FLAG_URGENT], TCPFlagNameMap[TCP_FLAG_ECE], TCPFlagNameMap[TCP_FLAG_RST],
+ TCPFlagNameMap[TCP_FLAG_CWR], TCPFlagNameMap[TCP_FLAG_PUSH]:
+ myLoopChar := myCmdChar
+ loopIndex := index
+ // we loop till we reach the end of TCP flags description
+ // exit conditions : we reach the end of tcp flags (we find & or ' ') or we reach the end of the line
+ for loopIndex < len(myCmd) &&
+ (myLoopChar != TCPFlagOpNameMap[TCP_FLAG_OP_AND] && myLoopChar != TCPFlagOpNameMap[TCP_FLAG_OP_OR]) {
+ // we check if inspected charater is a well known tcp flag and if it doesn't appear twice
+ if bit, isPresent := TCPFlagValueMap[myLoopChar]; isPresent && (bit&TCPFlag(operatorValue[1]) == 0) {
+ operatorValue[1] |= int(bit) // we set this flag
+ loopIndex++ // we move to next character
+ 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("flag %s appears multiple time or is not part of TCP flags", myLoopChar)
+ return nil, err
+ }
+ }
+ // we are done with flags, we give back the next cooming charater to the main loop
+ index = loopIndex
+ default:
+ err := fmt.Errorf("flag %s not part of tcp flags", myCmdChar)
+ return nil, err
}
- items = append(items, NewFlowSpecComponentItem(op, flag))
}
- return NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, items), nil
+ operatorValue[0] |= int(TCPFlagOpValueMap["E"])
+ tcpOperatorsFlagsValues = append(tcpOperatorsFlagsValues, operatorValue)
+ return tcpOperatorsFlagsValues, nil
}
func flowSpecEtherTypeParser(rf RouteFamily, args []string) (FlowSpecComponentInterface, error) {
@@ -3084,21 +3136,24 @@ func formatProto(op int, value int) string {
}
func formatFlag(op int, value int) string {
- and := " "
- ss := make([]string, 0, 2)
- if op&0x40 > 0 {
- and = "&"
+ var retString string
+ if op&TCP_FLAG_OP_MATCH > 0 {
+ retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_MATCH])
}
- if op&0x1 > 0 {
- ss = append(ss, "match")
+ if op&TCP_FLAG_OP_NOT > 0 {
+ retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_NOT])
}
- if op&0x2 > 0 {
- ss = append(ss, "not")
+ for flag, valueFlag := range TCPFlagValueMap {
+ if value&int(valueFlag) > 0 {
+ retString = fmt.Sprintf("%s%s", retString, flag)
+ }
}
- if len(ss) > 0 {
- return fmt.Sprintf("%s(%s)%s", and, strings.Join(ss, "|"), TCPFlag(value).String())
+ if op&TCP_FLAG_OP_AND > 0 {
+ retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_AND])
+ } else { // default is or
+ retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_OR])
}
- return fmt.Sprintf("%s%s", and, TCPFlag(value).String())
+ return retString
}
func formatFragment(op int, value int) string {
diff --git a/packet/bgp/constant.go b/packet/bgp/constant.go
index aa9b17b9..4888df7f 100644
--- a/packet/bgp/constant.go
+++ b/packet/bgp/constant.go
@@ -102,15 +102,19 @@ const (
TCP_FLAG_PUSH = 0x08
TCP_FLAG_ACK = 0x10
TCP_FLAG_URGENT = 0x20
+ TCP_FLAG_ECE = 0x40
+ TCP_FLAG_CWR = 0x80
)
var TCPFlagNameMap = map[TCPFlag]string{
- TCP_FLAG_FIN: "fin",
- TCP_FLAG_SYN: "syn",
- TCP_FLAG_RST: "rst",
- TCP_FLAG_PUSH: "push",
- TCP_FLAG_ACK: "ack",
- TCP_FLAG_URGENT: "urgent",
+ TCP_FLAG_FIN: "F",
+ TCP_FLAG_SYN: "S",
+ TCP_FLAG_RST: "R",
+ TCP_FLAG_PUSH: "P",
+ TCP_FLAG_ACK: "A",
+ TCP_FLAG_URGENT: "U",
+ TCP_FLAG_CWR: "C",
+ TCP_FLAG_ECE: "E",
}
var TCPFlagValueMap = map[string]TCPFlag{
@@ -120,11 +124,39 @@ var TCPFlagValueMap = map[string]TCPFlag{
TCPFlagNameMap[TCP_FLAG_PUSH]: TCP_FLAG_PUSH,
TCPFlagNameMap[TCP_FLAG_ACK]: TCP_FLAG_ACK,
TCPFlagNameMap[TCP_FLAG_URGENT]: TCP_FLAG_URGENT,
+ TCPFlagNameMap[TCP_FLAG_CWR]: TCP_FLAG_CWR,
+ TCPFlagNameMap[TCP_FLAG_ECE]: TCP_FLAG_ECE,
+}
+
+type TCPFlagOp int
+
+const (
+ TCP_FLAG_OP_OR = 0x00
+ TCP_FLAG_OP_AND = 0x40
+ TCP_FLAG_OP_END = 0x80
+ TCP_FLAG_OP_NOT = 0x02
+ TCP_FLAG_OP_MATCH = 0x01
+)
+
+var TCPFlagOpNameMap = map[TCPFlagOp]string{
+ TCP_FLAG_OP_OR: " ",
+ TCP_FLAG_OP_AND: "&",
+ TCP_FLAG_OP_END: "E",
+ TCP_FLAG_OP_NOT: "!",
+ TCP_FLAG_OP_MATCH: "=",
+}
+
+var TCPFlagOpValueMap = map[string]TCPFlagOp{
+ TCPFlagOpNameMap[TCP_FLAG_OP_OR]: TCP_FLAG_OP_OR,
+ TCPFlagOpNameMap[TCP_FLAG_OP_AND]: TCP_FLAG_OP_AND,
+ TCPFlagOpNameMap[TCP_FLAG_OP_END]: TCP_FLAG_OP_END,
+ TCPFlagOpNameMap[TCP_FLAG_OP_NOT]: TCP_FLAG_OP_NOT,
+ TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]: TCP_FLAG_OP_MATCH,
}
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} {
+ 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} {
if f&v > 0 {
ss = append(ss, TCPFlagNameMap[v])
}
diff --git a/test/scenario_test/flow_spec_test.py b/test/scenario_test/flow_spec_test.py
index f3c6bff2..c8fa9068 100644
--- a/test/scenario_test/flow_spec_test.py
+++ b/test/scenario_test/flow_spec_test.py
@@ -44,7 +44,7 @@ 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 syn', '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']