diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index 316bf385eacdb5b282e53a8d9fc84a2ede86f7de..74b52871aeabf4259352f8748b614c039a66362e 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -254,6 +254,8 @@ af_name				return AF_NAME;
 af_arg				return AF_ARG;
 function_set_name		return FUNCTION_SET_NAME;
 pcbcnt				return PCBCNT;
+enable				return ENABLE;
+psk				return PSK;
 assoc_id			return ASSOC_ID;
 assoc_value			return ASSOC_VALUE;
 shmac_number_of_idents		return SHMAC_NUMBER_OF_IDENTS;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index f55e9378c82893c271077fe043c8ef871556be1e..78fc50187cf0eefa1e541ee075710556ad21e30f 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -573,6 +573,7 @@ static struct tcp_option *new_tcp_exp_fast_open_option(const char *cookie_string
 %token <reserved> OPTION
 %token <reserved> AF_NAME AF_ARG
 %token <reserved> FUNCTION_SET_NAME PCBCNT
+%token <reserved> ENABLE PSK
 %token <reserved> SRTO_ASSOC_ID SRTO_INITIAL SRTO_MAX SRTO_MIN
 %token <reserved> SINIT_NUM_OSTREAMS SINIT_MAX_INSTREAMS SINIT_MAX_ATTEMPTS
 %token <reserved> SINIT_MAX_INIT_TIMEO
@@ -691,6 +692,7 @@ static struct tcp_option *new_tcp_exp_fast_open_option(const char *cookie_string
 %type <expression> linger l_onoff l_linger
 %type <expression> accept_filter_arg af_name af_arg
 %type <expression> tcp_function_set function_set_name pcbcnt
+%type <expression> tcp_fastopen enable psk
 %type <udp_encaps_info> opt_udp_encaps_info
 %type <sctp_header_spec> sctp_header_spec
 %type <expression> sctp_assoc_id
@@ -3017,6 +3019,9 @@ expression
 | tcp_function_set  {
 	$$ = $1;
 }
+| tcp_fastopen      {
+	$$ = $1;
+}
 | sf_hdtr           {
 	$$ = $1;
 }
@@ -3436,6 +3441,48 @@ tcp_function_set
 }
 ;
 
+enable
+: ENABLE '=' INTEGER {
+	if (!is_valid_u32($3)) {
+		semantic_error("linger out of range");
+	}
+	$$ = new_integer_expression($3, "%lu");
+}
+| ENABLE '=' HEX_INTEGER {
+	if (!is_valid_u32($3)) {
+		semantic_error("linger out of range");
+	}
+	$$ = new_integer_expression($3, "%lu");
+}
+| ENABLE '=' ELLIPSIS {
+	$$ = new_expression(EXPR_ELLIPSIS);
+}
+;
+
+psk
+: PSK '=' WORD {
+	$$ = new_expression(EXPR_HEX_WORD);
+	$$->value.string = $3;
+}
+| PSK '=' ELLIPSIS {
+	$$ = new_expression(EXPR_ELLIPSIS);
+}
+;
+
+
+tcp_fastopen
+: '{' enable ',' psk '}' {
+#if defined(__FreeBSD__)
+	$$ = new_expression(EXPR_TCP_FASTOPEN);
+	$$->value.tcp_fastopen = calloc(1, sizeof(struct tcp_fastopen_expr));
+	$$->value.tcp_fastopen->enable = $2;
+	$$->value.tcp_fastopen->psk = $4;
+#else
+	$$ = NULL;
+#endif
+}
+;
+
 sf_hdtr
 : '{' SF_HDTR_HEADERS '(' decimal_integer ')' '=' array ','
       SF_HDTR_TRAILERS '('decimal_integer ')' '=' array '}' {
@@ -3458,7 +3505,7 @@ srto_initial
 	if (!is_valid_u32($3)){
 		semantic_error("srto_initial out of range");
 	}
-        $$ = new_integer_expression($3, "%u");
+	$$ = new_integer_expression($3, "%u");
 }
 | SRTO_INITIAL '=' ELLIPSIS { $$ = new_expression(EXPR_ELLIPSIS); }
 ;
diff --git a/gtests/net/packetdrill/run_system_call.c b/gtests/net/packetdrill/run_system_call.c
index 773409c01d8e4a4303d6c20c8167eda1925bff5a..198f853cde41cfa89c53528da314c5a722bdf6b9 100644
--- a/gtests/net/packetdrill/run_system_call.c
+++ b/gtests/net/packetdrill/run_system_call.c
@@ -3362,6 +3362,39 @@ static int check_tcp_function_set(struct tcp_function_set_expr *expr,
 }
 #endif
 
+#ifdef __FreeBSD__
+static int check_tcp_fastopen(struct tcp_fastopen_expr *expr,
+			      struct tcp_fastopen *tcp_fastopen,
+			      char **error) {
+	unsigned int i;
+
+	if (check_s32_expr(expr->enable, tcp_fastopen->enable,
+	                   "tcp_fastopen.enable", error))
+		return STATUS_ERR;
+	if (expr->psk->type != EXPR_ELLIPSIS) {
+		if (strlen(expr->psk->value.string) != 2 * TCP_FASTOPEN_PSK_LEN) {
+			asprintf(error, "tcp_fastopen.psk: expected length: %zd, actual length: %d\n",
+			         strlen(expr->psk->value.string),
+			         TCP_FASTOPEN_PSK_LEN);
+			return STATUS_ERR;
+		}
+		for (i = 0; i < TCP_FASTOPEN_PSK_LEN; i++) {
+			char buf[3];
+
+			buf[0] = expr->psk->value.string[2 * i];
+			buf[1] = expr->psk->value.string[2 * i + 1];
+			buf[2] = '\0';
+			if (tcp_fastopen->psk[i] != (uint8_t)strtoul(buf, NULL, 16)) {
+				asprintf(error, "tcp_fastopen.psk[%u]: expected: 0x%s, actual: 0x%02x\n",
+					 i, buf, tcp_fastopen->psk[i]);
+				return STATUS_ERR;
+			}
+		}
+	}
+	return STATUS_OK;
+}
+#endif
+
 static int syscall_getsockopt(struct state *state, struct syscall_spec *syscall,
 			      struct expression_list *args, char **error)
 {
@@ -3705,6 +3738,16 @@ static int syscall_getsockopt(struct state *state, struct syscall_spec *syscall,
 		live_optlen = (socklen_t)sizeof(struct tcp_function_set);
 		break;
 	}
+#endif
+#ifdef __FreeBSD__
+	case EXPR_TCP_FASTOPEN: {
+		struct tcp_fastopen *live_tcp_fastopen = malloc(sizeof(struct tcp_fastopen));
+
+		memset(live_tcp_fastopen, 0, sizeof(struct tcp_fastopen));
+		live_optval = live_tcp_fastopen;
+		live_optlen = (socklen_t)sizeof(struct tcp_fastopen);
+		break;
+	}
 #endif
 	case EXPR_LIST:
 		s32_bracketed_arg(args, 3, &script_optval, error);
@@ -3867,6 +3910,11 @@ static int syscall_getsockopt(struct state *state, struct syscall_spec *syscall,
 	case EXPR_TCP_FUNCTION_SET:
 		result = check_tcp_function_set(val_expression->value.tcp_function_set, live_optval, error);
 		break;
+#endif
+#ifdef __FreeBSD__
+	case EXPR_TCP_FASTOPEN:
+		result = check_tcp_fastopen(val_expression->value.tcp_fastopen, live_optval, error);
+		break;
 #endif
 	case EXPR_LIST:
 		if (*(int*)live_optval != script_optval) {
@@ -3975,6 +4023,9 @@ static int syscall_setsockopt(struct state *state, struct syscall_spec *syscall,
 #ifdef TCP_FUNCTION_BLK
 	struct tcp_function_set tcp_function_set;
 #endif
+#ifdef __FreeBSD__
+	struct tcp_fastopen tcp_fastopen;
+#endif
 
 	if (check_arg_count(args, 5, error))
 		return STATUS_ERR;
@@ -4673,6 +4724,39 @@ static int syscall_setsockopt(struct state *state, struct syscall_spec *syscall,
 			optlen = (socklen_t)sizeof(struct tcp_function_set);
 		}
 		break;
+#endif
+#ifdef __FreeBSD__
+	case EXPR_TCP_FASTOPEN: {
+		unsigned int i, len;
+		const char *hexstring = val_expression->value.tcp_fastopen->psk->value.string;
+
+		memset(&tcp_fastopen, 0, sizeof(struct tcp_fastopen));
+		if (get_s32(val_expression->value.tcp_fastopen->enable,
+			    &tcp_fastopen.enable, error)) {
+			return STATUS_ERR;
+		}
+		if (check_type(val_expression->value.tcp_fastopen->psk, EXPR_HEX_WORD, error)) {
+			return STATUS_ERR;
+		}
+		len = (unsigned int)strlen(hexstring) / 2;
+		if (len > TCP_FASTOPEN_PSK_LEN) {
+			asprintf(error, "psk too long: %s", hexstring);
+			return STATUS_ERR;
+		}
+		for (i = 0; i < len; i ++) {
+			char buf[3];
+
+			buf[0] = hexstring[2 * i];
+			buf[1] = hexstring[2 * i + 1];
+			buf[2] = '\0';
+			tcp_fastopen.psk[i] = (uint8_t)strtoul(buf, NULL, 16);
+		}
+		optval = &tcp_fastopen;
+		if (!optlen_provided) {
+			optlen = (socklen_t)sizeof(struct tcp_fastopen);
+		}
+		break;
+	}
 #endif
 	default:
 		asprintf(error, "unsupported value type: %s",
diff --git a/gtests/net/packetdrill/script.c b/gtests/net/packetdrill/script.c
index 3fc360fb99911c886d2a3a77b3ce1d8fa615d2ee..26e28a72c13fff0b2fee44efa20abf501fa21823 100644
--- a/gtests/net/packetdrill/script.c
+++ b/gtests/net/packetdrill/script.c
@@ -24,6 +24,7 @@
 
 #include "script.h"
 
+#include <ctype.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <stdlib.h>
@@ -108,6 +109,7 @@ struct expression_type_entry expression_type_table[] = {
 	{ EXPR_ELLIPSIS,                    "ellipsis"                        },
 	{ EXPR_INTEGER,                     "integer"                         },
 	{ EXPR_WORD,                        "word"                            },
+	{ EXPR_HEX_WORD,                    "hex word"                        },
 	{ EXPR_STRING,                      "string"                          },
 	{ EXPR_SOCKET_ADDRESS_IPV4,         "sockaddr_in"                     },
 	{ EXPR_SOCKET_ADDRESS_IPV6,         "sockaddr_in6"                    },
@@ -124,6 +126,7 @@ struct expression_type_entry expression_type_table[] = {
 #if defined(__FreeBSD__)
 	{ EXPR_SF_HDTR,                     "sf_hdtr"                         },
 	{ EXPR_TCP_FUNCTION_SET,            "tcp_function_set"                },
+	{ EXPR_TCP_FASTOPEN,                "tcp_fastopen"                    },
 #endif
 	{ EXPR_SCTP_RTOINFO,                "sctp_rtoinfo"                    },
 	{ EXPR_SCTP_INITMSG,                "sctp_initmsg"                    },
@@ -370,6 +373,34 @@ static int unescape_cstring_expression(const char *input_string,
 	return STATUS_OK;
 }
 
+static int hex_word_expression(const char *input_string,
+			       struct expression *out, char **error)
+{
+	size_t bytes = strlen(input_string) + 1;
+	out->type = EXPR_HEX_WORD;
+	out->value.string = (char *)malloc(bytes);
+	const char *c_in = input_string;
+	char *c_out = out->value.string;
+
+	if ((bytes - 1)% 2) {
+		asprintf(error, "odd number of hexadecimal digits: %zu", bytes);
+		return STATUS_ERR;
+	}
+	while (*c_in != '\0') {
+		if (isxdigit(*c_in)) {
+			*c_out = toupper(*c_in);
+		} else {
+			asprintf(error, "unsupported hexadecimal digit: '%c'",
+				 *c_in);
+			return STATUS_ERR;
+		}
+		++c_in;
+		++c_out;
+	}
+	*c_out = *c_in;
+	return STATUS_OK;
+}
+
 void free_expression(struct expression *expression)
 {
 	if (expression == NULL)
@@ -403,6 +434,12 @@ void free_expression(struct expression *expression)
 		free_expression(expression->value.tcp_function_set->pcbcnt);
 		free(expression->value.tcp_function_set);
 		break;
+	case EXPR_TCP_FASTOPEN:
+		assert(expression->value.tcp_fastopen);
+		free_expression(expression->value.tcp_fastopen->enable);
+		free_expression(expression->value.tcp_fastopen->psk);
+		free(expression->value.tcp_fastopen);
+		break;
 #endif
 	case EXPR_SCTP_RTOINFO:
 		assert(expression->value.sctp_rtoinfo);
@@ -818,6 +855,10 @@ void free_expression(struct expression *expression)
 		assert(expression->value.string);
 		free(expression->value.string);
 		break;
+	case EXPR_HEX_WORD:
+		assert(expression->value.string);
+		free(expression->value.string);
+		break;
 	case EXPR_STRING:
 		assert(expression->value.string);
 		free(expression->value.string);
@@ -1139,6 +1180,34 @@ static int evaluate_tcp_function_set_expression(struct expression *in,
 	return STATUS_OK;
 }
 
+static int evaluate_tcp_fastopen_expression(struct expression *in,
+						struct expression *out,
+						char **error)
+{
+	struct tcp_fastopen_expr *in_tcp_fastopen;
+	struct tcp_fastopen_expr *out_tcp_fastopen;
+
+	assert(in->type == EXPR_TCP_FASTOPEN);
+	assert(in->value.tcp_fastopen);
+	assert(out->type == EXPR_TCP_FASTOPEN);
+
+	out->value.tcp_fastopen = calloc(1, sizeof(struct tcp_fastopen_expr));
+
+	in_tcp_fastopen = in->value.tcp_fastopen;
+	out_tcp_fastopen = out->value.tcp_fastopen;
+
+	if (evaluate(in_tcp_fastopen->enable,
+		     &out_tcp_fastopen->enable,
+		     error))
+		return STATUS_ERR;
+	if (evaluate(in_tcp_fastopen->psk,
+		     &out_tcp_fastopen->psk,
+		     error))
+		return STATUS_ERR;
+
+	return STATUS_OK;
+}
+
 static int evaluate_sf_hdtr_expression(struct expression *in,
 				       struct expression *out, char **error)
 {
@@ -3016,6 +3085,9 @@ static int evaluate(struct expression *in,
 	case EXPR_TCP_FUNCTION_SET:
 		result = evaluate_tcp_function_set_expression(in, out, error);
 		break;
+	case EXPR_TCP_FASTOPEN:
+		result = evaluate_tcp_fastopen_expression(in, out, error);
+		break;
 #endif
 	case EXPR_SCTP_RTOINFO:
 		result = evaluate_sctp_rtoinfo_expression(in, out, error);
@@ -3164,6 +3236,10 @@ static int evaluate(struct expression *in,
 				  &out->value.num, error))
 			return STATUS_ERR;
 		break;
+	case EXPR_HEX_WORD:
+		if (hex_word_expression(in->value.string, out, error))
+			return STATUS_ERR;
+		break;
 	case EXPR_STRING:
 		if (unescape_cstring_expression(in->value.string, out, error))
 			return STATUS_ERR;
diff --git a/gtests/net/packetdrill/script.h b/gtests/net/packetdrill/script.h
index 5e21a2c54c24ea83455fadd22b5b0f7b44993905..50edcdc2538b441228f52083b2c995226d6fdd91 100644
--- a/gtests/net/packetdrill/script.h
+++ b/gtests/net/packetdrill/script.h
@@ -38,6 +38,7 @@ enum expression_t {
 	EXPR_INTEGER,		  /* integer in 'num' */
 	EXPR_LINGER,		  /* struct linger for SO_LINGER */
 	EXPR_WORD,		  /* unquoted word in 'string' */
+	EXPR_HEX_WORD,		  /* unquoted hex word in 'string' */
 	EXPR_STRING,		  /* double-quoted string in 'string' */
 	EXPR_SOCKET_ADDRESS_IPV4, /* sockaddr_in in 'socket_address_ipv4' */
 	EXPR_SOCKET_ADDRESS_IPV6, /* sockaddr_in6 in 'socket_address_ipv6' */
@@ -53,6 +54,7 @@ enum expression_t {
 #if defined(__FreeBSD__)
 	EXPR_SF_HDTR,		  /* struct sf_hdtr for sendfile */
 	EXPR_TCP_FUNCTION_SET,	  /* struct tcp_function_set */
+	EXPR_TCP_FASTOPEN,	  /* struct tcp_fastopen */
 #endif
 	EXPR_SCTP_RTOINFO,	  /* struct sctp_rtoinfo for SCTP_RTOINFO */
 	EXPR_SCTP_INITMSG,	  /* struct sctp_initmsg for SCTP_INITMSG */
@@ -127,6 +129,7 @@ struct expression {
 #if defined(__FreeBSD__)
 		struct sf_hdtr_expr *sf_hdtr;
 		struct tcp_function_set_expr *tcp_function_set;
+		struct tcp_fastopen_expr *tcp_fastopen;
 #endif
 		struct sctp_rtoinfo_expr *sctp_rtoinfo;
 		struct sctp_initmsg_expr *sctp_initmsg;
@@ -252,6 +255,11 @@ struct tcp_function_set_expr {
 	struct expression *function_set_name;
 	struct expression *pcbcnt;
 };
+
+struct tcp_fastopen_expr {
+	struct expression *enable;
+	struct expression *psk;
+};
 #endif
 
 /* Parse tree for a sctp_rtoinfo struct in a [gs]etsockopt syscall. */