You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2021/05/10 16:03:43 UTC

[trafficcontrol] branch master updated: Add Per-DS HTTP/2 and TLS Versions Support (#5802)

This is an automated email from the ASF dual-hosted git repository.

neuman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 667b555  Add Per-DS HTTP/2 and TLS Versions Support (#5802)
667b555 is described below

commit 667b5552e5a184c08c5ef6ffb4cd2b5276db8f13
Author: Robert O Butts <ro...@users.noreply.github.com>
AuthorDate: Mon May 10 10:03:23 2021 -0600

    Add Per-DS HTTP/2 and TLS Versions Support (#5802)
    
    * Add t3c ssl_server_name.yaml
    
    * Move docs DS Params to DS page
    
    As requested in PR Review.
    
    * Changed docs hyphens to match text length
    
    As requested in PR Review.
    
    * Change changelog to changed docs file
    
    As requested in PR Review.
    
    * Add docs profile overview link to ds params
    
    As requested in PR Review.
---
 CHANGELOG.md                                     |   1 +
 docs/source/overview/delivery_services.rst       |  19 ++
 docs/source/overview/profiles_and_parameters.rst |   3 +-
 lib/go-atscfg/meta.go                            |   2 +
 lib/go-atscfg/meta_test.go                       |   5 +
 lib/go-atscfg/remapdotconfig.go                  | 150 +++++----
 lib/go-atscfg/remapdotconfig_test.go             |  10 +-
 lib/go-atscfg/snidotyaml.go                      | 129 +++++++
 lib/go-atscfg/sslservernamedotyaml.go            | 383 +++++++++++++++++++++
 lib/go-atscfg/sslservernamedotyaml_test.go       | 411 +++++++++++++++++++++++
 traffic_ops_ort/atstccfg/cfgfile/routing.go      |   2 +
 traffic_ops_ort/atstccfg/cfgfile/wrappers.go     |  42 +++
 traffic_ops_ort/atstccfg/config/config.go        | 102 +++---
 traffic_ops_ort/t3c/README.md                    |  76 +++--
 traffic_ops_ort/t3c/config/config.go             |  53 +--
 traffic_ops_ort/t3c/torequest/torequest.go       |   7 +-
 16 files changed, 1225 insertions(+), 170 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9915938..16e01c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Added integration to use ACME to generate new SSL certificates.
 - Add a Federation to the Ansible Dataset Loader
 - Added asynchronous status to ACME certificate generation.
+- Added per Delivery Service HTTP/2 and TLS Versions support, via ssl_server_name.yaml and sni.yaml. See overview/delivery_services and t3c docs.
 - Added headers to Traffic Portal, Traffic Ops, and Traffic Monitor to opt out of tracking users via Google FLoC.
 - Add logging scope for logging.yaml generation for ATS 9 support
 - `DELETE` request method for `deliveryservices/xmlId/{name}/urlkeys` and `deliveryservices/{id}/urlkeys`.
diff --git a/docs/source/overview/delivery_services.rst b/docs/source/overview/delivery_services.rst
index c2317ab..34d4c05 100644
--- a/docs/source/overview/delivery_services.rst
+++ b/docs/source/overview/delivery_services.rst
@@ -1023,3 +1023,22 @@ A text-based unique identifier for a Delivery Service. Many :ref:`to-api` endpoi
 .. [#cardinality] In source code and :ref:`to-api` responses, the "Long Description" fields of a Delivery Service are "0-indexed" - hence the names differing slightly from the ones displayed in user-friendly UIs.
 .. [#dupOrigin] These Delivery Services Types are vulnerable to what this writer likes to call the "Duplicate Origin Problem". This problem is tracked by :issue:`3537`.
 .. [#httpOnlyRegex] These regular expression types can only appear in the Match List of HTTP-:ref:`Routed <ds-types>` Delivery Services.
+
+
+.. _ds-parameters:
+
+Delivery Service Parameters
+----------
+Features which are new, experimental, or not significant enough to be first-class Delivery Service fields are often added as Parameters. To use these, add a Profile to the Delivery Service, with the given Parameter assigned.
+
+parent.config
+'''''''''''''
+The following Parameters must have the Config File ``parent.config`` to take effect.
+
+- ``try_all_primaries_before_secondary`` - on a Delivery Service Profile, if this exists, try all primary parents before failing over to secondary parents, which may be ideal if objects are unlikely to be in cache. The default behavior is to immediately fail to a secondary, which is ideal if objects are likely to be in cache, as the first consistent-hashed secondary parent will be the primary parent in its own cachegroup and therefore receive requests for that object from clients near it [...]
+- ``enable_h2`` - on a Delivery Service Profile, if this is ``true``, enable HTTP/2 for client requests. Note ATS must also be listening for HTTP/2 in records.config, or this will have no effect. Note this setting is not in the parent.config config file, but either ssl_server_name.yaml in ATS 8 or sni.yaml in ATS 9. The Parameter has the "parent.config" config file for consistency.
+- ``tls_versions`` - on a Delivery Service Profile, if this exists, enable the given comma-delimited TLS versions for client requests. For example, ``1.1,1.2,1.3``. Note ATS must also be accepting those TLS versions in records.config, or this will have no effect. Note this setting is not in the parent.config config file, but either ssl_server_name.yaml in ATS 8 or sni.yaml in ATS 9. The Parameter has the "parent.config" config file for consistency.
+
+Additionally, :term:`Delivery Service` :ref:`Profiles <ds-profile>` can have special Parameters with the :ref:`parameter-name` "mso.parent_retry" to :ref:`multi-site-origin-qht`.
+
+.. seealso:: To see how the :ref:`Values <parameter-value>` of these Parameters are interpreted, refer to the `Apache Traffic Server documentation on the parent.config configuration file <https://docs.trafficserver.apache.org/en/7.1.x/admin-guide/files/parent.config.en.html>`_
diff --git a/docs/source/overview/profiles_and_parameters.rst b/docs/source/overview/profiles_and_parameters.rst
index 8ed2b5b..160ba1a 100644
--- a/docs/source/overview/profiles_and_parameters.rst
+++ b/docs/source/overview/profiles_and_parameters.rst
@@ -21,6 +21,8 @@ Profiles and Parameters
 ***********************
 :dfn:`Profiles` are a collection of configuration options, defined partially by the Profile's Type_ (not to be confused with the more general ":term:`Type`" used by many other things in Traffic Control) and partially by the :dfn:`Parameters` set on them. Mainly, Profiles and Parameters are used to configure :term:`cache servers`, but they can also be used to configure parts of (nearly) any Traffic Control component, and can even be linked with more abstract concepts like :ref:`Delivery S [...]
 
+.. seealso:: For Delivery Service Profile Parameters, see :ref:`ds-parameters`.
+
 .. _profiles:
 
 Profiles
@@ -490,7 +492,6 @@ This configuration file is generated entirely from :term:`Cache Group` relations
 - ``qstring``
 - ``psel.qstring_handling``
 - ``not_a_parent`` - unlike the other Parameters listed (which have a 1:1 correspondence with Apache Traffic Server configuration options), this Parameter affects the generation of :term:`parent` relationships between :term:`cache servers`. When a Parameter with this :ref:`parameter-name` and Config File exists on a :ref:`Profile <profiles>` used by a :term:`cache server`, it will not be added as a :term:`parent` of any other :term:`cache server`, regardless of :term:`Cache Group` hierar [...]
-- ``try_all_primaries_before_secondary`` - on a Delivery Service Profile, if this exists, try all primary parents before failing over to secondary parents, which may be ideal if objects are unlikely to be in cache. The default behavior is to immediately fail to a secondary, which is ideal if objects are likely to be in cache, as the first consistent-hashed secondary parent will be the primary parent in its own cachegroup and therefore receive requests for that object from clients near it [...]
 
 Additionally, :term:`Delivery Service` :ref:`Profiles <ds-profile>` can have special Parameters with the :ref:`parameter-name` "mso.parent_retry" to :ref:`multi-site-origin-qht`.
 
diff --git a/lib/go-atscfg/meta.go b/lib/go-atscfg/meta.go
index c7b7719..ec605df 100644
--- a/lib/go-atscfg/meta.go
+++ b/lib/go-atscfg/meta.go
@@ -437,6 +437,7 @@ func requiredFiles8() []string {
 		"plugin.config",
 		"records.config",
 		"remap.config",
+		"ssl_server_name.yaml",
 		"storage.config",
 		"volume.config",
 	}
@@ -454,6 +455,7 @@ func requiredFiles9() []string {
 		"plugin.config",
 		"records.config",
 		"remap.config",
+		"sni.yaml",
 		"storage.config",
 		"volume.config",
 	}
diff --git a/lib/go-atscfg/meta_test.go b/lib/go-atscfg/meta_test.go
index 70d791f..5d57249 100644
--- a/lib/go-atscfg/meta_test.go
+++ b/lib/go-atscfg/meta_test.go
@@ -167,6 +167,11 @@ func TestMakeMetaConfig(t *testing.T) {
 				t.Errorf("expected location '%v', actual '%v'", expected, cf.Path)
 			}
 		},
+		"ssl_server_name.yaml": func(cf CfgMeta) {
+			if expected := cfgPath; cf.Path != expected {
+				t.Errorf("expected location '%v', actual '%v'", expected, cf.Path)
+			}
+		},
 	}
 
 	for _, cfgFile := range cfg {
diff --git a/lib/go-atscfg/remapdotconfig.go b/lib/go-atscfg/remapdotconfig.go
index f2e448b..4ac9c9f 100644
--- a/lib/go-atscfg/remapdotconfig.go
+++ b/lib/go-atscfg/remapdotconfig.go
@@ -233,33 +233,39 @@ func getServerConfigRemapDotConfigForEdge(
 	textLines := []string{}
 
 	for _, ds := range dses {
-		for _, dsRegex := range dsRegexes[tc.DeliveryServiceName(*ds.XMLID)] {
-			if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) {
-				continue
-			}
+		if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) {
+			continue
+		}
 
-			topology, hasTopology := nameTopologies[TopologyName(*ds.Topology)]
-			if *ds.Topology != "" && hasTopology {
-				topoIncludesServer, err := topologyIncludesServerNullable(topology, server)
-				if err != nil {
-					return "", warnings, errors.New("getting topology server inclusion: " + err.Error())
-				}
-				if !topoIncludesServer {
-					continue
-				}
+		topology, hasTopology := nameTopologies[TopologyName(*ds.Topology)]
+		if *ds.Topology != "" && hasTopology {
+			topoIncludesServer, err := topologyIncludesServerNullable(topology, server)
+			if err != nil {
+				return "", warnings, errors.New("getting topology server inclusion: " + err.Error())
 			}
-			remapText := ""
-			if *ds.Type == tc.DSTypeAnyMap {
-				if ds.RemapText == nil {
-					warnings = append(warnings, "ds '"+*ds.XMLID+"' is ANY_MAP, but has no remap text - skipping")
-					continue
-				}
-				remapText = *ds.RemapText + "\n"
-				textLines = append(textLines, remapText)
+			if !topoIncludesServer {
 				continue
 			}
+		}
+		remapText := ""
+		if *ds.Type == tc.DSTypeAnyMap {
+			if ds.RemapText == nil {
+				warnings = append(warnings, "ds '"+*ds.XMLID+"' is ANY_MAP, but has no remap text - skipping")
+				continue
+			}
+			remapText = *ds.RemapText + "\n"
+			textLines = append(textLines, remapText)
+			continue
+		}
+
+		requestFQDNs, err := getDSRequestFQDNs(&ds, dsRegexes[tc.DeliveryServiceName(*ds.XMLID)], server, cdnDomain)
+		if err != nil {
+			warnings = append(warnings, "error getting ds '"+*ds.XMLID+"' request fqdns, skipping! Error: "+err.Error())
+			continue
+		}
 
-			remapLines, err := makeEdgeDSDataRemapLines(ds, dsRegex, server, cdnDomain)
+		for _, requestFQDN := range requestFQDNs {
+			remapLines, err := makeEdgeDSDataRemapLines(ds, requestFQDN, server, cdnDomain)
 			if err != nil {
 				warnings = append(warnings, "DS '"+*ds.XMLID+"' - skipping! : "+err.Error())
 				continue
@@ -271,7 +277,7 @@ func getServerConfigRemapDotConfigForEdge(
 					profilecacheKeyConfigParams = profilesCacheKeyConfigParams[*ds.ProfileID]
 				}
 				remapWarns := []string{}
-				remapText, remapWarns, err = buildEdgeRemapLine(cacheURLConfigParams, atsMajorVersion, server, serverPackageParamData, remapText, ds, dsRegex, line.From, line.To, profilecacheKeyConfigParams, cacheGroups, nameTopologies)
+				remapText, remapWarns, err = buildEdgeRemapLine(cacheURLConfigParams, atsMajorVersion, server, serverPackageParamData, remapText, ds, line.From, line.To, profilecacheKeyConfigParams, cacheGroups, nameTopologies)
 				warnings = append(warnings, remapWarns...)
 				if err != nil {
 					return "", warnings, err
@@ -281,8 +287,8 @@ func getServerConfigRemapDotConfigForEdge(
 				}
 				remapText += "\n"
 			}
-			textLines = append(textLines, remapText)
 		}
+		textLines = append(textLines, remapText)
 	}
 
 	text := header
@@ -301,7 +307,6 @@ func buildEdgeRemapLine(
 	pData map[string]string,
 	text string,
 	ds DeliveryService,
-	dsRegex tc.DeliveryServiceRegex,
 	mapFrom string,
 	mapTo string,
 	cacheKeyConfigParams map[string]string,
@@ -441,56 +446,31 @@ type remapLine struct {
 // Returns nil, if the given server and ds have no remap lines, i.e. the DS match is not a host regex, or has no origin FQDN.
 func makeEdgeDSDataRemapLines(
 	ds DeliveryService,
-	dsRegex tc.DeliveryServiceRegex,
+	requestFQDN string,
+	//	dsRegex tc.DeliveryServiceRegex,
 	server *Server,
 	cdnDomain string,
 ) ([]remapLine, error) {
-	if tc.DSMatchType(dsRegex.Type) != tc.DSMatchTypeHostRegex || ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
-		return nil, nil
-	}
-	if dsRegex.Pattern == "" {
-		return nil, errors.New("ds missing regex pattern")
-	}
 	if ds.Protocol == nil {
-		return nil, errors.New("ds missing protocol")
-	}
-	if cdnDomain == "" {
-		return nil, errors.New("ds missing domain")
+		return nil, errors.New("ds had nil protocol")
 	}
 
 	remapLines := []remapLine{}
-	hostRegex := dsRegex.Pattern
 	mapTo := *ds.OrgServerFQDN + "/"
 
-	mapFromHTTP := "http://" + hostRegex + "/"
-	mapFromHTTPS := "https://" + hostRegex + "/"
-	if strings.HasSuffix(hostRegex, `.*`) {
-		re := hostRegex
-		re = strings.Replace(re, `\`, ``, -1)
-		re = strings.Replace(re, `.*`, ``, -1)
-
-		hName := "__http__"
-		if ds.Type.IsDNS() {
-			if ds.RoutingName == nil {
-				return nil, errors.New("ds is dns, but missing routing name")
-			}
-			hName = *ds.RoutingName
-		}
-
-		portStr := ""
-		if hName == "__http__" && server.TCPPort != nil && *server.TCPPort > 0 && *server.TCPPort != 80 {
-			portStr = ":" + strconv.Itoa(*server.TCPPort)
-		}
-
-		httpsPortStr := ""
-		if hName == "__http__" && server.HTTPSPort != nil && *server.HTTPSPort > 0 && *server.HTTPSPort != 443 {
-			httpsPortStr = ":" + strconv.Itoa(*server.HTTPSPort)
-		}
+	portStr := ""
+	if !ds.Type.IsDNS() && server.TCPPort != nil && *server.TCPPort > 0 && *server.TCPPort != 80 {
+		portStr = ":" + strconv.Itoa(*server.TCPPort)
+	}
 
-		mapFromHTTP = "http://" + hName + re + cdnDomain + portStr + "/"
-		mapFromHTTPS = "https://" + hName + re + cdnDomain + httpsPortStr + "/"
+	httpsPortStr := ""
+	if !ds.Type.IsDNS() && server.HTTPSPort != nil && *server.HTTPSPort > 0 && *server.HTTPSPort != 443 {
+		httpsPortStr = ":" + strconv.Itoa(*server.HTTPSPort)
 	}
 
+	mapFromHTTP := "http://" + requestFQDN + portStr + "/"
+	mapFromHTTPS := "https://" + requestFQDN + httpsPortStr + "/"
+
 	if *ds.Protocol == tc.DSProtocolHTTP || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS {
 		remapLines = append(remapLines, remapLine{From: mapFromHTTP, To: mapTo})
 	}
@@ -715,3 +695,47 @@ func (ks keyVals) Less(i, j int) bool {
 	}
 	return ks[i].Val < ks[j].Val
 }
+
+// getDSRequestFQDNs returns the FQDNs that clients will request from the edge.
+func getDSRequestFQDNs(ds *DeliveryService, regexes []tc.DeliveryServiceRegex, server *Server, cdnDomain string) ([]string, error) {
+	if server.HostName == nil {
+		return nil, errors.New("server missing hostname")
+	}
+
+	fqdns := []string{}
+	for _, dsRegex := range regexes {
+		if tc.DSMatchType(dsRegex.Type) != tc.DSMatchTypeHostRegex || ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
+			continue
+		}
+		if dsRegex.Pattern == "" {
+			return nil, errors.New("ds missing regex pattern")
+		}
+		if ds.Protocol == nil {
+			return nil, errors.New("ds missing protocol")
+		}
+		if cdnDomain == "" {
+			return nil, errors.New("ds missing domain")
+		}
+
+		hostRegex := dsRegex.Pattern
+		fqdn := hostRegex
+
+		if strings.HasSuffix(hostRegex, `.*`) {
+			re := hostRegex
+			re = strings.Replace(re, `\`, ``, -1)
+			re = strings.Replace(re, `.*`, ``, -1)
+
+			hName := *server.HostName
+			if ds.Type.IsDNS() {
+				if ds.RoutingName == nil {
+					return nil, errors.New("ds is dns, but missing routing name")
+				}
+				hName = *ds.RoutingName
+			}
+
+			fqdn = hName + re + cdnDomain
+		}
+		fqdns = append(fqdns, fqdn)
+	}
+	return fqdns, nil
+}
diff --git a/lib/go-atscfg/remapdotconfig_test.go b/lib/go-atscfg/remapdotconfig_test.go
index d110139..962a19f 100644
--- a/lib/go-atscfg/remapdotconfig_test.go
+++ b/lib/go-atscfg/remapdotconfig_test.go
@@ -1896,7 +1896,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 				tc.DeliveryServiceRegex{
 					Type:      string(tc.DSMatchTypeHeaderRegex),
 					SetNumber: 0,
-					Pattern:   "myregexpattern2",
+					Pattern:   "myregexpattern3",
 				},
 			},
 		},
@@ -1906,7 +1906,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 				tc.DeliveryServiceRegex{
 					Type:      "",
 					SetNumber: 0,
-					Pattern:   "myregexpattern2",
+					Pattern:   "myregexpattern4",
 				},
 			},
 		},
@@ -1916,7 +1916,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 				tc.DeliveryServiceRegex{
 					Type:      "nonexistenttype",
 					SetNumber: 0,
-					Pattern:   "myregexpattern2",
+					Pattern:   "myregexpattern5",
 				},
 			},
 		},
@@ -1926,7 +1926,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 				tc.DeliveryServiceRegex{
 					Type:      string(tc.DSMatchTypeHostRegex),
 					SetNumber: 0,
-					Pattern:   "myregexpattern2",
+					Pattern:   "myregexpattern6",
 				},
 			},
 		},
@@ -1936,7 +1936,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 				tc.DeliveryServiceRegex{
 					Type:      string(tc.DSMatchTypeHostRegex),
 					SetNumber: 0,
-					Pattern:   "myregexpattern2",
+					Pattern:   "myregexpattern7",
 				},
 			},
 		},
diff --git a/lib/go-atscfg/snidotyaml.go b/lib/go-atscfg/snidotyaml.go
new file mode 100644
index 0000000..762ebde
--- /dev/null
+++ b/lib/go-atscfg/snidotyaml.go
@@ -0,0 +1,129 @@
+package atscfg
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+const SNIDotYAMLFileName = "sni.yaml"
+
+const ContentTypeSNIDotYAML = ContentTypeYAML
+const LineCommentSNIDotYAML = LineCommentYAML
+
+// SNIDotYAMLOpts contains settings to configure sni.yaml generation options.
+type SNIDotYAMLOpts struct {
+	// VerboseComments is whether to add informative comments to the generated file, about what was generated and why.
+	// Note this does not include the header comment, which is configured separately with HdrComment.
+	// These comments are human-readable and not guaranteed to be consistent between versions. Automating anything based on them is strongly discouraged.
+	VerboseComments bool
+
+	// HdrComment is the header comment to include at the beginning of the file.
+	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
+	// To omit the header comment, pass the empty string.
+	HdrComment string
+
+	// DefaultTLSVersions is the list of TLS versions to enable on delivery services with no Parameter.
+	DefaultTLSVersions []TLSVersion
+
+	// DefaultEnableH2 is whether to disable H2 on delivery services with no Parameter.
+	DefaultEnableH2 bool
+}
+
+func MakeSNIDotYAML(
+	server *Server,
+	dses []DeliveryService,
+	dss []tc.DeliveryServiceServer,
+	dsRegexArr []tc.DeliveryServiceRegexes,
+	tcParentConfigParams []tc.Parameter,
+	cdn *tc.CDN,
+	topologies []tc.Topology,
+	cacheGroupArr []tc.CacheGroupNullable,
+	serverCapabilities map[int]map[ServerCapability]struct{},
+	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
+	opt SNIDotYAMLOpts,
+) (Cfg, error) {
+	if len(opt.DefaultTLSVersions) == 0 {
+		opt.DefaultTLSVersions = DefaultDefaultTLSVersions
+	}
+
+	sslDatas, warnings, err := GetServerSSLData(
+		server,
+		dses,
+		dss,
+		dsRegexArr,
+		tcParentConfigParams,
+		cdn,
+		topologies,
+		cacheGroupArr,
+		serverCapabilities,
+		dsRequiredCapabilities,
+		opt.DefaultTLSVersions,
+		opt.DefaultEnableH2,
+	)
+	if err != nil {
+		return Cfg{}, makeErr(warnings, "getting ssl data: "+err.Error())
+	}
+
+	txt := ""
+	if opt.HdrComment != "" {
+		txt += makeHdrComment(opt.HdrComment)
+	}
+
+	txt += `sni:` + "\n"
+
+	seenFQDNs := map[string]struct{}{}
+
+	for _, sslData := range sslDatas {
+		tlsVersionsATS := []string{}
+		for _, tlsVersion := range sslData.TLSVersions {
+			tlsVersionsATS = append(tlsVersionsATS, `'`+tlsVersionsToATS[tlsVersion]+`'`)
+		}
+
+		for _, requestFQDN := range sslData.RequestFQDNs {
+			// TODO let active DSes take precedence?
+			if _, ok := seenFQDNs[requestFQDN]; ok {
+				warnings = append(warnings, "ds '"+sslData.DSName+"' had the same FQDN '"+requestFQDN+"' as some other delivery service, skipping!")
+				continue
+			}
+			seenFQDNs[requestFQDN] = struct{}{}
+
+			dsTxt := "\n"
+			if opt.VerboseComments {
+				dsTxt += LineCommentYAML + ` ds '` + sslData.DSName + `'` + "\n"
+			}
+			dsTxt += `- fqdn: '` + requestFQDN + `'`
+			dsTxt += "\n" + `  disable_h2: ` + strconv.FormatBool(!sslData.EnableH2)
+			dsTxt += "\n" + `  valid_tls_versions_in: [` + strings.Join(tlsVersionsATS, `,`) + `]`
+
+			txt += dsTxt + "\n"
+		}
+	}
+
+	return Cfg{
+		Text:        txt,
+		ContentType: ContentTypeSNIDotYAML,
+		LineComment: LineCommentSNIDotYAML,
+		Warnings:    warnings,
+	}, nil
+}
diff --git a/lib/go-atscfg/sslservernamedotyaml.go b/lib/go-atscfg/sslservernamedotyaml.go
new file mode 100644
index 0000000..7286c80
--- /dev/null
+++ b/lib/go-atscfg/sslservernamedotyaml.go
@@ -0,0 +1,383 @@
+package atscfg
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"errors"
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+const ContentTypeYAML = "application/yaml; charset=us-ascii" // Note YAML has no IANA standard mime type. This is one of several common usages, and is likely to be the standardized value. If you're reading this, please check IANA to see if YAML has been added, and change this to the IANA definition if so. Also note we include 'charset=us-ascii' because YAML is commonly UTF-8, but ATS is likely to be unable to handle UTF.
+const LineCommentYAML = LineCommentHash
+
+const SSLServerNameYAMLFileName = "ssl_server_name.yaml"
+
+const ContentTypeSSLServerNameYAML = ContentTypeYAML
+const LineCommentSSLServerNameYAML = LineCommentYAML
+
+// DefaultDefaultEnableH2 is whether Delivery Services will have HTTP/2 enabled by default if they don't have an explicit Parameter, and no Opt is passed to the Make func.
+// We disable by default, to prevent potentially enabling broken clients.
+const DefaultDefaultEnableH2 = false
+
+type TLSVersion string
+
+const TLSVersion1p0 = TLSVersion("1.0")
+const TLSVersion1p1 = TLSVersion("1.1")
+const TLSVersion1p2 = TLSVersion("1.2")
+const TLSVersion1p3 = TLSVersion("1.3")
+const TLSVersionInvalid = TLSVersion("")
+
+// StringToTLSVersion returns the TLSVersion or TLSVersionInvalid if the string is not a TLS Version enum.
+func StringToTLSVersion(st string) TLSVersion {
+	switch TLSVersion(st) {
+	case TLSVersion1p0:
+		return TLSVersion1p0
+	case TLSVersion1p1:
+		return TLSVersion1p1
+	case TLSVersion1p2:
+		return TLSVersion1p2
+	case TLSVersion1p3:
+		return TLSVersion1p3
+	}
+	return TLSVersionInvalid
+}
+
+// tlsVersionsToATS maps TLS version strings to the string used by ATS in ssl_server_name.yaml.
+var tlsVersionsToATS = map[TLSVersion]string{
+	TLSVersion1p0: "TLSv1",
+	TLSVersion1p1: "TLSv1_1",
+	TLSVersion1p2: "TLSv1_2",
+	TLSVersion1p3: "TLSv1_3",
+}
+
+const SSLServerNameYAMLParamEnableH2 = "enable_h2"
+const SSLServerNameYAMLParamTLSVersions = "tls_versions"
+
+// DefaultDefaultTLSVersions is the list of TLS versions to enable by default, if no Parameter exists and no Opt is passed to the Make func.
+// By default, we enable all, even insecure versions.
+// As a CDN, Traffic Control assumes it should not break clients, and it's the client's responsibility to use secure protocols.
+// Note this enables certain downgrade attacks. Operators or tenants concerned about these attacks should disable older TLS versions.
+var DefaultDefaultTLSVersions = []TLSVersion{
+	TLSVersion1p0,
+	TLSVersion1p1,
+	TLSVersion1p2,
+	TLSVersion1p3,
+}
+
+// SSLServerNameYAMLOpts contains settings to configure ssl_server_name.yaml generation options.
+type SSLServerNameYAMLOpts struct {
+	// VerboseComments is whether to add informative comments to the generated file, about what was generated and why.
+	// Note this does not include the header comment, which is configured separately with HdrComment.
+	// These comments are human-readable and not guaranteed to be consistent between versions. Automating anything based on them is strongly discouraged.
+	VerboseComments bool
+
+	// HdrComment is the header comment to include at the beginning of the file.
+	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
+	// To omit the header comment, pass the empty string.
+	HdrComment string
+
+	// DefaultTLSVersions is the list of TLS versions to enable on delivery services with no Parameter.
+	DefaultTLSVersions []TLSVersion
+
+	// DefaultEnableH2 is whether to disable H2 on delivery services with no Parameter.
+	DefaultEnableH2 bool
+}
+
+func MakeSSLServerNameYAML(
+	server *Server,
+	dses []DeliveryService,
+	dss []tc.DeliveryServiceServer,
+	dsRegexArr []tc.DeliveryServiceRegexes,
+	tcParentConfigParams []tc.Parameter,
+	cdn *tc.CDN,
+	topologies []tc.Topology,
+	cacheGroupArr []tc.CacheGroupNullable,
+	serverCapabilities map[int]map[ServerCapability]struct{},
+	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
+	opt SSLServerNameYAMLOpts,
+) (Cfg, error) {
+	if len(opt.DefaultTLSVersions) == 0 {
+		opt.DefaultTLSVersions = DefaultDefaultTLSVersions
+	}
+
+	sslDatas, warnings, err := GetServerSSLData(
+		server,
+		dses,
+		dss,
+		dsRegexArr,
+		tcParentConfigParams,
+		cdn,
+		topologies,
+		cacheGroupArr,
+		serverCapabilities,
+		dsRequiredCapabilities,
+		opt.DefaultTLSVersions,
+		opt.DefaultEnableH2,
+	)
+
+	if err != nil {
+		return Cfg{}, makeErr(warnings, "getting ssl data: "+err.Error())
+	}
+
+	txt := ""
+	if opt.HdrComment != "" {
+		txt += makeHdrComment(opt.HdrComment)
+	}
+
+	seenFQDNs := map[string]struct{}{}
+
+	for _, sslData := range sslDatas {
+		tlsVersionsATS := []string{}
+		for _, tlsVersion := range sslData.TLSVersions {
+			tlsVersionsATS = append(tlsVersionsATS, `'`+tlsVersionsToATS[tlsVersion]+`'`)
+		}
+
+		for _, requestFQDN := range sslData.RequestFQDNs {
+			// TODO let active DSes take precedence?
+			if _, ok := seenFQDNs[requestFQDN]; ok {
+				warnings = append(warnings, "ds '"+sslData.DSName+"' had the same FQDN '"+requestFQDN+"' as some other delivery service, skipping!")
+				continue
+			}
+			seenFQDNs[requestFQDN] = struct{}{}
+
+			dsTxt := "\n"
+			if opt.VerboseComments {
+				dsTxt += LineCommentYAML + ` ds '` + sslData.DSName + `'` + "\n"
+			}
+			dsTxt += `- fqdn: '` + requestFQDN + `'`
+			dsTxt += "\n" + `  disable_h2: ` + strconv.FormatBool(!sslData.EnableH2)
+			dsTxt += "\n" + `  valid_tls_versions_in: [` + strings.Join(tlsVersionsATS, `,`) + `]`
+
+			txt += dsTxt + "\n"
+		}
+
+	}
+
+	return Cfg{
+		Text:        txt,
+		ContentType: ContentTypeSSLServerNameYAML,
+		LineComment: LineCommentSSLServerNameYAML,
+		Warnings:    warnings,
+	}, nil
+}
+
+// SSLData has the DS data needed for both sni.yaml (ATS 9+)  and ssl_server_name.yaml (ATS 8).
+type SSLData struct {
+	DSName       string
+	RequestFQDNs []string
+	EnableH2     bool
+	TLSVersions  []TLSVersion
+}
+
+// GetServerSSLData gets the SSLData for all Delivery Services assigned to the given Server, any warnings, and any error.
+func GetServerSSLData(
+	server *Server,
+	dses []DeliveryService,
+	dss []tc.DeliveryServiceServer,
+	dsRegexArr []tc.DeliveryServiceRegexes,
+	tcParentConfigParams []tc.Parameter,
+	cdn *tc.CDN,
+	topologies []tc.Topology,
+	cacheGroupArr []tc.CacheGroupNullable,
+	serverCapabilities map[int]map[ServerCapability]struct{},
+	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
+	defaultTLSVersions []TLSVersion,
+	defaultEnableH2 bool,
+) ([]SSLData, []string, error) {
+	warnings := []string{}
+
+	if server.Profile == nil {
+		return nil, warnings, errors.New("this server missing Profile")
+	}
+
+	dsRegexes := makeDSRegexMap(dsRegexArr)
+
+	parentConfigParamsWithProfiles, err := tcParamsToParamsWithProfiles(tcParentConfigParams)
+	if err != nil {
+		warnings = append(warnings, "error getting profiles from Traffic Ops Parameters, Parameters will not be considered for generation! : "+err.Error())
+		parentConfigParamsWithProfiles = []parameterWithProfiles{}
+	}
+
+	profileParentConfigParams := map[string]map[string]string{} // map[profileName][paramName]paramVal
+	for _, param := range parentConfigParamsWithProfiles {
+		for _, profile := range param.ProfileNames {
+			if _, ok := profileParentConfigParams[profile]; !ok {
+				profileParentConfigParams[profile] = map[string]string{}
+			}
+			profileParentConfigParams[profile][param.Name] = param.Value
+		}
+	}
+
+	cacheGroups, err := makeCGMap(cacheGroupArr)
+	if err != nil {
+		return nil, warnings, errors.New("making cachegroup map: " + err.Error())
+	}
+
+	nameTopologies := makeTopologyNameMap(topologies)
+
+	sort.Sort(dsesSortByName(dses))
+
+	sslDatas := []SSLData{}
+
+	for _, ds := range dses {
+		hasDS, err := dsUsesServer(&ds, server, dss, nameTopologies, cacheGroups, serverCapabilities, dsRequiredCapabilities)
+		if err != nil {
+			warnings = append(warnings, "error checking if ds uses this server, considering false! Error: "+err.Error())
+			continue
+		}
+		if !hasDS {
+			continue
+		}
+
+		dsParentConfigParams := map[string]string{}
+		if ds.ProfileName != nil {
+			dsParentConfigParams = profileParentConfigParams[*ds.ProfileName]
+		}
+
+		requestFQDNs, err := getDSRequestFQDNs(&ds, dsRegexes[tc.DeliveryServiceName(*ds.XMLID)], server, cdn.DomainName)
+		if err != nil {
+			warnings = append(warnings, "error getting ds '"+*ds.XMLID+"' request fqdns, skipping! Error: "+err.Error())
+			continue
+		}
+
+		enableH2 := defaultEnableH2
+		tlsVersions := defaultTLSVersions
+
+		paramValEnableH2 := dsParentConfigParams[SSLServerNameYAMLParamEnableH2]
+		paramValEnableH2 = strings.TrimSpace(paramValEnableH2)
+		paramValEnableH2 = strings.ToLower(paramValEnableH2)
+
+		if paramValEnableH2 != "" {
+			enableH2 = strings.HasPrefix(paramValEnableH2, "t") || strings.HasPrefix(paramValEnableH2, "y")
+		}
+
+		paramValTLSVersions := dsParentConfigParams[SSLServerNameYAMLParamTLSVersions]
+		paramValTLSVersions = strings.Replace(paramValTLSVersions, " ", "", -1)
+		paramValTLSVersions = strings.TrimSpace(paramValTLSVersions)
+
+		paramTLSVersions := []TLSVersion{}
+		if paramValTLSVersions != "" {
+			// Allow delimiting with commas, semicolons, spaces, or newlines.
+			delim := ","
+			if !strings.Contains(paramValTLSVersions, delim) {
+				delim = ";"
+			}
+			if !strings.Contains(paramValTLSVersions, delim) {
+				delim = " "
+			}
+			if !strings.Contains(paramValTLSVersions, delim) {
+				delim = "\n"
+			}
+
+			tlsVersionsParamArr := strings.Split(paramValTLSVersions, delim)
+			for _, tlsVersion := range tlsVersionsParamArr {
+				if _, ok := tlsVersionsToATS[TLSVersion(tlsVersion)]; !ok {
+					warnings = append(warnings, "ds '"+*ds.XMLID+"' had unknown "+SSLServerNameYAMLParamTLSVersions+" parameter '"+tlsVersion+"' - ignoring!")
+					continue
+				}
+				paramTLSVersions = append(paramTLSVersions, TLSVersion(tlsVersion))
+			}
+		}
+
+		if len(paramTLSVersions) != 0 {
+			tlsVersions = paramTLSVersions
+		}
+
+		sslDatas = append(sslDatas, SSLData{
+			DSName:       *ds.XMLID,
+			RequestFQDNs: requestFQDNs,
+			EnableH2:     enableH2,
+			TLSVersions:  tlsVersions,
+		})
+	}
+
+	return sslDatas, warnings, nil
+}
+
+func dsUsesServer(
+	ds *DeliveryService,
+	server *Server,
+	dss []tc.DeliveryServiceServer,
+	nameTopologies map[TopologyName]tc.Topology,
+	cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable,
+	serverCapabilities map[int]map[ServerCapability]struct{},
+	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
+) (bool, error) {
+	if ds.XMLID == nil || *ds.XMLID == "" {
+		return false, errors.New("ds missing xmlId")
+	} else if ds.ID == nil {
+		return false, errors.New("ds missing id")
+	} else if server.ID == nil {
+		return false, errors.New("server missing id")
+	} else if ds.Type == nil {
+		return false, errors.New("ds missing type")
+	}
+
+	if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) {
+		return false, nil
+	}
+
+	serverParentCGData, err := getParentCacheGroupData(server, cacheGroups)
+	if err != nil {
+		return false, errors.New("getting server parent cachegroup data: " + err.Error())
+	}
+	cacheIsTopLevel := isTopLevelCache(serverParentCGData)
+
+	if !cacheIsTopLevel && (ds.Topology == nil || *ds.Topology == "") {
+		if !dsAssignedServer(*ds.ID, *server.ID, dss) {
+			return false, nil
+		}
+	}
+
+	if ds.Topology != nil && *ds.Topology != "" {
+		topology, ok := nameTopologies[TopologyName(*ds.Topology)]
+		if !ok {
+			return false, errors.New("ds topology '" + *ds.Topology + "' not found in topologies")
+		}
+
+		serverPlacement, err := getTopologyPlacement(tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups, ds)
+		if err != nil {
+			return false, errors.New("getting topology placement: " + err.Error())
+		}
+		if !serverPlacement.InTopology {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}
+
+// dsAssignedServer returns whether the Delivery Service Servers has an assignment between the server and the DS.
+// Does not check Topologies, or parentage. Only useful for Edges and pre-topology DSS.
+func dsAssignedServer(dsID int, serverID int, dsses []tc.DeliveryServiceServer) bool {
+	for _, dss := range dsses {
+		if dss.Server == nil || dss.DeliveryService == nil {
+			continue // TODO warn?
+		}
+		if *dss.Server == serverID && *dss.DeliveryService == dsID {
+			return true
+		}
+	}
+	return false
+}
diff --git a/lib/go-atscfg/sslservernamedotyaml_test.go b/lib/go-atscfg/sslservernamedotyaml_test.go
new file mode 100644
index 0000000..4c0bcb7
--- /dev/null
+++ b/lib/go-atscfg/sslservernamedotyaml_test.go
@@ -0,0 +1,411 @@
+package atscfg
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/lib/go-util"
+)
+
+func TestMakeSSLServerNameYAML(t *testing.T) {
+	opts := SSLServerNameYAMLOpts{VerboseComments: false, HdrComment: "myHeaderComment"}
+
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.Protocol = util.IntPtr(int(tc.DSProtocolHTTPAndHTTPS))
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(43)
+	ds1Type := tc.DSTypeDNS
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+
+	dses := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		tc.Parameter{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamAlgorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+	}
+
+	server := makeTestParentServer()
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG")
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG")
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+
+	topologies := []tc.Topology{}
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = server.Cachegroup
+	eCG.ID = server.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG := &tc.CacheGroupNullable{}
+	mCG.Name = mid0.Cachegroup
+	mCG.ID = mid0.CachegroupID
+	mCGType := tc.CacheGroupMidTypeName
+	mCG.Type = &mCGType
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG}
+
+	dss := []tc.DeliveryServiceServer{
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds0.ID),
+		},
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds1.ID),
+		},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	dsr := []tc.DeliveryServiceRegexes{
+		tc.DeliveryServiceRegexes{
+			DSName: *ds0.XMLID,
+			Regexes: []tc.DeliveryServiceRegex{
+				tc.DeliveryServiceRegex{
+					Type:      string(tc.DSMatchTypeHostRegex),
+					SetNumber: 0,
+					Pattern:   `.*\.ds0\..*`,
+				},
+			},
+		},
+	}
+
+	cfg, err := MakeSSLServerNameYAML(server, dses, dss, dsr, parentConfigParams, cdn, topologies, cgs, serverCapabilities, dsRequiredCapabilities, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+	txt := cfg.Text
+
+	if !strings.Contains(txt, `fqdn: 'myserver.ds0.cdndomain.example'`) {
+		t.Errorf("expected ds0 fqdn, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `disable_h2: true`) {
+		t.Errorf("expected h2 disabled for ds with no parameters, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `['TLSv1','TLSv1_1','TLSv1_2','TLSv1_3']`) {
+		t.Errorf("expected all TLS versions for ds with no parameters, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+}
+
+func TestMakeSSLServerNameYAMLParams(t *testing.T) {
+	opts := SSLServerNameYAMLOpts{VerboseComments: false, HdrComment: "myHeaderComment"}
+
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.Protocol = util.IntPtr(int(tc.DSProtocolHTTPAndHTTPS))
+	ds0.ProfileName = util.StrPtr("ds0profile")
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(43)
+	ds1Type := tc.DSTypeDNS
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+
+	dses := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		tc.Parameter{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamAlgorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       SSLServerNameYAMLParamEnableH2,
+			ConfigFile: "parent.config",
+			Value:      "true",
+			Profiles:   []byte(`["ds0profile"]`),
+		},
+		tc.Parameter{
+			Name:       SSLServerNameYAMLParamTLSVersions,
+			ConfigFile: "parent.config",
+			Value:      "1.1,1.2",
+			Profiles:   []byte(`["ds0profile"]`),
+		},
+	}
+
+	server := makeTestParentServer()
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG")
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG")
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+
+	topologies := []tc.Topology{}
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = server.Cachegroup
+	eCG.ID = server.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG := &tc.CacheGroupNullable{}
+	mCG.Name = mid0.Cachegroup
+	mCG.ID = mid0.CachegroupID
+	mCGType := tc.CacheGroupMidTypeName
+	mCG.Type = &mCGType
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG}
+
+	dss := []tc.DeliveryServiceServer{
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds0.ID),
+		},
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds1.ID),
+		},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	dsr := []tc.DeliveryServiceRegexes{
+		tc.DeliveryServiceRegexes{
+			DSName: *ds0.XMLID,
+			Regexes: []tc.DeliveryServiceRegex{
+				tc.DeliveryServiceRegex{
+					Type:      string(tc.DSMatchTypeHostRegex),
+					SetNumber: 0,
+					Pattern:   `.*\.ds0\..*`,
+				},
+			},
+		},
+	}
+
+	cfg, err := MakeSSLServerNameYAML(server, dses, dss, dsr, parentConfigParams, cdn, topologies, cgs, serverCapabilities, dsRequiredCapabilities, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+	txt := cfg.Text
+
+	if !strings.Contains(txt, `fqdn: 'myserver.ds0.cdndomain.example'`) {
+		t.Errorf("expected ds0 fqdn, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `disable_h2: false`) {
+		t.Errorf("expected h2 enabled for ds with parameter, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `['TLSv1_1','TLSv1_2']`) {
+		t.Errorf("expected TLS 1.1,1.2 for ds with parameters, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+}
+
+func TestMakeSSLServerNameYAMLParamInvalid(t *testing.T) {
+	opts := SSLServerNameYAMLOpts{VerboseComments: false, HdrComment: "myHeaderComment"}
+
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.Protocol = util.IntPtr(int(tc.DSProtocolHTTPAndHTTPS))
+	ds0.ProfileName = util.StrPtr("ds0profile")
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(43)
+	ds1Type := tc.DSTypeDNS
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+
+	dses := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		tc.Parameter{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamAlgorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       SSLServerNameYAMLParamEnableH2,
+			ConfigFile: "parent.config",
+			Value:      "true",
+			Profiles:   []byte(`["ds0profile"]`),
+		},
+		tc.Parameter{
+			Name:       SSLServerNameYAMLParamTLSVersions,
+			ConfigFile: "parent.config",
+			Value:      "1.3,1.invalid,foo,bar,1.1", // invalid params should warn and skip
+			Profiles:   []byte(`["ds0profile"]`),
+		},
+	}
+
+	server := makeTestParentServer()
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG")
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG")
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+
+	topologies := []tc.Topology{}
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = server.Cachegroup
+	eCG.ID = server.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG := &tc.CacheGroupNullable{}
+	mCG.Name = mid0.Cachegroup
+	mCG.ID = mid0.CachegroupID
+	mCGType := tc.CacheGroupMidTypeName
+	mCG.Type = &mCGType
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG}
+
+	dss := []tc.DeliveryServiceServer{
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds0.ID),
+		},
+		tc.DeliveryServiceServer{
+			Server:          util.IntPtr(*server.ID),
+			DeliveryService: util.IntPtr(*ds1.ID),
+		},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	dsr := []tc.DeliveryServiceRegexes{
+		tc.DeliveryServiceRegexes{
+			DSName: *ds0.XMLID,
+			Regexes: []tc.DeliveryServiceRegex{
+				tc.DeliveryServiceRegex{
+					Type:      string(tc.DSMatchTypeHostRegex),
+					SetNumber: 0,
+					Pattern:   `.*\.ds0\..*`,
+				},
+			},
+		},
+	}
+
+	cfg, err := MakeSSLServerNameYAML(server, dses, dss, dsr, parentConfigParams, cdn, topologies, cgs, serverCapabilities, dsRequiredCapabilities, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+	txt := cfg.Text
+
+	if !strings.Contains(txt, `fqdn: 'myserver.ds0.cdndomain.example'`) {
+		t.Errorf("expected ds0 fqdn, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `disable_h2: false`) {
+		t.Errorf("expected h2 enabled for ds with parameter, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+	if !strings.Contains(txt, `['TLSv1_3','TLSv1_1']`) {
+		t.Errorf("expected TLS 1.3,1.1 for ds with valid and invalid parameter, actual ''%+v'' warnings ''%+v''", txt, cfg.Warnings)
+	}
+}
diff --git a/traffic_ops_ort/atstccfg/cfgfile/routing.go b/traffic_ops_ort/atstccfg/cfgfile/routing.go
index f21f18d..01ef2de 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/routing.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/routing.go
@@ -96,6 +96,8 @@ var configFileLiteralFuncs = []ConfigFileLiteralFunc{
 	{"regex_revalidate.config", MakeRegexRevalidateDotConfig},
 	{"remap.config", MakeRemapDotConfig},
 	{"ssl_multicert.config", MakeSSLMultiCertDotConfig},
+	{"ssl_server_name.yaml", MakeSSLServerNameYAML},
+	{"sni.yaml", MakeSNIDotYAML},
 	{"storage.config", MakeStorageDotConfig},
 	{"sysctl.conf", MakeSysCtlDotConf},
 	{"volume.config", MakeVolumeDotConfig},
diff --git a/traffic_ops_ort/atstccfg/cfgfile/wrappers.go b/traffic_ops_ort/atstccfg/cfgfile/wrappers.go
index b21875d..6d99bc8 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/wrappers.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/wrappers.go
@@ -109,6 +109,48 @@ func MakeLoggingDotYAML(toData *config.TOData, fileName string, hdrCommentTxt st
 	return atscfg.MakeLoggingDotYAML(toData.Server, toData.ServerParams, hdrCommentTxt)
 }
 
+func MakeSSLServerNameYAML(toData *config.TOData, fileName string, hdrCommentTxt string, cfg config.TCCfg) (atscfg.Cfg, error) {
+	return atscfg.MakeSSLServerNameYAML(
+		toData.Server,
+		toData.DeliveryServices,
+		toData.DeliveryServiceServers,
+		toData.DeliveryServiceRegexes,
+		toData.ParentConfigParams,
+		toData.CDN,
+		toData.Topologies,
+		toData.CacheGroups,
+		toData.ServerCapabilities,
+		toData.DSRequiredCapabilities,
+		atscfg.SSLServerNameYAMLOpts{
+			HdrComment:         hdrCommentTxt,
+			VerboseComments:    true, // TODO add a CLI flag
+			DefaultTLSVersions: cfg.DefaultTLSVersions,
+			DefaultEnableH2:    cfg.DefaultEnableH2,
+		},
+	)
+}
+
+func MakeSNIDotYAML(toData *config.TOData, fileName string, hdrCommentTxt string, cfg config.TCCfg) (atscfg.Cfg, error) {
+	return atscfg.MakeSNIDotYAML(
+		toData.Server,
+		toData.DeliveryServices,
+		toData.DeliveryServiceServers,
+		toData.DeliveryServiceRegexes,
+		toData.ParentConfigParams,
+		toData.CDN,
+		toData.Topologies,
+		toData.CacheGroups,
+		toData.ServerCapabilities,
+		toData.DSRequiredCapabilities,
+		atscfg.SNIDotYAMLOpts{
+			HdrComment:         hdrCommentTxt,
+			VerboseComments:    true, // TODO add a CLI flag
+			DefaultTLSVersions: cfg.DefaultTLSVersions,
+			DefaultEnableH2:    cfg.DefaultEnableH2,
+		},
+	)
+}
+
 func MakeLogsXMLDotConfig(toData *config.TOData, fileName string, hdrCommentTxt string, cfg config.TCCfg) (atscfg.Cfg, error) {
 	return atscfg.MakeLogsXMLDotConfig(toData.Server, toData.ServerParams, hdrCommentTxt)
 }
diff --git a/traffic_ops_ort/atstccfg/config/config.go b/traffic_ops_ort/atstccfg/config/config.go
index 5db78c3..c8257d3 100644
--- a/traffic_ops_ort/atstccfg/config/config.go
+++ b/traffic_ops_ort/atstccfg/config/config.go
@@ -48,26 +48,28 @@ var ErrNotFound = errors.New("not found")
 var ErrBadRequest = errors.New("bad request")
 
 type Cfg struct {
-	CacheHostName   string
-	DisableProxy    bool
-	GetData         string
-	ListPlugins     bool
-	LogLocationErr  string
-	LogLocationInfo string
-	LogLocationWarn string
-	NumRetries      int
-	RevalOnly       bool
-	SetQueueStatus  string
-	SetRevalStatus  string
-	TOInsecure      bool
-	TOPass          string
-	TOTimeout       time.Duration
-	TOURL           *url.URL
-	TOUser          string
-	Dir             string
-	ViaRelease      bool
-	SetDNSLocalBind bool
-	ParentComments  bool
+	CacheHostName      string
+	DisableProxy       bool
+	GetData            string
+	ListPlugins        bool
+	LogLocationErr     string
+	LogLocationInfo    string
+	LogLocationWarn    string
+	NumRetries         int
+	RevalOnly          bool
+	SetQueueStatus     string
+	SetRevalStatus     string
+	TOInsecure         bool
+	TOPass             string
+	TOTimeout          time.Duration
+	TOURL              *url.URL
+	TOUser             string
+	Dir                string
+	ViaRelease         bool
+	SetDNSLocalBind    bool
+	ParentComments     bool
+	DefaultEnableH2    bool
+	DefaultTLSVersions []atscfg.TLSVersion
 }
 
 type TCCfg struct {
@@ -105,6 +107,8 @@ func GetCfg() (Cfg, error) {
 	viaRelease := flag.BoolP("via-string-release", "", false, "Whether to use the Release value from the RPM package as a replacement for the ATS version specified in the build that is returned in the Via and Server headers from ATS.")
 	dnsLocalBind := flag.BoolP("dns-local-bind", "", false, "Whether to use the server's Service Addresses to set the ATS DNS local bind address.")
 	disableParentConfigComments := flag.BoolP("disable-parent-config-comments", "", false, "Disable adding a comments to parent.config individual lines")
+	defaultEnableH2 := flag.BoolP("default-client-enable-h2", "", false, "Whether to enable HTTP/2 on Delivery Services by default, if they have no explicit Parameter. This is irrelevant if ATS records.config is not serving H2. If omitted, H2 is disabled.")
+	defaultTLSVersionsStr := flag.StringP("default-client-tls-versions", "", "", "Comma-delimited list of default TLS versions for Delivery Services with no Parameter, e.g. '--default-tls-versions=1.1,1.2,1.3'. If omitted, all versions are enabled.")
 
 	flag.Parse()
 
@@ -151,6 +155,22 @@ func GetCfg() (Cfg, error) {
 		return Cfg{}, errors.New("Missing required argument --cache-host-name. " + usageStr)
 	}
 
+	defaultTLSVersions := atscfg.DefaultDefaultTLSVersions
+
+	*defaultTLSVersionsStr = strings.TrimSpace(*defaultTLSVersionsStr)
+	if len(*defaultTLSVersionsStr) > 0 {
+		defaultTLSVersionsStrs := strings.Split(*defaultTLSVersionsStr, ",")
+
+		defaultTLSVersions = []atscfg.TLSVersion{}
+		for _, tlsVersionStr := range defaultTLSVersionsStrs {
+			tlsVersion := atscfg.StringToTLSVersion(tlsVersionStr)
+			if tlsVersion == atscfg.TLSVersionInvalid {
+				return Cfg{}, errors.New("unknown TLS Version '" + tlsVersionStr + "' in '" + *defaultTLSVersionsStr + "'")
+			}
+			defaultTLSVersions = append(defaultTLSVersions, tlsVersion)
+		}
+	}
+
 	toURLParsed, err := url.Parse(*toURL)
 	if err != nil {
 		return Cfg{}, errors.New("parsing Traffic Ops URL from " + urlSourceStr + " '" + *toURL + "': " + err.Error())
@@ -159,26 +179,28 @@ func GetCfg() (Cfg, error) {
 	}
 
 	cfg := Cfg{
-		LogLocationErr:  *logLocationErr,
-		LogLocationWarn: *logLocationWarn,
-		LogLocationInfo: *logLocationInfo,
-		NumRetries:      *numRetries,
-		TOInsecure:      *toInsecure,
-		TOPass:          *toPass,
-		TOTimeout:       time.Millisecond * time.Duration(*toTimeoutMS),
-		TOURL:           toURLParsed,
-		TOUser:          *toUser,
-		ListPlugins:     *listPlugins,
-		CacheHostName:   *cacheHostName,
-		GetData:         *getData,
-		SetRevalStatus:  *setRevalStatus,
-		SetQueueStatus:  *setQueueStatus,
-		RevalOnly:       *revalOnly,
-		DisableProxy:    *disableProxy,
-		Dir:             *dir,
-		ViaRelease:      *viaRelease,
-		SetDNSLocalBind: *dnsLocalBind,
-		ParentComments:  !(*disableParentConfigComments),
+		LogLocationErr:     *logLocationErr,
+		LogLocationWarn:    *logLocationWarn,
+		LogLocationInfo:    *logLocationInfo,
+		NumRetries:         *numRetries,
+		TOInsecure:         *toInsecure,
+		TOPass:             *toPass,
+		TOTimeout:          time.Millisecond * time.Duration(*toTimeoutMS),
+		TOURL:              toURLParsed,
+		TOUser:             *toUser,
+		ListPlugins:        *listPlugins,
+		CacheHostName:      *cacheHostName,
+		GetData:            *getData,
+		SetRevalStatus:     *setRevalStatus,
+		SetQueueStatus:     *setQueueStatus,
+		RevalOnly:          *revalOnly,
+		DisableProxy:       *disableProxy,
+		Dir:                *dir,
+		ViaRelease:         *viaRelease,
+		SetDNSLocalBind:    *dnsLocalBind,
+		ParentComments:     !(*disableParentConfigComments),
+		DefaultEnableH2:    *defaultEnableH2,
+		DefaultTLSVersions: defaultTLSVersions,
 	}
 	if err := log.InitCfg(cfg); err != nil {
 		return Cfg{}, errors.New("Initializing loggers: " + err.Error() + "\n")
diff --git a/traffic_ops_ort/t3c/README.md b/traffic_ops_ort/t3c/README.md
index bc4af14..6a7a9e6 100644
--- a/traffic_ops_ort/t3c/README.md
+++ b/traffic_ops_ort/t3c/README.md
@@ -1,50 +1,52 @@
-# T3C
+# t3c
 
 t3c is a transliteration of traffic_ops_ort.pl script to the go language.
 It is designed to replace the traffic_ops_ort.pl perl script and it is used to apply 
 configuration from Traffic Control, stored in Traffic Ops, to the cache.
 
-Typical usage is to install T3C on the cache machine, and then run it periodically via a CRON job.
+Typical usage is to install `t3c` on the cache machine, and then run it periodically via a CRON job.
 
-**NOTE** T3C requires and uses /opt/ort/atstccfg
+**NOTE** `t3c` requires and uses /opt/ort/atstccfg
 
 ## Options
 
-T3C has the following command-line options:
+The `t3c` app has the following command-line options:
 
 
 long option                             | short | default | description
 --------------------------------------- | ------| ------- | ------------------------------------------------------------------------------------
---cache-hostname=[hostname]             | -H    | ""      | override the short hostname of the OS for config generation.
---dispersion=[seconds]                  | -D    | 300     | wait a random number of seconds between 0 and [seconds] before starting.
---login-dispersion=[seconds]            | -l    | 0       | wait a random number of seconds between 0 and [seconds] before login.
---log-location-debug=[value]            | -d    | stdout  | Where to log debugs. May be a file path, stdout, stderr, or null
---log-location-error=[value]            | -e    | stdout  | Where to log errors. May be a file path, stdout, stderr, or null
---log-location-info=[value]             | -i    | stdout  | Where to log info messages. May be a file path, stdout, stderr, or null
---log-location-warn=[value]             | -w    | stdout  | Where to log warning messages. May be a file path, stdout, stderr, or null
---num-retries=[number]                  | -r    | 3       | retry connection to Traffic Ops URL [number] times.
---rev-proxy-disable=['true' or 'false'] | -p    | false   | bypass the reverse proxy even if one has been configured.
---reval-wait-time=[seconds]             | -T    | 60      | wait a random number of seconds between 0 and [seconds] before revlidation
---run-mode=[mode]                       | -m    | report  | The mode of operation, where mode is [badass|report|revalidate|syncds].
---skip-os-check=['true' or 'false']     | -s    | false   | bypass the check for a supported CentOS version.
---traffic-ops-timeout-milliseconds=[ms] | -t    | 30000   | The Traffic Ops request timeout in milliseconds.
---traffic-ops-password=[password]       | -P    | ""      | TrafficOps password. Required if not set with the environment variable TO_PASS
---traffic-ops-url=[url]                 | -u    | ""      | TrafficOps URL. Required if not set with the environment variable TO_URL
---traffic-ops-user=[username]           | -U    | ""      | TrafficOps username. Required if not set with the environment variable TO_USER
---trafficserver-home=[directory]        | -R    | ""      | Used to specify an alternate install location for ATS, otherwise its set from the RPM.
---dns-local-bind=['true' or 'false']    | -b    | false   | set the ATS config to bind to the Server's Service Address in Traffic Ops for DNS.
---wait-for-parents=['true' or 'false']  | -W    | true    | do not update if parent_pending = 1 in the update json.
---git=['yes' or 'no' or 'auto']         | -g    | auto    | track changes in git. If yes, create and commit to a repo. If auto, commit if a repo exists.
+--cache-hostname=[hostname]                    | -H    | ""      | override the short hostname of the OS for config generation.
+--dispersion=[seconds]                         | -D    | 300     | wait a random number of seconds between 0 and [seconds] before starting.
+--login-dispersion=[seconds]                   | -l    | 0       | wait a random number of seconds between 0 and [seconds] before login.
+--log-location-debug=[value]                   | -d    | stdout  | Where to log debugs. May be a file path, stdout, stderr, or null
+--log-location-error=[value]                   | -e    | stdout  | Where to log errors. May be a file path, stdout, stderr, or null
+--log-location-info=[value]                    | -i    | stdout  | Where to log info messages. May be a file path, stdout, stderr, or null
+--log-location-warn=[value]                    | -w    | stdout  | Where to log warning messages. May be a file path, stdout, stderr, or null
+--num-retries=[number]                         | -r    | 3       | retry connection to Traffic Ops URL [number] times.
+--rev-proxy-disable=['true' or 'false']        | -p    | false   | bypass the reverse proxy even if one has been configured.
+--reval-wait-time=[seconds]                    | -T    | 60      | wait a random number of seconds between 0 and [seconds] before revlidation
+--run-mode=[mode]                              | -m    | report  | The mode of operation, where mode is [badass|report|revalidate|syncds].
+--skip-os-check=['true' or 'false']            | -s    | false   | bypass the check for a supported CentOS version.
+--traffic-ops-timeout-milliseconds=[ms]        | -t    | 30000   | The Traffic Ops request timeout in milliseconds.
+--traffic-ops-password=[password]              | -P    | ""      | TrafficOps password. Required if not set with the environment variable TO_PASS
+--traffic-ops-url=[url]                        | -u    | ""      | TrafficOps URL. Required if not set with the environment variable TO_URL
+--traffic-ops-user=[username]                  | -U    | ""      | TrafficOps username. Required if not set with the environment variable TO_USER
+--trafficserver-home=[directory]               | -R    | ""      | Used to specify an alternate install location for ATS, otherwise its set from the RPM.
+--dns-local-bind=['true' or 'false']           | -b    | false   | set the ATS config to bind to the Server's Service Address in Traffic Ops for DNS.
+--wait-for-parents=['true' or 'false']         | -W    | true    | do not update if parent_pending = 1 in the update json.
+--git=['yes' or 'no' or 'auto']                | -g    | auto    | track changes in git. If yes, create and commit to a repo. If auto, commit if a repo exists.
+--default-client-enable-h2=['true' or 'false'] | -2    | false   | Whether to enable HTTP/2 on Delivery Services by default, if they have no explicit Parameter.
+--default-client-tls-versions=[versions]       | -v    | ""      | Comma-delimited list of default TLS versions for Delivery Services with no Parameter, e.g. '1.1,1.2,1.3'. If omitted, all versions are enabled.
 
 # Modes
 
-T3C can be run in a number of modes.
+The `t3c` app can be run in a number of modes.
 
 The syncds mode is the normal mode of operation, which should typically be run periodically via cron or a similar tool.
 
-The badass mode is typically an emergency-fix mode, which will override and replace all files with the configuration generated from the current Traffic Ops data, regardless whether T3C (presumably incorrectly) thinks the files need updating or not. It is recommended to run this mode when something goes wrong, and the configuration on the cache is incorrect, and the data in Traffic Ops and config generation is believed to be correct. It is not recommended to run this in normal operation;  [...]
+The badass mode is typically an emergency-fix mode, which will override and replace all files with the configuration generated from the current Traffic Ops data, regardless whether `t3c` (presumably incorrectly) thinks the files need updating or not. It is recommended to run this mode when something goes wrong, and the configuration on the cache is incorrect, and the data in Traffic Ops and config generation is believed to be correct. It is not recommended to run this in normal operation [...]
 
-The revalidate mode will apply Revalidations from Traffic Ops (regex_revalidate.config) but no other configuration. This mode was intended to quickly apply revalidations when T3C took a long time to run. It is less relevant with T3C's current speed, but may still be useful on slow networks or very large deployments.
+The revalidate mode will apply Revalidations from Traffic Ops (regex_revalidate.config) but no other configuration. This mode was intended to quickly apply revalidations when `t3c` took a long time to run. It is less relevant with the current speed of `t3c` but may still be useful on slow networks or very large deployments.
 
 mode        | description
 ------------| ---
@@ -55,7 +57,7 @@ revalidate  | checks for updated revalidations in Traffic Ops and applies them
 
 # Behavior
 
-When T3C is run, it will:
+When `t3c` is run, it will:
 
 1. Delete all of its temporary directories over a week old. Currently, the base temp directory is hard-coded to /tmp/ort.
 1. Determine if Updates have been Queued on the server (by checking the Server's Update Pending or Revalidate Pending flag in Traffic Ops).
@@ -71,15 +73,15 @@ When T3C is run, it will:
     1. **NOTE** the default profiles distributed by Traffic Control have an ATS chkconfig with a runlevel before networking is enabled, which is likely incorrect.
     1. **NOTE** this is not used by CentOS 7+ and ATS 7+. SystemD does not use chkconfig, and ATS 7+ uses a SystemD script not an init script.
 1. Process each config file
-    1. If T3C is in revalidate mode, this will only be regex_revalidate.config
+    1. If `t3c` is in revalidate mode, this will only be regex_revalidate.config
     1. Perform any special processing. See [Special Processing](#special-processing).
     1. If a file exists at the path of the file, load it from disk and compare the two.
     1. If there are no changes, don't apply the new file.
     1. If there are changes, backup the existing file in the temp directory, and write the new file.
 1. If configuration was changed which requires an ATS reload to apply, perform a service reload of ATS.
-1. If configuration was changed which requires an ATS restart to apply, and T3C is in badass mode, perform a service restart of ATS.
-1. If a sysctl.conf config file was changed, and T3C is in badass mode, run `sysctl -p`.
-1. If a ntpd.conf config file was changed, and T3C is in badass mode, perform a service restart of ntpd.
+1. If configuration was changed which requires an ATS restart to apply, and `t3c` is in badass mode, perform a service restart of ATS.
+1. If a sysctl.conf config file was changed, and `t3c` is in badass mode, run `sysctl -p`.
+1. If a ntpd.conf config file was changed, and `t3c` is in badass mode, perform a service restart of ntpd.
 1. Update Traffic Ops to unset the Update Pending or Revalidate Pending flag of this Server.
 
 # Special Processing
@@ -88,7 +90,7 @@ Certain config files perform extra processing.
 
 ## Global replacements
 
-All config files have certain text directives replaced. This is done by the atstccfg config generator before the file is returned to T3C.
+All config files have certain text directives replaced. This is done by the atstccfg config generator before the file is returned to `t3c`.
 
 * `__SERVER_TCP_PORT__` is replaced with the Server's Port from Traffic Ops; unless the server's port is 80, 0, or null, in which case any occurrences preceded by a colon are removed.
 * `__CACHE_IPV4__` is replaced with the Server's IP address from Traffic Ops.
@@ -98,7 +100,7 @@ All config files have certain text directives replaced. This is done by the atst
 
 ## remap.config
 
-T3C processes `##OVERRIDE##` directives in the remap.config file.
+The `t3c` app processes `##OVERRIDE##` directives in the remap.config file.
 
 The ##OVERRIDE## template string allows the Delivery Service Raw Remap Text field to override to fully override the Delivery Service’s line in the remap.config ATS configuration file, generated by Traffic Ops. The end result is the original, generated line commented out, prepended with ##OVERRIDDEN## and the ##OVERRIDE## rule is activated in its place. This behavior is used to incrementally deploy plugins used in this configuration file. Normally, this entails cloning the Delivery Servic [...]
 
@@ -106,10 +108,10 @@ The ##OVERRIDE## template string allows the Delivery Service Raw Remap Text fiel
 
 This is presumed to be a udev file for devices which are block devices to be used as disk storage by ATS.
 
-T3C verifies all devices in the file are owned by the owner listed in the file, and logs errors otherwise.
+The `t3c` app verifies all devices in the file are owned by the owner listed in the file, and logs errors otherwise.
 
-T3C verifies all devices in the file do not have filesystems. If any device has a filesystem, T3C assumes it was a mistake to assign as an ATS storage device, and logs a fatal error.
+The `t3c` app verifies all devices in the file do not have filesystems. If any device has a filesystem, `t3c` assumes it was a mistake to assign as an ATS storage device, and logs a fatal error.
 
 # Trivia
 
-T3C stands for "TrafficOps Cache Control and Configuration.", 3 C's.
+The "t3c" stands for "Traffic Control Cache Config."
diff --git a/traffic_ops_ort/t3c/config/config.go b/traffic_ops_ort/t3c/config/config.go
index c74f9b9..02d3a64 100644
--- a/traffic_ops_ort/t3c/config/config.go
+++ b/traffic_ops_ort/t3c/config/config.go
@@ -114,7 +114,9 @@ type Cfg struct {
 	// UseGit is whether to create and maintain a git repo of config changes.
 	// Note this only applies to the ATS config directory inferred or set via the flag.
 	//      It does not do anything for config files generated outside that location.
-	UseGit UseGitFlag
+	UseGit                   UseGitFlag
+	DefaultClientEnableH2    *bool
+	DefaultClientTLSVersions *string
 }
 
 type UseGitFlag string
@@ -216,6 +218,9 @@ func GetCfg() (Cfg, error) {
 	dnsLocalBindPtr := getopt.BoolLong("dns-local-bind", 'b', "[true | false] whether to use the server's Service Addresses to set the ATS DNS local bind address")
 	helpPtr := getopt.BoolLong("help", 'h', "Print usage information and exit")
 	useGitStr := getopt.StringLong("git", 'g', "auto", "Create and use a git repo in the config directory. Options are yes, no, and auto. If yes, create and use. If auto, use if it exist. Default is auto.")
+	defaultEnableH2 := getopt.BoolLong("default-client-enable-h2", '2', "Whether to enable HTTP/2 on Delivery Services by default, if they have no explicit Parameter. This is irrelevant if ATS records.config is not serving H2. If omitted, H2 is disabled.")
+	defaultClientTLSVersions := getopt.StringLong("default-client-tls-versions", 'v', "", "Comma-delimited list of default TLS versions for Delivery Services with no Parameter, e.g. --default-tls-versions='1.1,1.2,1.3'. If omitted, all versions are enabled.")
+
 	getopt.Parse()
 
 	dispersion := time.Second * time.Duration(*dispersionPtr)
@@ -337,28 +342,30 @@ func GetCfg() (Cfg, error) {
 	yumOptions := os.Getenv("YUM_OPTIONS")
 
 	cfg := Cfg{
-		Dispersion:          dispersion,
-		LogLocationDebug:    logLocationDebug,
-		LogLocationErr:      logLocationError,
-		LogLocationInfo:     logLocationInfo,
-		LogLocationWarn:     logLocationWarn,
-		LoginDispersion:     loginDispersion,
-		CacheHostName:       cacheHostName,
-		SvcManagement:       svcManagement,
-		Retries:             retries,
-		RevalWaitTime:       revalWaitTime,
-		ReverseProxyDisable: reverseProxyDisable,
-		RunMode:             runMode,
-		SkipOSCheck:         skipOsCheck,
-		TOInsecure:          toInsecure,
-		TOTimeoutMS:         toTimeoutMS,
-		TOUser:              toUser,
-		TOPass:              toPass,
-		TOURL:               toURL,
-		DNSLocalBind:        dnsLocalBind,
-		WaitForParents:      waitForParents,
-		YumOptions:          yumOptions,
-		UseGit:              useGit,
+		Dispersion:               dispersion,
+		LogLocationDebug:         logLocationDebug,
+		LogLocationErr:           logLocationError,
+		LogLocationInfo:          logLocationInfo,
+		LogLocationWarn:          logLocationWarn,
+		LoginDispersion:          loginDispersion,
+		CacheHostName:            cacheHostName,
+		SvcManagement:            svcManagement,
+		Retries:                  retries,
+		RevalWaitTime:            revalWaitTime,
+		ReverseProxyDisable:      reverseProxyDisable,
+		RunMode:                  runMode,
+		SkipOSCheck:              skipOsCheck,
+		TOInsecure:               toInsecure,
+		TOTimeoutMS:              toTimeoutMS,
+		TOUser:                   toUser,
+		TOPass:                   toPass,
+		TOURL:                    toURL,
+		DNSLocalBind:             dnsLocalBind,
+		WaitForParents:           waitForParents,
+		YumOptions:               yumOptions,
+		UseGit:                   useGit,
+		DefaultClientEnableH2:    defaultEnableH2,
+		DefaultClientTLSVersions: defaultClientTLSVersions,
 	}
 
 	if err = log.InitCfg(cfg); err != nil {
diff --git a/traffic_ops_ort/t3c/torequest/torequest.go b/traffic_ops_ort/t3c/torequest/torequest.go
index 0251fbf..67eb86c 100644
--- a/traffic_ops_ort/t3c/torequest/torequest.go
+++ b/traffic_ops_ort/t3c/torequest/torequest.go
@@ -227,10 +227,15 @@ func (r *TrafficOpsReq) atsTcExecCommand(cmdstr string, queueState int, revalSta
 	if r.Cfg.TOInsecure == true {
 		args = append(args, "--traffic-ops-insecure")
 	}
-
 	if r.Cfg.DNSLocalBind {
 		args = append(args, "--dns-local-bind")
 	}
+	if r.Cfg.DefaultClientEnableH2 != nil {
+		args = append(args, "--default-client-enable-h2="+strconv.FormatBool(*r.Cfg.DefaultClientEnableH2))
+	}
+	if r.Cfg.DefaultClientTLSVersions != nil {
+		args = append(args, "--default-client-tls-versions="+*r.Cfg.DefaultClientTLSVersions+"")
+	}
 
 	switch cmdstr {
 	case "chkconfig":