diff --git a/bridge/vlan.c b/bridge/vlan.c
index 0d142bc9055d..09884870df81 100644
--- a/bridge/vlan.c
+++ b/bridge/vlan.c
@@ -33,6 +33,7 @@ static void usage(void)
 		"Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ tunnel_info id TUNNEL_ID ]\n"
 		"                                                     [ pvid ] [ untagged ]\n"
 		"                                                     [ self ] [ master ]\n"
+		"       bridge vlan { set } vid VLAN_ID dev DEV [ state STP_STATE ]\n"
 		"       bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n"
 		"       bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n");
 	exit(-1);
@@ -241,6 +242,100 @@ static int vlan_modify(int cmd, int argc, char **argv)
 	return 0;
 }
 
+static int vlan_option_set(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct br_vlan_msg	bvm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_NEWVLAN,
+		.bvm.family = PF_BRIDGE,
+	};
+	struct bridge_vlan_info vinfo = {};
+	struct rtattr *afspec;
+	short vid_end = -1;
+	char *d = NULL;
+	short vid = -1;
+	int state = -1;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "vid") == 0) {
+			char *p;
+
+			NEXT_ARG();
+			p = strchr(*argv, '-');
+			if (p) {
+				*p = '\0';
+				p++;
+				vid = atoi(*argv);
+				vid_end = atoi(p);
+				if (vid >= vid_end || vid_end >= 4096) {
+					fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
+						vid, vid_end);
+					return -1;
+				}
+			} else {
+				vid = atoi(*argv);
+			}
+		} else if (strcmp(*argv, "state") == 0) {
+			char *endptr;
+
+			NEXT_ARG();
+			state = strtol(*argv, &endptr, 10);
+			if (!(**argv != '\0' && *endptr == '\0'))
+				state = parse_stp_state(*argv);
+			if (state == -1) {
+				fprintf(stderr, "Error: invalid STP state\n");
+				return -1;
+			}
+		} else {
+			if (matches(*argv, "help") == 0)
+				NEXT_ARG();
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || vid == -1) {
+		fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+		return -1;
+	}
+
+	req.bvm.ifindex = ll_name_to_index(d);
+	if (req.bvm.ifindex == 0) {
+		fprintf(stderr, "Cannot find network device \"%s\"\n", d);
+		return -1;
+	}
+
+	if (vid >= 4096) {
+		fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+		return -1;
+	}
+	afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY);
+	afspec->rta_type |= NLA_F_NESTED;
+
+	vinfo.flags = BRIDGE_VLAN_INFO_ONLY_OPTS;
+	vinfo.vid = vid;
+	addattr_l(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_INFO, &vinfo,
+		  sizeof(vinfo));
+	if (vid_end != -1)
+		addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_RANGE,
+			  vid_end);
+	if (state >= 0)
+		addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE, state);
+	addattr_nest_end(&req.n, afspec);
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
 /* In order to use this function for both filtering and non-filtering cases
  * we need to make it a tristate:
  * return -1 - if filtering we've gone over so don't continue
@@ -667,6 +762,8 @@ int do_vlan(int argc, char **argv)
 		if (matches(*argv, "tunnelshow") == 0) {
 			return vlan_show(argc-1, argv+1, VLAN_SHOW_TUNNELINFO);
 		}
+		if (matches(*argv, "set") == 0)
+			return vlan_option_set(argc-1, argv+1);
 		if (matches(*argv, "help") == 0)
 			usage();
 	} else {
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index 9d8663bd23cc..90dcae73ce71 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -138,6 +138,15 @@ bridge \- show / manipulate bridge addresses and devices
 .BR pvid " ] [ " untagged " ] [ "
 .BR self " ] [ " master " ] "
 
+.ti -8
+.BR "bridge vlan set"
+.B dev
+.I DEV
+.B vid
+.IR VID " [ "
+.B state
+.IR STP_STATE " ] "
+
 .ti -8
 .BR "bridge vlan" " [ " show " | " tunnelshow " ] [ "
 .B dev
@@ -813,6 +822,61 @@ The
 .BR "pvid " and " untagged"
 flags are ignored.
 
+.SS bridge vlan set - change vlan filter entry's options
+
+This command changes vlan filter entry's options.
+
+.TP
+.BI dev " NAME"
+the interface with which this vlan is associated.
+
+.TP
+.BI vid " VID"
+the VLAN ID that identifies the vlan.
+
+.TP
+.BI state " STP_STATE "
+the operation state of the vlan. One may enter STP state name (case insensitive), or one of the
+numbers below. Negative inputs are ignored, and unrecognized names return an
+error. Note that the state is set only for the vlan of the specified device, e.g. if it is
+a bridge port then the state will be set only for the vlan of the port.
+
+.B 0
+- vlan is in STP
+.B DISABLED
+state. Make this vlan completely inactive for STP. This is also called
+BPDU filter and could be used to disable STP on an untrusted vlan.
+.sp
+
+.B 1
+- vlan is in STP
+.B LISTENING
+state. Only valid if STP is enabled on the bridge. In this
+state the vlan listens for STP BPDUs and drops all other traffic frames.
+.sp
+
+.B 2
+- vlan is in STP
+.B LEARNING
+state. Only valid if STP is enabled on the bridge. In this
+state the vlan will accept traffic only for the purpose of updating MAC
+address tables.
+.sp
+
+.B 3
+- vlan is in STP
+.B FORWARDING
+state. This is the default vlan state.
+.sp
+
+.B 4
+- vlan is in STP
+.B BLOCKING
+state. Only valid if STP is enabled on the bridge. This state
+is used during the STP election process. In this state, the vlan will only process
+STP BPDUs.
+.sp
+
 .SS bridge vlan show - list vlan configuration.
 
 This command displays the current VLAN filter table.
